QuickMath/src/main/java/io/gitlab/jfronny/quickmath/BytecodeTransformer.java

140 lines
7.3 KiB
Java

package io.gitlab.jfronny.quickmath;
import io.gitlab.jfronny.libjf.unsafe.asm.AsmConfig;
import io.gitlab.jfronny.libjf.unsafe.asm.AsmTransformer;
import io.gitlab.jfronny.libjf.unsafe.asm.patch.Patch;
import io.gitlab.jfronny.libjf.unsafe.asm.patch.PatchUtil;
import org.objectweb.asm.*;
import org.objectweb.asm.tree.*;
import java.util.Map;
import java.util.Set;
public class BytecodeTransformer implements AsmConfig {
private static final String math = "java/lang/Math";
private static final String random = "java/util/Random";
private static final String mathUtil = "io/gitlab/jfronny/quickmath/MathUtil";
private static final String mathHelperIntermediary = "net.minecraft.class_3532";
private static final String mathHelper = PatchUtil.getRemappedInternal(mathHelperIntermediary);
private static final String mojangRandomIntermediary = "net.minecraft.class_5819";
private static final String mojangRandom = PatchUtil.getRemappedInternal(mojangRandomIntermediary);
private static final String mathHelperRandomUuid = mth("method_15378", "(Lnet/minecraft/class_5819;)Ljava/util/UUID;");
private static final Map<String, String> mth = DMap.of( // Maps methods in mathHelper to QuickMäth MathUtil methods
mth("method_15374", "(F)F"), "sinM",
mth("method_15362", "(F)F"), "cosM",
mth("method_15355", "(F)F"), "sqrtM",
mth("method_15375", "(D)I"), "floor"
);
private static final Map<String, String> rnd = DMap.of( // Maps methods in Minecraft Random to QuickMäth MathUtil methods
rnd("method_43054", "()I"), "nextInt",
rnd("method_43048", "(I)I"), "nextInt",
rnd("method_43055", "()J"), "nextLong",
rnd("method_43056", "()Z"), "nextBoolean",
rnd("method_43057", "()F"), "nextFloat",
rnd("method_43058", "()D"), "random",
rnd("method_43059", "()D"), "random"
);
private static final Map<String, Boolean> stat = Map.of( // Maps QuickMäth MathUtil methods to booleans representing whether to overwrite them
"sin", Cfg.corruptTrigonometry2,
"cos", Cfg.corruptTrigonometry2,
"sinM", Cfg.corruptTrigonometry,
"cosM", Cfg.corruptTrigonometry,
//"sqrt", Cfg.corruptGenericMath,
"sqrtM", Cfg.corruptGenericMath,
//"floor", Cfg.corruptGenericMath2,
// "nextInt", Cfg.corruptGenericMath2,
"nextLong", Cfg.corruptGenericMath2,
"nextBoolean", Cfg.corruptGenericMath2,
// "nextFloat", Cfg.corruptGenericMath2,
"random", Cfg.corruptGenericMath2
);
private static String mth(String method, String descriptor) {
return AsmTransformer.MAPPING_RESOLVER.mapMethodName(AsmTransformer.INTERMEDIARY, mathHelperIntermediary, method, descriptor);
}
private static String rnd(String method, String descriptor) {
return AsmTransformer.MAPPING_RESOLVER.mapMethodName(AsmTransformer.INTERMEDIARY, mojangRandomIntermediary, method, descriptor);
}
@Override
public Set<String> skipClasses() {
return null;
}
@Override
public Set<Patch> getPatches() {
return Set.of(this::patchInvokes);
}
private void patchInvokes(ClassNode klazz) {
for (MethodNode method : klazz.methods) {
if (klazz.name.equals(mathHelper) && method.name.equals(mathHelperRandomUuid)) { // UUIDs still need to work
if (Cfg.debugAsm) {
ModMain.LOGGER.info("Skipped replacing method calls in MathHelper.randomUuid");
}
continue;
}
for (AbstractInsnNode insn : method.instructions.toArray()) {
if (insn.getOpcode() == Opcodes.INVOKESTATIC || insn.getOpcode() == Opcodes.INVOKEVIRTUAL || insn.getOpcode() == Opcodes.INVOKEINTERFACE) {
String insNew = null;
MethodInsnNode mIns = (MethodInsnNode) insn;
// Resolve a possible replacement method in QuickMäth MathUtil
if (mIns.owner.equals(math)) {
if (stat.containsKey(mIns.name))
insNew = mIns.name;
} else if (mIns.owner.equals(mathHelper)) {
if (mth.containsKey(mIns.name))
insNew = mth.get(mIns.name);
} else if (mIns.owner.equals(mojangRandom)) {
if (rnd.containsKey(mIns.name))
insNew = rnd.get(mIns.name);
} else if (mIns.owner.equals(random)) {
insNew = switch (mIns.name) {
case "nextInt" -> "nextInt";
case "nextLong" -> "nextLong";
case "nextBoolean" -> "nextBoolean";
case "nextFloat" -> "nextFloat";
case "nextDouble", "nextGaussian" -> "random";
default -> null;
};
}
// Check whether the method should be replaced
if (!klazz.name.equals(mathUtil) && insNew != null && stat.containsKey(insNew) && stat.get(insNew)) {
String originalOwner = mIns.owner;
String originalName = mIns.name;
// Pop the instance when calling an instance method
if (mIns.getOpcode() != Opcodes.INVOKESTATIC) {
Type[] params = Type.getArgumentTypes(mIns.desc);
// This implementation only works with 0 or 1 parameters of category 1 computational types
// This means that doubles and longs are unsupported
if (params.length > 1)
throw new IllegalArgumentException("The quickmeth bytecode transformer does not support more than one argument");
for (Type param : params) {
if (param.getSize() != 1)
throw new IllegalStateException("The quickmeth bytecode transformer only supports category 1 computational types");
}
// If a parameter is present, swap the object to the top, then pop
if (params.length == 1)
method.instructions.insertBefore(mIns, new InsnNode(Opcodes.SWAP));
// Pop the object instance, leaving the parameter if it exists
method.instructions.insertBefore(mIns, new InsnNode(Opcodes.POP));
}
// Invoke the static method
mIns.setOpcode(Opcodes.INVOKESTATIC);
mIns.owner = mathUtil;
mIns.name = insNew;
mIns.itf = false;
if (Cfg.debugAsm) {
ModMain.LOGGER.info("Replaced call to L" + originalOwner + ";" + originalName + mIns.desc
+ " in " + klazz.name
+ " with L" + mIns.owner + ";" + mIns.name + mIns.desc);
}
}
}
}
}
}
}