Skip to content

Commit c555d52

Browse files
committed
Cleanup hook manager
1 parent e346fc9 commit c555d52

File tree

9 files changed

+217
-168
lines changed

9 files changed

+217
-168
lines changed

build.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,12 @@
2929
</classpath>
3030
</javac>
3131
</target>
32+
<target name="zip" depends="jar">
33+
<zip destfile="modlauncher.zip">
34+
<zipfileset dir="." includes="modlauncher.jar"/>
35+
<zipfileset dir="." includes="modlauncher.bat"/>
36+
<zipfileset dir="mods" prefix="mods"/>
37+
<zipfileset dir="lib" includes="javassist.jar"/>
38+
</zip>
39+
</target>
3240
</project>

src/org/gotti/wurmunlimited/modloader/ModLoader.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import java.util.Properties;
1515
import java.util.logging.Logger;
1616

17-
import org.gotti.wurmunlimited.modloader.classhooks.HookBuilder;
17+
import org.gotti.wurmunlimited.modloader.classhooks.HookManager;
1818
import org.gotti.wurmunlimited.modloader.interfaces.Configurable;
1919
import org.gotti.wurmunlimited.modloader.interfaces.WurmMod;
2020

@@ -53,7 +53,7 @@ public WurmMod loadModFromInfo(Path modInfo) throws IOException {
5353
}
5454

5555
try {
56-
ClassLoader classloader = HookBuilder.getInstance().getLoader();
56+
ClassLoader classloader = HookManager.getInstance().getLoader();
5757
final String classpath = properties.getProperty("classpath");
5858
if (classpath != null) {
5959
classloader = createClassLoader(modname, classpath, classloader);

src/org/gotti/wurmunlimited/modloader/ProxyServerHook.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,19 @@
33
import java.lang.reflect.InvocationHandler;
44
import java.lang.reflect.Method;
55

6-
import org.gotti.wurmunlimited.modloader.classhooks.HookBuilder;
6+
import org.gotti.wurmunlimited.modloader.classhooks.HookManager;
77

8+
/**
9+
* Hook into com.wurmonline.server.Server.startRunning()
10+
*
11+
* The InvocationHandler calls startRunning() first, then fires onServerStarted event
12+
*/
813
public class ProxyServerHook extends ServerHook {
914

1015
public ProxyServerHook() {
1116

12-
HookBuilder.getInstance().registerHook("com.wurmonline.server.Server", "startRunning", "()V", new InvocationHandler() {
13-
17+
HookManager.getInstance().registerHook("com.wurmonline.server.Server", "startRunning", "()V", new InvocationHandler() {
18+
1419
@Override
1520
public Object invoke(Object wrapped, Method method, Object[] args) throws Throwable {
1621
Object result = method.invoke(wrapped, args);

src/org/gotti/wurmunlimited/modloader/classhooks/HookBuilder.java

Lines changed: 0 additions & 158 deletions
This file was deleted.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package org.gotti.wurmunlimited.modloader.classhooks;
2+
3+
public class HookException extends RuntimeException {
4+
5+
public HookException(Throwable e) {
6+
super(e);
7+
}
8+
9+
public HookException(String message) {
10+
super(message);
11+
}
12+
13+
private static final long serialVersionUID = -8955817806357327378L;
14+
15+
}
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
package org.gotti.wurmunlimited.modloader.classhooks;
2+
3+
import java.lang.reflect.InvocationHandler;
4+
import java.lang.reflect.Method;
5+
import java.util.HashMap;
6+
import java.util.HashSet;
7+
import java.util.Map;
8+
import java.util.Set;
9+
10+
import javassist.CannotCompileException;
11+
import javassist.ClassPool;
12+
import javassist.CtClass;
13+
import javassist.CtMethod;
14+
import javassist.CtNewMethod;
15+
import javassist.Loader;
16+
import javassist.NotFoundException;
17+
18+
public class HookManager {
19+
20+
// Javassist class pool
21+
private ClassPool classPool;
22+
23+
// Javassist class loader
24+
private Loader loader;
25+
26+
// Invocation targets
27+
private Map<String, InvocationTarget> invocationTargets = new HashMap<>();
28+
29+
// Instance
30+
private static HookManager instance;
31+
32+
private HookManager() {
33+
classPool = ClassPool.getDefault();
34+
loader = new Loader(classPool);
35+
}
36+
37+
public static synchronized HookManager getInstance() {
38+
if (instance == null) {
39+
instance = new HookManager();
40+
}
41+
return instance;
42+
}
43+
44+
public ClassPool getClassPool() {
45+
return classPool;
46+
}
47+
48+
public Loader getLoader() {
49+
return loader;
50+
}
51+
52+
/**
53+
* Create a unique method name in the class. The name is generated from the baseName + "$" + number
54+
*
55+
* @param ctClass
56+
* Class
57+
* @param baseName
58+
* method base name
59+
* @return unique name
60+
*/
61+
private static String getUniqueMethodName(CtClass ctClass, String baseName) {
62+
Set<String> usedNames = new HashSet<>();
63+
for (CtMethod method : ctClass.getDeclaredMethods()) {
64+
usedNames.add(method.getName());
65+
}
66+
67+
int i = 1;
68+
do {
69+
String methodName = String.format("%s$%d", baseName, i++);
70+
if (!usedNames.contains(methodName)) {
71+
return methodName;
72+
}
73+
} while (true);
74+
}
75+
76+
private InvocationTarget createHook(CtClass ctClass, ClassHook classHook) throws NotFoundException, CannotCompileException {
77+
CtMethod origMethod = ctClass.getMethod(classHook.getMethodName(), classHook.getMethodType());
78+
79+
origMethod.setName(getUniqueMethodName(ctClass, classHook.getMethodName()));
80+
81+
CtMethod newMethod = CtNewMethod.copy(origMethod, classHook.getMethodName(), ctClass, null);
82+
83+
CtClass[] exceptionTypes = origMethod.getExceptionTypes();
84+
Class<?>[] exceptionClasses = new Class<?>[exceptionTypes.length];
85+
for (int i = 0; i < exceptionTypes.length; i++) {
86+
try {
87+
exceptionClasses[i] = loader.loadClass(exceptionTypes[i].getName());
88+
} catch (ClassNotFoundException e) {
89+
throw new CannotCompileException(e);
90+
}
91+
}
92+
93+
InvocationTarget invocationTarget = new InvocationTarget(classHook.getInvocationHandler(), origMethod.getName(), origMethod.getLongName(), exceptionClasses);
94+
95+
String type = newMethod.getReturnType().getName();
96+
StringBuilder builder = new StringBuilder();
97+
builder.append("{\n");
98+
builder.append(String.format("Object result = org.gotti.wurmunlimited.modloader.classhooks.HookBuilder.getInstance().invoke(this,\"%s\",$args);\n", origMethod.getLongName()));
99+
if (!"void".equals(type)) {
100+
builder.append(String.format("return (%s)result;\n", type));
101+
}
102+
builder.append("\n}");
103+
104+
newMethod.setBody(builder.toString());
105+
ctClass.addMethod(newMethod);
106+
107+
return invocationTarget;
108+
}
109+
110+
/**
111+
* Register a hook.
112+
*
113+
* @param className
114+
* Class name to hook
115+
* @param methodName
116+
* Method to hook
117+
* @param methodType
118+
* Method signature to hook
119+
* @param invocationHandler
120+
* InvocationHandler to call
121+
*/
122+
public void registerHook(String className, String methodName, String methodType, InvocationHandler invocationHandler) {
123+
ClassHook classHook = new ClassHook(methodName, methodType, invocationHandler);
124+
try {
125+
CtClass ctClass = classPool.get(className);
126+
InvocationTarget target = createHook(ctClass, classHook);
127+
invocationTargets.put(target.getIdentifier(), target);
128+
} catch (NotFoundException | CannotCompileException e) {
129+
throw new HookException(e);
130+
}
131+
}
132+
133+
/**
134+
* Invoke the InvocationHandler for a class hook
135+
*
136+
* @param object
137+
* Hooked class object
138+
* @param wrappedMethod
139+
* Hooked method
140+
* @param args
141+
* Call arguments
142+
* @return Call result
143+
* @throws Throwable
144+
* Throwables
145+
*/
146+
public Object invoke(Object object, String wrappedMethod, Object[] args) throws Throwable {
147+
// Get the invocation target
148+
InvocationTarget invocationTarget = invocationTargets.get(wrappedMethod);
149+
if (invocationTarget == null) {
150+
throw new HookException("Uninstrumented method " + wrappedMethod);
151+
}
152+
153+
try {
154+
// Get the called method
155+
Method method = invocationTarget.resolveMethod(object.getClass());
156+
157+
// Call the invocation handler
158+
return invocationTarget.getInvocationHandler().invoke(object, method, args);
159+
} catch (Throwable e) {
160+
for (Class<?> exceptionType : invocationTarget.getExceptionTypes()) {
161+
if (exceptionType.isInstance(e)) {
162+
throw e;
163+
}
164+
}
165+
throw new HookException(e);
166+
}
167+
}
168+
}

0 commit comments

Comments
 (0)