Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.evacipated.cardcrawl.modthespire.lib;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SpireMethod {
String onConflict() default "Postfix";

Class<?> returnType();

String POSTFIX = "Postfix";

String PREFIX = "Prefix";
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@

import com.evacipated.cardcrawl.modthespire.Loader;
import com.evacipated.cardcrawl.modthespire.lib.SpireField;
import com.evacipated.cardcrawl.modthespire.lib.SpireMethod;
import com.evacipated.cardcrawl.modthespire.lib.StaticSpireField;
import javassist.*;
import javassist.bytecode.*;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.AnnotationImpl;
import javassist.expr.ExprEditor;
import javassist.expr.MethodCall;
import javassist.expr.NewExpr;
import javassist.expr.*;

import java.lang.reflect.Proxy;
import java.util.Random;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand All @@ -21,6 +20,9 @@ public class ClassPatchInfo extends PatchInfo
private CtClass ctPatchClass;
private CtClass ctClassToPatch;

private static final Map<String, CtMethod> methodCache = new HashMap<>();
private static final Map<String, List<String>> methodBodyCache = new HashMap<>();

public ClassPatchInfo(CtClass ctClassToPatch, CtClass ctPatchClass)
{
super(null, null);
Expand Down Expand Up @@ -208,10 +210,104 @@ public void doPatch() throws PatchingException
}
}
}
for (CtMethod m : ctPatchClass.getDeclaredMethods()) {
if (m.hasAnnotation(SpireMethod.class)) {
CtClass[] params = m.getParameterTypes();
SpireMethod anno = (SpireMethod) m.getAnnotation(SpireMethod.class);
boolean hasReturnValue = !anno.returnType().equals(void.class);
if (Loader.DEBUG) {
System.out.println(" - Adding Method: " + ctClassToPatch.getName() + "." + m.getName() + " " + m.getSignature());
System.out.print(" Params: ");
for (CtClass param : params) {
System.out.print(param.getSimpleName() + ", ");
}
System.out.println();
}
CtClass returnType;
if (anno.returnType().isPrimitive()) {
switch (anno.returnType().getName()) {
case "int":
returnType = CtPrimitiveType.intType;
break;
case "float":
returnType = CtPrimitiveType.floatType;
break;
case "double":
returnType = CtPrimitiveType.doubleType;
break;
case "long":
returnType = CtPrimitiveType.longType;
break;
case "char":
returnType = CtPrimitiveType.charType;
break;
case "byte":
returnType = CtPrimitiveType.byteType;
break;
case "short":
returnType = CtPrimitiveType.shortType;
break;
case "boolean":
returnType = CtPrimitiveType.booleanType;
break;
case "void":
returnType = CtPrimitiveType.voidType;
break;
default:
throw new CannotCompileException("Unknown primitive type: " + anno.returnType().getName());
}
} else {
returnType = ctClassToPatch.getClassPool().getCtClass(anno.returnType().getName());
}
CtMethod newMethod = new CtMethod(returnType, m.getName(), Arrays.copyOfRange(params, hasReturnValue ? 2 : 1, params.length), ctClassToPatch);
String methodBody = ctPatchClass.getName() + "." + m.getName() + "($0%s" + "$$);";
if (hasReturnValue) {
methodBody = String.format(methodBody, ",retVal,");
} else {
methodBody = String.format(methodBody, ",");
}
try {
ctClassToPatch.addMethod(newMethod);
List<String> src = new ArrayList<>();
src.add("java.lang.Object[] retVal = new java.lang.Object[1];");
src.add(methodBody);
src.add(String.format("return%s", hasReturnValue ? " ($r) retVal[0];" : ";"));
methodBodyCache.put(Descriptor.ofMethod(newMethod.getReturnType(), newMethod.getParameterTypes()) + ctClassToPatch.getName() + newMethod.getName(), src);
methodCache.put(Descriptor.ofMethod(newMethod.getReturnType(), newMethod.getParameterTypes()) + ctClassToPatch.getName() + newMethod.getName(), newMethod);
StringBuilder tmp = new StringBuilder();
tmp.append("{");
for (String s : src) {
tmp.append(s).append("\n");
}
tmp.append("}");
newMethod.setBody(tmp.toString());

ctClassToPatch.setModifiers(ctClassToPatch.getModifiers() | Modifier.PUBLIC);
if ((ctClassToPatch.getModifiers() & Modifier.ABSTRACT) > 0) {
ctClassToPatch.setModifiers(ctClassToPatch.getModifiers() & ~Modifier.ABSTRACT);
}
} catch (DuplicateMemberException duplicateMemberException) {
newMethod = methodCache.get(Descriptor.ofMethod(newMethod.getReturnType(), Arrays.copyOfRange(params, hasReturnValue ? 2 : 1, params.length)) + ctClassToPatch.getName() + newMethod.getName());
List<String> src = methodBodyCache.get(Descriptor.ofMethod(newMethod.getReturnType(), Arrays.copyOfRange(params, hasReturnValue ? 2 : 1, params.length)) + ctClassToPatch.getName() + newMethod.getName());
if (SpireMethod.POSTFIX.equals(anno.onConflict())) {
src.add(src.size() - 1, methodBody);
} else {
src.add(1, methodBody);
}
StringBuilder tmp = new StringBuilder();
tmp.append("{");
for (String s : src) {
tmp.append(s).append("\n");
}
tmp.append("}");
newMethod.setBody(tmp.toString());
}
}
}
if (Loader.DEBUG) {
System.out.println();
}
} catch (CannotCompileException | NotFoundException e) {
} catch (CannotCompileException | NotFoundException | ClassNotFoundException e) {
throw new PatchingException(e);
}
}
Expand Down