Implement player-related compatibility functions

This commit is contained in:
Gabriel Tofvesson 2021-07-02 20:40:35 +02:00
parent 4ca08401ff
commit be046690e3
4 changed files with 143 additions and 39 deletions

View File

@ -9,38 +9,10 @@ import static dev.w1zzrd.spigot.wizcompat.packet.Reflect.*;
public final class EntityCreator {
private EntityCreator() { throw new UnsupportedOperationException("Functional class"); }
private static Package getNativeMonsterPackage(final Player from) {
// Given player wll be an instance of CraftPlayer
final Package bukkitEntityPackage = from.getClass().getPackage();
final Class<?> craftShulker = loadClass(bukkitEntityPackage, "CraftShulker");
assert craftShulker != null;
// CraftShulker constructor accepts minecraft EntityShulker instance as second argument
final Class<?> nativeEntityShulker = craftShulker.getDeclaredConstructors()[0].getParameterTypes()[1];
// EntityShulker is classified squarely as a monster, so it should be grouped with all other hostiles
return nativeEntityShulker.getPackage();
}
private static Package getNativePacketPackage(final Player from) {
final Method sendPacket = findDeclaredMethod(
reflectGetField(reflectGetField(from, "entity"), "playerConnection", "networkManager").getClass(),
new String[]{ "sendPacket" },
new Object[]{ null }
);
return sendPacket.getParameterTypes()[0].getPackage();
}
private static Object getMinecraftServerFromWorld(final Object worldServer) {
return reflectGetField(worldServer, "server", "D");
}
private static Object getWorldServerFromPlayer(final Player from) {
return reflectGetField(from.getWorld(), "world");
}
private static Object getMonsterEntityType(final Class<?> entityClass) {
final Class<?> type_EntityTypes = entityClass.getDeclaredConstructors()[0].getParameterTypes()[0];
@ -51,7 +23,7 @@ public final class EntityCreator {
final Package versionPackage = getNativeMonsterPackage(target);
final Class<?> type_Entity = loadClass(versionPackage, entityClassName);
final Object nativeWorld = getWorldServerFromPlayer(target);
final Object nativeWorld = Players.getWorldServerFromPlayer(target);
assert type_Entity != null;
final Object entityType = getMonsterEntityType(type_Entity);
@ -66,13 +38,9 @@ public final class EntityCreator {
return createFakeMonster(target, "EntityShulker");
}
public static void sendPacket(final Player target, final Object packet) {
reflectInvoke(reflectGetField(reflectGetField(target, "entity"), "playerConnection", "networkManager"), new String[]{ "sendPacket" }, packet);
}
public static void sendEntitySpawnPacket(final Player target, final Object entity) {
final Package versionPackage = getNativePacketPackage(target);
sendPacket(target, reflectConstruct(loadClass(versionPackage, "PacketPlayOutSpawnEntityLiving", "game.PacketPlayOutSpawnEntityLiving"), entity));
Packets.sendPacket(target, reflectConstruct(loadClass(versionPackage, "PacketPlayOutSpawnEntityLiving", "game.PacketPlayOutSpawnEntityLiving"), entity));
}
public static void sendEntityMetadataPacket(final Player target, final Object entity) {
@ -94,7 +62,7 @@ public final class EntityCreator {
);
}
sendPacket(
Packets.sendPacket(
target,
constr1
);
@ -102,7 +70,7 @@ public final class EntityCreator {
public static void sendEntityDespawnPacket(final Player target, final int entityID) {
final Package versionPackage = getNativePacketPackage(target);
sendPacket(target, reflectConstruct(loadClass(versionPackage, "PacketPlayOutEntityDestroy", "game.PacketPlayOutEntityDestroy"), entityID));
Packets.sendPacket(target, reflectConstruct(loadClass(versionPackage, "PacketPlayOutEntityDestroy", "game.PacketPlayOutEntityDestroy"), entityID));
}
public static int getEntityID(final Object entity) {

View File

@ -0,0 +1,13 @@
package dev.w1zzrd.spigot.wizcompat.packet;
import org.bukkit.entity.Player;
import static dev.w1zzrd.spigot.wizcompat.packet.Players.getEntityFromPlayer;
import static dev.w1zzrd.spigot.wizcompat.packet.Reflect.reflectGetField;
import static dev.w1zzrd.spigot.wizcompat.packet.Reflect.reflectInvoke;
public class Packets {
public static void sendPacket(final Player target, final Object packet) {
reflectInvoke(reflectGetField(getEntityFromPlayer(target), "playerConnection", "networkManager"), new String[]{ "sendPacket" }, packet);
}
}

View File

@ -0,0 +1,78 @@
package dev.w1zzrd.spigot.wizcompat.packet;
import org.bukkit.GameMode;
import org.bukkit.entity.Player;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
import static dev.w1zzrd.spigot.wizcompat.packet.Reflect.*;
public final class Players {
private Players() { throw new UnsupportedOperationException("Functional class"); }
public static void sendPlayerGameModePacket(final Player target, final GameMode gameMode) {
final Object entity = getEntityFromPlayer(target);
final Package versionPackage = getNativePacketPackage(target);
final Class<?> type_PacketPlayOutPlayerInfo = loadClass(versionPackage, "PacketPlayOutPlayerInfo", "game.PacketPlayOutPlayerInfo");
final Class<?> type_EnumPlayerInfoAction = loadClass(versionPackage, "PacketPlayOutPlayerInfo$EnumPlayerInfoAction", "game.PacketPlayOutPlayerInfo$EnumPlayerInfoAction");
final Class<?> type_PlayerInfoData = loadClass(versionPackage, "PacketPlayOutPlayerInfo$PlayerInfoData", "game.PacketPlayOutPlayerInfo$PlayerInfoData");
assert type_PacketPlayOutPlayerInfo != null;
assert type_EnumPlayerInfoAction != null;
assert type_PlayerInfoData != null;
final Class<?> type_GameMode = type_PlayerInfoData.getDeclaredConstructors()[0].getParameterTypes()[2];
final Object profile = reflectInvoke(entity, new String[]{ "getProfile" });
final Integer ping = reflectGetField(entity, int.class, "ping", "e");
Object nativeGameMode = null;
for (final Enum<?> e : (Enum<?>[])reflectInvokeStatic(type_GameMode, new String[]{ "values" }))
if (e.name().equalsIgnoreCase(gameMode.name())) {
nativeGameMode = e;
break;
}
// Use deprecated implementation as a last resort
if (nativeGameMode == null)
nativeGameMode = reflectInvokeStatic(type_GameMode, new String[]{ "getById" }, gameMode.getValue());
Object listName = reflectInvoke(entity, new String[]{ "getPlayerListName" });
if (listName == null)
listName = reflectGetField(entity, "listName");
// Syntactic sugar can go suck a fat one
// I didn't realize the constructor took an array type, because I completely missed the "..."
final Object entityArray = Array.newInstance(entity.getClass(), 1);
Array.set(entityArray, 0, entity);
final Object packet = reflectConstruct(type_PacketPlayOutPlayerInfo, reflectGetStaticField(type_EnumPlayerInfoAction, "UPDATE_GAME_MODE", "b"), entityArray);
assert packet != null;
final List<Object> playerInfo = new ArrayList<>();
playerInfo.add(reflectConstruct(type_PlayerInfoData, profile, ping, nativeGameMode, listName));
reflectSetField(packet, List.class, playerInfo);
Packets.sendPacket(target, packet);
}
static Object getWorldServerFromPlayer(final Player from) {
return reflectGetField(from.getWorld(), "world");
}
public static Object getEntityFromPlayer(final Player player) {
try {
return reflectInvoke(player, new String[]{ "getHandle" });
} catch(Throwable t) {
t.printStackTrace();
}
return reflectGetField(player, "entity");
}
}

View File

@ -1,5 +1,7 @@
package dev.w1zzrd.spigot.wizcompat.packet;
import org.bukkit.entity.Player;
import java.lang.reflect.*;
public final class Reflect {
@ -17,10 +19,13 @@ public final class Reflect {
do {
for (final Method check : current.getDeclaredMethods())
if (contains(methodNames, check.getName()) && argsMatch(check.getParameterTypes(), args))
if ((methodNames.length == 0 || contains(methodNames, check.getName())) && argsMatch(check.getParameterTypes(), args))
return check;
current = current.getSuperclass();
if (current == null)
return null;
} while (true);
}
@ -29,10 +34,13 @@ public final class Reflect {
do {
for (final Field check : current.getDeclaredFields())
if (contains(fieldNames, check.getName()) && (expectedType == null || check.getType().equals(expectedType)))
if ((fieldNames.length == 0 || contains(fieldNames, check.getName())) && (expectedType == null || check.getType().equals(expectedType)))
return check;
current = current.getSuperclass();
if (current == null)
return null;
} while (true);
}
@ -56,6 +64,19 @@ public final class Reflect {
return null;
}
public static Object reflectInvokeStatic(final Class<?> target, final String[] methodNames, final Object... args) {
final Method targetMethod = findDeclaredMethod(target, methodNames, args);
targetMethod.setAccessible(true);
try {
return targetMethod.invoke(null, args);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
public static void reflectSetStaticField(final Class<?> target, final Object value, final String... fieldNames) {
reflectSetStaticField(target, null, value, fieldNames);
}
@ -71,7 +92,7 @@ public final class Reflect {
}
public static <T> T reflectGetStaticField(final Class<?> target, final String... fieldNames) {
return (T) reflectGetStaticField(target, null, fieldNames);
return reflectGetStaticField(target, null, fieldNames);
}
public static <T, R> T reflectGetGenericStaticField(final Class<?> target, final Class<T> fieldType, final Class<R> genericType) {
@ -194,6 +215,30 @@ public final class Reflect {
(primitive == double.class && objectType == Double.class);
}
static Package getNativeMonsterPackage(final Player from) {
// Given player wll be an instance of CraftPlayer
final Package bukkitEntityPackage = from.getClass().getPackage();
final Class<?> craftShulker = loadClass(bukkitEntityPackage, "CraftShulker");
assert craftShulker != null;
// CraftShulker constructor accepts minecraft EntityShulker instance as second argument
final Class<?> nativeEntityShulker = craftShulker.getDeclaredConstructors()[0].getParameterTypes()[1];
// EntityShulker is classified squarely as a monster, so it should be grouped with all other hostiles
return nativeEntityShulker.getPackage();
}
static Package getNativePacketPackage(final Player from) {
final Method sendPacket = findDeclaredMethod(
reflectGetField(reflectGetField(from, "entity"), "playerConnection", "networkManager").getClass(),
new String[]{ "sendPacket" },
new Object[]{ null }
);
return sendPacket.getParameterTypes()[0].getPackage();
}
private interface DeclarationGetter<T, R> {
R[] getDeclared(final Class<? super T> t);
}