Skip to content

Commit e346fc9

Browse files
committed
Initial class hook support
1 parent 142b5e7 commit e346fc9

File tree

15 files changed

+382
-72
lines changed

15 files changed

+382
-72
lines changed

build.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<jar destfile="modlauncher.jar">
55
<manifest>
66
<attribute name="Main-Class" value="org.gotti.wurmunlimited.serverlauncher.ServerLauncher" />
7-
<attribute name="Class-Path" value="server.jar common.jar lib/activation.jar lib/annotations.jar lib/commons-codec-1.6.jar lib/controlsfx-8.20.8.jar lib/controlsfx-8.20.8-sources.jar lib/httpclient-4.2.5.jar lib/httpcore-4.2.4.jar lib/javaws.jar lib/jsr305.jar lib/jtwitter.jar lib/mail.jar lib/mysql-connector.jar lib/mysql-connector-java-5.1.23-bin.jar lib/servlet.jar lib/signpost-core.jar lib/sqlite-jdbc-3.8.11.2.jar lib/SteamServerJni.jar" />
7+
<attribute name="Class-Path" value="javassist.jar server.jar common.jar lib/activation.jar lib/annotations.jar lib/commons-codec-1.6.jar lib/controlsfx-8.20.8.jar lib/controlsfx-8.20.8-sources.jar lib/httpclient-4.2.5.jar lib/httpcore-4.2.4.jar lib/javaws.jar lib/jsr305.jar lib/jtwitter.jar lib/mail.jar lib/mysql-connector.jar lib/mysql-connector-java-5.1.23-bin.jar lib/servlet.jar lib/signpost-core.jar lib/sqlite-jdbc-3.8.11.2.jar lib/SteamServerJni.jar" />
88
</manifest>
99
<fileset dir="bin.ant">
1010
<exclude name="org/gotti/wurmunlimited/mods/spellmod/**"/>
@@ -24,6 +24,7 @@
2424
<fileset dir="lib">
2525
<include name="server.jar"/>
2626
<include name="common.jar"/>
27+
<include name="javassist.jar"/>
2728
</fileset>
2829
</classpath>
2930
</javac>

mods/spellmod.properties

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,9 @@ allowAllSpells=false
1111
# true: Allow a priest to cast spells from the same light. (i.e. Fo priests can cast Vyn and Mag spells but not Lib spells)
1212
# false: No effect
1313
allowLightSpells=true
14+
# true: There are no restrictions on the number of faith gains per day
15+
# false: Only 5 faith gains per day are possible
16+
unlimitedPrayers=false
17+
# true: There is no delay between faith gains
18+
# false: Prayers only yield faith if they are at least 20 minutes apart
19+
noPrayerDelay=false

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

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

17-
import org.gotti.wurmunlimited.mods.Configurable;
18-
import org.gotti.wurmunlimited.mods.WurmMod;
17+
import org.gotti.wurmunlimited.modloader.classhooks.HookBuilder;
18+
import org.gotti.wurmunlimited.modloader.interfaces.Configurable;
19+
import org.gotti.wurmunlimited.modloader.interfaces.WurmMod;
1920

2021
public class ModLoader {
2122

@@ -52,7 +53,7 @@ public WurmMod loadModFromInfo(Path modInfo) throws IOException {
5253
}
5354

5455
try {
55-
ClassLoader classloader = this.getClass().getClassLoader();
56+
ClassLoader classloader = HookBuilder.getInstance().getLoader();
5657
final String classpath = properties.getProperty("classpath");
5758
if (classpath != null) {
5859
classloader = createClassLoader(modname, classpath, classloader);
Lines changed: 8 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,22 @@
11
package org.gotti.wurmunlimited.modloader;
22

3-
import java.lang.reflect.Field;
43
import java.lang.reflect.InvocationHandler;
54
import java.lang.reflect.Method;
6-
import java.lang.reflect.Proxy;
7-
import java.util.logging.Level;
8-
import java.util.logging.Logger;
95

10-
import com.wurmonline.server.Server;
6+
import org.gotti.wurmunlimited.modloader.classhooks.HookBuilder;
117

128
public class ProxyServerHook extends ServerHook {
139

1410
public ProxyServerHook() {
1511

16-
Server instance = Server.getInstance();
17-
Server serverProxy = (Server) (Proxy.newProxyInstance(Server.class.getClassLoader(), new Class[] { Server.class }, new ServerInvocationHandler(instance)));
18-
19-
try {
20-
Field field = ReflectionUtil.getField(Server.class, "instance");
21-
ReflectionUtil.setPrivateField(Server.class, field, serverProxy);
22-
} catch (IllegalAccessException | ClassCastException | NoSuchFieldException e) {
23-
Logger.getLogger(this.getClass().getName()).log(Level.WARNING, e.getMessage(), e);
24-
}
25-
}
26-
27-
public class ServerInvocationHandler implements InvocationHandler {
28-
private Server wrapped;
29-
30-
public ServerInvocationHandler(Server server) {
31-
wrapped = server;
32-
}
33-
34-
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
35-
Object result = method.invoke(wrapped, args);
36-
if ("startRunning".equals(method.getName())) {
12+
HookBuilder.getInstance().registerHook("com.wurmonline.server.Server", "startRunning", "()V", new InvocationHandler() {
13+
14+
@Override
15+
public Object invoke(Object wrapped, Method method, Object[] args) throws Throwable {
16+
Object result = method.invoke(wrapped, args);
3717
fireOnServerStarted();
18+
return result;
3819
}
39-
return result;
40-
}
20+
});
4121
}
42-
4322
}

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
import java.util.logging.Level;
66
import java.util.logging.Logger;
77

8-
import org.gotti.wurmunlimited.mods.ServerStartedListener;
9-
import org.gotti.wurmunlimited.mods.WurmMod;
8+
import org.gotti.wurmunlimited.modloader.interfaces.ServerStartedListener;
9+
import org.gotti.wurmunlimited.modloader.interfaces.WurmMod;
1010

1111
public class ServerHook {
1212

@@ -30,4 +30,8 @@ public void fireOnServerStarted() {
3030
}
3131
}
3232
}
33+
34+
public static ServerHook createServerHook() {
35+
return new ProxyServerHook();
36+
}
3337
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package org.gotti.wurmunlimited.modloader.classhooks;
2+
3+
import java.lang.reflect.InvocationHandler;
4+
5+
public class ClassHook {
6+
private String methodName;
7+
private String methodType;
8+
private InvocationHandler invocationHandler;
9+
10+
public ClassHook(String methodName, String methodType, InvocationHandler invocationHandler) {
11+
this.setMethodName(methodName);
12+
this.setMethodType(methodType);
13+
this.setInvocationHandler(invocationHandler);
14+
}
15+
16+
public String getMethodName() {
17+
return methodName;
18+
}
19+
20+
protected void setMethodName(String methodName) {
21+
this.methodName = methodName;
22+
}
23+
24+
public String getMethodType() {
25+
return methodType;
26+
}
27+
28+
protected void setMethodType(String methodType) {
29+
this.methodType = methodType;
30+
}
31+
32+
public InvocationHandler getInvocationHandler() {
33+
return invocationHandler;
34+
}
35+
36+
protected void setInvocationHandler(InvocationHandler invocationHandler) {
37+
this.invocationHandler = invocationHandler;
38+
}
39+
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package org.gotti.wurmunlimited.modloader.classhooks;
2+
3+
import java.lang.reflect.InvocationHandler;
4+
import java.lang.reflect.Method;
5+
import java.util.ArrayList;
6+
import java.util.HashMap;
7+
import java.util.HashSet;
8+
import java.util.List;
9+
import java.util.Map;
10+
import java.util.Set;
11+
import java.util.logging.Level;
12+
import java.util.logging.Logger;
13+
14+
import javassist.CannotCompileException;
15+
import javassist.ClassPool;
16+
import javassist.CtClass;
17+
import javassist.CtMethod;
18+
import javassist.CtNewMethod;
19+
import javassist.Loader;
20+
import javassist.NotFoundException;
21+
import javassist.Translator;
22+
23+
public class HookBuilder {
24+
25+
private ClassPool classPool;
26+
27+
private Loader loader;
28+
29+
private Map<String, List<ClassHook>> classes = new HashMap<>();
30+
31+
private Map<String, InvocationTarget> invocationHandlers = new HashMap<>();
32+
33+
private static HookBuilder instance;
34+
35+
private HookBuilder() {
36+
classPool = ClassPool.getDefault();
37+
loader = new Loader(classPool);
38+
try {
39+
loader.addTranslator(classPool, new HookBuilderTranslator());
40+
} catch (CannotCompileException | NotFoundException e) {
41+
Logger.getLogger(this.getClass().getName()).log(Level.WARNING, null, e);
42+
}
43+
}
44+
45+
public static synchronized HookBuilder getInstance() {
46+
if (instance == null) {
47+
instance = new HookBuilder();
48+
}
49+
return instance;
50+
}
51+
52+
public ClassPool getClassPool() {
53+
return classPool;
54+
}
55+
56+
public Loader getLoader() {
57+
return loader;
58+
}
59+
60+
private class HookBuilderTranslator implements Translator {
61+
62+
@Override
63+
public void start(ClassPool classPool) throws NotFoundException, CannotCompileException {
64+
}
65+
66+
@Override
67+
public void onLoad(ClassPool classPool, String className) throws NotFoundException, CannotCompileException {
68+
System.out.println(className);
69+
if (classes.containsKey(className)) {
70+
addClassHooks(classPool, className, classes.get(className));
71+
}
72+
}
73+
74+
75+
}
76+
77+
private static String getUniqueMethodName(CtClass ctClass, String baseName) {
78+
Set<String> usedNames = new HashSet<>();
79+
for (CtMethod method : ctClass.getDeclaredMethods()) {
80+
usedNames.add(method.getName());
81+
}
82+
83+
int i = 1;
84+
do {
85+
String methodName = String.format("%s$%d", baseName, i++);
86+
if (!usedNames.contains(methodName)) {
87+
return methodName;
88+
}
89+
} while (true);
90+
}
91+
92+
private void addClassHooks(ClassPool pool, String className, List<ClassHook> classHooks) throws NotFoundException, CannotCompileException {
93+
CtClass ctClass = pool.get(className);
94+
for (ClassHook classHook : classHooks) {
95+
CtMethod origMethod = ctClass.getMethod(classHook.getMethodName(), classHook.getMethodType());
96+
97+
origMethod.setName(getUniqueMethodName(ctClass, classHook.getMethodName()));
98+
99+
CtMethod newMethod = CtNewMethod.copy(origMethod, classHook.getMethodName(), ctClass, null);
100+
101+
CtClass[] exceptionTypes = origMethod.getExceptionTypes();
102+
Class<?>[] exceptionClasses = new Class<?>[exceptionTypes.length];
103+
for (int i = 0; i < exceptionTypes.length; i++) {
104+
try {
105+
exceptionClasses[i] = loader.loadClass(exceptionTypes[i].getName());
106+
} catch (ClassNotFoundException e) {
107+
throw new CannotCompileException(e);
108+
}
109+
}
110+
111+
InvocationTarget invocationTarget = new InvocationTarget(classHook.getInvocationHandler(), origMethod.getName(), exceptionClasses);
112+
invocationHandlers.put(origMethod.getLongName(), invocationTarget);
113+
114+
String type = newMethod.getReturnType().getName();
115+
StringBuilder builder = new StringBuilder();
116+
builder.append("{\nObject result = org.gotti.wurmunlimited.modloader.classhooks.HookBuilder.getInstance().invoke(this,\"");
117+
builder.append(origMethod.getLongName());
118+
builder.append("\",$args);\n");
119+
if (!"void".equals(type)) {
120+
builder.append("return (" + type + ")result;\n");
121+
}
122+
builder.append("\n}");
123+
124+
newMethod.setBody(builder.toString());
125+
ctClass.addMethod(newMethod);
126+
}
127+
}
128+
129+
public void registerHook(String className, String methodName, String methodType, InvocationHandler invocationHandler) {
130+
List<ClassHook> classHooks = classes.get(className);
131+
if (classHooks == null) {
132+
classHooks = new ArrayList<>();
133+
classes.put(className, classHooks);
134+
}
135+
136+
classHooks.add(new ClassHook(methodName, methodType, invocationHandler));
137+
}
138+
139+
public Object invoke(Object object, String wrappedMethod, Object[] args) throws Throwable {
140+
InvocationTarget invocationTarget = invocationHandlers.get(wrappedMethod);
141+
if (invocationTarget == null) {
142+
throw new RuntimeException("Uninstrumented method " + wrappedMethod);
143+
}
144+
145+
try {
146+
Method method = invocationTarget.resolveMethod(object.getClass());
147+
148+
return invocationTarget.getInvocationHandler().invoke(object, method, args);
149+
} catch (Throwable e) {
150+
for (Class<?> exceptionType : invocationTarget.getExceptionTypes()) {
151+
if (exceptionType.isInstance(e)) {
152+
throw e;
153+
}
154+
}
155+
throw new RuntimeException(e);
156+
}
157+
}
158+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package org.gotti.wurmunlimited.modloader.classhooks;
2+
3+
import java.lang.reflect.InvocationHandler;
4+
import java.lang.reflect.Method;
5+
6+
public class InvocationTarget {
7+
8+
private String methodName;
9+
10+
private Method method;
11+
12+
private InvocationHandler invocationHandler;
13+
14+
private Class<?>[] exceptionTypes;
15+
16+
public InvocationTarget(InvocationHandler invocationHandler, String methodName, Class<?>[] exceptionTypes) {
17+
this.setMethod(null);
18+
this.setMethodName(methodName);
19+
this.setInvocationHandler(invocationHandler);
20+
this.setExceptionTypes(exceptionTypes);
21+
}
22+
23+
public Method resolveMethod(Class<? extends Object> targetClass) throws NoSuchMethodException {
24+
if (getMethod() != null) {
25+
return getMethod();
26+
}
27+
for (Method m : targetClass.getDeclaredMethods()) {
28+
if (m.getName().equals(getMethodName())) {
29+
setMethod(m);
30+
return getMethod();
31+
}
32+
}
33+
throw new NoSuchMethodException(getMethodName());
34+
}
35+
36+
public String getMethodName() {
37+
return methodName;
38+
}
39+
40+
protected void setMethodName(String methodName) {
41+
this.methodName = methodName;
42+
}
43+
44+
public Method getMethod() {
45+
return method;
46+
}
47+
48+
protected void setMethod(Method method) {
49+
this.method = method;
50+
}
51+
52+
public InvocationHandler getInvocationHandler() {
53+
return invocationHandler;
54+
}
55+
56+
protected void setInvocationHandler(InvocationHandler invocationHandler) {
57+
this.invocationHandler = invocationHandler;
58+
}
59+
60+
public Class<?>[] getExceptionTypes() {
61+
return exceptionTypes;
62+
}
63+
64+
protected void setExceptionTypes(Class<?>[] exceptionTypes) {
65+
this.exceptionTypes = exceptionTypes;
66+
}
67+
}

src/org/gotti/wurmunlimited/mods/Configurable.java renamed to src/org/gotti/wurmunlimited/modloader/interfaces/Configurable.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package org.gotti.wurmunlimited.mods;
1+
package org.gotti.wurmunlimited.modloader.interfaces;
22

33
import java.util.Properties;
44

src/org/gotti/wurmunlimited/mods/ServerStartedListener.java renamed to src/org/gotti/wurmunlimited/modloader/interfaces/ServerStartedListener.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package org.gotti.wurmunlimited.mods;
1+
package org.gotti.wurmunlimited.modloader.interfaces;
22

33
public interface ServerStartedListener {
44

0 commit comments

Comments
 (0)