From fe27bd5e6042a23c71e6847c7c0fba836ea7fa36 Mon Sep 17 00:00:00 2001 From: Flowsqy <47575244+Flowsqy@users.noreply.github.com> Date: Wed, 29 Dec 2021 17:12:27 +0100 Subject: [PATCH] Add reflection implementation structure --- nms/reflection/pom.xml | 5 + .../nms/reflection/FakeArmorStandImpl.java | 22 ++ .../nms/reflection/FakeEntityImpl.java | 33 ++ .../nms/reflection/FakeItemImpl.java | 22 ++ .../shopchest/nms/reflection/JsonBuilder.java | 243 +++++++++++++++ .../nms/reflection/PlatformImpl.java | 25 ++ .../nms/reflection/ReflectionUtils.java | 285 ++++++++++++++++++ .../nms/reflection/ShopChestDebug.java | 30 ++ 8 files changed, 665 insertions(+) create mode 100644 nms/reflection/src/main/java/de/epiceric/shopchest/nms/reflection/FakeArmorStandImpl.java create mode 100644 nms/reflection/src/main/java/de/epiceric/shopchest/nms/reflection/FakeEntityImpl.java create mode 100644 nms/reflection/src/main/java/de/epiceric/shopchest/nms/reflection/FakeItemImpl.java create mode 100644 nms/reflection/src/main/java/de/epiceric/shopchest/nms/reflection/JsonBuilder.java create mode 100644 nms/reflection/src/main/java/de/epiceric/shopchest/nms/reflection/PlatformImpl.java create mode 100644 nms/reflection/src/main/java/de/epiceric/shopchest/nms/reflection/ReflectionUtils.java create mode 100644 nms/reflection/src/main/java/de/epiceric/shopchest/nms/reflection/ShopChestDebug.java diff --git a/nms/reflection/pom.xml b/nms/reflection/pom.xml index 635de26..253819d 100644 --- a/nms/reflection/pom.xml +++ b/nms/reflection/pom.xml @@ -23,6 +23,11 @@ shopchest-nms-interface provided + + org.inventivetalent + reflectionhelper + provided + \ No newline at end of file diff --git a/nms/reflection/src/main/java/de/epiceric/shopchest/nms/reflection/FakeArmorStandImpl.java b/nms/reflection/src/main/java/de/epiceric/shopchest/nms/reflection/FakeArmorStandImpl.java new file mode 100644 index 0000000..6f3d86e --- /dev/null +++ b/nms/reflection/src/main/java/de/epiceric/shopchest/nms/reflection/FakeArmorStandImpl.java @@ -0,0 +1,22 @@ +package de.epiceric.shopchest.nms.reflection; + +import de.epiceric.shopchest.nms.FakeArmorStand; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +public class FakeArmorStandImpl extends FakeEntityImpl implements FakeArmorStand { + + public FakeArmorStandImpl(ShopChestDebug debug) { + super(debug); + } + + @Override + public void sendData(String name, Iterable receivers) { + + } + + @Override + public void setLocation(Location location, Iterable receivers) { + + } +} diff --git a/nms/reflection/src/main/java/de/epiceric/shopchest/nms/reflection/FakeEntityImpl.java b/nms/reflection/src/main/java/de/epiceric/shopchest/nms/reflection/FakeEntityImpl.java new file mode 100644 index 0000000..8d59d69 --- /dev/null +++ b/nms/reflection/src/main/java/de/epiceric/shopchest/nms/reflection/FakeEntityImpl.java @@ -0,0 +1,33 @@ +package de.epiceric.shopchest.nms.reflection; + +import de.epiceric.shopchest.nms.FakeEntity; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +import java.util.UUID; + +public class FakeEntityImpl implements FakeEntity { + + protected final int entityId; + protected final ShopChestDebug debug; + + public FakeEntityImpl(ShopChestDebug debug) { + this.entityId = ReflectionUtils.getFreeEntityId(); + this.debug = debug; + } + + @Override + public int getEntityId() { + return entityId; + } + + @Override + public void spawn(UUID uuid, Location location, Iterable receivers) { + + } + + @Override + public void remove(Iterable receivers) { + + } +} diff --git a/nms/reflection/src/main/java/de/epiceric/shopchest/nms/reflection/FakeItemImpl.java b/nms/reflection/src/main/java/de/epiceric/shopchest/nms/reflection/FakeItemImpl.java new file mode 100644 index 0000000..2250ad3 --- /dev/null +++ b/nms/reflection/src/main/java/de/epiceric/shopchest/nms/reflection/FakeItemImpl.java @@ -0,0 +1,22 @@ +package de.epiceric.shopchest.nms.reflection; + +import de.epiceric.shopchest.nms.FakeItem; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +public class FakeItemImpl extends FakeEntityImpl implements FakeItem { + + public FakeItemImpl(ShopChestDebug debug) { + super(debug); + } + + @Override + public void sendData(ItemStack item, Iterable receivers) { + + } + + @Override + public void resetVelocity(Iterable receivers) { + + } +} diff --git a/nms/reflection/src/main/java/de/epiceric/shopchest/nms/reflection/JsonBuilder.java b/nms/reflection/src/main/java/de/epiceric/shopchest/nms/reflection/JsonBuilder.java new file mode 100644 index 0000000..3db5cf3 --- /dev/null +++ b/nms/reflection/src/main/java/de/epiceric/shopchest/nms/reflection/JsonBuilder.java @@ -0,0 +1,243 @@ +package de.epiceric.shopchest.nms.reflection; + +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.inventivetalent.reflection.resolver.FieldResolver; +import org.inventivetalent.reflection.resolver.minecraft.NMSClassResolver; + +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class JsonBuilder { + + public static class Part { + private String value; + + public Part() { + this("", true); + } + + public Part(Object value) { + this(value, value instanceof CharSequence); + } + + public Part(Object value, boolean appendQuotes) { + if (appendQuotes) { + this.value = "\"" + value + "\""; + } else { + this.value = String.valueOf(value); + } + } + + @Override + public String toString() { + return value; + } + + public PartArray toArray() { + return new PartArray(this); + } + + public PartMap toMap() { + PartMap map = new PartMap(); + map.setValue("text", new Part()); + map.setValue("extra", toArray()); + return map; + } + } + + public static class PartMap extends Part { + private Map values = new HashMap<>(); + + public PartMap() { + } + + public PartMap(Map values) { + this.values.putAll(values); + } + + public void setValue(String key, Part value) { + values.put(key, value); + } + + public void removeValue(String key) { + values.remove(key); + } + + @Override + public String toString() { + StringJoiner joiner = new StringJoiner(",", "{", "}"); + values.forEach((key, value) -> joiner.add("\"" + key + "\":" + value.toString())); + return joiner.toString(); + } + + @Override + public PartMap toMap() { + return this; + } + } + + public static class PartArray extends Part { + private List parts = new ArrayList<>(); + + public PartArray(Part... parts) { + this.parts.addAll(Arrays.asList(parts)); + } + + public void addPart(Part part) { + parts.add(part); + } + + @Override + public String toString() { + StringJoiner joiner = new StringJoiner(",", "[", "]"); + parts.forEach(part -> joiner.add(part.toString())); + return joiner.toString(); + } + + @Override + public PartArray toArray() { + return this; + } + } + + private static final Pattern PART_PATTERN = Pattern.compile("((§(?:[a-fA-Fk-oK-OrR0-9]|#[a-fA-F0-9]{6}))+)([^§]*)"); + private static final Pattern HEX_PATTERN = Pattern.compile("(§[a-fA-F0-9]){6}"); + + private Part rootPart; + private ShopChestDebug debug; + + private final NMSClassResolver nmsClassResolver = new NMSClassResolver(); + private Class iChatBaseComponentClass = nmsClassResolver.resolveSilent("network.chat.IChatBaseComponent"); + private Class packetPlayOutChatClass = nmsClassResolver.resolveSilent("network.protocol.game.PacketPlayOutChat"); + private Class chatSerializerClass = nmsClassResolver.resolveSilent("ChatSerializer", "network.chat.IChatBaseComponent$ChatSerializer"); + private Class chatMessageTypeClass; + + public JsonBuilder(ShopChestDebug debug) { + this.debug = debug; + + if (ReflectionUtils.getMajorVersion() >= 16) { + chatMessageTypeClass = nmsClassResolver.resolveSilent("network.chat.ChatMessageType"); + } + + Class[] requiredClasses = new Class[] { + iChatBaseComponentClass, packetPlayOutChatClass, chatSerializerClass + }; + + for (Class c : requiredClasses) { + if (c == null) { + debug.debug("Failed to instantiate JsonBuilder: Could not find all required classes"); + return; + } + } + } + + public static Part parse(String text) { + Matcher hexMatcher = HEX_PATTERN.matcher(text); + while (hexMatcher.find()) { + String hexCode = hexMatcher.group(0).replace("§", ""); + text = text.replace(hexMatcher.group(0), "§#" + hexCode); + } + + Matcher matcher = PART_PATTERN.matcher(text); + + if (!matcher.find()) { + return new Part(text); + } + + matcher.reset(); + + PartArray array = new PartArray(new Part()); + int lastEndIndex = 0; + + while (matcher.find()) { + int startIndex = matcher.start(); + int endIndex = matcher.end(); + + if (lastEndIndex != startIndex) { + String betweenMatches = text.substring(lastEndIndex, startIndex); + array.addPart(new Part(betweenMatches)); + } + + String format = matcher.group(1); + String value = matcher.group(3); + + PartMap part = new PartMap(); + part.setValue("text", new Part(value)); + + String[] formats = format.split("§"); + for (String f : formats) { + switch (f.toLowerCase()) { + case "": + break; + case "k": + part.setValue("obuscated", new Part(true)); + break; + case "l": + part.setValue("bold", new Part(true)); + break; + case "m": + part.setValue("strikethrough", new Part(true)); + break; + case "n": + part.setValue("underlined", new Part(true)); + break; + case "o": + part.setValue("italic", new Part(true)); + break; + case "r": + part.removeValue("obfuscated"); + part.removeValue("bold"); + part.removeValue("strikethrough"); + part.removeValue("underlined"); + part.removeValue("italic"); + part.removeValue("color"); + break; + default: + if (f.startsWith("#")) { + part.setValue("color", new Part(f)); + } else { + part.setValue("color", new Part(ChatColor.getByChar(f).name().toLowerCase())); + } + } + } + + array.addPart(part); + lastEndIndex = endIndex; + } + + return array; + } + + @Override + public String toString() { + return rootPart.toString(); + } + + public Part getRootPart() { + return rootPart; + } + + public void setRootPart(Part rootPart) { + this.rootPart = rootPart; + } + + public void sendJson(Player p) { + try { + Object iChatBaseComponent = chatSerializerClass.getMethod("a", String.class).invoke(null, toString()); + Object packetPlayOutChat = ReflectionUtils.getMajorVersion() < 16 + ? packetPlayOutChatClass.getConstructor(iChatBaseComponentClass).newInstance(iChatBaseComponent) + : packetPlayOutChatClass.getConstructor(iChatBaseComponentClass, chatMessageTypeClass, UUID.class) + .newInstance(iChatBaseComponent, (new FieldResolver(chatMessageTypeClass)).resolve("CHAT", "a").get(null), UUID.randomUUID()); + + ReflectionUtils.sendPacket(debug, packetPlayOutChat, p); + debug.debug("Sent JSON: " + toString()); + } catch (ReflectiveOperationException e) { + debug.getLogger().severe("Failed to send JSON with reflection"); + debug.debug("Failed to send JSON with reflection: " + toString()); + debug.debug(e); + } + } + +} diff --git a/nms/reflection/src/main/java/de/epiceric/shopchest/nms/reflection/PlatformImpl.java b/nms/reflection/src/main/java/de/epiceric/shopchest/nms/reflection/PlatformImpl.java new file mode 100644 index 0000000..c12948a --- /dev/null +++ b/nms/reflection/src/main/java/de/epiceric/shopchest/nms/reflection/PlatformImpl.java @@ -0,0 +1,25 @@ +package de.epiceric.shopchest.nms.reflection; + +import de.epiceric.shopchest.nms.FakeArmorStand; +import de.epiceric.shopchest.nms.FakeItem; +import de.epiceric.shopchest.nms.Platform; + +public class PlatformImpl implements Platform { + + private final ShopChestDebug debug; + + public PlatformImpl(ShopChestDebug debug) { + this.debug = debug; + } + + + @Override + public FakeArmorStand createFakeArmorStand() { + return new FakeArmorStandImpl(debug); + } + + @Override + public FakeItem createFakeItem() { + return new FakeItemImpl(debug); + } +} diff --git a/nms/reflection/src/main/java/de/epiceric/shopchest/nms/reflection/ReflectionUtils.java b/nms/reflection/src/main/java/de/epiceric/shopchest/nms/reflection/ReflectionUtils.java new file mode 100644 index 0000000..f32f12f --- /dev/null +++ b/nms/reflection/src/main/java/de/epiceric/shopchest/nms/reflection/ReflectionUtils.java @@ -0,0 +1,285 @@ +package de.epiceric.shopchest.nms.reflection; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.inventivetalent.reflection.resolver.FieldResolver; +import org.inventivetalent.reflection.resolver.minecraft.NMSClassResolver; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; + +public class ReflectionUtils { + static NMSClassResolver nmsClassResolver = new NMSClassResolver(); + static Class entityClass = nmsClassResolver.resolveSilent("world.entity.Entity"); + static Class entityArmorStandClass = nmsClassResolver.resolveSilent("world.entity.decoration.EntityArmorStand"); + static Class entityItemClass = nmsClassResolver.resolveSilent("world.entity.item.EntityItem"); + static Class dataWatcherClass = nmsClassResolver.resolveSilent("network.syncher.DataWatcher"); + static Class dataWatcherObjectClass = nmsClassResolver.resolveSilent("network.syncher.DataWatcherObject"); + static Class chatSerializerClass = nmsClassResolver.resolveSilent("ChatSerializer", "network.chat.IChatBaseComponent$ChatSerializer"); + + private ReflectionUtils() {} + + /** + * Create a NMS data watcher object to send via a {@code PacketPlayOutEntityMetadata} packet. + * Gravity will be disabled and the custom name will be displayed if available. + * @param customName Custom Name of the entity or {@code null} + * @param nmsItemStack NMS ItemStack or {@code null} if armor stand + */ + public static Object createDataWatcher(ShopChestDebug debug, String customName, Object nmsItemStack) { + String version = getServerVersion(); + int majorVersion = getMajorVersion(); + + try { + byte entityFlags = nmsItemStack == null ? (byte) 0b100000 : 0; // invisible if armor stand + byte armorStandFlags = nmsItemStack == null ? (byte) 0b10000 : 0; // marker (since 1.8_R2) + + Object dataWatcher = dataWatcherClass.getConstructor(entityClass).newInstance((Object) null); + if (majorVersion < 9) { + if (getRevision() == 1) armorStandFlags = 0; // Marker not supported on 1.8_R1 + + Method a = dataWatcherClass.getMethod("a", int.class, Object.class); + a.invoke(dataWatcher, 0, entityFlags); // flags + a.invoke(dataWatcher, 1, (short) 300); // air ticks (?) + a.invoke(dataWatcher, 3, (byte) (customName != null ? 1 : 0)); // custom name visible + a.invoke(dataWatcher, 2, customName != null ? customName : ""); // custom name + a.invoke(dataWatcher, 4, (byte) 1); // silent + a.invoke(dataWatcher, 10, nmsItemStack == null ? armorStandFlags : nmsItemStack); // item / armor stand flags + } else { + Method register = dataWatcherClass.getMethod("register", dataWatcherObjectClass, Object.class); + String[] dataWatcherObjectFieldNames; + + if ("v1_9_R1".equals(version)) { + dataWatcherObjectFieldNames = new String[] {"ax", "ay", "aA", "az", "aB", null, "c", "a"}; + } else if ("v1_9_R2".equals(version)){ + dataWatcherObjectFieldNames = new String[] {"ay", "az", "aB", "aA", "aC", null, "c", "a"}; + } else if ("v1_10_R1".equals(version)) { + dataWatcherObjectFieldNames = new String[] {"aa", "az", "aB", "aA", "aC", "aD", "c", "a"}; + } else if ("v1_11_R1".equals(version)) { + dataWatcherObjectFieldNames = new String[] {"Z", "az", "aB", "aA", "aC", "aD", "c", "a"}; + } else if ("v1_12_R1".equals(version) || "v1_12_R2".equals(version)) { + dataWatcherObjectFieldNames = new String[] {"Z", "aA", "aC", "aB", "aD", "aE", "c", "a"}; + } else if ("v1_13_R1".equals(version) || "v1_13_R2".equals(version)) { + dataWatcherObjectFieldNames = new String[] {"ac", "aD", "aF", "aE", "aG", "aH", "b", "a"}; + } else if ("v1_14_R1".equals(version)) { + dataWatcherObjectFieldNames = new String[] {"W", "AIR_TICKS", "aA", "az", "aB", "aC", "ITEM", "b"}; + } else if ("v1_15_R1".equals(version)) { + dataWatcherObjectFieldNames = new String[] {"T", "AIR_TICKS", "aA", "az", "aB", "aC", "ITEM", "b"}; + } else if ("v1_16_R1".equals(version)) { + dataWatcherObjectFieldNames = new String[] {"T", "AIR_TICKS", "ay", "ax", "az", "aA", "ITEM", "b"}; + } else if ("v1_16_R2".equals(version) || "v1_16_R3".equals(version)) { + dataWatcherObjectFieldNames = new String[] {"S", "AIR_TICKS", "ar", "aq", "as", "at", "ITEM", "b"}; + } else { + return null; + } + + Field fEntityFlags = entityClass.getDeclaredField(dataWatcherObjectFieldNames[0]); + Field fAirTicks = entityClass.getDeclaredField(dataWatcherObjectFieldNames[1]); + Field fNameVisible = entityClass.getDeclaredField(dataWatcherObjectFieldNames[2]); + Field fCustomName = entityClass.getDeclaredField(dataWatcherObjectFieldNames[3]); + Field fSilent = entityClass.getDeclaredField(dataWatcherObjectFieldNames[4]); + Field fNoGravity = majorVersion >= 10 ? entityClass.getDeclaredField(dataWatcherObjectFieldNames[5]) : null; + Field fItem = entityItemClass.getDeclaredField(dataWatcherObjectFieldNames[6]); + Field fArmorStandFlags = entityArmorStandClass.getDeclaredField(dataWatcherObjectFieldNames[7]); + + fEntityFlags.setAccessible(true); + fAirTicks.setAccessible(true); + fNameVisible.setAccessible(true); + fCustomName.setAccessible(true); + fSilent.setAccessible(true); + if (majorVersion >= 10) fNoGravity.setAccessible(true); + fItem.setAccessible(true); + fArmorStandFlags.setAccessible(true); + + register.invoke(dataWatcher, fEntityFlags.get(null), entityFlags); + register.invoke(dataWatcher, fAirTicks.get(null), 300); + register.invoke(dataWatcher, fNameVisible.get(null), customName != null); + register.invoke(dataWatcher, fSilent.get(null), true); + if (majorVersion < 13) register.invoke(dataWatcher, fCustomName.get(null), customName != null ? customName : ""); + + if (nmsItemStack != null) { + register.invoke(dataWatcher, fItem.get(null), majorVersion < 11 ? com.google.common.base.Optional.of(nmsItemStack) : nmsItemStack); + } else { + register.invoke(dataWatcher, fArmorStandFlags.get(null), armorStandFlags); + } + + if (majorVersion >= 10) { + register.invoke(dataWatcher, fNoGravity.get(null), true); + if (majorVersion >= 13) { + if (customName != null) { + Object iChatBaseComponent = chatSerializerClass.getMethod("a", String.class).invoke(null, JsonBuilder.parse(customName).toString()); + register.invoke(dataWatcher, fCustomName.get(null), Optional.of(iChatBaseComponent)); + } else { + register.invoke(dataWatcher, fCustomName.get(null), Optional.empty()); + } + } + } + } + return dataWatcher; + } catch (InstantiationException | InvocationTargetException | NoSuchFieldException | IllegalAccessException | NoSuchMethodException e) { + debug.getLogger().severe("Failed to create data watcher!"); + debug.debug("Failed to create data watcher"); + debug.debug(e); + } + return null; + } + + /** + * Get a free entity ID for use in {@link #createPacketSpawnEntity(ShopChestDebug, int, UUID, Location, EntityType)} + * + * @return The id or {@code -1} if a free entity ID could not be retrieved. + */ + public static int getFreeEntityId() { + try { + Field entityCountField = new FieldResolver(entityClass).resolve("entityCount", "b"); + entityCountField.setAccessible(true); + if (entityCountField.getType() == int.class) { + int id = entityCountField.getInt(null); + entityCountField.setInt(null, id+1); + return id; + } else if (entityCountField.getType() == AtomicInteger.class) { + return ((AtomicInteger) entityCountField.get(null)).incrementAndGet(); + } + + return -1; + } catch (Exception e) { + return -1; + } + } + + /** + * Create a {@code PacketPlayOutSpawnEntity} object. + * Only {@link EntityType#ARMOR_STAND} and {@link EntityType#DROPPED_ITEM} are supported! + */ + public static Object createPacketSpawnEntity(ShopChestDebug debug, int id, UUID uuid, Location loc, EntityType type) { + try { + Class packetPlayOutSpawnEntityClass = nmsClassResolver.resolveSilent("network.protocol.game.PacketPlayOutSpawnEntity"); + Class entityTypesClass = nmsClassResolver.resolveSilent("world.entity.EntityTypes"); + + boolean isPre9 = getMajorVersion() < 9; + boolean isPre14 = getMajorVersion() < 14; + + double y = loc.getY(); + if (type == EntityType.ARMOR_STAND && !getServerVersion().equals("v1_8_R1")) { + // Marker armor stand => lift by normal armor stand height + y += 1.975; + } + + Object packet = packetPlayOutSpawnEntityClass.getConstructor().newInstance(); + + Field[] fields = new Field[12]; + fields[0] = packetPlayOutSpawnEntityClass.getDeclaredField("a"); // ID + fields[1] = packetPlayOutSpawnEntityClass.getDeclaredField("b"); // UUID (Only 1.9+) + fields[2] = packetPlayOutSpawnEntityClass.getDeclaredField(isPre9 ? "b" : "c"); // Loc X + fields[3] = packetPlayOutSpawnEntityClass.getDeclaredField(isPre9 ? "c" : "d"); // Loc Y + fields[4] = packetPlayOutSpawnEntityClass.getDeclaredField(isPre9 ? "d" : "e"); // Loc Z + fields[5] = packetPlayOutSpawnEntityClass.getDeclaredField(isPre9 ? "e" : "f"); // Mot X + fields[6] = packetPlayOutSpawnEntityClass.getDeclaredField(isPre9 ? "f" : "g"); // Mot Y + fields[7] = packetPlayOutSpawnEntityClass.getDeclaredField(isPre9 ? "g" : "h"); // Mot Z + fields[8] = packetPlayOutSpawnEntityClass.getDeclaredField(isPre9 ? "h" : "i"); // Pitch + fields[9] = packetPlayOutSpawnEntityClass.getDeclaredField(isPre9 ? "i" : "j"); // Yaw + fields[10] = packetPlayOutSpawnEntityClass.getDeclaredField(isPre9 ? "j" : "k"); // Type + fields[11] = packetPlayOutSpawnEntityClass.getDeclaredField(isPre9 ? "k" : "l"); // Data + + for (Field field : fields) { + field.setAccessible(true); + } + + Object entityType = null; + if (!isPre14) { + entityType = entityTypesClass.getField(type == EntityType.ARMOR_STAND ? "ARMOR_STAND" : "ITEM").get(null); + } + + fields[0].set(packet, id); + if (!isPre9) fields[1].set(packet, uuid); + if (isPre9) { + fields[2].set(packet, (int)(loc.getX() * 32)); + fields[3].set(packet, (int)(y * 32)); + fields[4].set(packet, (int)(loc.getZ() * 32)); + } else { + fields[2].set(packet, loc.getX()); + fields[3].set(packet, y); + fields[4].set(packet, loc.getZ()); + } + fields[5].set(packet, 0); + fields[6].set(packet, 0); + fields[7].set(packet, 0); + fields[8].set(packet, 0); + fields[9].set(packet, 0); + if (isPre14) fields[10].set(packet, type == EntityType.ARMOR_STAND ? 78 : 2); + else fields[10].set(packet, entityType); + fields[11].set(packet, 0); + + return packet; + } catch (NoSuchMethodException | NoSuchFieldException | IllegalAccessException | InvocationTargetException | InstantiationException e) { + debug.getLogger().severe("Failed to create packet to spawn entity!"); + debug.debug("Failed to create packet to spawn entity!"); + debug.debug(e); + return null; + } + } + + /** + * Send a packet to a player + * @param debug An instance of the {@link ShopChestDebug} debug instance + * @param packet Packet to send + * @param player Player to which the packet should be sent + * @return {@code true} if the packet was sent, or {@code false} if an exception was thrown + */ + public static boolean sendPacket(ShopChestDebug debug, Object packet, Player player) { + try { + if (packet == null) { + debug.debug("Failed to send packet: Packet is null"); + return false; + } + + Class packetClass = nmsClassResolver.resolveSilent("network.protocol.Packet"); + if (packetClass == null) { + debug.debug("Failed to send packet: Could not find Packet class"); + return false; + } + + Object nmsPlayer = player.getClass().getMethod("getHandle").invoke(player); + Field fConnection = (new FieldResolver(nmsPlayer.getClass())).resolve("playerConnection", "b"); + Object playerConnection = fConnection.get(nmsPlayer); + + playerConnection.getClass().getMethod("sendPacket", packetClass).invoke(playerConnection, packet); + + return true; + } catch (NoSuchMethodException | NoSuchFieldException | IllegalAccessException | InvocationTargetException e) { + debug.getLogger().severe("Failed to send packet " + packet.getClass().getName()); + debug.debug("Failed to send packet " + packet.getClass().getName()); + debug.debug(e); + return false; + } + } + + /** + * @return The current server version with revision number (e.g. v1_9_R2, v1_10_R1) + */ + public static String getServerVersion() { + String packageName = Bukkit.getServer().getClass().getPackage().getName(); + + return packageName.substring(packageName.lastIndexOf('.') + 1); + } + + /** + * @return The revision of the current server version (e.g. 2 for v1_9_R2, 1 for v1_10_R1) + */ + public static int getRevision() { + return Integer.parseInt(getServerVersion().substring(getServerVersion().length() - 1)); + } + + /** + * @return The major version of the server (e.g. 9 for 1.9.2, 10 for 1.10) + */ + public static int getMajorVersion() { + return Integer.parseInt(getServerVersion().split("_")[1]); + } + +} diff --git a/nms/reflection/src/main/java/de/epiceric/shopchest/nms/reflection/ShopChestDebug.java b/nms/reflection/src/main/java/de/epiceric/shopchest/nms/reflection/ShopChestDebug.java new file mode 100644 index 0000000..f94ec0a --- /dev/null +++ b/nms/reflection/src/main/java/de/epiceric/shopchest/nms/reflection/ShopChestDebug.java @@ -0,0 +1,30 @@ +package de.epiceric.shopchest.nms.reflection; + +import java.util.function.Consumer; +import java.util.logging.Logger; + +public class ShopChestDebug { + + private final Logger logger; + private final Consumer debugConsumer; + private final Consumer throwableConsumer; + + public ShopChestDebug(Logger logger, Consumer debugConsumer, Consumer throwableConsumer) { + this.logger = logger; + this.debugConsumer = debugConsumer; + this.throwableConsumer = throwableConsumer; + } + + public Logger getLogger() { + return logger; + } + + public void debug(String message){ + debugConsumer.accept(message); + } + + public void debug(Throwable e){ + throwableConsumer.accept(e); + } + +}