Skip to content

Commit af9893b

Browse files
committed
feat: new bytecode replacement framework
1 parent 44c1ee9 commit af9893b

File tree

6 files changed

+144
-126
lines changed

6 files changed

+144
-126
lines changed

javaplugin/src/main/java/moe/nea/firmament/javaplugin/IntermediaryMethodReplacer.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public Void visitCompilationUnit(CompilationUnitTree node, Void unused) {
3131
public void replaceMethodName(JCTree.JCMethodInvocation node) {
3232
var select = node.getMethodSelect();
3333
if (!(select instanceof JCTree.JCFieldAccess fieldAccess)) return;
34-
if (!fieldAccess.name.contentEquals("methodName")) return;
34+
if (!fieldAccess.name.contentEquals("intermediaryMethod")) return;
3535
if (!(node.args.head instanceof JCTree.JCMemberReference methodReference)) {
3636
plugin.utils.reportError(sourceFile, node, "Please provide a Class::method reference directly (and nothing else)");
3737
return;
@@ -43,14 +43,17 @@ public void replaceMethodName(JCTree.JCMethodInvocation node) {
4343
type.tsym.flatName().toString(),
4444
clearName
4545
);
46-
fieldAccess.name = plugin.names.fromString("id");
47-
node.args = List.of(plugin.treeMaker.Literal(intermediaryName));
46+
fieldAccess.name = plugin.names.fromString("ofMethod");
47+
var args = List.<JCTree.JCExpression>of(plugin.treeMaker.Literal(intermediaryName.interMethodName()));
48+
args.tail = List.of(plugin.treeMaker.Literal(intermediaryName.interClassName()));
49+
args.tail.tail = node.args.tail;
50+
node.args = args;
4851
}
4952

5053
public void replaceClassName(JCTree.JCMethodInvocation node) {
5154
var select = node.getMethodSelect();
5255
if (!(select instanceof JCTree.JCFieldAccess fieldAccess)) return;
53-
if (!fieldAccess.name.contentEquals("className")) return;
56+
if (!fieldAccess.name.contentEquals("intermediaryClass")) return;
5457
if (node.getTypeArguments().size() != 1) {
5558
plugin.utils.reportError(sourceFile, node, "You need to explicitly provide the class you want the intermediary name for");
5659
return;
@@ -63,7 +66,7 @@ public void replaceClassName(JCTree.JCMethodInvocation node) {
6366
plugin.utils.reportError(sourceFile, node, "Unknown class name " + sourceName);
6467
return;
6568
}
66-
fieldAccess.name = plugin.names.fromString("id");
69+
fieldAccess.name = plugin.names.fromString("ofIntermediaryClass");
6770
node.typeargs = List.nil();
6871
node.args = List.of(plugin.treeMaker.Literal(mappedName));
6972
}

javaplugin/src/main/java/moe/nea/firmament/javaplugin/MappingTree.java

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,21 @@ public MappingTree(TinyFile tinyV2File, String sourceNamespace, String targetNam
1818
if (sourceIndex < 0)
1919
throw new RuntimeException("Could not find source namespace " + sourceNamespace + " in mappings file.");
2020
this.classLookup = tinyV2File
21-
.getClassEntries()
22-
.stream()
23-
.collect(Collectors.toMap(it -> it.getClassNames().get(sourceIndex), it -> it));
21+
.getClassEntries()
22+
.stream()
23+
.collect(Collectors.toMap(it -> it.getClassNames().get(sourceIndex), it -> it));
2424
targetIndex = tinyV2File.getHeader().getNamespaces().indexOf(targetNamespace);
2525
if (targetIndex < 0)
2626
throw new RuntimeException("Could not find target namespace " + targetNamespace + " in mappings file.");
2727
}
2828

29-
public String resolveMethodToIntermediary(String className, String methodName) {
29+
public record MethodCoordinate(
30+
String interClassName,
31+
String interMethodName
32+
) {
33+
}
34+
35+
public MethodCoordinate resolveMethodToIntermediary(String className, String methodName) {
3036
var classData = classLookup.get(className.replace(".", "/"));
3137
TinyMethod candidate = null;
3238
for (TinyMethod method : classData.getMethods()) {
@@ -37,7 +43,11 @@ public String resolveMethodToIntermediary(String className, String methodName) {
3743
candidate = method;
3844
}
3945
}
40-
return candidate.getMethodNames().get(targetIndex);
46+
if (candidate == null)
47+
throw new RuntimeException("Couldd not find candidate for method " + className + "." + methodName);
48+
return new MethodCoordinate(
49+
classData.getClassNames().get(targetIndex),
50+
candidate.getMethodNames().get(targetIndex));
4151
}
4252

4353
public String resolveClassToIntermediary(String className) {
@@ -46,6 +56,6 @@ public String resolveClassToIntermediary(String className) {
4656
return null;
4757
}
4858
return cls.getClassNames().get(targetIndex)
49-
.replace("/", ".");
59+
.replace("/", ".");
5060
}
5161
}

src/main/java/moe/nea/firmament/init/HandledScreenRiser.java

Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -23,35 +23,36 @@
2323
import java.util.function.Consumer;
2424

2525
public class HandledScreenRiser extends RiserUtils {
26-
@IntermediaryName(Screen.class)
27-
String Screen;
28-
@IntermediaryName(KeyInput.class)
29-
String KeyInput;
30-
@IntermediaryName(CharInput.class)
31-
String CharInput;
32-
@IntermediaryName(HandledScreen.class)
33-
String HandledScreen;
34-
Type mouseScrolledDesc = Type.getMethodType(Type.BOOLEAN_TYPE, Type.DOUBLE_TYPE, Type.DOUBLE_TYPE, Type.DOUBLE_TYPE, Type.DOUBLE_TYPE);
35-
String mouseScrolled = remapper.mapMethodName("intermediary", Intermediary.<Element>className(),
36-
Intermediary.methodName(Element::mouseScrolled),
37-
mouseScrolledDesc.getDescriptor());
38-
// boolean keyReleased(int keyCode, int scanCode, int modifiers)
39-
Type keyReleasedDesc = Type.getMethodType(Type.BOOLEAN_TYPE, getTypeForClassName(KeyInput));
40-
String keyReleased = remapper.mapMethodName("intermediary", Intermediary.<Element>className(),
41-
Intermediary.methodName(Element::keyReleased),
42-
keyReleasedDesc.getDescriptor());
43-
// public boolean charTyped(char chr, int modifiers)
44-
Type charTypedDesc = Type.getMethodType(Type.BOOLEAN_TYPE, getTypeForClassName(CharInput));
45-
String charTyped = remapper.mapMethodName("intermediary", Intermediary.<Element>className(),
46-
Intermediary.methodName(Element::charTyped),
47-
charTypedDesc.getDescriptor());
26+
Intermediary.InterClass Screen = Intermediary.<Screen>intermediaryClass();
27+
Intermediary.InterClass KeyInput = Intermediary.<KeyInput>intermediaryClass();
28+
Intermediary.InterClass CharInput = Intermediary.<CharInput>intermediaryClass();
29+
Intermediary.InterClass HandledScreen = Intermediary.<HandledScreen>intermediaryClass();
30+
Intermediary.InterMethod mouseScrolled = Intermediary.intermediaryMethod(
31+
Element::mouseScrolled,
32+
Intermediary.ofClass(boolean.class),
33+
Intermediary.ofClass(double.class),
34+
Intermediary.ofClass(double.class),
35+
Intermediary.ofClass(double.class),
36+
Intermediary.ofClass(double.class)
37+
);
38+
Intermediary.InterMethod keyReleased = Intermediary.intermediaryMethod(
39+
Element::keyReleased,
40+
Intermediary.ofClass(boolean.class),
41+
KeyInput
42+
);
43+
Intermediary.InterMethod charTyped = Intermediary.intermediaryMethod(
44+
Element::charTyped,
45+
Intermediary.ofClass(boolean.class),
46+
CharInput
47+
);
48+
4849

4950

5051
@Override
5152
public void addTinkerers() {
52-
ClassTinkerers.addTransformation(HandledScreen, this::addMouseScroll, true);
53-
ClassTinkerers.addTransformation(HandledScreen, this::addKeyReleased, true);
54-
ClassTinkerers.addTransformation(HandledScreen, this::addCharTyped, true);
53+
addTransformation(HandledScreen, this::addMouseScroll, true);
54+
addTransformation(HandledScreen, this::addKeyReleased, true);
55+
addTransformation(HandledScreen, this::addCharTyped, true);
5556
}
5657

5758
/**
@@ -85,7 +86,7 @@ void insertTrueHandler(MethodNode node,
8586

8687
void addKeyReleased(ClassNode classNode) {
8788
addSuperInjector(
88-
classNode, keyReleased, keyReleasedDesc, "keyReleased_firmament",
89+
classNode, keyReleased.mapped(), keyReleased.mappedDesc(), "keyReleased_firmament",
8990
insns -> {
9091
// ALOAD 0, load this
9192
insns.add(new VarInsnNode(Opcodes.ALOAD, 0));
@@ -96,7 +97,7 @@ void addKeyReleased(ClassNode classNode) {
9697

9798
void addCharTyped(ClassNode classNode) {
9899
addSuperInjector(
99-
classNode, charTyped, charTypedDesc, "charTyped_firmament",
100+
classNode, charTyped.mapped(), charTyped.mappedDesc(), "charTyped_firmament",
100101
insns -> {
101102
// ALOAD 0, load this
102103
insns.add(new VarInsnNode(Opcodes.ALOAD, 0));
@@ -124,7 +125,7 @@ void addSuperInjector(
124125
var insns = keyReleasedNode.instructions;
125126
loadArgs.accept(insns);
126127
// INVOKESPECIAL call super method
127-
insns.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, getTypeForClassName(Screen).getInternalName(),
128+
insns.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, Screen.mapped().getInternalName(),
128129
name, desc.getDescriptor()));
129130
// IRETURN return int on stack (booleans are int at runtime)
130131
insns.add(new InsnNode(Opcodes.IRETURN));
@@ -133,7 +134,7 @@ void addSuperInjector(
133134
insertTrueHandler(keyReleasedNode, loadArgs, insns -> {
134135
// INVOKEVIRTUAL call custom handler
135136
insns.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
136-
getTypeForClassName(HandledScreen).getInternalName(),
137+
HandledScreen.mapped().getInternalName(),
137138
firmamentName,
138139
desc.getDescriptor()));
139140
});
@@ -142,7 +143,7 @@ void addSuperInjector(
142143

143144
void addMouseScroll(ClassNode classNode) {
144145
addSuperInjector(
145-
classNode, mouseScrolled, mouseScrolledDesc, "mouseScrolled_firmament",
146+
classNode, mouseScrolled.mapped(), mouseScrolled.mappedDesc(), "mouseScrolled_firmament",
146147
insns -> {
147148
// ALOAD 0, load this
148149
insns.add(new VarInsnNode(Opcodes.ALOAD, 0));

src/main/java/moe/nea/firmament/init/Intermediary.java

Lines changed: 62 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -7,57 +7,66 @@
77
import java.util.List;
88

99
public class Intermediary {
10-
private static final MappingResolver RESOLVER = FabricLoader.getInstance().getMappingResolver();
11-
12-
static String methodName(Object object) {
13-
throw new AssertionError("Cannot be called at runtime");
14-
}
15-
16-
static <T> String className() {
17-
throw new AssertionError("Cannot be called at runtime");
18-
}
19-
20-
static String id(String source) {
21-
return source;
22-
}
23-
24-
// public record Class(
25-
// Type intermediaryClass
26-
// ) {
27-
// public Class(String intermediaryClass) {
28-
// this(Type.getObjectType(intermediaryClass.replace('.', '/')));
29-
// }
30-
//
31-
// public String getMappedName() {
32-
// return RESOLVER.mapClassName("intermediary", intermediaryClass.getInternalName()
33-
// .replace('/', '.'));
34-
// }
35-
// }
36-
//
37-
// public record Method(
38-
// Type intermediaryClassName,
39-
// String intermediaryMethodName,
40-
// Type intermediaryReturnType,
41-
// List<Type> intermediaryArgumentTypes
42-
// ) {
43-
// public Method(
44-
// String intermediaryClassName,
45-
// String intermediaryMethodName,
46-
// String intermediaryReturnType,
47-
// String... intermediaryArgumentTypes
48-
// ) {
49-
// this(intermediaryClassName, intermediaryMethodName, intermediaryReturnType, List.of(intermediaryArgumentTypes));
50-
// }
51-
//
52-
// public String getMappedMethodName() {
53-
// return RESOLVER.mapMethodName("intermediary",
54-
// intermediaryClassName.getInternalName().replace('/', '.'));
55-
// }
56-
//
57-
// public Type getIntermediaryDescriptor() {
58-
// return Type.getMethodType(intermediaryReturnType, intermediaryArgumentTypes.toArray(Type[]::new));
59-
// }
60-
//
61-
//
62-
// }
10+
private static final MappingResolver RESOLVER = FabricLoader.getInstance().getMappingResolver();
11+
12+
static InterMethod intermediaryMethod(Object object, InterClass returnType, InterClass... args) {
13+
throw new AssertionError("Cannot be called at runtime");
14+
}
15+
16+
static <T> InterClass intermediaryClass() {
17+
throw new AssertionError("Cannot be called at runtime");
18+
}
19+
20+
public static InterClass ofIntermediaryClass(String interClass) {
21+
return new InterClass(Type.getObjectType(interClass.replace('.', '/')));
22+
}
23+
24+
public static InterClass ofClass(Class<?> unmappedClass) {
25+
return new InterClass(Type.getType(unmappedClass));
26+
}
27+
28+
public static InterMethod ofMethod(String intermediary, String ownerType, InterClass returnType, InterClass... argTypes) {
29+
return new InterMethod(intermediary, ofIntermediaryClass(ownerType), returnType, List.of(argTypes));
30+
}
31+
32+
public record InterClass(
33+
Type intermediary
34+
) {
35+
public Type mapped() {
36+
if (intermediary().getSort() != Type.OBJECT)
37+
return intermediary();
38+
return Type.getObjectType(RESOLVER.mapClassName("intermediary", intermediary().getClassName())
39+
.replace('.', '/'));
40+
}
41+
}
42+
43+
public record InterMethod(
44+
String intermediary,
45+
InterClass ownerType,
46+
InterClass returnType,
47+
List<InterClass> argumentTypes
48+
) {
49+
public Type intermediaryDesc() {
50+
return Type.getMethodType(
51+
returnType.intermediary(),
52+
argumentTypes().stream().map(InterClass::intermediary).toArray(Type[]::new)
53+
);
54+
}
55+
56+
public Type mappedDesc() {
57+
return Type.getMethodType(
58+
returnType.mapped(),
59+
argumentTypes().stream().map(InterClass::mapped).toArray(Type[]::new)
60+
);
61+
}
62+
63+
public String mapped() {
64+
return RESOLVER.mapMethodName(
65+
"intermediary",
66+
ownerType.intermediary().getClassName(),
67+
intermediary(),
68+
intermediaryDesc().getDescriptor()
69+
);
70+
}
71+
}
6372
}

src/main/java/moe/nea/firmament/init/RiserUtils.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11

22
package moe.nea.firmament.init;
33

4+
import me.shedaniel.mm.api.ClassTinkerers;
45
import net.fabricmc.loader.api.FabricLoader;
56
import net.fabricmc.loader.api.MappingResolver;
67
import org.objectweb.asm.Type;
78
import org.objectweb.asm.tree.ClassNode;
89
import org.objectweb.asm.tree.MethodNode;
910

11+
import java.util.function.Consumer;
12+
1013
public abstract class RiserUtils {
1114
protected Type getTypeForClassName(String className) {
1215
return Type.getObjectType(className.replace('.', '/'));
@@ -24,4 +27,8 @@ protected MethodNode findMethod(ClassNode classNode, String name, Type desc) {
2427
return null;
2528
}
2629

30+
public void addTransformation(Intermediary.InterClass interClass, Consumer<ClassNode> transformer, boolean post) {
31+
ClassTinkerers.addTransformation(interClass.mapped().getClassName(), transformer, post);
32+
}
33+
2734
}

0 commit comments

Comments
 (0)