diff --git a/src/main/java/me/libraryaddict/disguise/LibsDisguises.java b/src/main/java/me/libraryaddict/disguise/LibsDisguises.java index c6c10ab4..b4e6ee67 100644 --- a/src/main/java/me/libraryaddict/disguise/LibsDisguises.java +++ b/src/main/java/me/libraryaddict/disguise/LibsDisguises.java @@ -67,6 +67,8 @@ public class LibsDisguises extends JavaPlugin { instance = this; + WatcherSanitizer.init(); + Plugin plugin = Bukkit.getPluginManager().getPlugin("ProtocolLib"); if (plugin == null || DisguiseUtilities.isOlderThan(DisguiseUtilities.getProtocolLibRequiredVersion(), plugin.getDescription().getVersion())) { @@ -105,8 +107,6 @@ public class LibsDisguises extends JavaPlugin { if (!reloaded) { commandConfig.load(); } - - WatcherSanitizer.init(); } catch (Throwable throwable) { getUpdateChecker().doUpdate(); diff --git a/src/main/java/me/libraryaddict/disguise/disguisetypes/Disguise.java b/src/main/java/me/libraryaddict/disguise/disguisetypes/Disguise.java index 5b05b7db..0a6611e1 100644 --- a/src/main/java/me/libraryaddict/disguise/disguisetypes/Disguise.java +++ b/src/main/java/me/libraryaddict/disguise/disguisetypes/Disguise.java @@ -4,7 +4,6 @@ import com.comphenix.protocol.PacketType; import com.comphenix.protocol.PacketType.Play.Server; import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.events.PacketContainer; -import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.wrappers.EnumWrappers.NativeGameMode; import com.comphenix.protocol.wrappers.EnumWrappers.PlayerInfoAction; import com.comphenix.protocol.wrappers.PlayerInfoData; @@ -16,7 +15,10 @@ import me.libraryaddict.disguise.DisguiseAPI; import me.libraryaddict.disguise.DisguiseConfig; import me.libraryaddict.disguise.LibsDisguises; import me.libraryaddict.disguise.disguisetypes.TargetedDisguise.TargetType; -import me.libraryaddict.disguise.disguisetypes.watchers.*; +import me.libraryaddict.disguise.disguisetypes.watchers.AbstractHorseWatcher; +import me.libraryaddict.disguise.disguisetypes.watchers.AgeableWatcher; +import me.libraryaddict.disguise.disguisetypes.watchers.BoatWatcher; +import me.libraryaddict.disguise.disguisetypes.watchers.ZombieWatcher; import me.libraryaddict.disguise.events.DisguiseEvent; import me.libraryaddict.disguise.events.UndisguiseEvent; import me.libraryaddict.disguise.utilities.DisguiseUtilities; @@ -29,7 +31,6 @@ import me.libraryaddict.disguise.utilities.reflection.ReflectionManager; import me.libraryaddict.disguise.utilities.translations.LibsMsg; import net.md_5.bungee.api.ChatMessageType; import org.bukkit.Bukkit; -import org.bukkit.Location; import org.bukkit.NamespacedKey; import org.bukkit.boss.BarColor; import org.bukkit.boss.BarStyle; @@ -38,7 +39,6 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.*; import org.bukkit.metadata.FixedMetadataValue; import org.bukkit.scheduler.BukkitRunnable; -import org.bukkit.util.Vector; import java.lang.reflect.InvocationTargetException; import java.util.*; @@ -71,7 +71,7 @@ public abstract class Disguise { /** * If set, how long before disguise expires */ - private long disguiseExpires; + protected long disguiseExpires; /** * For when plugins may want to assign custom data to a disguise, such as who owns it */ @@ -370,7 +370,7 @@ public abstract class Disguise { return this; } - private void doActionBar() { + protected void doActionBar() { if (getNotifyBar() == DisguiseConfig.NotifyBar.ACTION_BAR && getEntity() instanceof Player && !getEntity().hasPermission("libsdisguises.noactionbar") && DisguiseAPI.getDisguise(getEntity()) == Disguise.this) { ((Player) getEntity()).spigot().sendMessage(ChatMessageType.ACTION_BAR, LibsMsg.ACTION_BAR_MESSAGE.getChat(getDisguiseName())); @@ -394,227 +394,18 @@ public abstract class Disguise { } private void createRunnable() { - if (runnable != null) { + if (runnable != null && !runnable.isCancelled()) { runnable.cancel(); } - final boolean alwaysSendVelocity; - - switch (getType()) { - case EXPERIENCE_ORB: - case WITHER_SKULL: - case FIREWORK: - alwaysSendVelocity = true; - break; - default: - alwaysSendVelocity = false; - break; - } - - final Double vectorY; - - switch (getType()) { - case FIREWORK: - case WITHER_SKULL: - vectorY = 0.000001D; - break; - case EXPERIENCE_ORB: - vectorY = 0.0221; - break; - default: - vectorY = null; - break; - } - final TargetedDisguise disguise = (TargetedDisguise) this; // A scheduler to clean up any unused disguises. - runnable = new BukkitRunnable() { - private int blockX, blockY, blockZ, facing; - private int deadTicks = 0; - private int actionBarTicks = -1; - private long lastRefreshed = System.currentTimeMillis(); - - @Override - public void run() { - if (!isDisguiseInUse() || getEntity() == null) { - cancel(); - runnable = null; - return; - } - - if (++actionBarTicks % 15 == 0) { - actionBarTicks = 0; - - doActionBar(); - } - - // If entity is no longer valid. Remove it. - if (getEntity() instanceof Player && !((Player) getEntity()).isOnline()) { - removeDisguise(); - } else if (disguiseExpires > 0 && - (DisguiseConfig.isDynamicExpiry() ? disguiseExpires-- == 1 : disguiseExpires < System.currentTimeMillis())) { // If disguise expired - removeDisguise(); - - if (getEntity() instanceof Player) { - LibsMsg.EXPIRED_DISGUISE.send(getEntity()); - } - - return; - } else if (!getEntity().isValid()) { - // If it has been dead for 30+ ticks - // This is to ensure that this disguise isn't removed while clients think its the real entity - // The delay is because if it sends the destroy entity packets straight away, then it means no - // death animation - // This is probably still a problem for wither and enderdragon deaths. - if (deadTicks++ > (getType() == DisguiseType.ENDER_DRAGON ? 200 : 20)) { - if (isRemoveDisguiseOnDeath()) { - removeDisguise(); - } - } - - return; - } - - deadTicks = 0; - - // If the disguise type is tnt, we need to resend the entity packet else it will turn invisible - if (getType() == DisguiseType.FIREWORK || getType() == DisguiseType.EVOKER_FANGS) { - if (lastRefreshed + ((getType() == DisguiseType.FIREWORK ? 40 : 23) * 50) < System.currentTimeMillis()) { - lastRefreshed = System.currentTimeMillis(); - - DisguiseUtilities.refreshTrackers(disguise); - } - } - - if (isModifyBoundingBox()) { - DisguiseUtilities.doBoundingBox(disguise); - } - - if (getType() == DisguiseType.BAT && !((BatWatcher) getWatcher()).isHanging()) { - return; - } - - doVelocity(vectorY, alwaysSendVelocity); - - if (getType() == DisguiseType.EXPERIENCE_ORB) { - PacketContainer packet = new PacketContainer(Server.REL_ENTITY_MOVE); - - packet.getIntegers().write(0, getEntity().getEntityId()); - - try { - for (Player player : DisguiseUtilities.getPerverts(disguise)) { - if (getEntity() != player) { - ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet, false); - continue; - } else if (!isSelfDisguiseVisible() || !(getEntity() instanceof Player)) { - continue; - } - - PacketContainer selfPacket = packet.shallowClone(); - - selfPacket.getModifier().write(0, DisguiseAPI.getSelfDisguiseId()); - - try { - ProtocolLibrary.getProtocolManager().sendServerPacket((Player) getEntity(), selfPacket, false); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } - } - } catch (InvocationTargetException e) { - e.printStackTrace(); - } - } - } - }; + runnable = new DisguiseRunnable(this); runnable.runTaskTimer(LibsDisguises.getInstance(), 1, 1); } - private void doVelocity(Double vectorY, boolean alwaysSendVelocity) { - // If the vectorY isn't 0. Cos if it is. Then it doesn't want to send any vectors. - // If this disguise has velocity sending enabled and the entity is flying. - if (isVelocitySent() && vectorY != null && (alwaysSendVelocity || !getEntity().isOnGround())) { - Vector vector = getEntity().getVelocity(); - - // If the entity doesn't have velocity changes already - You know. I really can't wrap my - // head about the - // if statement. - // But it doesn't seem to do anything wrong.. - if (vector.getY() != 0 && !(vector.getY() < 0 && alwaysSendVelocity && getEntity().isOnGround())) { - return; - } - - // If disguise isn't a experience orb, or the entity isn't standing on the ground - if (getType() != DisguiseType.EXPERIENCE_ORB || !getEntity().isOnGround()) { - PacketContainer lookPacket = null; - - if (getType() == DisguiseType.WITHER_SKULL && DisguiseConfig.isWitherSkullPacketsEnabled()) { - lookPacket = new PacketContainer(Server.ENTITY_LOOK); - - StructureModifier mods = lookPacket.getModifier(); - lookPacket.getIntegers().write(0, getEntity().getEntityId()); - Location loc = getEntity().getLocation(); - - mods.write(4, DisguiseUtilities.getYaw(getType(), getEntity().getType(), (byte) Math.floor(loc.getYaw() * 256.0F / 360.0F))); - mods.write(5, DisguiseUtilities.getPitch(getType(), getEntity().getType(), (byte) Math.floor(loc.getPitch() * 256.0F / 360.0F))); - - if (isSelfDisguiseVisible() && getEntity() instanceof Player) { - PacketContainer selfLookPacket = lookPacket.shallowClone(); - - selfLookPacket.getIntegers().write(0, DisguiseAPI.getSelfDisguiseId()); - - try { - ProtocolLibrary.getProtocolManager().sendServerPacket((Player) getEntity(), selfLookPacket, false); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } - } - } - - try { - PacketContainer velocityPacket = new PacketContainer(Server.ENTITY_VELOCITY); - - StructureModifier mods = velocityPacket.getIntegers(); - - // Write entity ID - mods.write(0, getEntity().getEntityId()); - mods.write(1, (int) (vector.getX() * 8000)); - mods.write(3, (int) (vector.getZ() * 8000)); - - for (Player player : DisguiseUtilities.getPerverts(this)) { - PacketContainer tempVelocityPacket = velocityPacket.shallowClone(); - mods = tempVelocityPacket.getIntegers(); - - // If the viewing player is the disguised player - if (getEntity() == player) { - // If not using self disguise, continue - if (!isSelfDisguiseVisible()) { - continue; - } - - // Write self disguise ID - mods.write(0, DisguiseAPI.getSelfDisguiseId()); - } - - mods.write(2, (int) (8000D * (vectorY * ReflectionManager.getPing(player)) * 0.069D)); - - if (lookPacket != null && player != getEntity()) { - ProtocolLibrary.getProtocolManager().sendServerPacket(player, lookPacket, false); - } - - ProtocolLibrary.getProtocolManager().sendServerPacket(player, tempVelocityPacket, false); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - // If we need to send a packet to update the exp position as it likes to gravitate client - // sided to - // players. - } - } - /** * Get the disguised entity * @@ -1189,13 +980,12 @@ public abstract class Disguise { DisguiseUtilities.refreshTrackers((TargetedDisguise) this); // If he is a player, then self disguise himself - Bukkit.getScheduler(). - scheduleSyncDelayedTask(LibsDisguises.getInstance(), new Runnable() { - @Override - public void run() { - DisguiseUtilities.setupFakeDisguise(Disguise.this); - } - }, 2); + Bukkit.getScheduler().scheduleSyncDelayedTask(LibsDisguises.getInstance(), new Runnable() { + @Override + public void run() { + DisguiseUtilities.setupFakeDisguise(Disguise.this); + } + }, 2); if (isHidePlayer() && getEntity() instanceof Player) { PacketContainer removeTab = new PacketContainer(PacketType.Play.Server.PLAYER_INFO); diff --git a/src/main/java/me/libraryaddict/disguise/disguisetypes/DisguiseRunnable.java b/src/main/java/me/libraryaddict/disguise/disguisetypes/DisguiseRunnable.java new file mode 100644 index 00000000..6625f6c8 --- /dev/null +++ b/src/main/java/me/libraryaddict/disguise/disguisetypes/DisguiseRunnable.java @@ -0,0 +1,227 @@ +package me.libraryaddict.disguise.disguisetypes; + +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.ProtocolLibrary; +import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.reflect.StructureModifier; +import me.libraryaddict.disguise.DisguiseAPI; +import me.libraryaddict.disguise.DisguiseConfig; +import me.libraryaddict.disguise.disguisetypes.watchers.BatWatcher; +import me.libraryaddict.disguise.utilities.DisguiseUtilities; +import me.libraryaddict.disguise.utilities.reflection.ReflectionManager; +import me.libraryaddict.disguise.utilities.translations.LibsMsg; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.util.Vector; + +import java.lang.reflect.InvocationTargetException; + +/** + * Created by libraryaddict on 20/05/2021. + */ +class DisguiseRunnable extends BukkitRunnable { + private int blockX, blockY, blockZ, facing; + private int deadTicks = 0; + private int actionBarTicks = -1; + private long lastRefreshed = System.currentTimeMillis(); + private Disguise disguise; + final Double vectorY; + final boolean alwaysSendVelocity; + + public DisguiseRunnable(Disguise disguise) { + switch (disguise.getType()) { + case FIREWORK: + case WITHER_SKULL: + vectorY = 0.000001D; + alwaysSendVelocity = true; + break; + case EXPERIENCE_ORB: + vectorY = 0.0221; + alwaysSendVelocity = true; + break; + default: + vectorY = null; + alwaysSendVelocity = false; + break; + } + + } + + @Override + public void run() { + if (!disguise.isDisguiseInUse() || disguise.getEntity() == null) { + cancel(); + return; + } + + if (++actionBarTicks % 15 == 0) { + actionBarTicks = 0; + + disguise.doActionBar(); + } + + // If entity is no longer valid. Remove it. + if (disguise.getEntity() instanceof Player && !((Player) disguise.getEntity()).isOnline()) { + disguise.removeDisguise(); + } else if (disguise.disguiseExpires > 0 && (DisguiseConfig.isDynamicExpiry() ? disguise.disguiseExpires-- == 1 : + disguise.disguiseExpires < System.currentTimeMillis())) { // If disguise expired + disguise.removeDisguise(); + + if (disguise.getEntity() instanceof Player) { + LibsMsg.EXPIRED_DISGUISE.send(disguise.getEntity()); + } + + return; + } else if (!disguise.getEntity().isValid()) { + // If it has been dead for 30+ ticks + // This is to ensure that this disguise isn't removed while clients think its the real entity + // The delay is because if it sends the destroy entity packets straight away, then it means no + // death animation + // This is probably still a problem for wither and enderdragon deaths. + if (deadTicks++ > (disguise.getType() == DisguiseType.ENDER_DRAGON ? 200 : 20)) { + if (disguise.isRemoveDisguiseOnDeath()) { + disguise.removeDisguise(); + } + } + + return; + } + + deadTicks = 0; + + // If the disguise type is tnt, we need to resend the entity packet else it will turn invisible + if (disguise.getType() == DisguiseType.FIREWORK || disguise.getType() == DisguiseType.EVOKER_FANGS) { + if (lastRefreshed + ((disguise.getType() == DisguiseType.FIREWORK ? 40 : 23) * 50) < System.currentTimeMillis()) { + lastRefreshed = System.currentTimeMillis(); + + DisguiseUtilities.refreshTrackers((TargetedDisguise) disguise); + } + } + + if (disguise.isModifyBoundingBox()) { + DisguiseUtilities.doBoundingBox((TargetedDisguise) disguise); + } + + if (disguise.getType() == DisguiseType.BAT && !((BatWatcher) disguise.getWatcher()).isHanging()) { + return; + } + + doVelocity(vectorY, alwaysSendVelocity); + + if (disguise.getType() == DisguiseType.EXPERIENCE_ORB) { + PacketContainer packet = new PacketContainer(PacketType.Play.Server.REL_ENTITY_MOVE); + + packet.getIntegers().write(0, disguise.getEntity().getEntityId()); + + try { + for (Player player : DisguiseUtilities.getPerverts(disguise)) { + if (disguise.getEntity() != player) { + ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet, false); + continue; + } else if (!disguise.isSelfDisguiseVisible() || !(disguise.getEntity() instanceof Player)) { + continue; + } + + PacketContainer selfPacket = packet.shallowClone(); + + selfPacket.getModifier().write(0, DisguiseAPI.getSelfDisguiseId()); + + try { + ProtocolLibrary.getProtocolManager().sendServerPacket((Player) disguise.getEntity(), selfPacket, false); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + } + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + } + } + + private void doVelocity(Double vectorY, boolean alwaysSendVelocity) { + // If the vectorY isn't 0. Cos if it is. Then it doesn't want to send any vectors. + // If this disguise has velocity sending enabled and the entity is flying. + if (disguise.isVelocitySent() && vectorY != null && (alwaysSendVelocity || !disguise.getEntity().isOnGround())) { + Vector vector = disguise.getEntity().getVelocity(); + + // If the entity doesn't have velocity changes already - You know. I really can't wrap my + // head about the + // if statement. + // But it doesn't seem to do anything wrong.. + if (vector.getY() != 0 && !(vector.getY() < 0 && alwaysSendVelocity && disguise.getEntity().isOnGround())) { + return; + } + + // If disguise isn't a experience orb, or the entity isn't standing on the ground + if (disguise.getType() != DisguiseType.EXPERIENCE_ORB || !disguise.getEntity().isOnGround()) { + PacketContainer lookPacket = null; + + if (disguise.getType() == DisguiseType.WITHER_SKULL && DisguiseConfig.isWitherSkullPacketsEnabled()) { + lookPacket = new PacketContainer(PacketType.Play.Server.ENTITY_LOOK); + + StructureModifier mods = lookPacket.getModifier(); + lookPacket.getIntegers().write(0, disguise.getEntity().getEntityId()); + Location loc = disguise.getEntity().getLocation(); + + mods.write(4, + DisguiseUtilities.getYaw(disguise.getType(), disguise.getEntity().getType(), (byte) Math.floor(loc.getYaw() * 256.0F / 360.0F))); + mods.write(5, DisguiseUtilities + .getPitch(disguise.getType(), disguise.getEntity().getType(), (byte) Math.floor(loc.getPitch() * 256.0F / 360.0F))); + + if (disguise.isSelfDisguiseVisible() && disguise.getEntity() instanceof Player) { + PacketContainer selfLookPacket = lookPacket.shallowClone(); + + selfLookPacket.getIntegers().write(0, DisguiseAPI.getSelfDisguiseId()); + + try { + ProtocolLibrary.getProtocolManager().sendServerPacket((Player) disguise.getEntity(), selfLookPacket, false); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + } + } + + try { + PacketContainer velocityPacket = new PacketContainer(PacketType.Play.Server.ENTITY_VELOCITY); + + StructureModifier mods = velocityPacket.getIntegers(); + + // Write entity ID + mods.write(0, disguise.getEntity().getEntityId()); + mods.write(1, (int) (vector.getX() * 8000)); + mods.write(3, (int) (vector.getZ() * 8000)); + + for (Player player : DisguiseUtilities.getPerverts(disguise)) { + PacketContainer tempVelocityPacket = velocityPacket.shallowClone(); + mods = tempVelocityPacket.getIntegers(); + + // If the viewing player is the disguised player + if (disguise.getEntity() == player) { + // If not using self disguise, continue + if (!disguise.isSelfDisguiseVisible()) { + continue; + } + + // Write self disguise ID + mods.write(0, DisguiseAPI.getSelfDisguiseId()); + } + + mods.write(2, (int) (8000D * (vectorY * ReflectionManager.getPing(player)) * 0.069D)); + + if (lookPacket != null && player != disguise.getEntity()) { + ProtocolLibrary.getProtocolManager().sendServerPacket(player, lookPacket, false); + } + + ProtocolLibrary.getProtocolManager().sendServerPacket(player, tempVelocityPacket, false); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + // If we need to send a packet to update the exp position as it likes to gravitate client + // sided to + // players. + } + } +} diff --git a/src/main/java/me/libraryaddict/disguise/utilities/reflection/asm/Asm13.java b/src/main/java/me/libraryaddict/disguise/utilities/reflection/asm/Asm13.java index 8acdd10b..8b10e02f 100644 --- a/src/main/java/me/libraryaddict/disguise/utilities/reflection/asm/Asm13.java +++ b/src/main/java/me/libraryaddict/disguise/utilities/reflection/asm/Asm13.java @@ -3,7 +3,6 @@ package me.libraryaddict.disguise.utilities.reflection.asm; import org.objectweb.asm.*; import java.io.IOException; -import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Map; @@ -11,8 +10,7 @@ import java.util.Map; * Created by libraryaddict on 17/02/2020. */ public class Asm13 { - public byte[] createClassWithoutMethods(String className, ArrayList> illegalMethods) - throws IOException, InvocationTargetException, IllegalAccessException, NoSuchFieldException { + public byte[] createClassWithoutMethods(String className, ArrayList> illegalMethods) throws IOException { className = className.replace(".", "/") + ".class"; ClassReader cr = new ClassReader(getClass().getClassLoader().getResourceAsStream(className)); @@ -20,7 +18,6 @@ public class Asm13 { cr.accept(new ClassVisitor(Opcodes.ASM5, writer) { public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { - Map.Entry entry = illegalMethods.stream().filter(e -> e.getKey().equals(name) && e.getValue().equals(desc)).findFirst().orElse(null); diff --git a/src/main/java/me/libraryaddict/disguise/utilities/reflection/asm/AsmLoader.java b/src/main/java/me/libraryaddict/disguise/utilities/reflection/asm/AsmLoader.java index 3517581b..404dffa1 100644 --- a/src/main/java/me/libraryaddict/disguise/utilities/reflection/asm/AsmLoader.java +++ b/src/main/java/me/libraryaddict/disguise/utilities/reflection/asm/AsmLoader.java @@ -7,8 +7,6 @@ import java.io.BufferedInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; @@ -28,7 +26,6 @@ public class AsmLoader { private final File filePath = new File(LibsDisguises.getInstance().getDataFolder(), "libs/org-ow2-asm-9.1.jar"); private boolean asmExists; private URLClassLoader classLoader; - private LibsJarFile libsJarFile; public AsmLoader() { try { @@ -47,28 +44,6 @@ public class AsmLoader { } } - public void load() { - try { - ClassLoader pluginClassLoader = getClass().getClassLoader(); - Class c = Class.forName("org.bukkit.plugin.java.PluginClassLoader"); - Field file = c.getDeclaredField("file"); - file.setAccessible(true); - - libsJarFile = new LibsJarFile((File) file.get(pluginClassLoader)); - - Field field = c.getDeclaredField("jar"); - field.setAccessible(true); - - Field modifiersField = Field.class.getDeclaredField("modifiers"); - modifiersField.setAccessible(true); - modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); - - field.set(pluginClassLoader, libsJarFile); - } catch (Throwable e) { - e.printStackTrace(); - } - } - public void unload() { try { classLoader.close(); diff --git a/src/main/java/me/libraryaddict/disguise/utilities/reflection/asm/FakePluginCreator.java b/src/main/java/me/libraryaddict/disguise/utilities/reflection/asm/FakePluginCreator.java new file mode 100644 index 00000000..96416a24 --- /dev/null +++ b/src/main/java/me/libraryaddict/disguise/utilities/reflection/asm/FakePluginCreator.java @@ -0,0 +1,120 @@ +package me.libraryaddict.disguise.utilities.reflection.asm; + +import me.libraryaddict.disguise.LibsDisguises; +import me.libraryaddict.disguise.utilities.reflection.ReflectionManager; +import org.apache.commons.lang.StringUtils; +import org.bukkit.configuration.file.YamlConfiguration; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.Enumeration; +import java.util.Map; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; + +/** + * Created by libraryaddict on 20/05/2021. + */ +public class FakePluginCreator { + public String getPluginYAML() { + return "name: LibsDisguisesVersioning\nmain: " + getPluginClassPath().replace(".class", "").replace("/", ".") + + "\ndescription: Plugin created by Libs Disguises for " + + "compatibility with different versions\nversion: 1.0.0\nauthor: libraryaddict\napi-version: '1.13'\nsoftdepend: [ProtocolLib, LibsDisguises]"; + } + + public File getDestination() { + return new File(LibsDisguises.getInstance().getDataFolder(), "libs/LibsDisguisesCompat.jar"); + } + + public String getPluginClassPath() { + return "me/libraryaddict/disguise/utilities/reflection/asm/LibsDisguisesCompat.class"; + } + + public String getVersion() throws Exception { + File dest = getDestination(); + + if (!dest.exists()) { + return null; + } + + JarFile jarFile = new JarFile(dest); + + try (InputStream stream = jarFile.getInputStream(jarFile.getEntry("version.txt"))) { + return new BufferedReader(new InputStreamReader(stream)).lines().collect(Collectors.joining("\n")); + } + } + + public String getOurVersion() { + YamlConfiguration pluginYml = ReflectionManager.getPluginYAML(LibsDisguises.getInstance().getFile()); + + String buildNo = StringUtils.stripToNull(pluginYml.getString("build-number")); + + return buildNo != null && buildNo.matches("[0-9]+") ? ReflectionManager.getBukkitVersion() + " " + Integer.parseInt(buildNo) : + ReflectionManager.getBukkitVersion() + " CUSTOM"; + } + + public void createJar(String ourVersion, Map classes) throws Exception { + File dest = getDestination(); + + if (!dest.getParentFile().exists()) { + dest.getParentFile().mkdirs(); + } + + if (dest.exists()) { + dest.delete(); + } + + JarOutputStream out = new JarOutputStream(new FileOutputStream(dest)); + + out.putNextEntry(new ZipEntry("plugin.yml")); + out.write(getPluginYAML().getBytes(StandardCharsets.UTF_8)); + out.closeEntry(); + + // Write our main plugin class + try (JarFile jar = new JarFile(LibsDisguises.getInstance().getFile())) { + Enumeration entries = jar.entries(); + String mainPath = getPluginClassPath().replace(".class", ""); + + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + + if (!entry.getName().equals(mainPath) && !entry.getName().startsWith("me/libraryaddict/disguise/disguisetypes/")) { + continue; + } + + if (classes.containsKey(entry.getName())) { + continue; + } + + out.putNextEntry(new ZipEntry(entry.getName().equals(mainPath) ? getPluginClassPath() : entry.getName())); + + try (InputStream stream = jar.getInputStream(entry)) { + int nRead; + byte[] data = new byte[1024]; + while ((nRead = stream.read(data, 0, data.length)) != -1) { + out.write(data, 0, nRead); + } + } + + out.closeEntry(); + } + } catch (Exception ex) { + ex.printStackTrace(); + } + + for (Map.Entry entry : classes.entrySet()) { + out.putNextEntry(new ZipEntry(entry.getKey())); + out.write(entry.getValue()); + out.closeEntry(); + } + + out.putNextEntry(new ZipEntry("version.txt")); + out.write(ourVersion.getBytes(StandardCharsets.UTF_8)); + out.closeEntry(); + + out.close(); + } +} diff --git a/src/main/java/me/libraryaddict/disguise/utilities/reflection/asm/LibsDisguisesCompat.java b/src/main/java/me/libraryaddict/disguise/utilities/reflection/asm/LibsDisguisesCompat.java new file mode 100644 index 00000000..7620e3ed --- /dev/null +++ b/src/main/java/me/libraryaddict/disguise/utilities/reflection/asm/LibsDisguisesCompat.java @@ -0,0 +1,9 @@ +package me.libraryaddict.disguise.utilities.reflection.asm; + +import org.bukkit.plugin.java.JavaPlugin; + +/** + * Created by libraryaddict on 20/05/2021. + */ +public class LibsDisguisesCompat extends JavaPlugin { +} diff --git a/src/main/java/me/libraryaddict/disguise/utilities/reflection/asm/LibsJarFile.java b/src/main/java/me/libraryaddict/disguise/utilities/reflection/asm/LibsJarFile.java deleted file mode 100644 index 7ee3f405..00000000 --- a/src/main/java/me/libraryaddict/disguise/utilities/reflection/asm/LibsJarFile.java +++ /dev/null @@ -1,39 +0,0 @@ -package me.libraryaddict.disguise.utilities.reflection.asm; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.util.HashMap; -import java.util.jar.JarFile; -import java.util.zip.ZipEntry; - -/** - * Created by libraryaddict on 15/05/2021. - */ -public class LibsJarFile extends JarFile { - private final HashMap customFiles = new HashMap<>(); - - public LibsJarFile(File file) throws IOException { - super(file); - } - - @Override - public synchronized InputStream getInputStream(ZipEntry ze) throws IOException { - if (customFiles.containsKey(ze.getName())) { - return new ByteArrayInputStream(customFiles.get(ze.getName())); - } - - return super.getInputStream(ze); - } - - public void addClass(String name, byte[] bytes) { - customFiles.put(name, bytes); - } - - @Override - public void close() throws IOException { - customFiles.clear(); - super.close(); - } -} diff --git a/src/main/java/me/libraryaddict/disguise/utilities/reflection/asm/WatcherSanitizer.java b/src/main/java/me/libraryaddict/disguise/utilities/reflection/asm/WatcherSanitizer.java index 5d3e8f07..d9760df6 100644 --- a/src/main/java/me/libraryaddict/disguise/utilities/reflection/asm/WatcherSanitizer.java +++ b/src/main/java/me/libraryaddict/disguise/utilities/reflection/asm/WatcherSanitizer.java @@ -3,9 +3,12 @@ package me.libraryaddict.disguise.utilities.reflection.asm; import com.google.gson.Gson; import me.libraryaddict.disguise.LibsDisguises; import me.libraryaddict.disguise.utilities.reflection.ReflectionManager; +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.java.JavaPluginLoader; +import java.io.File; import java.io.InputStream; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -69,11 +72,29 @@ public class WatcherSanitizer { } catch (NoSuchFieldException | IllegalAccessException ignored) { } + if (Bukkit.getPluginManager().getPlugin("LibsDisguisesVersioning") != null) { + throw new IllegalStateException("Why is LibsDisguisesVersioning already active? Did the server owner do something.. Weird?"); + } + + FakePluginCreator fakePluginCreator = new FakePluginCreator(); + + String ourVers = fakePluginCreator.getOurVersion(); + + try { + if (ourVers != null && ourVers.equals(fakePluginCreator.getVersion()) && !ourVers.contains(" CUSTOM")) { + loadPlugin(fakePluginCreator); + return; + } + } catch (Exception e) { + e.printStackTrace(); + } + + LibsDisguises.getInstance().getLogger().info("Creating a new version compatibility jar"); + ArrayList mapped = new ArrayList<>(); try (InputStream stream = LibsDisguises.getInstance().getResource("ANTI_PIRACY_ENCRYPTION")) { AsmLoader loader = new AsmLoader(); - loader.load(); Object obj; Method getBytes; @@ -109,15 +130,18 @@ public class WatcherSanitizer { list.add(new HashMap.SimpleEntry(info.getMethod(), info.getDescriptor())); } + Map classes = new HashMap<>(); + for (Map.Entry>> entry : toRemove.entrySet()) { byte[] bytes = (byte[]) getBytes.invoke(obj, entry.getKey(), entry.getValue()); mapped.add(entry.getKey()); - String name = entry.getKey().replace(".", "/") + ".class"; - - loader.getLibsJarFile().addClass(name, bytes); + classes.put(entry.getKey().replace(".", "/") + ".class", bytes); } + fakePluginCreator.createJar(ourVers, classes); + loadPlugin(fakePluginCreator); + if (!loader.isAsmExists()) { loader.unload(); } @@ -126,4 +150,32 @@ public class WatcherSanitizer { LibsDisguises.getInstance().getLogger().severe("Registered: " + new Gson().toJson(mapped)); } } + + private static void loadPlugin(FakePluginCreator fakePluginCreator) throws Exception { + LibsDisguises.getInstance().getLogger().info("Starting version support plugin: LibsDisguisesVersioning"); + Method method = Class.forName("org.bukkit.plugin.PluginManager", false, WatcherSanitizer.class.getClassLoader().getParent()) + .getMethod("loadPlugin", File.class); + + Plugin plugin = (Plugin) method.invoke(Bukkit.getPluginManager(), fakePluginCreator.getDestination()); + + Class pluginClassLoader = Class.forName("org.bukkit.plugin.java.PluginClassLoader"); + + Field loaderField = JavaPluginLoader.class.getDeclaredField("loaders"); + loaderField.setAccessible(true); + List loaderList = (List) loaderField.get(LibsDisguises.getInstance().getPluginLoader()); + + Field pluginOwner = pluginClassLoader.getDeclaredField("plugin"); + pluginOwner.setAccessible(true); + + // Move Lib's Disguises to load its classes after the new plugin + for (Object o : loaderList) { + if (pluginOwner.get(o) != LibsDisguises.getInstance()) { + continue; + } + + loaderList.remove(o); + loaderList.add(o); + break; + } + } } diff --git a/src/main/java/me/libraryaddict/disguise/utilities/watchers/CompileMethods.java b/src/main/java/me/libraryaddict/disguise/utilities/watchers/CompileMethods.java index 3ae7e95d..cfa82812 100644 --- a/src/main/java/me/libraryaddict/disguise/utilities/watchers/CompileMethods.java +++ b/src/main/java/me/libraryaddict/disguise/utilities/watchers/CompileMethods.java @@ -5,6 +5,7 @@ import me.libraryaddict.disguise.utilities.LibsPremium; import me.libraryaddict.disguise.utilities.reflection.ClassGetter; import me.libraryaddict.disguise.utilities.reflection.NmsAddedIn; import me.libraryaddict.disguise.utilities.reflection.NmsRemovedIn; +import me.libraryaddict.disguise.utilities.reflection.asm.FakePluginCreator; import me.libraryaddict.disguise.utilities.sounds.DisguiseSoundEnums; import me.libraryaddict.disguise.utilities.sounds.SoundGroup; import org.apache.commons.lang.StringUtils; @@ -34,6 +35,14 @@ public class CompileMethods { public static void main(String[] args) { doMethods(); doSounds(); + moveCompat(); + } + + private static void moveCompat() { + FakePluginCreator creator = new FakePluginCreator(); + + File compatFile = new File("target/classes/" + creator.getPluginClassPath()); + compatFile.renameTo(new File(compatFile.getParentFile(), compatFile.getName().replace(".class", ""))); } private static void doSounds() { @@ -91,8 +100,7 @@ public class CompileMethods { } private static void doMethods() { - ArrayList> classes = - ClassGetter.getClassesForPackage(FlagWatcher.class, "me.libraryaddict.disguise.disguisetypes.watchers"); + ArrayList> classes = ClassGetter.getClassesForPackage(FlagWatcher.class, "me.libraryaddict.disguise.disguisetypes.watchers"); ArrayList sorted = new ArrayList<>(); @@ -106,12 +114,10 @@ public class CompileMethods { for (Method method : c.getMethods()) { if (!FlagWatcher.class.isAssignableFrom(method.getDeclaringClass())) { continue; - } else if (method.getParameterCount() > 1 && !method.isAnnotationPresent(NmsAddedIn.class) && - !method.isAnnotationPresent(NmsRemovedIn.class)) { + } else if (method.getParameterCount() > 1 && !method.isAnnotationPresent(NmsAddedIn.class) && !method.isAnnotationPresent(NmsRemovedIn.class)) { continue; - } else if (!(method.getName().startsWith("set") && method.getParameterCount() == 1) && - !method.getName().startsWith("get") && !method.getName().startsWith("has") && - !method.getName().startsWith("is")) { + } else if (!(method.getName().startsWith("set") && method.getParameterCount() == 1) && !method.getName().startsWith("get") && + !method.getName().startsWith("has") && !method.getName().startsWith("is")) { continue; } else if (method.getName().equals("removePotionEffect")) { continue; @@ -141,8 +147,7 @@ public class CompileMethods { descriptor = ":" + getMethodDescriptor(method) + ":" + added + ":" + removed; } - String s = - method.getDeclaringClass().getSimpleName() + ":" + method.getName() + ":" + param + descriptor; + String s = method.getDeclaringClass().getSimpleName() + ":" + method.getName() + ":" + param + descriptor; if (methods.contains(s)) { continue; diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 982faac1..9a2eef43 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -6,7 +6,7 @@ build-date: ${timestamp} build-number: ${build.number} author: libraryaddict authors: [Byteflux, Navid K.] -softdepend: [ProtocolLib] +softdepend: [ProtocolLib, LibsDisguisesVersioning] api-version: '1.13' commands: libsdisguises: