package de.geolykt.starloader.transformers; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.jetbrains.annotations.NotNull; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.FieldInsnNode; import org.objectweb.asm.tree.FieldNode; import org.objectweb.asm.tree.LdcInsnNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Simple transformer that inlines the proper values from the starplane-annotations string source, without remapping them * - that is it uses the references as-is. * *

Should only be used in a development environment as this transformer is unlikely to work in a production environment, * where a remapping step needs to be added on top. * * @since 4.0.0 */ public class StarplaneAnnotationsInlineTransformer extends ASMTransformer { private static class MemberTriple { @NotNull final String owner; @NotNull final String name; @NotNull final String desc; @SuppressWarnings("null") public MemberTriple(String owner, String name, String desc) { this.owner = owner; this.name = name; this.desc = desc; } @Override public int hashCode() { return this.owner.hashCode() ^ this.name.hashCode() ^ this.desc.hashCode(); } @Override public boolean equals(Object obj) { if (obj instanceof MemberTriple) { MemberTriple other = (MemberTriple) obj; return this.owner.equals(other.owner) && this.name.equals(other.name) && this.desc.equals(other.desc); } return false; } } private static final Logger LOGGER = LoggerFactory.getLogger(StarplaneAnnotationsInlineTransformer.class); @Override public boolean accept(@NotNull ClassNode node) { Map memberMapRequests = new HashMap<>(); Map memberMapFormat = new HashMap<>(); Map classMapRequests = new HashMap<>(); boolean transformed = false; for (FieldNode field : node.fields) { Iterable annotations = field.invisibleAnnotations; if (annotations == null) { continue; } Iterator it = annotations.iterator(); while (it.hasNext()) { AnnotationNode annotation = it.next(); if (annotation.desc.equals("Lde/geolykt/starloader/starplane/annotations/StarplaneReobfuscateReference;")) { it.remove(); transformed = true; break; } else if (annotation.desc.equals("Lde/geolykt/starloader/starplane/annotations/RemapClassReference;")) { it.remove(); transformed = true; if (annotation.values == null || annotation.values.size() == 0) { LOGGER.error("Field {}.{}:{} is annotated with de/geolykt/starloader/starplane/annotations/RemapClassReference, but neither the 'name' nor the 'type' value of the annotation is set.", node.name, field.name, field.desc); break; } if (annotation.values.size() == 4) { LOGGER.error("Field {}.{}:{} is annotated with de/geolykt/starloader/starplane/annotations/RemapClassReference, but both the 'name' and the 'type' value of the annotation is set. Consider only setting one of these values.", node.name, field.name, field.desc); break; } String typeName; if (annotation.values.get(0).equals("name")) { typeName = ((String) annotation.values.get(0)).replace('.', '/'); } else if (annotation.values.get(0).equals("type")) { typeName = ((Type) annotation.values.get(1)).getInternalName(); } else { LOGGER.error("Erroneous annotation value: " + annotation.values.get(0) + " for RemapClassReference. Are you depending on the wrong starplane-annotations version?"); break; } classMapRequests.put(new MemberTriple(node.name, field.name, field.desc), typeName); } else if (annotation.desc.equals("Lde/geolykt/starloader/starplane/annotations/RemapMemberReference;")) { it.remove(); if (annotation.values == null) { LOGGER.error("Field {}.{}:{} is annotated with de/geolykt/starloader/starplane/annotations/RemapMemberReference, but does not define any of the required values.", node.name, field.name, field.desc); break; } if (annotation.values.size() >= 10) { LOGGER.error("Field {}.{}:{} is annotated with de/geolykt/starloader/starplane/annotations/RemapMemberReference, but more than the required values of the annotation is set. Consider removing duplicates.", node.name, field.name, field.desc); break; } String typeName = null; String memberName = null; String memberDesc = null; String format = null; for (int i = 0; i < annotation.values.size(); i += 2) { String valueName = ((String) annotation.values.get(i)); if (valueName.equals("ownerType")) { typeName = ((Type) annotation.values.get(i + 1)).getInternalName(); } else if (valueName.equals("owner")) { typeName = ((String) annotation.values.get(i + 1)).replace('.', '/'); } else if (valueName.equals("name")) { memberName = (String) annotation.values.get(i + 1); } else if (valueName.equals("desc")) { if (memberDesc != null) { LOGGER.error("Field {}.{}:{} is annotated with de/geolykt/starloader/starplane/annotations/RemapMemberReference, but multiple values contain descriptor-giving values. Consider removing duplicated.", node.name, field.name, field.desc); break; } memberDesc = (String) annotation.values.get(i + 1); } else if (valueName.equals("descType")) { if (memberDesc != null) { LOGGER.error("Field {}.{}:{} is annotated with de/geolykt/starloader/starplane/annotations/RemapMemberReference, but multiple values contain descriptor-giving values. Consider removing duplicated.", node.name, field.name, field.desc); break; } memberDesc = ((Type) annotation.values.get(i + 1)).getDescriptor(); } else if (valueName.equals("methodDesc")) { AnnotationNode methodDesc = (AnnotationNode) annotation.values.get(i + 1); int args; int ret; if (methodDesc.values.get(0).equals("args")) { args = 1; ret = 3; } else { ret = 1; args = 3; } String argDesc = ""; List arglist = (List) methodDesc.values.get(args); for (int j = 0; j < arglist.size(); j++) { Type arg = (Type) arglist.get(j); if (arg == null) { throw new AssertionError(); } argDesc += arg.getDescriptor(); } if (memberDesc != null) { LOGGER.error("Field {}.{}:{} is annotated with de/geolykt/starloader/starplane/annotations/RemapMemberReference, but multiple values contain descriptor-giving values. Consider removing duplicated.", node.name, field.name, field.desc); break; } memberDesc = "(" + argDesc + ")" + ((Type) methodDesc.values.get(ret)).getDescriptor(); } else if (valueName.equals("format")) { format = ((String[]) annotation.values.get(i + 1))[1]; } else { LOGGER.error("Erroneous annotation value: {} for RemapMemberReference. Are you depending on the wrong starplane-annotations version?", valueName); break; } } if (typeName == null) { LOGGER.error("Field {}.{}:{} is annotated with de/geolykt/starloader/starplane/annotations/RemapMemberReference, but neither the 'owner' nor the 'ownerType' value of the annotation is set. Consider setting one of these values.", node.name, field.name, field.desc); break; } MemberTriple targetTriple = new MemberTriple(typeName, memberName, memberDesc); MemberTriple fieldTriple = new MemberTriple(node.name, field.name, field.desc); memberMapFormat.put(fieldTriple, format); memberMapRequests.put(fieldTriple, targetTriple); } } } for (MethodNode method : node.methods) { for (AbstractInsnNode insn = method.instructions.getFirst(); insn != null; insn = insn.getNext()) { if (insn.getOpcode() != Opcodes.INVOKESTATIC) { continue; } MethodInsnNode minsn = (MethodInsnNode) insn; if (!minsn.owner.equals("de/geolykt/starloader/starplane/annotations/ReferenceSource")) { continue; } if (!minsn.name.equals("getStringValue") || !minsn.desc.equals("()Ljava/lang/String;")) { continue; } AbstractInsnNode nextInsn = insn.getNext(); while (nextInsn != null && (nextInsn.getOpcode() == -1 || nextInsn.getOpcode() == Opcodes.ALOAD)) { nextInsn = nextInsn.getNext(); } if (nextInsn == null) { LOGGER.error("Method {}.{} {} contains a rouge ReferenceSource.getStringValue() call.", node.name, method.name, method.desc); break; } if (nextInsn.getOpcode() != Opcodes.PUTSTATIC && nextInsn.getOpcode() != Opcodes.PUTFIELD) { LOGGER.error("Method {}.{} {} contains a call to ReferenceSource.getStringValue() that is not immediately assigned to a field.", node.name, method.name, method.desc); continue; } MemberTriple assignmentTriple = new MemberTriple(((FieldInsnNode) nextInsn).owner, ((FieldInsnNode) nextInsn).name, ((FieldInsnNode) nextInsn).desc); String cl = classMapRequests.get(assignmentTriple); String replacementLdc; if (cl != null) { replacementLdc = cl; } else { MemberTriple member = memberMapRequests.get(assignmentTriple); String format = memberMapFormat.get(assignmentTriple); if (member == null || format == null) { LOGGER.error("Method {}.{} {} contains a call to ReferenceSource.getStringValue() that is assigned to {}.{} {} which is not annotated with a starplane remapping annotation. (Note: this feature does not work across classes!)", node.name, method.name, method.desc, assignmentTriple.owner, assignmentTriple.name, assignmentTriple.desc); continue; } boolean isMethod = member.desc.codePointAt(0) == '('; if (format.equals("OWNER")) { replacementLdc = member.owner; } else if (format.equals("NAME")) { replacementLdc = member.name; } else if (format.equals("DESCRIPTOR")) { replacementLdc = member.desc; } else if (format.equals("COMBINED_LEGACY")) { if (isMethod) { replacementLdc = member.owner + "." + member.name + member.desc; } else { replacementLdc = member.owner + "." + member.name + " " + member.desc; } } else { LOGGER.error("Method {}.{} {} contains a call to ReferenceSource.getStringValue() that is assigned to {}.{} {} which uses an unsupported format. (Are you using the right version of starplane-annotations?)", node.name, method.name, method.desc, assignmentTriple.owner, assignmentTriple.name, assignmentTriple.desc); continue; } } transformed = true; method.instructions.set(insn, new LdcInsnNode(replacementLdc)); insn = nextInsn; } } return transformed; } @Override public boolean isValidTarget(@NotNull String internalName) { return true; } @Override public int getPriority() { return -10_010; } }