From 0cda9f6153ed36f7c5f0f09643a67fd0b669ad4d Mon Sep 17 00:00:00 2001 From: Tobias de Bruijn Date: Thu, 18 Mar 2021 18:58:40 +0100 Subject: [PATCH 1/2] Changed the way I reload for the player who is applying the skin --- .gitignore | 1 + build.gradle | 4 +- .../changeSkin/SkinChangeOrchestrator.java | 210 +++++++++++++- .../SkinFixer/util/ReflectionUtil.java | 263 ++++++++++++++++++ 4 files changed, 461 insertions(+), 17 deletions(-) create mode 100644 src/main/java/nl/thedutchmc/SkinFixer/util/ReflectionUtil.java diff --git a/.gitignore b/.gitignore index 0789026..e7fa793 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /.gradle/ */build/* build/ +server/ #Eclipse *.classpath diff --git a/build.gradle b/build.gradle index f20e118..0851169 100644 --- a/build.gradle +++ b/build.gradle @@ -51,8 +51,8 @@ dependencies { compileOnly 'org.spigotmc:spigot-api:1.16.1-R0.1-SNAPSHOT' //NMS classes - compile project('Spigot_1_16_R1') - compile project('Spigot_1_16_R2') + //compile project('Spigot_1_16_R1') + //compile project('Spigot_1_16_R2') compile project('Spigot_1_16_R3') } diff --git a/src/main/java/nl/thedutchmc/SkinFixer/changeSkin/SkinChangeOrchestrator.java b/src/main/java/nl/thedutchmc/SkinFixer/changeSkin/SkinChangeOrchestrator.java index cf46ab0..2b4d070 100644 --- a/src/main/java/nl/thedutchmc/SkinFixer/changeSkin/SkinChangeOrchestrator.java +++ b/src/main/java/nl/thedutchmc/SkinFixer/changeSkin/SkinChangeOrchestrator.java @@ -1,5 +1,10 @@ package nl.thedutchmc.SkinFixer.changeSkin; +import java.lang.reflect.Array; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.util.HashSet; +import java.util.Set; import java.util.UUID; import org.bukkit.Bukkit; @@ -10,11 +15,14 @@ import org.json.JSONObject; import org.json.JSONTokener; +import com.google.common.hash.Hashing; + import net.md_5.bungee.api.ChatColor; import nl.thedutchmc.SkinFixer.SkinFixer; import nl.thedutchmc.SkinFixer.SkinObject; import nl.thedutchmc.SkinFixer.changeSkin.changeGameProfile.*; import nl.thedutchmc.SkinFixer.fileHandlers.StorageHandler; +import nl.thedutchmc.SkinFixer.util.ReflectionUtil; public class SkinChangeOrchestrator { @@ -86,6 +94,7 @@ private static void changeSkin(String skinValue, String skinSignature, UUID call @Override public void run() { //NMS is version dependant. So we need to set the correct class to use. + //TODO switch this over to use reflection switch(SkinFixer.NMS_VERSION) { case "v1_16_R1": ChangeGameProfile_1_16_r1.changeProfile(player.getUniqueId(), skinValue, skinSignature); break; case "v1_16_R2": ChangeGameProfile_1_16_r2.changeProfile(player.getUniqueId(), skinValue, skinSignature); break; @@ -106,31 +115,202 @@ public void run() { player.sendMessage(ChatColor.GOLD + "Done."); } + private static void applySkin(Player player, String skinValue, String skinSignature) { + } + + /** + * Reload the Player so the Player's new skin shows for other players and the Player itself + * @param player The Player to reload + */ private static void reloadPlayer(Player player) { new BukkitRunnable() { @Override public void run() { - Location loc = player.getLocation().clone(); + Location playerLocation = player.getLocation().clone(); //Reload the player for all online players - for(Player p : Bukkit.getOnlinePlayers()) { + //This is so all other Players can see the new skin + Bukkit.getOnlinePlayers().forEach(p -> { p.hidePlayer(SkinFixer.INSTANCE, player); p.showPlayer(SkinFixer.INSTANCE, player); - } + }); - World teleportToWorld = null; - for(World w : Bukkit.getWorlds()) { - if(!w.equals(loc.getWorld())) teleportToWorld = w; - } - - player.teleport(new Location(teleportToWorld, 0, 255, 0)); - new BukkitRunnable() { - @Override - public void run() { - player.teleport(loc); - player.updateInventory(); + //Remove and re-add the Player to the world + //This is so the Player who applied the skin can see their new skin + try { + //Get the CraftPlayer class and turn our regular Player into an EntityPlayer + Class craftPlayerClass = ReflectionUtil.getBukkitClass("entity.CraftPlayer"); + Object entityPlayer = ReflectionUtil.invokeMethod(craftPlayerClass, player, "getHandle"); + + //Get the CraftWorld class and turn our regular World into a WorldServer + Class craftWorldClass = ReflectionUtil.getBukkitClass("CraftWorld"); + Object worldServer = ReflectionUtil.invokeMethod(craftWorldClass, playerLocation.getWorld(), "getHandle"); + + //Get the PlayerInteractManagar, + //From that get the Gamemode of the player as an EnumGamemode and the numerical ID for that + Object playerIntManager = ReflectionUtil.getObject(entityPlayer, "playerInteractManager"); + Enum enumGamemode = (Enum) ReflectionUtil.invokeMethod(playerIntManager, "getGameMode"); + int gamemodeId = (int) ReflectionUtil.invokeMethod(enumGamemode, "getId"); + + //Get the World's seed, and hash it with sha256 + Object seed = ReflectionUtil.invokeMethod(playerLocation.getWorld(), "getSeed"); + long seedHashed = Hashing.sha256().hashString(seed.toString(), StandardCharsets.UTF_8).asLong(); + + //Get the EnumGamemode value from the gamemode ID. + //We can't use ReflectionUtil to invoke the method because that convert + //the primitive int to it's wrapper Integer. + Method getGamemodeByIdMethod = ReflectionUtil.getMethod(enumGamemode.getClass(), "getById", int.class); + Object gamemodeEnumConst = getGamemodeByIdMethod.invoke(null, gamemodeId); + + //PacketPlayOutRespawn Class + Class playPacketOutRespawnClass = ReflectionUtil.getNmsClass("PacketPlayOutRespawn"); + + //Instantiate the PacketPlayOutRespawn packet, + //Different Minecraft versions have slightly different constructors, so we have multiple + Object packetPlayOutRespawn; + try { + // 1.16.1 + + //TypeKey and DimensionKey + Object typeKey = ReflectionUtil.invokeMethod(worldServer.getClass().getSuperclass(), worldServer, "getTypeKey"); + Object dimensionKey = ReflectionUtil.invokeMethod(worldServer.getClass().getSuperclass(), worldServer, "getDimensionKey"); + + //Instantiate the Packet + packetPlayOutRespawn = ReflectionUtil.invokeConstructor(playPacketOutRespawnClass, + new Class[] { + typeKey.getClass(), + dimensionKey.getClass(), + long.class, + enumGamemode.getClass(), + enumGamemode.getClass(), + boolean.class, + boolean.class, + boolean.class + }, new Object[] { + typeKey, + dimensionKey, + seedHashed, + gamemodeEnumConst, + gamemodeEnumConst, + + //isDebugWorld is in the nms class World, which WorldServer extends + ReflectionUtil.invokeMethod(worldServer.getClass().getSuperclass(), worldServer, "isDebugWorld"), + + //isFlatWorld is in the nms class WorldServer for some reason (I expected it in the nms class World) + ReflectionUtil.invokeMethod(worldServer, "isFlatWorld"), + true + }); + + } catch(Exception ignored) { + // 1.16.2+ + + // DimensionManager and DimensionKey + Object dimensionManager = ReflectionUtil.invokeMethod(worldServer.getClass().getSuperclass(), worldServer, "getDimensionManager"); + Object dimensionKey = ReflectionUtil.invokeMethod(worldServer.getClass().getSuperclass(), worldServer, "getDimensionKey"); + + /* PacketPlayOutRespawn: + * Mojang's variable names to their 'I know what this is'-name + * a: (DimensionManager) DimensionManager + * b: (ResourceKey) ResourceKey + * c: (long) Sha256 of the seed + * d: (GameType) PlayerGameType + * e: (GameType) previousPlayerGameType + * f: (boolean) isDebug + * g: (boolean) isFlat + * h: (boolean) keepAllPlayerData + * */ + packetPlayOutRespawn = ReflectionUtil.invokeConstructor(playPacketOutRespawnClass, + new Class[] { + dimensionManager.getClass(), + dimensionKey.getClass(), + long.class, + enumGamemode.getClass(), + enumGamemode.getClass(), + boolean.class, + boolean.class, + boolean.class + }, new Object[] { + dimensionManager, + dimensionKey, + seedHashed, + gamemodeEnumConst, + gamemodeEnumConst, + + //isDebugWorld is in the nms class World, which WorldServer extends + ReflectionUtil.invokeMethod(worldServer.getClass().getSuperclass(), worldServer, "isDebugWorld"), + + //isFlatWorld is in the nms class WorldServer for some reason (I expected it in the nms class World) + ReflectionUtil.invokeMethod(worldServer, "isFlatWorld"), + true + }); + } + + //PacketPlayOutPosition + Class packetPlayOutPositionClass = ReflectionUtil.getNmsClass("PacketPlayOutPosition"); + Object packetPlayOutPosition = ReflectionUtil.invokeConstructor(packetPlayOutPositionClass, + new Class[] { double.class, double.class, double.class, float.class, float.class, Set.class, int.class }, + new Object[] { playerLocation.getX(), playerLocation.getY(), playerLocation.getZ(), playerLocation.getYaw(), playerLocation.getPitch(), new HashSet>(), 0 }); + + //PacketPlayOutHeldItem + Class packetPlayOutHeldItemSlotClass = ReflectionUtil.getNmsClass("PacketPlayOutHeldItemSlot"); + Object packetPlayOutHeldItemSlot = ReflectionUtil.invokeConstructor(packetPlayOutHeldItemSlotClass, + new Class[] { int.class }, + new Object[] { player.getInventory().getHeldItemSlot() }); + + //Get the EntityPlayers' connection + Object playerConnection = ReflectionUtil.getObject(entityPlayer, "playerConnection"); + + //Get the Enum constants for REMOVE_PLAYER and ADD_PLAYER + Class packetPlayOutPlayerInfo = ReflectionUtil.getNmsClass("PacketPlayOutPlayerInfo"); + Object removePlayerEnumConst = ReflectionUtil.getEnum(packetPlayOutPlayerInfo, "EnumPlayerInfoAction", "REMOVE_PLAYER"); + Object addPlayerEnumConst = ReflectionUtil.getEnum(packetPlayOutPlayerInfo, "EnumPlayerInfoAction", "ADD_PLAYER"); + + //Create an Array of EntityPlayer with size = 1 and add our player to it + Object entityPlayerArr = Array.newInstance(entityPlayer.getClass(), 1); + Array.set(entityPlayerArr, 0, entityPlayer); + + //Construct a PacketPlayOutPlayerInfo with intention REMOVE_PLAYER + Object packetPlayOutRemovePlayer = ReflectionUtil.invokeConstructor(packetPlayOutPlayerInfo, + new Class[] { removePlayerEnumConst.getClass(), entityPlayerArr.getClass() }, + new Object[] { removePlayerEnumConst, entityPlayerArr }); + + //Construct a PacketPlayOutPlayerInfo with intention ADD_PLAYER + Object packetPlayOutAddPlayer = ReflectionUtil.invokeConstructor(packetPlayOutPlayerInfo, + new Class[] { addPlayerEnumConst.getClass(), entityPlayerArr.getClass() }, + new Object[] { addPlayerEnumConst, entityPlayerArr }); + + //Get the generic Packet class + Class packetClass = ReflectionUtil.getNmsClass("Packet"); + + //Send both the PacketPlayOutPlayerInfo packets + ReflectionUtil.invokeMethod(playerConnection, "sendPacket", new Class[] { packetClass }, new Object[] { packetPlayOutRemovePlayer }); + ReflectionUtil.invokeMethod(playerConnection, "sendPacket", new Class[] { packetClass }, new Object[] { packetPlayOutAddPlayer }); + + //Send the PacketPlayOutRespawn packet + ReflectionUtil.invokeMethod(playerConnection, "sendPacket", new Class[] { packetClass }, new Object[] { packetPlayOutRespawn }); + + //Update the Player's abilities + ReflectionUtil.invokeMethod(entityPlayer, "updateAbilities"); + + //Send both the PacketPlayOutPosition and PacketPlayOutHeldItem packets + ReflectionUtil.invokeMethod(playerConnection, "sendPacket", new Class[] { packetClass }, new Object[] {packetPlayOutPosition}); + ReflectionUtil.invokeMethod(playerConnection, "sendPacket", new Class[] { packetClass }, new Object[] {packetPlayOutHeldItemSlot}); + + //Update the Player's healthbar and inventory + ReflectionUtil.invokeMethod(player, "updateScaledHealth"); + ReflectionUtil.invokeMethod(player, "updateInventory"); + ReflectionUtil.invokeMethod(entityPlayer, "triggerHealthUpdate"); + + //If the Player is OP, we have to toggle it off and back on really quickly for it to work + if(player.isOp()) { + Bukkit.getScheduler().runTask(SkinFixer.INSTANCE, () -> { + player.setOp(false); + player.setOp(true); + }); } - }.runTaskLater(SkinFixer.INSTANCE, 5L); + } catch(Exception e) { + e.printStackTrace(); + } } }.runTask(SkinFixer.INSTANCE); } diff --git a/src/main/java/nl/thedutchmc/SkinFixer/util/ReflectionUtil.java b/src/main/java/nl/thedutchmc/SkinFixer/util/ReflectionUtil.java new file mode 100644 index 0000000..d0b24ad --- /dev/null +++ b/src/main/java/nl/thedutchmc/SkinFixer/util/ReflectionUtil.java @@ -0,0 +1,263 @@ +package nl.thedutchmc.SkinFixer.util; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.bukkit.Bukkit; + +public class ReflectionUtil { + + public static String SERVER_VERSION; + + static { + //Load the Bukit class + try { + Class.forName("org.bukkit.Bukkit"); + } catch (ClassNotFoundException ignored) {} + + //Get the version String + SERVER_VERSION = Bukkit.getServer().getClass().getPackage().getName().substring(Bukkit.getServer().getClass().getPackage().getName().lastIndexOf('.') + 1); + } + + /** + * Get a Class from the org.bukkit.craftbukkit.SERVER_VERSION. package + * @param className The name of the class + * @return Returns the Class + * @throws ClassNotFoundException Thrown when the Class was not found + */ + public static Class getBukkitClass(String className) throws ClassNotFoundException { + return Class.forName("org.bukkit.craftbukkit." + SERVER_VERSION + "." + className); + } + + /** + * Get a Class from the net.minecraft.server.SERVER_VERSION. package + * @param className The name of the class + * @return Returns the Class + * @throws ClassNotFoundException Thrown when the Class was not found + */ + public static Class getNmsClass(String className) throws ClassNotFoundException { + return Class.forName("net.minecraft.server." + SERVER_VERSION + "." + className); + } + + /** + * Get the Constructor of a Class + * @param clazz The Class in which the Constructor is defined + * @param args Arguments taken by the Constructor + * @return Returns the Constructor + * @throws NoSuchMethodException Thrown when no Constructor in the Class was found with the provided combination of arguments + */ + public static Constructor getConstructor(Class clazz, Class... args) throws NoSuchMethodException { + Constructor con = clazz.getConstructor(args); + con.setAccessible(true); + + return con; + } + + /** + * Get an Enum from an Enum constant + * @param clazz The Class in which the Enum is defined + * @param constant The name of the Enum Constant + * @return Returns the Enum + * @throws ClassNotFoundException + */ + public static Enum getEnum(Class clazz, String constant) throws ClassNotFoundException { + Class c = Class.forName(clazz.getName()); + Enum[] constants = (Enum[]) c.getEnumConstants(); + + for(Enum e : constants) { + if(e.name().equalsIgnoreCase(constant)) { + return e; + } + } + + return null; + } + + /** + * Get an Enum constant by it's name and constant + * @param clazz The Class in which the Enum is defined + * @param enumname The name of the Enum + * @param constant The name of the Constant + * @return Returns the Enum + * @throws ClassNotFoundException + */ + public static Enum getEnum(Class clazz, String enumname, String constant) throws ClassNotFoundException { + Class c = Class.forName(clazz.getName() + "$" + enumname); + Enum[] econstants = (Enum[]) c.getEnumConstants(); + + for (Enum e : econstants) { + if (e.name().equalsIgnoreCase(constant)) { + return e; + } + } + + return null; + } + + /** + * Get a Field + * @param clazz The Class in which the Field is defined + * @param fieldName The name of the Field + * @return Returns the Field + * @throws NoSuchFieldException Thrown when the Field was not present in the Class + */ + public static Field getField(Class clazz, String fieldName) throws NoSuchFieldException { + Field f = clazz.getDeclaredField(fieldName); + f.setAccessible(true); + return f; + } + + /** + * Get a Method + * @param clazz The Class in which the Method is defined + * @param methodName The name of the method + * @param args The argument types the method takes + * @return Returns the Method + * @throws NoSuchMethodException + */ + public static Method getMethod(Class clazz, String methodName, Class... args) throws NoSuchMethodException { + Method m = clazz.getDeclaredMethod(methodName, args); + m.setAccessible(true); + return m; + } + + /** + * Invoke a Method which takes no arguments. The Class in which the Method is defined is derived from the provided Object + * @param obj The object to invoke the method on + * @param methodName The name of the Method + * @return Returns the result of the method, can be null if the method returns void + * @throws NoSuchMethodException + * @throws IllegalAccessException + * @throws IllegalArgumentException + * @throws InvocationTargetException + */ + public static Object invokeMethod(Object obj, String methodName) throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { + Method m = getMethod(obj.getClass(), methodName); + return m.invoke(obj); + } + + /** + * Invoke a Method where the argument types are derived from the provided arguments. The Class in which the Method is defined is derived from the provided Object + * @param obj The object to invoke the method on + * @param methodName The name of the Method + * @param args The arguments to pass to the Method + * @return Returns the result of the method, can be null if the method returns void + * @throws NoSuchMethodException + * @throws IllegalAccessException + * @throws IllegalArgumentException + * @throws InvocationTargetException + */ + public static Object invokeMethod(Object obj, String methodName, Object[] args) throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { + return invokeMethod(obj.getClass(), obj, methodName, args); + } + + /** + * Invoke a Method where the argument types are explicitly given (Helpful when working with primitives). The Class in which the Method is defined is derived from the provided Object. + * @param obj The Object to invoke the method on + * @param methodName The name of the Method + * @param argTypes The types of arguments as a Class array + * @param args The arguments as an object array + * @return Returns the result of the method, can be null if the method returns void + * @throws NoSuchMethodException + * @throws IllegalAccessException + * @throws IllegalArgumentException + * @throws InvocationTargetException + */ + public static Object invokeMethod(Object obj, String methodName, Class[] argTypes, Object[] args) throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { + Method m = getMethod(obj.getClass(), methodName, argTypes); + return m.invoke(obj, args); + } + + /** + * Invoke a Method where the Class where to find the method is explicitly given (Helpful if the method is located in a superclass). The argument types are derived from the provided arguments + * @param clazz The Class where the method is located + * @param obj The Object to invoke the method on + * @param method The name of the method + * @param args The arguments to be passed to the method + * @return Returns the result of the method, can be null if the method returns void + * @throws NoSuchMethodException + * @throws IllegalAccessException + * @throws IllegalArgumentException + * @throws InvocationTargetException + */ + public static Object invokeMethod(Class clazz, Object obj, String method, Object... args) throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { + Class[] argTypes = new Class[args.length]; + for(int i = 0; i < args.length; i++) { + argTypes[i] = args[i].getClass(); + } + + Method m = getMethod(clazz, method, argTypes); + + return m.invoke(obj, args); + } + + /** + * Get the value of a Field, where the Class in which the field is defined is derived from the provided Object + * @param obj The object in which the field is located, and from which to get the value + * @param name The name of the Field to get the value from + * @return Returns the value of the Field + * @throws NoSuchFieldException + * @throws IllegalArgumentException + * @throws IllegalAccessException + */ + public static Object getObject(Object obj, String name) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException { + Field f = getField(obj.getClass(), name); + return f.get(obj); + } + + /** + * Get the value of a Field, where the Class in which the Field is defined is explicitly given. (Helpful when the Field is in a superclass) + * @param obj The Object to get the value from + * @param clazz The Class in which the Field is defined + * @param name The name of the Field + * @return Returns the value of the Field + * @throws NoSuchFieldException + * @throws IllegalArgumentException + * @throws IllegalAccessException + */ + public static Object getObject(Object obj, Class clazz, String name) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException { + Field f = getField(clazz, name); + return f.get(obj); + } + + /** + * Invoke a Class' constructor. The argument types are derived from the provided arguments + * @param clazz The Class in which the Constructor is defined + * @param args The arguments to pass to the Constructor + * @return Returns an instance of the provided Class in which the Constructor is located + * @throws NoSuchMethodException + * @throws InstantiationException + * @throws IllegalAccessException + * @throws IllegalArgumentException + * @throws InvocationTargetException + */ + public static Object invokeConstructor(Class clazz, Object... args) throws NoSuchMethodException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { + Class[] argTypes = new Class[args.length]; + for(int i = 0; i < args.length; i++) { + argTypes[i] = args[i].getClass(); + } + + Constructor con = getConstructor(clazz, argTypes); + + return con.newInstance(args); + } + + /** + * Invoke a Class' Constructor, where the argument types are explicitly given (Helpful when working with primitives) + * @param clazz The Class in which the Constructor is defined + * @param argTypes The argument types + * @param args The arguments to pass to the constructor + * @return Returns an instance of the provided Class in which the Constructor is located + * @throws NoSuchMethodException + * @throws InstantiationException + * @throws IllegalAccessException + * @throws IllegalArgumentException + * @throws InvocationTargetException + */ + public static Object invokeConstructor(Class clazz, Class[] argTypes, Object[] args) throws NoSuchMethodException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { + Constructor con = getConstructor(clazz, argTypes); + return con.newInstance(args); + } +} \ No newline at end of file From 024c94a21d769e6bac573546a1b27c639b676b23 Mon Sep 17 00:00:00 2001 From: Tobias de Bruijn Date: Thu, 18 Mar 2021 21:11:35 +0100 Subject: [PATCH 2/2] Code Quality & Version dependency - Switched from Apache's HTTP client to my own HttpLib - Switched from org.json to Google's gson - Added LOG4J for JDA's Logging needs - Switched to reflection for applying the skin, this means we're less version dependant (It works until Mojang changes its code) - Added several Util functions - Added a Triple, this is sortof to emulate Rust's Result - Renamed and moved some things around to comform to Google's naming standards and to make the codebase more organized --- Spigot_1_16_R1/build.gradle | 5 - .../ChangeGameProfile_1_16_r1.java | 54 -------- Spigot_1_16_R2/build.gradle | 5 - .../ChangeGameProfile_1_16_r2.java | 50 ------- Spigot_1_16_R3/build.gradle | 5 - .../ChangeGameProfile_1_16_r3.java | 50 ------- build.gradle | 25 ++-- .../nl/thedutchmc/SkinFixer/SkinFixer.java | 12 +- .../SkinFixer/apis/MineskinApi.java | 67 ++++++++++ .../thedutchmc/SkinFixer/apis/MojangApi.java | 46 +++++++ .../changeSkin/CheckUserAgainstMojang.java | 74 ----------- .../SkinFixer/changeSkin/GetSkin.java | 91 ------------- ...chestrator.java => SkinChangeHandler.java} | 123 +++++++++++------- .../GetCodeCommandExecutor.java | 4 +- .../SetSkinCommandExecutor.java | 10 +- .../SkinFixerCommandExecutor.java | 2 +- .../AddNewSkin.java | 2 +- .../MessageReceivedEventListener.java | 2 +- .../SkinFixer/gson/GetSkinResponse.java | 34 +++++ .../SkinFixer/gson/MojangAuthResponse.java | 13 ++ .../PlayerJoinEventListener.java | 22 ++-- .../SkinFixer/util/ReflectionUtil.java | 48 ++++++- .../nl/thedutchmc/SkinFixer/util/Triple.java | 27 ++++ .../nl/thedutchmc/SkinFixer/util/Utils.java | 27 ++++ src/main/resources/log4j.properties | 5 + 25 files changed, 383 insertions(+), 420 deletions(-) delete mode 100644 Spigot_1_16_R1/build.gradle delete mode 100644 Spigot_1_16_R1/src/main/java/nl/thedutchmc/SkinFixer/changeSkin/changeGameProfile/ChangeGameProfile_1_16_r1.java delete mode 100644 Spigot_1_16_R2/build.gradle delete mode 100644 Spigot_1_16_R2/src/main/java/nl/thedutchmc/SkinFixer/changeSkin/changeGameProfile/ChangeGameProfile_1_16_r2.java delete mode 100644 Spigot_1_16_R3/build.gradle delete mode 100644 Spigot_1_16_R3/src/main/java/nl/thedutchmc/SkinFixer/changeSkin/changeGameProfile/ChangeGameProfile_1_16_r3.java create mode 100644 src/main/java/nl/thedutchmc/SkinFixer/apis/MineskinApi.java create mode 100644 src/main/java/nl/thedutchmc/SkinFixer/apis/MojangApi.java delete mode 100644 src/main/java/nl/thedutchmc/SkinFixer/changeSkin/CheckUserAgainstMojang.java delete mode 100644 src/main/java/nl/thedutchmc/SkinFixer/changeSkin/GetSkin.java rename src/main/java/nl/thedutchmc/SkinFixer/changeSkin/{SkinChangeOrchestrator.java => SkinChangeHandler.java} (72%) rename src/main/java/nl/thedutchmc/SkinFixer/{commandHandlers => commandexecutors}/GetCodeCommandExecutor.java (89%) rename src/main/java/nl/thedutchmc/SkinFixer/{commandHandlers => commandexecutors}/SetSkinCommandExecutor.java (83%) rename src/main/java/nl/thedutchmc/SkinFixer/{commandHandlers => commandexecutors}/SkinFixerCommandExecutor.java (96%) rename src/main/java/nl/thedutchmc/SkinFixer/{commonEventMethods => common}/AddNewSkin.java (92%) create mode 100644 src/main/java/nl/thedutchmc/SkinFixer/gson/GetSkinResponse.java create mode 100644 src/main/java/nl/thedutchmc/SkinFixer/gson/MojangAuthResponse.java rename src/main/java/nl/thedutchmc/SkinFixer/{minecraftEvents => minecraftevents}/PlayerJoinEventListener.java (51%) create mode 100644 src/main/java/nl/thedutchmc/SkinFixer/util/Triple.java create mode 100644 src/main/java/nl/thedutchmc/SkinFixer/util/Utils.java create mode 100644 src/main/resources/log4j.properties diff --git a/Spigot_1_16_R1/build.gradle b/Spigot_1_16_R1/build.gradle deleted file mode 100644 index 09d3d84..0000000 --- a/Spigot_1_16_R1/build.gradle +++ /dev/null @@ -1,5 +0,0 @@ -apply plugin: 'java' -dependencies { - compileOnly 'org.spigotmc:spigot:1.16.1-R0.1-SNAPSHOT' -} - diff --git a/Spigot_1_16_R1/src/main/java/nl/thedutchmc/SkinFixer/changeSkin/changeGameProfile/ChangeGameProfile_1_16_r1.java b/Spigot_1_16_R1/src/main/java/nl/thedutchmc/SkinFixer/changeSkin/changeGameProfile/ChangeGameProfile_1_16_r1.java deleted file mode 100644 index c6a326e..0000000 --- a/Spigot_1_16_R1/src/main/java/nl/thedutchmc/SkinFixer/changeSkin/changeGameProfile/ChangeGameProfile_1_16_r1.java +++ /dev/null @@ -1,54 +0,0 @@ -package nl.thedutchmc.SkinFixer.changeSkin.changeGameProfile; - -import java.util.UUID; - -import org.bukkit.Bukkit; -import org.bukkit.craftbukkit.v1_16_R1.entity.CraftPlayer; -import org.bukkit.entity.Player; - -import com.mojang.authlib.GameProfile; -import com.mojang.authlib.properties.Property; -import com.mojang.authlib.properties.PropertyMap; - -import net.minecraft.server.v1_16_R1.EntityPlayer; -import net.minecraft.server.v1_16_R1.PacketPlayOutPlayerInfo; -import net.minecraft.server.v1_16_R1.PacketPlayOutPlayerInfo.EnumPlayerInfoAction; - -/* It is normal for all net.minecraft.server and org.bukkit.craftbukkit imports - * to error when you are not working on that version. - * Gradle cannot handle multiple versions - */ -public class ChangeGameProfile_1_16_r1 { - - public static void changeProfile(UUID uuid, String skinValue, String skinSignature) { - Player player = Bukkit.getPlayer(uuid); - - //Fetch the EntityPlayer and their GameProfile - EntityPlayer ep = ((CraftPlayer)player).getHandle(); - GameProfile gp = ep.getProfile(); - - //Get the skin texture property - PropertyMap pm = gp.getProperties(); - - //Check if the propertyMap contains a texture value, if so, remove it. - if(pm.containsKey("textures")) { - Property property = pm.get("textures").iterator().next(); - pm.remove("textures", property); - } - - //Remove the old texture, and set the new one. - pm.put("textures", new Property("textures", skinValue, skinSignature)); - - //Reload the skin for the player itself - reloadSkinForSelf(player); - } - - public static void reloadSkinForSelf(Player player) { - final EntityPlayer ep = ((CraftPlayer) player).getHandle(); - final PacketPlayOutPlayerInfo removeInfo = new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.REMOVE_PLAYER, ep); - final PacketPlayOutPlayerInfo addInfo = new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.ADD_PLAYER, ep); - - ep.playerConnection.sendPacket(removeInfo); - ep.playerConnection.sendPacket(addInfo); - } -} diff --git a/Spigot_1_16_R2/build.gradle b/Spigot_1_16_R2/build.gradle deleted file mode 100644 index 3613e21..0000000 --- a/Spigot_1_16_R2/build.gradle +++ /dev/null @@ -1,5 +0,0 @@ -apply plugin: 'java' -dependencies { - compileOnly 'org.spigotmc:spigot:1.16.2-R0.1-SNAPSHOT' -} - diff --git a/Spigot_1_16_R2/src/main/java/nl/thedutchmc/SkinFixer/changeSkin/changeGameProfile/ChangeGameProfile_1_16_r2.java b/Spigot_1_16_R2/src/main/java/nl/thedutchmc/SkinFixer/changeSkin/changeGameProfile/ChangeGameProfile_1_16_r2.java deleted file mode 100644 index dc8c77f..0000000 --- a/Spigot_1_16_R2/src/main/java/nl/thedutchmc/SkinFixer/changeSkin/changeGameProfile/ChangeGameProfile_1_16_r2.java +++ /dev/null @@ -1,50 +0,0 @@ -package nl.thedutchmc.SkinFixer.changeSkin.changeGameProfile; - -import java.util.UUID; - -import org.bukkit.Bukkit; -import org.bukkit.craftbukkit.v1_16_R2.entity.CraftPlayer; -import org.bukkit.entity.Player; - -import com.mojang.authlib.GameProfile; -import com.mojang.authlib.properties.Property; -import com.mojang.authlib.properties.PropertyMap; - -import net.minecraft.server.v1_16_R2.EntityPlayer; -import net.minecraft.server.v1_16_R2.PacketPlayOutPlayerInfo; -import net.minecraft.server.v1_16_R2.PacketPlayOutPlayerInfo.EnumPlayerInfoAction; - -public class ChangeGameProfile_1_16_r2 { - - public static void changeProfile(UUID uuid, String skinValue, String skinSignature) { - Player player = Bukkit.getPlayer(uuid); - - //Fetch the EntityPlayer and their GameProfile - EntityPlayer ep = ((CraftPlayer)player).getHandle(); - GameProfile gp = ep.getProfile(); - - //Get the skin texture property - PropertyMap pm = gp.getProperties(); - - //Check if the propertyMap contains a texture value, if so, remove it. - if(pm.containsKey("textures")) { - Property property = pm.get("textures").iterator().next(); - pm.remove("textures", property); - } - - //Remove the old texture, and set the new one. - pm.put("textures", new Property("textures", skinValue, skinSignature)); - - //Reload the skin for the player itself - reloadSkinForSelf(player); - } - - public static void reloadSkinForSelf(Player player) { - final EntityPlayer ep = ((CraftPlayer) player).getHandle(); - final PacketPlayOutPlayerInfo removeInfo = new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.REMOVE_PLAYER, ep); - final PacketPlayOutPlayerInfo addInfo = new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.ADD_PLAYER, ep); - - ep.playerConnection.sendPacket(removeInfo); - ep.playerConnection.sendPacket(addInfo); - } -} diff --git a/Spigot_1_16_R3/build.gradle b/Spigot_1_16_R3/build.gradle deleted file mode 100644 index 09cc934..0000000 --- a/Spigot_1_16_R3/build.gradle +++ /dev/null @@ -1,5 +0,0 @@ -apply plugin: 'java' -dependencies { - compileOnly 'org.spigotmc:spigot:1.16.4-R0.1-SNAPSHOT' -} - diff --git a/Spigot_1_16_R3/src/main/java/nl/thedutchmc/SkinFixer/changeSkin/changeGameProfile/ChangeGameProfile_1_16_r3.java b/Spigot_1_16_R3/src/main/java/nl/thedutchmc/SkinFixer/changeSkin/changeGameProfile/ChangeGameProfile_1_16_r3.java deleted file mode 100644 index f7ccca7..0000000 --- a/Spigot_1_16_R3/src/main/java/nl/thedutchmc/SkinFixer/changeSkin/changeGameProfile/ChangeGameProfile_1_16_r3.java +++ /dev/null @@ -1,50 +0,0 @@ -package nl.thedutchmc.SkinFixer.changeSkin.changeGameProfile; - -import java.util.UUID; - -import org.bukkit.Bukkit; -import org.bukkit.craftbukkit.v1_16_R3.entity.CraftPlayer; -import org.bukkit.entity.Player; - -import com.mojang.authlib.GameProfile; -import com.mojang.authlib.properties.Property; -import com.mojang.authlib.properties.PropertyMap; - -import net.minecraft.server.v1_16_R3.EntityPlayer; -import net.minecraft.server.v1_16_R3.PacketPlayOutPlayerInfo; -import net.minecraft.server.v1_16_R3.PacketPlayOutPlayerInfo.EnumPlayerInfoAction; - -public class ChangeGameProfile_1_16_r3 { - - public static void changeProfile(UUID uuid, String skinValue, String skinSignature) { - Player player = Bukkit.getPlayer(uuid); - - //Fetch the EntityPlayer and their GameProfile - EntityPlayer ep = ((CraftPlayer)player).getHandle(); - GameProfile gp = ep.getProfile(); - - //Get the skin texture property - PropertyMap pm = gp.getProperties(); - - //Check if the propertyMap contains a texture value, if so, remove it. - if(pm.containsKey("textures")) { - Property property = pm.get("textures").iterator().next(); - pm.remove("textures", property); - } - - //Remove the old texture, and set the new one. - pm.put("textures", new Property("textures", skinValue, skinSignature)); - - //Reload the skin for the player itself - reloadSkinForSelf(player); - } - - public static void reloadSkinForSelf(Player player) { - final EntityPlayer ep = ((CraftPlayer) player).getHandle(); - final PacketPlayOutPlayerInfo removeInfo = new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.REMOVE_PLAYER, ep); - final PacketPlayOutPlayerInfo addInfo = new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.ADD_PLAYER, ep); - - ep.playerConnection.sendPacket(removeInfo); - ep.playerConnection.sendPacket(addInfo); - } -} diff --git a/build.gradle b/build.gradle index 0851169..2e2c3b5 100644 --- a/build.gradle +++ b/build.gradle @@ -18,10 +18,10 @@ allprojects { repositories { jcenter() - mavenLocal() mavenCentral() maven{ url "https://hub.spigotmc.org/nexus/content/repositories/snapshots" } maven{ url "https://oss.sonatype.org/content/repositories/snapshots" } + maven{ url 'https://jitpack.io' } } processResources { @@ -36,30 +36,31 @@ allprojects { } dependencies { - //Apache HTTP Client - compile 'org.apache.httpcomponents:httpclient:4.5.12' + //HTTP client + compile 'com.github.TheDutchMC:HttpLib:1.1' + + //Logging framework + compile 'org.slf4j:slf4j-log4j12:1.7.29' //JSON - compile 'org.json:json:20200518' + compile 'com.google.code.gson:gson:2.8.6' //JDA - compile ('net.dv8tion:JDA:4.2.0_168') { + compile('net.dv8tion:JDA:4.2.0_168') { exclude module: 'opus-java' } - //Spigot 1.16.1 + //Spigot API compileOnly 'org.spigotmc:spigot-api:1.16.1-R0.1-SNAPSHOT' - - //NMS classes - //compile project('Spigot_1_16_R1') - //compile project('Spigot_1_16_R2') - compile project('Spigot_1_16_R3') } shadowJar() { classifier = '' - relocate 'net.dv8tion.jda', 'nl.thedutchmc.libs.jda' + relocate 'net.dv8tion.jda', 'nl.thedutchmc.skinfixer.libs.net.dv8tion.jda' + relocate 'com.google.code.gson', 'nl.thedutchmc.skinfixer.libs.com.google.code.gson' + relocate 'nl.thedutchmc.httplib', 'nl.thedutchmc.skinfixer.libs.nl.thedutchmc.httplib' + relocate 'org.slf4j', 'nl.thedutchmc.skinfixer.libs.org.slf4j' } task testJar(type: ShadowJar) { diff --git a/src/main/java/nl/thedutchmc/SkinFixer/SkinFixer.java b/src/main/java/nl/thedutchmc/SkinFixer/SkinFixer.java index b92b351..ea6f7d3 100644 --- a/src/main/java/nl/thedutchmc/SkinFixer/SkinFixer.java +++ b/src/main/java/nl/thedutchmc/SkinFixer/SkinFixer.java @@ -1,14 +1,16 @@ package nl.thedutchmc.SkinFixer; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; import org.bukkit.Bukkit; import org.bukkit.plugin.java.JavaPlugin; -import nl.thedutchmc.SkinFixer.commandHandlers.GetCodeCommandExecutor; -import nl.thedutchmc.SkinFixer.commandHandlers.SetSkinCommandExecutor; -import nl.thedutchmc.SkinFixer.commandHandlers.SkinFixerCommandExecutor; +import nl.thedutchmc.SkinFixer.commandexecutors.GetCodeCommandExecutor; +import nl.thedutchmc.SkinFixer.commandexecutors.SetSkinCommandExecutor; +import nl.thedutchmc.SkinFixer.commandexecutors.SkinFixerCommandExecutor; import nl.thedutchmc.SkinFixer.fileHandlers.ConfigurationHandler; import nl.thedutchmc.SkinFixer.fileHandlers.StorageHandler; -import nl.thedutchmc.SkinFixer.minecraftEvents.PlayerJoinEventListener; +import nl.thedutchmc.SkinFixer.minecraftevents.PlayerJoinEventListener; public class SkinFixer extends JavaPlugin { @@ -18,6 +20,8 @@ public class SkinFixer extends JavaPlugin { public static final String NMS_VERSION = Bukkit.getServer().getClass().getPackage().getName().substring(23); public static String PLUGIN_VERSION; + public static final Logger LOGGER = LogManager.getLogger(SkinFixer.class); + @Override public void onEnable() { INSTANCE = this; diff --git a/src/main/java/nl/thedutchmc/SkinFixer/apis/MineskinApi.java b/src/main/java/nl/thedutchmc/SkinFixer/apis/MineskinApi.java new file mode 100644 index 0000000..7b8a8fa --- /dev/null +++ b/src/main/java/nl/thedutchmc/SkinFixer/apis/MineskinApi.java @@ -0,0 +1,67 @@ +package nl.thedutchmc.SkinFixer.apis; + +import java.io.IOException; +import java.util.HashMap; + +import com.google.gson.Gson; + +import nl.thedutchmc.SkinFixer.SkinFixer; +import nl.thedutchmc.SkinFixer.gson.GetSkinResponse; +import nl.thedutchmc.SkinFixer.util.Triple; +import nl.thedutchmc.SkinFixer.util.Utils; +import nl.thedutchmc.httplib.Http; +import nl.thedutchmc.httplib.Http.RequestMethod; +import nl.thedutchmc.httplib.Http.ResponseObject; + + +public class MineskinApi { + + public Triple getSkin(String skinUrl, boolean slim) { + HashMap urlParameters = new HashMap<>(); + urlParameters.put("User-Agent", "SkinFixer"); + urlParameters.put("url", skinUrl); + + if(slim) { + urlParameters.put("model", "slim"); + } + + ResponseObject apiResponse; + try { + apiResponse = new Http().makeRequest(RequestMethod.POST, "https://api.mineskin.org/generate/url", urlParameters, null, null, null); + } catch(IOException e) { + SkinFixer.logWarn("An IOException occured while fetching a skin."); + //TODO logDebug + + return new Triple(false, null, Utils.getStackTrace(e)); + } + + if(apiResponse.getResponseCode() != 200) { + SkinFixer.logWarn("The MineSkin API returned an unexpected result: " + apiResponse.getConnectionMessage()); + return new Triple(false, null, apiResponse.getConnectionMessage()); + } + + final Gson gson = new Gson(); + return new Triple(true, gson.fromJson(apiResponse.getMessage(), GetSkinResponse.class), null); + } + + public Triple getSkinOfPremiumPlayer(String uuid) { + + ResponseObject apiResponse; + try { + apiResponse = new Http().makeRequest(RequestMethod.GET, "https://api.mineskin.org/generate/user/" + uuid, null, null, null, null); + } catch(IOException e) { + SkinFixer.logWarn("An IOException occured while fetching a skin."); + //TODO logDebug + + return new Triple(false, null, Utils.getStackTrace(e)); + } + + if(apiResponse.getResponseCode() != 200) { + SkinFixer.logWarn("The MineSkin API returned an unexpected result: " + apiResponse.getConnectionMessage()); + return new Triple(false, null, apiResponse.getConnectionMessage()); + } + + final Gson gson = new Gson(); + return new Triple(true, gson.fromJson(apiResponse.getMessage(), GetSkinResponse.class), null); + } +} diff --git a/src/main/java/nl/thedutchmc/SkinFixer/apis/MojangApi.java b/src/main/java/nl/thedutchmc/SkinFixer/apis/MojangApi.java new file mode 100644 index 0000000..26ea01d --- /dev/null +++ b/src/main/java/nl/thedutchmc/SkinFixer/apis/MojangApi.java @@ -0,0 +1,46 @@ +package nl.thedutchmc.SkinFixer.apis; + +import java.io.IOException; + +import com.google.gson.Gson; + +import nl.thedutchmc.SkinFixer.SkinFixer; +import nl.thedutchmc.SkinFixer.gson.MojangAuthResponse; +import nl.thedutchmc.SkinFixer.util.Triple; +import nl.thedutchmc.SkinFixer.util.Utils; +import nl.thedutchmc.httplib.Http; +import nl.thedutchmc.httplib.Http.MediaFormat; +import nl.thedutchmc.httplib.Http.RequestMethod; +import nl.thedutchmc.httplib.Http.ResponseObject; + +public class MojangApi { + + public Triple getUuidFromMojang(String playername) { + String requestBody = "[\"" + playername + "\"]"; + + ResponseObject apiResponse; + try { + apiResponse = new Http().makeRequest(RequestMethod.POST, "https://api.mojang.com/profiles/minecraft", null, MediaFormat.JSON, requestBody, null); + } catch(IOException e) { + SkinFixer.logWarn("An IOException occured while fetching a UUID from Mojang."); + //TODO logDebug + + return new Triple(false, null, Utils.getStackTrace(e)); + } + + if(apiResponse.getResponseCode() != 200) { + SkinFixer.logWarn("The Mojang API returned an unexpected result: " + apiResponse.getConnectionMessage()); + return new Triple(false, null, apiResponse.getConnectionMessage()); + } + + + final Gson gson = new Gson(); + MojangAuthResponse[] authResponse = gson.fromJson(apiResponse.getMessage(), MojangAuthResponse[].class); + + if(authResponse.length == 0) { + return new Triple(true, null, null); + } + + return new Triple(true, authResponse[0], null); + } +} \ No newline at end of file diff --git a/src/main/java/nl/thedutchmc/SkinFixer/changeSkin/CheckUserAgainstMojang.java b/src/main/java/nl/thedutchmc/SkinFixer/changeSkin/CheckUserAgainstMojang.java deleted file mode 100644 index 20a9177..0000000 --- a/src/main/java/nl/thedutchmc/SkinFixer/changeSkin/CheckUserAgainstMojang.java +++ /dev/null @@ -1,74 +0,0 @@ -package nl.thedutchmc.SkinFixer.changeSkin; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.ParseException; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.util.EntityUtils; -import org.json.JSONArray; -import org.json.JSONObject; -import org.json.JSONTokener; - -public class CheckUserAgainstMojang { - - public static String premiumUser(String playername) { - HttpClient httpClient = HttpClients.createDefault(); - HttpPost httpPost = new HttpPost("https://api.mojang.com/profiles/minecraft"); - - String jsonOut = "[\"" + playername + "\"]"; - - StringEntity requestEntity = new StringEntity( - jsonOut, - ContentType.APPLICATION_JSON); - - httpPost.setEntity(requestEntity); - - HttpResponse response = null; - try { - response = httpClient.execute(httpPost); - } catch (IOException e) { - e.printStackTrace(); - } - - HttpEntity entity = response.getEntity(); - - String json = ""; - try { - json = EntityUtils.toString(entity, StandardCharsets.UTF_8); - } catch (ParseException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - - JSONTokener tokener = new JSONTokener(json); - JSONArray array = (JSONArray) tokener.nextValue(); - for(Object o : array) { - JSONObject jsonObj = (JSONObject) o; - String uuid = (String) jsonObj.getString("id"); - return insertDashUUID(uuid); - } - - return null; - } - - private static String insertDashUUID(String uuid) { - StringBuilder sb = new StringBuilder(uuid); - sb.insert(8, "-"); - sb = new StringBuilder(sb.toString()); - sb.insert(13, "-"); - sb = new StringBuilder(sb.toString()); - sb.insert(18, "-"); - sb = new StringBuilder(sb.toString()); - sb.insert(23, "-"); - - return sb.toString(); - } -} diff --git a/src/main/java/nl/thedutchmc/SkinFixer/changeSkin/GetSkin.java b/src/main/java/nl/thedutchmc/SkinFixer/changeSkin/GetSkin.java deleted file mode 100644 index 91255b7..0000000 --- a/src/main/java/nl/thedutchmc/SkinFixer/changeSkin/GetSkin.java +++ /dev/null @@ -1,91 +0,0 @@ -package nl.thedutchmc.SkinFixer.changeSkin; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.List; -import java.util.Scanner; - -import org.apache.http.HttpResponse; -import org.apache.http.NameValuePair; -import org.apache.http.client.ClientProtocolException; -import org.apache.http.client.HttpClient; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.message.BasicNameValuePair; - -public class GetSkin { - - - public static String getSkin(String skinUrl, boolean slim) { - - HttpClient httpclient = HttpClients.createDefault(); - HttpPost httppost = new HttpPost("https://api.mineskin.org/generate/url"); - - // Request parameters and other properties. - List params = new ArrayList(2); - params.add(new BasicNameValuePair("User-Agent", "Java Spigot Plugin")); - params.add(new BasicNameValuePair("url", skinUrl)); - - if(slim) { - params.add(new BasicNameValuePair("model", "slim")); - } - - try { - httppost.setEntity(new UrlEncodedFormEntity(params, "UTF-8")); - - //Execute and get the response. - HttpResponse response = httpclient.execute(httppost); - Scanner sc = new Scanner(response.getEntity().getContent()); - - StringBuilder responseBuilder = new StringBuilder(); - - while(sc.hasNext()) { - responseBuilder.append(sc.next()); - } - - sc.close(); - return responseBuilder.toString(); - - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } catch (ClientProtocolException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - - return null; - } - - public static String getSkinOfValidPlayer(String uuid) { - HttpClient httpclient = HttpClients.createDefault(); - HttpGet httpGet = new HttpGet("https://api.mineskin.org/generate/user/" + uuid); - - try { - //Execute and get the response. - HttpResponse response = httpclient.execute(httpGet); - Scanner sc = new Scanner(response.getEntity().getContent()); - - StringBuilder responseBuilder = new StringBuilder(); - - while(sc.hasNext()) { - responseBuilder.append(sc.next()); - } - - sc.close(); - return responseBuilder.toString(); - - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } catch (ClientProtocolException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - - return null; - } -} diff --git a/src/main/java/nl/thedutchmc/SkinFixer/changeSkin/SkinChangeOrchestrator.java b/src/main/java/nl/thedutchmc/SkinFixer/changeSkin/SkinChangeHandler.java similarity index 72% rename from src/main/java/nl/thedutchmc/SkinFixer/changeSkin/SkinChangeOrchestrator.java rename to src/main/java/nl/thedutchmc/SkinFixer/changeSkin/SkinChangeHandler.java index 2b4d070..d73eee9 100644 --- a/src/main/java/nl/thedutchmc/SkinFixer/changeSkin/SkinChangeOrchestrator.java +++ b/src/main/java/nl/thedutchmc/SkinFixer/changeSkin/SkinChangeHandler.java @@ -3,28 +3,28 @@ import java.lang.reflect.Array; import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; +import java.util.Collection; import java.util.HashSet; import java.util.Set; import java.util.UUID; import org.bukkit.Bukkit; import org.bukkit.Location; -import org.bukkit.World; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitRunnable; -import org.json.JSONObject; -import org.json.JSONTokener; import com.google.common.hash.Hashing; import net.md_5.bungee.api.ChatColor; import nl.thedutchmc.SkinFixer.SkinFixer; import nl.thedutchmc.SkinFixer.SkinObject; -import nl.thedutchmc.SkinFixer.changeSkin.changeGameProfile.*; +import nl.thedutchmc.SkinFixer.apis.MineskinApi; import nl.thedutchmc.SkinFixer.fileHandlers.StorageHandler; +import nl.thedutchmc.SkinFixer.gson.GetSkinResponse; import nl.thedutchmc.SkinFixer.util.ReflectionUtil; +import nl.thedutchmc.SkinFixer.util.Triple; -public class SkinChangeOrchestrator { +public class SkinChangeHandler { public static void changeSkinJson(String skinUrl, UUID internalUuid, UUID externalUuid, boolean slim, boolean isPremium) { @@ -34,44 +34,30 @@ public static void changeSkinJson(String skinUrl, UUID internalUuid, UUID extern @Override public void run() { - Player player = Bukkit.getPlayer(internalUuid); - - String value, signature; - + Player player = Bukkit.getPlayer(internalUuid); player.sendMessage(ChatColor.GOLD + "Fetching skin value and signature..."); //Fetch the skin from Mineskin.org's API - String skinJson = null; + Triple apiResponse; if(isPremium ) { - skinJson = GetSkin.getSkinOfValidPlayer(externalUuid.toString()); + apiResponse = new MineskinApi().getSkinOfPremiumPlayer(externalUuid.toString()); } else { - skinJson = GetSkin.getSkin(skinUrl, slim); + apiResponse = new MineskinApi().getSkin(skinUrl, slim); } - - //Get the skin texture value, and the skin texture signature - JSONTokener tokener = new JSONTokener(skinJson); - - //Descent to the Texture object - JSONObject full = (JSONObject) tokener.nextValue(); - JSONObject data = (JSONObject) full.get("data"); - JSONObject texture = (JSONObject) data.get("texture"); - //Grab the value and signature - value = (String) texture.get("value"); - signature = (String) texture.get("signature"); + if(!apiResponse.getA()) { + player.sendMessage(ChatColor.RED + "Something went wrong applying your skin:\n" + ChatColor.GRAY + apiResponse.getC()); + return; + } - changeSkin(value, signature, internalUuid, slim); + GetSkinResponse skinResponse = apiResponse.getB(); + changeSkin(skinResponse.getData().getTexture().getValue(), skinResponse.getData().getTexture().getSignature(), internalUuid, slim); } }.runTaskAsynchronously(SkinFixer.INSTANCE); } public static void changeSkinFromObject(SkinObject skin) { - new BukkitRunnable() { - @Override - public void run() { - changeSkin(skin.getValue(), skin.getSignature(), skin.getOwner(), skin.getSlim()); - } - }.runTaskAsynchronously(SkinFixer.INSTANCE); + changeSkin(skin.getValue(), skin.getSignature(), skin.getOwner(), skin.getSlim()); } private static void changeSkin(String skinValue, String skinSignature, UUID caller, boolean slim ) { @@ -90,25 +76,7 @@ private static void changeSkin(String skinValue, String skinSignature, UUID call player.sendMessage(ChatColor.GOLD + "Applying skin..."); - new BukkitRunnable() { - @Override - public void run() { - //NMS is version dependant. So we need to set the correct class to use. - //TODO switch this over to use reflection - switch(SkinFixer.NMS_VERSION) { - case "v1_16_R1": ChangeGameProfile_1_16_r1.changeProfile(player.getUniqueId(), skinValue, skinSignature); break; - case "v1_16_R2": ChangeGameProfile_1_16_r2.changeProfile(player.getUniqueId(), skinValue, skinSignature); break; - case "v1_16_R3": ChangeGameProfile_1_16_r3.changeProfile(player.getUniqueId(), skinValue, skinSignature); break; - default: - //We dont support the version that the user is running, so we inform them of this. - //Calls to the Bukkit API may only be sync, so it's inside a BukkitRunnable - Player p = Bukkit.getPlayer(caller); - p.sendMessage(ChatColor.RED + "This server is using a Minecraft version that is not supported by SkinFixer!"); - p.sendMessage(ChatColor.RED + "You are running NMS version " + SkinFixer.NMS_VERSION); - } - } - }.runTask(SkinFixer.INSTANCE); - + applySkin(player, skinValue, skinSignature); reloadPlayer(player); //Inform the player that we're done @@ -116,6 +84,63 @@ public void run() { } private static void applySkin(Player player, String skinValue, String skinSignature) { + new BukkitRunnable() { + + @Override + public void run() { + try { + Class craftPlayerClass = ReflectionUtil.getBukkitClass("entity.CraftPlayer"); + Object entityPlayer = ReflectionUtil.invokeMethod(craftPlayerClass, player, "getHandle"); + + //Get the GameProfile and the PropertyMap inside the Profile + Class entityHumanClass = ReflectionUtil.getNmsClass("EntityHuman"); + Object gameProfile = ReflectionUtil.invokeMethod(entityHumanClass, entityPlayer, "getProfile"); + Object propertyMap = ReflectionUtil.invokeMethod(gameProfile, "getProperties"); + + //Check if the PropertyMap contains the 'textures' property + //If so remove it + //The containsKey method is in the ForwardingMultimap class, which PropertyMap extends + Class forwardingMultimapClass = com.google.common.collect.ForwardingMultimap.class; + Boolean containsKeyTextures = (Boolean) ReflectionUtil.invokeMethod(forwardingMultimapClass, propertyMap, "containsKey", new Class[] { Object.class }, new Object[] { "textures" }); + if(containsKeyTextures) { + Object textures = ReflectionUtil.invokeMethod(forwardingMultimapClass, propertyMap, "get", new Class[] { Object.class }, new Object[] { "textures" }); + Object texturesIter = ReflectionUtil.invokeMethod(Collection.class, textures, "iterator"); + Object iterNext = ReflectionUtil.invokeMethod(texturesIter, "next"); + + ReflectionUtil.invokeMethod(forwardingMultimapClass, propertyMap, "remove", new Class[] { Object.class, Object.class }, new Object[] { "textures", iterNext }); + } + + //Create a new 'textures' Property with the new skinValue and skinSignature + //and put it in the PropertyMap + Class propertyClass = Class.forName("com.mojang.authlib.properties.Property"); + Object newProperty = ReflectionUtil.invokeConstructor(propertyClass, "textures", skinValue, skinSignature); + ReflectionUtil.invokeMethod(forwardingMultimapClass, propertyMap, "put", new Class[] { Object.class, Object.class }, new Object[] { "textures", newProperty }); + + //Get the Enum constants REMOVE_PLAYER and ADD_PLAYER + Class packetPlayOutPlayerInfoClass = ReflectionUtil.getNmsClass("PacketPlayOutPlayerInfo"); + Object removePlayerEnumConstant = ReflectionUtil.getEnum(packetPlayOutPlayerInfoClass, "EnumPlayerInfoAction", "REMOVE_PLAYER"); + Object addPlayerEnumConstant = ReflectionUtil.getEnum(packetPlayOutPlayerInfoClass, "EnumPlayerInfoAction", "ADD_PLAYER"); + + //Create an Array of EntityPlayer with size = 1 and add our player to it + Object entityPlayerArr = Array.newInstance(entityPlayer.getClass(), 1); + Array.set(entityPlayerArr, 0, entityPlayer); + + //Create two PacketPlayOutPlayerInfo packets, one for removing the player and one for re-adding the player + Object PacketPlayOutPlayerInfoRemovePlayer = ReflectionUtil.invokeConstructor(packetPlayOutPlayerInfoClass, removePlayerEnumConstant, entityPlayerArr); + Object PacketPlayOutPlayerInfoAddPlayer = ReflectionUtil.invokeConstructor(packetPlayOutPlayerInfoClass, addPlayerEnumConstant, entityPlayerArr); + + //Get the Player's connection and the generic Packet class + Object playerConnection = ReflectionUtil.getObject(entityPlayer, "playerConnection"); + Class packetClass = ReflectionUtil.getNmsClass("Packet"); + + //Send the two Packets + ReflectionUtil.invokeMethod(playerConnection, "sendPacket", new Class[] { packetClass }, new Object[] { PacketPlayOutPlayerInfoRemovePlayer }); + ReflectionUtil.invokeMethod(playerConnection, "sendPacket", new Class[] { packetClass }, new Object[] { PacketPlayOutPlayerInfoAddPlayer }); + } catch(Exception e) { + e.printStackTrace(); + } + } + }.runTask(SkinFixer.INSTANCE); } /** diff --git a/src/main/java/nl/thedutchmc/SkinFixer/commandHandlers/GetCodeCommandExecutor.java b/src/main/java/nl/thedutchmc/SkinFixer/commandexecutors/GetCodeCommandExecutor.java similarity index 89% rename from src/main/java/nl/thedutchmc/SkinFixer/commandHandlers/GetCodeCommandExecutor.java rename to src/main/java/nl/thedutchmc/SkinFixer/commandexecutors/GetCodeCommandExecutor.java index 0a2c49a..f4b8776 100644 --- a/src/main/java/nl/thedutchmc/SkinFixer/commandHandlers/GetCodeCommandExecutor.java +++ b/src/main/java/nl/thedutchmc/SkinFixer/commandexecutors/GetCodeCommandExecutor.java @@ -1,4 +1,4 @@ -package nl.thedutchmc.SkinFixer.commandHandlers; +package nl.thedutchmc.SkinFixer.commandexecutors; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; @@ -6,7 +6,7 @@ import org.bukkit.entity.Player; import net.md_5.bungee.api.ChatColor; -import nl.thedutchmc.SkinFixer.commonEventMethods.AddNewSkin; +import nl.thedutchmc.SkinFixer.common.AddNewSkin; public class GetCodeCommandExecutor implements CommandExecutor { diff --git a/src/main/java/nl/thedutchmc/SkinFixer/commandHandlers/SetSkinCommandExecutor.java b/src/main/java/nl/thedutchmc/SkinFixer/commandexecutors/SetSkinCommandExecutor.java similarity index 83% rename from src/main/java/nl/thedutchmc/SkinFixer/commandHandlers/SetSkinCommandExecutor.java rename to src/main/java/nl/thedutchmc/SkinFixer/commandexecutors/SetSkinCommandExecutor.java index a3b8773..57b3156 100644 --- a/src/main/java/nl/thedutchmc/SkinFixer/commandHandlers/SetSkinCommandExecutor.java +++ b/src/main/java/nl/thedutchmc/SkinFixer/commandexecutors/SetSkinCommandExecutor.java @@ -1,4 +1,4 @@ -package nl.thedutchmc.SkinFixer.commandHandlers; +package nl.thedutchmc.SkinFixer.commandexecutors; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; @@ -7,7 +7,7 @@ import net.md_5.bungee.api.ChatColor; import nl.thedutchmc.SkinFixer.SkinFixer; -import nl.thedutchmc.SkinFixer.changeSkin.SkinChangeOrchestrator; +import nl.thedutchmc.SkinFixer.changeSkin.SkinChangeHandler; import nl.thedutchmc.SkinFixer.fileHandlers.StorageHandler; public class SetSkinCommandExecutor implements CommandExecutor { @@ -51,14 +51,14 @@ public boolean onCommand(CommandSender sender, Command command, String label, St if(args.length == 2) { if(args[1].equals("true")) { //Slim model - SkinChangeOrchestrator.changeSkinJson(url, p.getUniqueId(), null, true, false); + SkinChangeHandler.changeSkinJson(url, p.getUniqueId(), null, true, false); } else { //Regular model - SkinChangeOrchestrator.changeSkinJson(url, p.getUniqueId(), null, false, false); + SkinChangeHandler.changeSkinJson(url, p.getUniqueId(), null, false, false); } } else { //Regular model - SkinChangeOrchestrator.changeSkinJson(url, p.getUniqueId(), null, false, false); + SkinChangeHandler.changeSkinJson(url, p.getUniqueId(), null, false, false); } SkinFixer.STORAGE.updateConfig(); diff --git a/src/main/java/nl/thedutchmc/SkinFixer/commandHandlers/SkinFixerCommandExecutor.java b/src/main/java/nl/thedutchmc/SkinFixer/commandexecutors/SkinFixerCommandExecutor.java similarity index 96% rename from src/main/java/nl/thedutchmc/SkinFixer/commandHandlers/SkinFixerCommandExecutor.java rename to src/main/java/nl/thedutchmc/SkinFixer/commandexecutors/SkinFixerCommandExecutor.java index b50f84b..7db9a98 100644 --- a/src/main/java/nl/thedutchmc/SkinFixer/commandHandlers/SkinFixerCommandExecutor.java +++ b/src/main/java/nl/thedutchmc/SkinFixer/commandexecutors/SkinFixerCommandExecutor.java @@ -1,4 +1,4 @@ -package nl.thedutchmc.SkinFixer.commandHandlers; +package nl.thedutchmc.SkinFixer.commandexecutors; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; diff --git a/src/main/java/nl/thedutchmc/SkinFixer/commonEventMethods/AddNewSkin.java b/src/main/java/nl/thedutchmc/SkinFixer/common/AddNewSkin.java similarity index 92% rename from src/main/java/nl/thedutchmc/SkinFixer/commonEventMethods/AddNewSkin.java rename to src/main/java/nl/thedutchmc/SkinFixer/common/AddNewSkin.java index 27162eb..75d64d7 100644 --- a/src/main/java/nl/thedutchmc/SkinFixer/commonEventMethods/AddNewSkin.java +++ b/src/main/java/nl/thedutchmc/SkinFixer/common/AddNewSkin.java @@ -1,4 +1,4 @@ -package nl.thedutchmc.SkinFixer.commonEventMethods; +package nl.thedutchmc.SkinFixer.common; import java.util.Random; diff --git a/src/main/java/nl/thedutchmc/SkinFixer/discordEvents/MessageReceivedEventListener.java b/src/main/java/nl/thedutchmc/SkinFixer/discordEvents/MessageReceivedEventListener.java index e191b39..67c307c 100644 --- a/src/main/java/nl/thedutchmc/SkinFixer/discordEvents/MessageReceivedEventListener.java +++ b/src/main/java/nl/thedutchmc/SkinFixer/discordEvents/MessageReceivedEventListener.java @@ -8,7 +8,7 @@ import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; import nl.thedutchmc.SkinFixer.JdaHandler; -import nl.thedutchmc.SkinFixer.commonEventMethods.AddNewSkin; +import nl.thedutchmc.SkinFixer.common.AddNewSkin; public class MessageReceivedEventListener extends ListenerAdapter { diff --git a/src/main/java/nl/thedutchmc/SkinFixer/gson/GetSkinResponse.java b/src/main/java/nl/thedutchmc/SkinFixer/gson/GetSkinResponse.java new file mode 100644 index 0000000..2f9f2d7 --- /dev/null +++ b/src/main/java/nl/thedutchmc/SkinFixer/gson/GetSkinResponse.java @@ -0,0 +1,34 @@ +package nl.thedutchmc.SkinFixer.gson; + +public class GetSkinResponse { + + private Data data; + + public class Data { + + private Texture texture; + + public Texture getTexture() { + return texture; + } + + public class Texture { + private String value; + private String signature; + + public String getSignature() { + return signature; + } + + public String getValue() { + return value; + } + } + + } + + public Data getData() { + return data; + } + +} diff --git a/src/main/java/nl/thedutchmc/SkinFixer/gson/MojangAuthResponse.java b/src/main/java/nl/thedutchmc/SkinFixer/gson/MojangAuthResponse.java new file mode 100644 index 0000000..41b4494 --- /dev/null +++ b/src/main/java/nl/thedutchmc/SkinFixer/gson/MojangAuthResponse.java @@ -0,0 +1,13 @@ +package nl.thedutchmc.SkinFixer.gson; + +import com.google.gson.annotations.SerializedName; + +public class MojangAuthResponse { + + @SerializedName("id") + private String uuid; + + public String getUuid() { + return uuid; + } +} diff --git a/src/main/java/nl/thedutchmc/SkinFixer/minecraftEvents/PlayerJoinEventListener.java b/src/main/java/nl/thedutchmc/SkinFixer/minecraftevents/PlayerJoinEventListener.java similarity index 51% rename from src/main/java/nl/thedutchmc/SkinFixer/minecraftEvents/PlayerJoinEventListener.java rename to src/main/java/nl/thedutchmc/SkinFixer/minecraftevents/PlayerJoinEventListener.java index 916c203..1c88dbe 100644 --- a/src/main/java/nl/thedutchmc/SkinFixer/minecraftEvents/PlayerJoinEventListener.java +++ b/src/main/java/nl/thedutchmc/SkinFixer/minecraftevents/PlayerJoinEventListener.java @@ -1,4 +1,4 @@ -package nl.thedutchmc.SkinFixer.minecraftEvents; +package nl.thedutchmc.SkinFixer.minecraftevents; import java.util.UUID; @@ -9,9 +9,12 @@ import nl.thedutchmc.SkinFixer.SkinFixer; import nl.thedutchmc.SkinFixer.SkinObject; -import nl.thedutchmc.SkinFixer.changeSkin.CheckUserAgainstMojang; -import nl.thedutchmc.SkinFixer.changeSkin.SkinChangeOrchestrator; +import nl.thedutchmc.SkinFixer.apis.MojangApi; +import nl.thedutchmc.SkinFixer.changeSkin.SkinChangeHandler; import nl.thedutchmc.SkinFixer.fileHandlers.StorageHandler; +import nl.thedutchmc.SkinFixer.gson.MojangAuthResponse; +import nl.thedutchmc.SkinFixer.util.Triple; +import nl.thedutchmc.SkinFixer.util.Utils; public class PlayerJoinEventListener implements Listener { @@ -21,10 +24,13 @@ public void onPlayerJoinEvent(PlayerJoinEvent event) { @Override public void run() { - String uuidAsString = CheckUserAgainstMojang.premiumUser(event.getPlayer().getName()); - if(uuidAsString != null) { - - SkinChangeOrchestrator.changeSkinJson(null, event.getPlayer().getUniqueId(), UUID.fromString(uuidAsString), false, true); + Triple mojangApiResponse = new MojangApi().getUuidFromMojang(event.getPlayer().getName()); + + if(!mojangApiResponse.getA()) { + SkinFixer.logWarn("Something went wrong fetching the UUID from Mojang."); + } else if(mojangApiResponse.getB() != null) { + String uuidDashedStr = Utils.insertDashUUID(mojangApiResponse.getB().getUuid()); + SkinChangeHandler.changeSkinJson(null, event.getPlayer().getUniqueId(), UUID.fromString(uuidDashedStr), false, true); return; } @@ -38,7 +44,7 @@ public void run() { @Override public void run() { - SkinChangeOrchestrator.changeSkinFromObject(skin); + SkinChangeHandler.changeSkinFromObject(skin); } }.runTaskLater(SkinFixer.INSTANCE, 5L); } diff --git a/src/main/java/nl/thedutchmc/SkinFixer/util/ReflectionUtil.java b/src/main/java/nl/thedutchmc/SkinFixer/util/ReflectionUtil.java index d0b24ad..a6ea604 100644 --- a/src/main/java/nl/thedutchmc/SkinFixer/util/ReflectionUtil.java +++ b/src/main/java/nl/thedutchmc/SkinFixer/util/ReflectionUtil.java @@ -174,7 +174,7 @@ public static Object invokeMethod(Object obj, String methodName, Class[] argT * Invoke a Method where the Class where to find the method is explicitly given (Helpful if the method is located in a superclass). The argument types are derived from the provided arguments * @param clazz The Class where the method is located * @param obj The Object to invoke the method on - * @param method The name of the method + * @param methodName The name of the method * @param args The arguments to be passed to the method * @return Returns the result of the method, can be null if the method returns void * @throws NoSuchMethodException @@ -182,17 +182,35 @@ public static Object invokeMethod(Object obj, String methodName, Class[] argT * @throws IllegalArgumentException * @throws InvocationTargetException */ - public static Object invokeMethod(Class clazz, Object obj, String method, Object... args) throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { + public static Object invokeMethod(Class clazz, Object obj, String methodName, Object... args) throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { Class[] argTypes = new Class[args.length]; for(int i = 0; i < args.length; i++) { argTypes[i] = args[i].getClass(); } - Method m = getMethod(clazz, method, argTypes); + Method m = getMethod(clazz, methodName, argTypes); return m.invoke(obj, args); } + /** + * Invoke a Method where the Class where the Method is defined is explicitly given, and the argument types are explicitly given + * @param clazz The Class in which the Method is located + * @param obj The Object on which to invoke the Method + * @param methodName The name of the Method + * @param argTypes Argument types + * @param args Arguments to pass to the method + * @return Returns the result of the method, can be null if the method returns void + * @throws NoSuchMethodException + * @throws IllegalAccessException + * @throws IllegalArgumentException + * @throws InvocationTargetException + */ + public static Object invokeMethod(Class clazz, Object obj, String methodName, Class[] argTypes, Object[] args) throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { + Method m = getMethod(clazz, methodName, argTypes); + return m.invoke(obj, args); + } + /** * Get the value of a Field, where the Class in which the field is defined is derived from the provided Object * @param obj The object in which the field is located, and from which to get the value @@ -260,4 +278,28 @@ public static Object invokeConstructor(Class clazz, Class[] argTypes, Obje Constructor con = getConstructor(clazz, argTypes); return con.newInstance(args); } + + /** + * For debugging purposes!
+ * Print all Methods in a Class with their parameters, will print to stdout + * @param clazz The Class to look in + */ + public static void printMethodsInClassTyped(Class clazz) { + System.out.println("Methods in " + clazz.getName() + ":"); + + for(Method m : clazz.getDeclaredMethods()) { + String print = m.getName() + "("; + for(int i = 0; i < m.getParameterTypes().length; i++) { + print += m.getParameterTypes()[i].getName(); + + if(i != m.getParameterTypes().length -1) { + print += ","; + } + + } + + print += ")"; + System.out.println(print); + } + } } \ No newline at end of file diff --git a/src/main/java/nl/thedutchmc/SkinFixer/util/Triple.java b/src/main/java/nl/thedutchmc/SkinFixer/util/Triple.java new file mode 100644 index 0000000..248ee29 --- /dev/null +++ b/src/main/java/nl/thedutchmc/SkinFixer/util/Triple.java @@ -0,0 +1,27 @@ +package nl.thedutchmc.SkinFixer.util; + +public class Triple { + + private A a; + private B b; + private C c; + + public Triple(A a, B b, C c) { + this.a = a; + this.b = b; + this.c = c; + } + + public A getA() { + return this.a; + } + + public B getB() { + return this.b; + } + + public C getC() { + return this.c; + } + +} diff --git a/src/main/java/nl/thedutchmc/SkinFixer/util/Utils.java b/src/main/java/nl/thedutchmc/SkinFixer/util/Utils.java new file mode 100644 index 0000000..b4a063c --- /dev/null +++ b/src/main/java/nl/thedutchmc/SkinFixer/util/Utils.java @@ -0,0 +1,27 @@ +package nl.thedutchmc.SkinFixer.util; + +import java.io.PrintWriter; +import java.io.StringWriter; + +public class Utils { + + public static String getStackTrace(Throwable t) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw, true); + t.printStackTrace(pw); + return sw.getBuffer().toString(); + } + + public static String insertDashUUID(String uuid) { + StringBuilder sb = new StringBuilder(uuid); + sb.insert(8, "-"); + sb = new StringBuilder(sb.toString()); + sb.insert(13, "-"); + sb = new StringBuilder(sb.toString()); + sb.insert(18, "-"); + sb = new StringBuilder(sb.toString()); + sb.insert(23, "-"); + + return sb.toString(); + } +} diff --git a/src/main/resources/log4j.properties b/src/main/resources/log4j.properties new file mode 100644 index 0000000..dc9b986 --- /dev/null +++ b/src/main/resources/log4j.properties @@ -0,0 +1,5 @@ +log4j.rootLogger=INFO, skinfixer + +log4j.appender.skinfixer=org.apache.log4j.ConsoleAppender +log4j.appender.skinfixer.layout=org.apache.log4j.PatternLayout +log4j.appender.skinfixer.layout.ConversionPattern=%m%n \ No newline at end of file