Skip to content

Commit

Permalink
Handle cases of ModifyArgs targeting INDYs in Gui
Browse files Browse the repository at this point in the history
  • Loading branch information
Su5eD committed Aug 9, 2024
1 parent 170e7e3 commit 05e2aa1
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,25 @@ public static List<List<AbstractInsnNode>> getInvocationInsns(MethodNode methodN
});
}

public static int getMethodCallOrdinal(MethodNode method, MethodInsnNode insn) {
List<MethodInsnNode> insns = new ArrayList<>();
for (AbstractInsnNode i : method.instructions) {
if (i instanceof MethodInsnNode m && InsnComparator.instructionsEqual(m, insn)) {
insns.add(m);
}
}
return insns.indexOf(insn);
}

public static int getArgIndex(String desc, Type type) {
List<Type> args = Arrays.asList(Type.getArgumentTypes(desc));
List<Type> found = args.stream().filter(type::equals).toList();
if (found.size() != 1) {
return -1;
}
return args.indexOf(found.getFirst());
}

@Nullable
public static List<AbstractInsnNode> findMethodCallParamInsns(MethodNode methodNode, MethodInsnNode insn) {
MethodCallInterpreter interpreter = MethodCallAnalyzer.analyzeInterpretMethod(methodNode, new MethodCallInterpreter(insn));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,11 @@ public void refresh(AnnotationNode annotationNode) {
this.annotationNode = annotationNode;
this.handleCache.values().forEach(v -> v.refresh(annotationNode));
}

public void setOrAppend(String key, Object value) {
getValue(key).ifPresentOrElse(
v -> v.set(value),
() -> appendValue(key, value)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
import com.google.common.collect.Multimap;
import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.*;
import org.sinytra.adapter.patch.analysis.locals.LocalVariableLookup;
import org.sinytra.adapter.patch.analysis.InsnComparator;
import org.sinytra.adapter.patch.analysis.MethodCallAnalyzer;
import org.sinytra.adapter.patch.analysis.MethodLabelComparator;
import org.sinytra.adapter.patch.analysis.locals.LocalVariableLookup;
import org.sinytra.adapter.patch.analysis.selector.AnnotationHandle;
import org.sinytra.adapter.patch.api.MethodContext;
import org.sinytra.adapter.patch.api.MixinConstants;
Expand All @@ -22,13 +24,11 @@
import org.sinytra.adapter.patch.transformer.pipeline.MethodTransformationPipeline;
import org.sinytra.adapter.patch.util.AdapterUtil;

import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.*;
import java.util.stream.Stream;

public class DynFixMethodComparison implements DynamicFixer<DynFixMethodComparison.Data> {
private static final Set<String> ACCEPTED_ANNOTATIONS = Set.of(MixinConstants.INJECT, MixinConstants.WRAP_OPERATION);
private static final Set<String> ACCEPTED_ANNOTATIONS = Set.of(MixinConstants.INJECT, MixinConstants.WRAP_OPERATION, MixinConstants.MODIFY_ARG);

public record Data(AbstractInsnNode cleanInjectionInsn) {}

Expand Down Expand Up @@ -58,6 +58,10 @@ public FixResult apply(ClassNode classNode, MethodNode methodNode, MethodContext
}
List<List<AbstractInsnNode>> hunkLabels = comparisonResult.patchedLabels();

if (methodContext.methodAnnotation().matchesDesc(MixinConstants.MODIFY_ARG)) {
return handleModifyArgInjectionPoint(cleanInjectionInsn, hunkLabels, methodContext);
}

if (methodContext.methodAnnotation().matchesDesc(MixinConstants.WRAP_OPERATION)) {
return handleWrapOperationToInstanceOf(cleanInjectionInsn, comparisonResult.cleanLabel(), hunkLabels, methodContext)
.or(() -> handleWrapOpertationNewInjectionPoint(cleanInjectionInsn, comparisonResult.cleanLabel(), hunkLabels, methodContext))
Expand Down Expand Up @@ -90,6 +94,93 @@ public FixResult apply(ClassNode classNode, MethodNode methodNode, MethodContext
return null;
}

// Handle ModifyArg when targetting INDY values
private static FixResult handleModifyArgInjectionPoint(AbstractInsnNode cleanInjectionInsn, List<List<AbstractInsnNode>> hunkLabels, MethodContext methodContext) {
if (!(cleanInjectionInsn instanceof MethodInsnNode minsn)) {
return null;
}

Type desiredType = Type.getArgumentTypes(methodContext.getMixinMethod().desc)[0];

int index = MethodCallAnalyzer.getArgIndex(minsn.desc, desiredType) + 1;
if (index == 0) {
return null;
}
List<AbstractInsnNode> cleanCallParamInsns = MethodCallAnalyzer.findMethodCallParamInsns(methodContext.findCleanInjectionTarget().methodNode(), minsn);
if (cleanCallParamInsns.size() <= index) {
return null;
}

AbstractInsnNode targetArgInsn = cleanCallParamInsns.get(index);
if (!(targetArgInsn instanceof InvokeDynamicInsnNode cleanIndy)) {
return null;
}

MethodContext.TargetPair cleanTarget = methodContext.findCleanInjectionTarget();
MethodContext.TargetPair dirtyTarget = methodContext.findDirtyInjectionTarget();
// Handle cases where the method has been split off
if (cleanIndy.bsmArgs.length > 2 && cleanIndy.bsmArgs[1] instanceof Handle handle && handle.getOwner().equals(dirtyTarget.classNode().name)) {
MethodNode cleanMethod = MethodCallAnalyzer.findMethodByUniqueName(cleanTarget.classNode(), handle.getName()).orElse(null);
if (cleanMethod == null) {
return null;
}
MethodNode dirtyMethod = MethodCallAnalyzer.findMethodByUniqueName(dirtyTarget.classNode(), handle.getName()).orElse(null);
if (dirtyMethod == null) {
return null;
}
if (DynFixSplitMethod.isDirtyDeprecatedMethod(cleanMethod, dirtyMethod)) {
List<MethodNode> invocations = DynFixSplitMethod.collectMethodInvocations(dirtyTarget.classNode(), dirtyMethod);
if (invocations != null) {
MethodNode last = invocations.getLast();
if (last.desc.equals(dirtyMethod.desc)) {
InvokeDynamicInsnNode clone = (InvokeDynamicInsnNode) cleanIndy.clone(Map.of());
clone.bsmArgs = Stream.of(clone.bsmArgs).toArray();
clone.bsmArgs[1] = new Handle(handle.getTag(), handle.getOwner(), last.name, handle.getDesc(), handle.isInterface());
cleanIndy = clone;
}
}
}
}

MethodNode dirtyMethod = methodContext.findDirtyInjectionTarget().methodNode();
List<MethodInsnNode> matches = new ArrayList<>();
for (List<AbstractInsnNode> label : hunkLabels) {
for (AbstractInsnNode insn : label) {
if (insn instanceof MethodInsnNode m && Stream.of(Type.getArgumentTypes(m.desc)).filter(desiredType::equals).count() == 1) {
int argIndex = MethodCallAnalyzer.getArgIndex(m.desc, desiredType) + 1;
if (argIndex == 0) {
continue;
}
List<AbstractInsnNode> list = MethodCallAnalyzer.findMethodCallParamInsns(dirtyMethod, m);
if (list.size() > argIndex && list.get(argIndex) instanceof InvokeDynamicInsnNode dirtyIndy && InsnComparator.instructionsEqual(cleanIndy, dirtyIndy)) {
matches.add(m);
}
}
}
}

if (matches.size() == 1) {
MethodInsnNode m = matches.getFirst();
String newInjectionPoint = Type.getObjectType(m.owner).getDescriptor() + m.name + m.desc;

Patch.Result result = MethodTransformationPipeline.builder(new ModifyInjectionPoint("INVOKE", newInjectionPoint, true, false))
.onSuccess(() -> (cls, mtd, mtx, ctx) -> {
int ordinal = MethodCallAnalyzer.getMethodCallOrdinal(dirtyMethod, m);
if (ordinal == -1) {
throw new IllegalStateException("Ordinal not found?");
}
AnnotationHandle handle = mtx.injectionPointAnnotationOrThrow();
handle.setOrAppend("ordinal", ordinal);
return Patch.Result.APPLY;
})
.apply(methodContext);

return FixResult.of(result, PatchAuditTrail.Match.FULL);
}

return null;
}

private static Optional<FixResult> handleWrapOpertationNewInjectionPoint(AbstractInsnNode cleanInjectionInsn, List<AbstractInsnNode> cleanLabel, List<List<AbstractInsnNode>> hunkLabels, MethodContext methodContext) {
if (!(cleanInjectionInsn instanceof MethodInsnNode minsn) || hunkLabels.size() != 1) {
return Optional.empty();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,33 +50,46 @@ public FixResult apply(ClassNode classNode, MethodNode methodNode, MethodContext

return null;
}

public static boolean isDirtyDeprecatedMethod(MethodNode clean, MethodNode dirty) {
return !AdapterUtil.hasAnnotation(clean.visibleAnnotations, DEPRECATED) && AdapterUtil.hasAnnotation(dirty.visibleAnnotations, DEPRECATED);
}

private static List<CandidateMethod> locateCandidates(MethodContext methodContext) {
MethodNode cleanTargetMethod = methodContext.findCleanInjectionTarget().methodNode();
ClassNode dirtyTargetClass = methodContext.findDirtyInjectionTarget().classNode();
MethodNode dirtyTargetMethod = methodContext.findDirtyInjectionTarget().methodNode();

// Check that a Deprecated annotation was added to the dirty method
if (AdapterUtil.hasAnnotation(cleanTargetMethod.visibleAnnotations, DEPRECATED) || !AdapterUtil.hasAnnotation(dirtyTargetMethod.visibleAnnotations, DEPRECATED)) {
return tryFindPartialCandidates(cleanTargetMethod, dirtyTargetClass, dirtyTargetMethod, methodContext);
}

@Nullable
public static List<MethodNode> collectMethodInvocations(ClassNode cls, MethodNode mtd) {
// Iterate over isns, leave out first and last elements
// Collect method invocations
// All labels must be finalized by a method invocation to pass
List<MethodNode> invocations = new ArrayList<>();
for (int i = 1; i < dirtyTargetMethod.instructions.size() - 1; i++) {
AbstractInsnNode insn = dirtyTargetMethod.instructions.get(i);
for (int i = 1; i < mtd.instructions.size() - 1; i++) {
AbstractInsnNode insn = mtd.instructions.get(i);
if (insn instanceof LabelNode) {
AbstractInsnNode previous = insn.getPrevious();
if (previous instanceof MethodInsnNode methodInsn && methodInsn.owner.equals(dirtyTargetClass.name)) {
MethodNode method = dirtyTargetClass.methods.stream().filter(m -> m.name.equals(methodInsn.name) && m.desc.equals(methodInsn.desc)).findFirst().orElseThrow();
if (previous instanceof MethodInsnNode methodInsn && methodInsn.owner.equals(cls.name)) {
MethodNode method = cls.methods.stream().filter(m -> m.name.equals(methodInsn.name) && m.desc.equals(methodInsn.desc)).findFirst().orElseThrow();
invocations.add(method);
} else if (previous == null || !OpcodeUtil.isReturnOpcode(previous.getOpcode())) {
return null;
}
}
}
return invocations;
}

private static List<CandidateMethod> locateCandidates(MethodContext methodContext) {
MethodNode cleanTargetMethod = methodContext.findCleanInjectionTarget().methodNode();
ClassNode dirtyTargetClass = methodContext.findDirtyInjectionTarget().classNode();
MethodNode dirtyTargetMethod = methodContext.findDirtyInjectionTarget().methodNode();

// Check that a Deprecated annotation was added to the dirty method
if (!isDirtyDeprecatedMethod(cleanTargetMethod, dirtyTargetMethod)) {
return tryFindPartialCandidates(cleanTargetMethod, dirtyTargetClass, dirtyTargetMethod, methodContext);
}

List<MethodNode> invocations = collectMethodInvocations(dirtyTargetClass, dirtyTargetMethod);
if (invocations == null) {
return null;
}

List<CandidateMethod> candidates = findInsnsCalls(invocations, methodContext);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,12 @@ void testSplitMethodInjectionTarget() throws Exception {
assertTargetMethod(),
assertInjectionPoint()
);
assertSameCode(
"org/sinytra/adapter/test/mixin/GuiMixin",
"afterMainHud",
assertTargetMethod(),
assertInjectionPoint()
);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import net.minecraft.client.DeltaTracker;
import net.minecraft.client.gui.Gui;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.LayeredDraw;
import net.minecraft.client.resources.MobEffectTextureManager;
import net.minecraft.core.Holder;
import net.minecraft.world.effect.MobEffectInstance;
Expand Down Expand Up @@ -98,4 +99,35 @@ private int moveHealthDown(int original) {
private int moveHealthDownExpected(int original) {
return original;
}

// https://github.com/SkyblockerMod/Skyblocker/blob/8cfd59bbf6c71d1de6ed35ad36b2abd801eddb71/src/main/java/de/hysky/skyblocker/mixins/InGameHudMixin.java#L97
@ModifyArg(
method = "<init>(Lnet/minecraft/client/Minecraft;)V",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/client/gui/LayeredDraw;add(Lnet/minecraft/client/gui/LayeredDraw$Layer;)Lnet/minecraft/client/gui/LayeredDraw;",
ordinal = 2
)
)
private LayeredDraw.Layer afterMainHud(LayeredDraw.Layer mainHudLayer) {
return (context, tickCounter) -> {
mainHudLayer.render(context, tickCounter);
System.out.println("Hello");
};
}

@ModifyArg(
method = "<init>(Lnet/minecraft/client/Minecraft;)V",
at = @At(
value = "INVOKE",
target = "Lnet/neoforged/neoforge/client/gui/GuiLayerManager;add(Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/client/gui/LayeredDraw$Layer;)Lnet/neoforged/neoforge/client/gui/GuiLayerManager;",
ordinal = 11
)
)
private LayeredDraw.Layer afterMainHudExpected(LayeredDraw.Layer mainHudLayer) {
return (context, tickCounter) -> {
mainHudLayer.render(context, tickCounter);
System.out.println("Hello");
};
}
}

0 comments on commit 05e2aa1

Please sign in to comment.