Skip to content

Commit

Permalink
Merge pull request #5 from Diona-testserver/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
Loyisa authored Aug 27, 2022
2 parents 7453dd8 + c0e2023 commit 9834308
Show file tree
Hide file tree
Showing 13 changed files with 214 additions and 151 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>io.github.diona-testserver</groupId>
<artifactId>PluginHooker</artifactId>
<version>0.6.0</version>
<version>1.0.0</version>
<packaging>jar</packaging>

<name>PluginHooker</name>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import org.bukkit.event.HandlerList;
import org.bukkit.plugin.Plugin;

public class DionaBukkitListenerEvent extends Event implements Cancellable {
public class BukkitListenerEvent extends Event implements Cancellable {

private static final HandlerList handlers = new HandlerList();

Expand All @@ -20,11 +20,11 @@ public class DionaBukkitListenerEvent extends Event implements Cancellable {
@Getter
private final DionaPlayer dionaPlayer;

public DionaBukkitListenerEvent(Plugin plugin, Event event) {
public BukkitListenerEvent(Plugin plugin, Event event) {
this(plugin, event, null);
}

public DionaBukkitListenerEvent(Plugin plugin, Event event, DionaPlayer dionaPlayer) {
public BukkitListenerEvent(Plugin plugin, Event event, DionaPlayer dionaPlayer) {
super(event.isAsynchronous());
this.plugin = plugin;
this.event = event;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;

public class DionaProtocolLibPacketEvent extends Event implements Cancellable {
public class ProtocolLibPacketEvent extends Event implements Cancellable {

private static final HandlerList handlers = new HandlerList();

Expand All @@ -20,7 +20,7 @@ public class DionaProtocolLibPacketEvent extends Event implements Cancellable {
@Getter
private final boolean outbound;

public DionaProtocolLibPacketEvent(PacketListener packetListener, PacketEvent packetEvent, boolean outbound) {
public ProtocolLibPacketEvent(PacketListener packetListener, PacketEvent packetEvent, boolean outbound) {
super(packetEvent.isAsynchronous());
this.packetListener = packetListener;
this.packetEvent = packetEvent;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,18 @@
package io.github.dionatestserver.pluginhooker.hook;

import com.sun.tools.attach.VirtualMachine;
import io.github.dionatestserver.pluginhooker.DionaPluginHooker;
import javassist.ClassPool;
import io.github.dionatestserver.pluginhooker.utils.AgentUtils;
import org.reflections.Reflections;

import java.io.File;
import java.io.OutputStream;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.nio.file.Files;
import java.util.List;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.Objects;
import java.util.logging.Logger;
import java.util.stream.Collectors;

public class HookerManager {

private final Logger logger = DionaPluginHooker.getInstance().getLogger();
private static final String AGENT_CLASS = "io.github.dionatestserver.pluginhooker.hook.PluginHookerAgent";

public HookerManager() {
List<Injector> injectors = this.getInjectorList();
Expand All @@ -36,26 +27,32 @@ public HookerManager() {
try {
injector.predefineClass();
logger.info( injector.getClassNameWithoutPackage() + " is now predefined!");
return false;
} catch (Exception e) {
logger.severe("Error while predefining " + injector.getClassNameWithoutPackage());
e.printStackTrace();
return true;
}
return false;
})
.collect(Collectors.toList());

if (definedClasses.size() == 0) return;

//init instrumentation field
File agentFile = this.generateAgentFile();

this.attachAgent(agentFile);
//init instrumentation field
try {
String agentClass = "io.github.dionatestserver.pluginhooker.hook.PluginHookerAgent";
File agentFile = AgentUtils.generateAgentFile(agentClass);
AgentUtils.attachSelf(Objects.requireNonNull(agentFile));
} catch (Exception e) {
logger.severe("Error while attaching agent");
e.printStackTrace();
}

definedClasses.forEach(injector -> {
try {
injector.redefineClass(PluginHookerAgent.instrumentation);
logger.info( injector.getClassNameWithoutPackage() + " is now redefined!");
logger.info(injector.getClassNameWithoutPackage() + " is now redefined!");
} catch (Exception e) {
logger.severe("Error while redefining " + injector.getClassNameWithoutPackage());
e.printStackTrace();
Expand All @@ -73,53 +70,4 @@ private List<Injector> getInjectorList() {
}
}).collect(Collectors.toList());
}

private File generateAgentFile() {
Manifest manifest = new Manifest();
manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
manifest.getMainAttributes().put(new Attributes.Name("Agent-Class"), AGENT_CLASS);
manifest.getMainAttributes().put(new Attributes.Name("Can-Redefine-Classes"), "true");

try {
File agentFile = new File(DionaPluginHooker.getInstance().getDataFolder(), "agent.jar");
if (!agentFile.exists()) {
agentFile.createNewFile();
}

OutputStream outputStream = Files.newOutputStream(agentFile.toPath());
JarOutputStream jarOutputStream = new JarOutputStream(outputStream, manifest);
jarOutputStream.putNextEntry(new JarEntry(AGENT_CLASS.replace(".", "/") + ".class"));


ClassPool pool = ClassPool.getDefault();
jarOutputStream.write(pool.get(AGENT_CLASS).toBytecode());

jarOutputStream.finish();
return agentFile;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

private String getPid() {
RuntimeMXBean bean = ManagementFactory.getRuntimeMXBean();
String pid = bean.getName();
if (pid.contains("@")) {
pid = pid.substring(0, pid.indexOf("@"));
}
return pid;
}

private void attachAgent(File agentFile) {
try {
System.loadLibrary("attach");
VirtualMachine vm = VirtualMachine.attach(this.getPid());
vm.loadAgent(agentFile.getAbsolutePath());
vm.detach();
} catch (Exception e) {
logger.severe("Error while attaching agent");
e.printStackTrace();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package io.github.dionatestserver.pluginhooker.hook;

import io.github.dionatestserver.pluginhooker.DionaPluginHooker;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.LoaderClassPath;
import javassist.util.proxy.DefineClassHelper;
import lombok.Getter;

import java.io.IOException;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Field;
Expand All @@ -18,50 +16,70 @@ public abstract class Injector {

protected static final ClassPool classPool = ClassPool.getDefault();

static {
classPool.appendClassPath(new LoaderClassPath(DionaPluginHooker.class.getClassLoader()));
}

protected final Class<?> neighbor;

protected final CtClass targetClass;

@Getter
protected final String targetClass;
protected final String targetClassName;

@Getter
protected final String classNameWithoutPackage;

protected final Class<?> neighbor;


public Injector(String targetClass, Class<?> neighbor) {
this.targetClass = targetClass;
public Injector(String targetClassName, Class<?> neighbor) {
this.targetClassName = targetClassName;
// split the class name
String[] className = this.getTargetClass().split("\\.");
String[] className = this.getTargetClassName().split("\\.");
// get the class name without the package
classNameWithoutPackage = className[className.length - 1];
this.neighbor = neighbor;

classPool.appendClassPath(new LoaderClassPath(DionaPluginHooker.class.getClassLoader()));

try {
this.initClassPath();
this.targetClass = classPool.get(targetClassName);
this.hookClass();
} catch (Exception e) {
throw new RuntimeException(e);
}
}

public boolean isTargetClassDefined() {
try {
Field classesField = ClassLoader.class.getDeclaredField("classes");
classesField.setAccessible(true);
AbstractList<Class<?>> classes = (AbstractList) classesField.get(neighbor.getClassLoader());
return classes.stream().anyMatch(clazz -> clazz.getName().equals(targetClass));
return classes.stream().anyMatch(clazz -> clazz.getName().equals(targetClassName));
} catch (Exception e) {
return false;
}
}

public void predefineClass() throws Exception {
CtClass hookedClass = this.generateHookedClass();

DefineClassHelper.toClass(targetClass, neighbor, neighbor.getClassLoader(), null, hookedClass.toBytecode());
DefineClassHelper.toClass(
targetClassName,
neighbor,
neighbor.getClassLoader(),
null,
targetClass.toBytecode()
);
}

public void redefineClass(Instrumentation instrumentation) throws Exception {
CtClass hookedClass = this.generateHookedClass();

instrumentation.redefineClasses(new ClassDefinition(Class.forName(targetClass), hookedClass.toBytecode()));
instrumentation.redefineClasses(
new ClassDefinition(Class.forName(targetClassName), targetClass.toBytecode())
);
}

public abstract CtClass generateHookedClass();
public abstract void hookClass() throws Exception;

public abstract boolean canHook();

protected abstract void initClassPath();

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

import io.github.dionatestserver.pluginhooker.DionaPluginHooker;
import io.github.dionatestserver.pluginhooker.config.DionaConfig;
import io.github.dionatestserver.pluginhooker.events.DionaBukkitListenerEvent;
import io.github.dionatestserver.pluginhooker.events.BukkitListenerEvent;
import io.github.dionatestserver.pluginhooker.player.DionaPlayer;
import org.bukkit.Bukkit;
import org.bukkit.entity.Entity;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.entity.Projectile;
import org.bukkit.event.Event;
import org.bukkit.event.block.*;
import org.bukkit.event.enchantment.EnchantItemEvent;
Expand All @@ -22,13 +23,19 @@
import org.bukkit.projectiles.ProjectileSource;

import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;

public class BukkitCallbackHandler {
private final Map<Class<? extends Event>, Function<Event, Player>> eventMap = new LinkedHashMap<>();

private final Map<Class<? extends Event>, Field> eventFieldCache = new LinkedHashMap<>();

private final Set<Class<? extends Event>> failedFieldCache = new HashSet<>();

public BukkitCallbackHandler() {
this.initEventMap();
}
Expand All @@ -50,13 +57,13 @@ public boolean handleBukkitEvent(Plugin plugin, Event event) {

DionaPlayer dionaPlayer = DionaPluginHooker.getPlayerManager().getDionaPlayer(this.getPlayerByEvent(event));
if (dionaPlayer == null) {
DionaBukkitListenerEvent bukkitListenerEvent = new DionaBukkitListenerEvent(plugin, event);
BukkitListenerEvent bukkitListenerEvent = new BukkitListenerEvent(plugin, event);
Bukkit.getPluginManager().callEvent(bukkitListenerEvent);

return bukkitListenerEvent.isCancelled();
} else {
if (dionaPlayer.getEnabledPlugins().contains(plugin)) {
DionaBukkitListenerEvent bukkitListenerEvent = new DionaBukkitListenerEvent(plugin, event, dionaPlayer);
BukkitListenerEvent bukkitListenerEvent = new BukkitListenerEvent(plugin, event, dionaPlayer);
Bukkit.getPluginManager().callEvent(bukkitListenerEvent);
return bukkitListenerEvent.isCancelled();
} else {
Expand All @@ -75,6 +82,12 @@ private Player getPlayerByEvent(Event event) {
Entity damager = ((EntityDamageByEntityEvent) event).getDamager();
if (damager instanceof Player)
return (Player) damager;
if (damager instanceof Projectile) {
Projectile projectile = (Projectile) damager;
ProjectileSource projectileSource = projectile.getShooter();
if (projectileSource instanceof Player)
return (Player) projectileSource;
}
}
Entity entity = ((EntityEvent) event).getEntity();
if (entity instanceof Player)
Expand All @@ -91,15 +104,29 @@ else if (event instanceof ProjectileLaunchEvent) {
// Try to get the player field from the event

if (DionaConfig.useReflectionToGetEventPlayer) {
try {
Field playerField = event.getClass().getDeclaredField("player");
if (!playerField.isAccessible()) playerField.setAccessible(true);
Object player = playerField.get(event);
if (player instanceof Player) {
return (Player) player;
if (this.failedFieldCache.contains(event.getClass())) {
return null;
}
Field playerField = this.eventFieldCache.getOrDefault(event.getClass(), null);
if (playerField == null) {
try {
playerField = event.getClass().getDeclaredField("player");
playerField.setAccessible(true);
Player player = (Player) playerField.get(event);
this.eventFieldCache.put(event.getClass(), playerField);
return player;
} catch (Exception e) {
this.failedFieldCache.add(event.getClass());
return null;
}
} catch (Exception ignored) {
}

try {
return (Player) playerField.get(event);
} catch (Exception e) {
return null;
}

}

return null;
Expand Down
Loading

0 comments on commit 9834308

Please sign in to comment.