From 3438b556b3882cccb03a70b566da65f41f42868e Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Mon, 25 Jul 2022 19:17:47 +0200 Subject: [PATCH] working on the reflection stuff some more... --- .../lib/internal/CoreLoadingPlugin.java | 4 + .../lib/reflection/MappingManager.java | 76 ++++++++-- .../lib/reflection/ReflectionUtil.java | 29 ++++ .../lib/reflection/UniversalClass.java | 49 ------- .../lib/reflection/UniversalField.java | 26 ---- .../lib/reflection/UniversalMethod.java | 33 ----- .../lib/reflection/storage/Lookup.java | 52 ++++++- .../{ => storage}/MappedString.java | 14 +- .../lib/reflection/types/UniversalClass.java | 102 ++++++++++++++ .../lib/reflection/types/UniversalField.java | 53 +++++++ .../lib/reflection/types/UniversalMethod.java | 132 ++++++++++++++++++ 11 files changed, 439 insertions(+), 131 deletions(-) create mode 100644 src/main/java/com/falsepattern/lib/reflection/ReflectionUtil.java delete mode 100644 src/main/java/com/falsepattern/lib/reflection/UniversalClass.java delete mode 100644 src/main/java/com/falsepattern/lib/reflection/UniversalField.java delete mode 100644 src/main/java/com/falsepattern/lib/reflection/UniversalMethod.java rename src/main/java/com/falsepattern/lib/reflection/{ => storage}/MappedString.java (64%) create mode 100644 src/main/java/com/falsepattern/lib/reflection/types/UniversalClass.java create mode 100644 src/main/java/com/falsepattern/lib/reflection/types/UniversalField.java create mode 100644 src/main/java/com/falsepattern/lib/reflection/types/UniversalMethod.java diff --git a/src/main/java/com/falsepattern/lib/internal/CoreLoadingPlugin.java b/src/main/java/com/falsepattern/lib/internal/CoreLoadingPlugin.java index 469b0fd..e0ea39f 100644 --- a/src/main/java/com/falsepattern/lib/internal/CoreLoadingPlugin.java +++ b/src/main/java/com/falsepattern/lib/internal/CoreLoadingPlugin.java @@ -1,5 +1,6 @@ package com.falsepattern.lib.internal; +import lombok.Getter; import lombok.val; import cpw.mods.fml.relauncher.IFMLLoadingPlugin; @@ -15,6 +16,8 @@ import java.util.Map; @Name(Tags.MODID) @SortingIndex(500) public class CoreLoadingPlugin implements IFMLLoadingPlugin { + @Getter + private static boolean obfuscated; static { try { Class.forName("thermos.Thermos"); @@ -46,6 +49,7 @@ public class CoreLoadingPlugin implements IFMLLoadingPlugin { @Override public void injectData(Map data) { + obfuscated = (Boolean) data.get("runtimeDeobfuscationEnabled"); } @Override diff --git a/src/main/java/com/falsepattern/lib/reflection/MappingManager.java b/src/main/java/com/falsepattern/lib/reflection/MappingManager.java index fa224c2..9b692b1 100644 --- a/src/main/java/com/falsepattern/lib/reflection/MappingManager.java +++ b/src/main/java/com/falsepattern/lib/reflection/MappingManager.java @@ -1,15 +1,19 @@ package com.falsepattern.lib.reflection; +import com.falsepattern.lib.internal.CoreLoadingPlugin; import com.falsepattern.lib.internal.FalsePatternLib; import com.falsepattern.lib.reflection.storage.Lookup; import com.falsepattern.lib.reflection.types.MappingType; import com.falsepattern.lib.reflection.types.NameType; +import com.falsepattern.lib.reflection.types.UniversalClass; +import com.falsepattern.lib.reflection.types.UniversalField; +import com.falsepattern.lib.reflection.types.UniversalMethod; import com.falsepattern.lib.util.ResourceUtil; -import lombok.NonNull; import lombok.SneakyThrows; import lombok.val; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; -import java.io.IOException; import java.util.HashMap; public class MappingManager { @@ -37,31 +41,75 @@ public class MappingManager { val fieldMappings = ResourceUtil.getResourceStringFromJar("/fields.csv", FalsePatternLib.class).split("\n"); for (int i = 1; i < fieldMappings.length; i++) { val line = fieldMappings[i].split(","); - val field = new UniversalField(line, stringPool); val clazz = internalLookup.get(MappingType.Notch, line[0].substring(0, line[0].lastIndexOf('/'))); - clazz.addField(field); + new UniversalField(clazz, line, stringPool); } } { val methodMappings = ResourceUtil.getResourceStringFromJar("/methods.csv", FalsePatternLib.class).split("\n"); for (int i = 1; i < methodMappings.length; i++) { val line = methodMappings[i].split(","); - val field = new UniversalMethod(line, stringPool); val clazz = internalLookup.get(MappingType.Notch, line[0].substring(0, line[0].lastIndexOf('/'))); - clazz.addMethod(field); + new UniversalMethod(clazz, line, stringPool); } } } - public static UniversalClass classForName(@NonNull NameType nameType, @NonNull MappingType mappingType, String className) { + public static UniversalClass classForName(NameType nameType, MappingType mappingType, String className) throws ClassNotFoundException { initialize(); - switch (nameType) { - case Internal: - return internalLookup.get(mappingType, className); - case Regular: - return regularLookup.get(mappingType, className); - default: - throw new IllegalArgumentException("Invalid enum value " + nameType); + try { + switch (nameType) { + case Internal: + return internalLookup.get(mappingType, className); + case Regular: + return regularLookup.get(mappingType, className); + default: + throw new IllegalArgumentException("Invalid enum value " + nameType); + } + } catch (Lookup.LookupException e) { + throw new ClassNotFoundException("Could not find class \"" + className + "\" with " + nameType.name().toLowerCase() + " name in the " + mappingType.name() + " mapping."); + } + } + + public static UniversalField getField(FieldInsnNode instruction) throws ClassNotFoundException, + NoSuchFieldException { + if (!CoreLoadingPlugin.isObfuscated()) { + try { + return classForName(NameType.Internal, MappingType.MCP, instruction.owner).getField(MappingType.MCP, instruction.name); + } catch (ClassNotFoundException e) { + throw new ClassNotFoundException("Could not find the class " + instruction.owner + " in the MCP mappings. Are you sure it's a Minecraft class? (we're in dev, cannot use SRG or Notch here)."); + } + } else { + try { + return classForName(NameType.Internal, MappingType.SRG, instruction.owner).getField(MappingType.SRG, instruction.name); + } catch (ClassNotFoundException e) { + try { + return classForName(NameType.Internal, MappingType.Notch, instruction.owner).getField(MappingType.Notch, instruction.name); + } catch (ClassNotFoundException ex) { + throw new ClassNotFoundException("Could not find the class " + instruction.owner + " neither in the SRG nor in the Notch mappings. Are you sure it's a Minecraft class? (we're in obf, cannot use MCP here)"); + } + } + } + } + + public static UniversalMethod getMethod(MethodInsnNode instruction) + throws ClassNotFoundException, NoSuchMethodException { + if (!CoreLoadingPlugin.isObfuscated()) { + try { + return classForName(NameType.Internal, MappingType.MCP, instruction.owner).getMethod(MappingType.MCP, instruction.name, instruction.desc); + } catch (ClassNotFoundException e) { + throw new ClassNotFoundException("Could not find the class " + instruction.owner + " in the MCP mappings. Are you sure it's a Minecraft class? (we're in dev, cannot use SRG or Notch here)."); + } + } else { + try { + return classForName(NameType.Internal, MappingType.SRG, instruction.owner).getMethod(MappingType.SRG, instruction.name, instruction.desc); + } catch (ClassNotFoundException e) { + try { + return classForName(NameType.Internal, MappingType.Notch, instruction.owner).getMethod(MappingType.Notch, instruction.name, instruction.desc); + } catch (ClassNotFoundException ex) { + throw new ClassNotFoundException("Could not find the class " + instruction.owner + " neither in the SRG nor in the Notch mappings. Are you sure it's a Minecraft class? (we're in obf, cannot use MCP here)"); + } + } } } } diff --git a/src/main/java/com/falsepattern/lib/reflection/ReflectionUtil.java b/src/main/java/com/falsepattern/lib/reflection/ReflectionUtil.java new file mode 100644 index 0000000..029e6bf --- /dev/null +++ b/src/main/java/com/falsepattern/lib/reflection/ReflectionUtil.java @@ -0,0 +1,29 @@ +package com.falsepattern.lib.reflection; + +import lombok.SneakyThrows; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +public class ReflectionUtil { + private static final Field f_modifiers; + static { + try { + f_modifiers = Field.class.getDeclaredField("modifiers"); + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } + f_modifiers.setAccessible(true); + } + + @SneakyThrows + public static void jailBreak(Field field) { + field.setAccessible(true); + f_modifiers.set(field, field.getModifiers() & ~Modifier.FINAL); + } + + public static void jailBreak(Method method) { + method.setAccessible(true); + } +} diff --git a/src/main/java/com/falsepattern/lib/reflection/UniversalClass.java b/src/main/java/com/falsepattern/lib/reflection/UniversalClass.java deleted file mode 100644 index ad35847..0000000 --- a/src/main/java/com/falsepattern/lib/reflection/UniversalClass.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.falsepattern.lib.reflection; - -import com.falsepattern.lib.reflection.storage.Lookup; -import com.falsepattern.lib.reflection.types.MappingType; -import com.falsepattern.lib.reflection.types.NameType; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.NonNull; -import lombok.ToString; -import lombok.experimental.Accessors; - -import java.util.Map; - -@Accessors(fluent = true) -@ToString -@EqualsAndHashCode -public class UniversalClass { - @Getter - public final MappedString internalName; - @Getter - public final MappedString regularName; - - private final Lookup fields = new Lookup<>(); - private final Lookup methods = new Lookup<>(); - - public UniversalClass(String[] names, Map stringPool) { - internalName = new MappedString(names, 0, 1, (str) -> str, stringPool); - regularName = new MappedString(names, 0, 1, (str) -> str.replace('/', '.'), stringPool); - } - - public void addField(UniversalField field) { - fields.unwrap(field.name, field); - } - - public void addMethod(UniversalMethod method) { - methods.unwrap(method.name, method); - } - - public String getName(@NonNull NameType nameType, @NonNull MappingType mappingType) { - switch (nameType) { - case Internal: - return internalName.get(mappingType); - case Regular: - return regularName.get(mappingType); - default: - throw new IllegalArgumentException("Invalid enum value " + nameType); - } - } -} diff --git a/src/main/java/com/falsepattern/lib/reflection/UniversalField.java b/src/main/java/com/falsepattern/lib/reflection/UniversalField.java deleted file mode 100644 index 0e02bdf..0000000 --- a/src/main/java/com/falsepattern/lib/reflection/UniversalField.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.falsepattern.lib.reflection; - -import com.falsepattern.lib.reflection.types.MappingType; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.NonNull; -import lombok.ToString; -import lombok.experimental.Accessors; - -import java.util.Map; - -@Accessors(fluent = true) -@ToString -@EqualsAndHashCode -public class UniversalField { - @Getter - public final MappedString name; - - public UniversalField(String[] names, Map stringPool) { - name = new MappedString(names, 0, 1, (str) -> str.substring(str.lastIndexOf('/') + 1), stringPool); - } - - public String getName(@NonNull MappingType mappingType) { - return name.get(mappingType); - } -} diff --git a/src/main/java/com/falsepattern/lib/reflection/UniversalMethod.java b/src/main/java/com/falsepattern/lib/reflection/UniversalMethod.java deleted file mode 100644 index 037cd8c..0000000 --- a/src/main/java/com/falsepattern/lib/reflection/UniversalMethod.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.falsepattern.lib.reflection; - -import com.falsepattern.lib.reflection.types.MappingType; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.NonNull; -import lombok.ToString; -import lombok.experimental.Accessors; - -import java.util.Map; - -@Accessors(fluent = true) -@ToString -@EqualsAndHashCode -public class UniversalMethod { - @Getter - public final MappedString name; - @Getter - public final MappedString descriptor; - - public UniversalMethod(String[] names, Map stringPool) { - name = new MappedString(names, 0, 2, (str) -> str.substring(str.lastIndexOf('/') + 1), stringPool); - descriptor = new MappedString(names, 1, 2, (str) -> str, stringPool); - } - - public String getName(@NonNull MappingType mappingType) { - return name.get(mappingType); - } - - public String getDescriptor(@NonNull MappingType mappingType) { - return descriptor.get(mappingType); - } -} diff --git a/src/main/java/com/falsepattern/lib/reflection/storage/Lookup.java b/src/main/java/com/falsepattern/lib/reflection/storage/Lookup.java index 06a0545..93d31fb 100644 --- a/src/main/java/com/falsepattern/lib/reflection/storage/Lookup.java +++ b/src/main/java/com/falsepattern/lib/reflection/storage/Lookup.java @@ -1,37 +1,77 @@ package com.falsepattern.lib.reflection.storage; -import com.falsepattern.lib.reflection.MappedString; import com.falsepattern.lib.reflection.types.MappingType; import lombok.EqualsAndHashCode; import lombok.NonNull; import lombok.experimental.Accessors; +import lombok.val; import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; import java.util.Map; +import java.util.Set; @Accessors(fluent = true) @EqualsAndHashCode public class Lookup { + private final Set values = new HashSet<>(); private final Map notch = new HashMap<>(); private final Map srg = new HashMap<>(); private final Map mcp = new HashMap<>(); - public void unwrap(MappedString mappedString, T value) { + public boolean contains(T value) { + return values.contains(value); + } + + public void unwrap(@NonNull MappedString mappedString, @NonNull T value) { + if (contains(value)) { + //Collision avoidance. + values.remove(value); + removeFirst(notch.entrySet().iterator(), value); + removeFirst(srg.entrySet().iterator(), value); + removeFirst(mcp.entrySet().iterator(), value); + } + values.add(value); notch.put(mappedString.notch, value); srg.put(mappedString.srg, value); mcp.put(mappedString.mcp, value); } - public T get(@NonNull MappingType mappingType, @NonNull String key) { + public T get(MappingType mappingType, String key) throws LookupException { + T result; switch (mappingType) { case Notch: - return notch.get(key); + result = notch.get(key); + break; case SRG: - return srg.get(key); + result = srg.get(key); + break; case MCP: - return mcp.get(key); + result = mcp.get(key); + break; default: throw new IllegalArgumentException("Invalid enum value " + mappingType); } + if (result == null) { + throw new LookupException("No such key " + key + " in " + mappingType); + } + return result; + } + + private static void removeFirst(Iterator> iterator, T value) { + while (iterator.hasNext()) { + val entry = iterator.next(); + if (entry.getValue().equals(value)) { + iterator.remove(); + return; + } + } + } + + public static class LookupException extends Exception { + public LookupException(String message) { + super(message); + } } } diff --git a/src/main/java/com/falsepattern/lib/reflection/MappedString.java b/src/main/java/com/falsepattern/lib/reflection/storage/MappedString.java similarity index 64% rename from src/main/java/com/falsepattern/lib/reflection/MappedString.java rename to src/main/java/com/falsepattern/lib/reflection/storage/MappedString.java index 56e8fbb..35be973 100644 --- a/src/main/java/com/falsepattern/lib/reflection/MappedString.java +++ b/src/main/java/com/falsepattern/lib/reflection/storage/MappedString.java @@ -1,9 +1,10 @@ -package com.falsepattern.lib.reflection; +package com.falsepattern.lib.reflection.storage; import com.falsepattern.lib.reflection.types.MappingType; +import lombok.AccessLevel; import lombok.EqualsAndHashCode; import lombok.Getter; -import lombok.NonNull; +import lombok.RequiredArgsConstructor; import lombok.ToString; import lombok.experimental.Accessors; @@ -14,6 +15,7 @@ import java.util.function.Function; @ToString @EqualsAndHashCode @Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) public class MappedString { public final String notch; public final String srg; @@ -24,7 +26,13 @@ public class MappedString { mcp = stringPool.computeIfAbsent(remapper.apply(source[offset + stride * 2]), (str) -> str); } - public String get(@NonNull MappingType type) { + public static MappedString fuse(MappedString a, MappedString b, String delimiter, Map stringPool) { + return new MappedString(stringPool.computeIfAbsent(a.notch + delimiter + b.notch, (str) -> str), + stringPool.computeIfAbsent(a.srg + delimiter + b.srg , (str) -> str), + stringPool.computeIfAbsent(a.mcp + delimiter + b.mcp , (str) -> str)); + } + + public String get(MappingType type) { switch (type) { case Notch: return notch; diff --git a/src/main/java/com/falsepattern/lib/reflection/types/UniversalClass.java b/src/main/java/com/falsepattern/lib/reflection/types/UniversalClass.java new file mode 100644 index 0000000..2107281 --- /dev/null +++ b/src/main/java/com/falsepattern/lib/reflection/types/UniversalClass.java @@ -0,0 +1,102 @@ +package com.falsepattern.lib.reflection.types; + +import com.falsepattern.lib.internal.CoreLoadingPlugin; +import com.falsepattern.lib.reflection.storage.Lookup; +import com.falsepattern.lib.reflection.storage.MappedString; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; +import lombok.experimental.Accessors; + +import java.util.Map; + +@Accessors(fluent = true) +@ToString(onlyExplicitlyIncluded = true) +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +public class UniversalClass { + @Getter + @ToString.Include + @EqualsAndHashCode.Include + public final MappedString internalName; + @Getter + @ToString.Include + @EqualsAndHashCode.Include + public final MappedString regularName; + + private final Lookup fields = new Lookup<>(); + private final Lookup methods = new Lookup<>(); + + private Class javaClassCache = null; + @Getter + private MappingType realClassMapping = null; + + public UniversalClass(String[] names, Map stringPool) { + internalName = new MappedString(names, 0, 1, (str) -> str, stringPool); + regularName = new MappedString(names, 0, 1, (str) -> str.replace('/', '.'), stringPool); + } + + public void addField(UniversalField field) { + if (field.parent != this) { + throw new IllegalArgumentException("Field's parent is not this class"); + } + fields.unwrap(field.name, field); + } + + public void addMethod(UniversalMethod method) { + if (method.parent != this) { + throw new IllegalArgumentException("Method's parent is not this class"); + } + methods.unwrap(method.fusedNameDescriptor, method); + } + + public Class asJavaClass() throws ClassNotFoundException { + if (javaClassCache != null) { + return javaClassCache; + } + if (!CoreLoadingPlugin.isObfuscated()) { + javaClassCache = Class.forName(regularName.mcp); + realClassMapping = MappingType.MCP; + } else { + try { + javaClassCache = Class.forName(regularName.srg); + realClassMapping = MappingType.SRG; + } catch (ClassNotFoundException e) { + //This should never happen, but we attempt recovery anyway + try { + javaClassCache = Class.forName(regularName.notch); + realClassMapping = MappingType.Notch; + } catch (ClassNotFoundException ex) { + throw e; + } + } + } + return javaClassCache; + } + + public String getName(NameType nameType, MappingType mappingType) { + switch (nameType) { + case Internal: + return internalName.get(mappingType); + case Regular: + return regularName.get(mappingType); + default: + throw new IllegalArgumentException("Invalid enum value " + nameType); + } + } + + public UniversalField getField(MappingType mappingType, String fieldName) throws NoSuchFieldException { + try { + return fields.get(mappingType, fieldName); + } catch (Lookup.LookupException e) { + throw new NoSuchFieldException(e.getMessage()); + } + } + + public UniversalMethod getMethod(MappingType mappingType, String methodName, String methodDescriptor) throws NoSuchMethodException { + try { + return methods.get(mappingType, methodName + methodDescriptor); + } catch (Lookup.LookupException e) { + throw new NoSuchMethodException(e.getMessage()); + } + } +} diff --git a/src/main/java/com/falsepattern/lib/reflection/types/UniversalField.java b/src/main/java/com/falsepattern/lib/reflection/types/UniversalField.java new file mode 100644 index 0000000..dc28d48 --- /dev/null +++ b/src/main/java/com/falsepattern/lib/reflection/types/UniversalField.java @@ -0,0 +1,53 @@ +package com.falsepattern.lib.reflection.types; + +import com.falsepattern.lib.reflection.ReflectionUtil; +import com.falsepattern.lib.reflection.storage.MappedString; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NonNull; +import lombok.ToString; +import lombok.experimental.Accessors; +import lombok.val; +import org.objectweb.asm.tree.FieldInsnNode; + +import java.lang.reflect.Field; +import java.util.Map; + +@Accessors(fluent = true) +@ToString +@EqualsAndHashCode +public class UniversalField { + @Getter + @ToString.Exclude + @EqualsAndHashCode.Exclude + public final UniversalClass parent; + + @Getter + public final MappedString name; + + private Field javaFieldCache = null; + + public UniversalField(@NonNull UniversalClass parent, String[] names, Map stringPool) { + this.parent = parent; + parent.addField(this); + name = new MappedString(names, 0, 1, (str) -> str.substring(str.lastIndexOf('/') + 1), stringPool); + } + + public String getName(MappingType mappingType) { + return name.get(mappingType); + } + + public Field asJavaField() throws ClassNotFoundException, NoSuchFieldException { + if (javaFieldCache != null) { + return javaFieldCache; + } + val parentClass = parent.asJavaClass(); + javaFieldCache = parentClass.getDeclaredField(getName(parent.realClassMapping())); + ReflectionUtil.jailBreak(javaFieldCache); + return javaFieldCache; + } + + public FieldInsnNode asInstruction(int opcode, MappingType mapping, String descriptor) { + return new FieldInsnNode(opcode, parent.getName(NameType.Internal, mapping), getName(mapping), descriptor); + } +} diff --git a/src/main/java/com/falsepattern/lib/reflection/types/UniversalMethod.java b/src/main/java/com/falsepattern/lib/reflection/types/UniversalMethod.java new file mode 100644 index 0000000..5932f5f --- /dev/null +++ b/src/main/java/com/falsepattern/lib/reflection/types/UniversalMethod.java @@ -0,0 +1,132 @@ +package com.falsepattern.lib.reflection.types; + +import com.falsepattern.lib.reflection.ReflectionUtil; +import com.falsepattern.lib.reflection.storage.MappedString; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NonNull; +import lombok.ToString; +import lombok.experimental.Accessors; +import lombok.val; +import org.objectweb.asm.tree.MethodInsnNode; + +import java.lang.reflect.Array; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Map; + +@Accessors(fluent = true) +@ToString +@EqualsAndHashCode +public class UniversalMethod { + @Getter + @ToString.Exclude + @EqualsAndHashCode.Exclude + public final UniversalClass parent; + + @Getter + public final MappedString name; + @Getter + public final MappedString descriptor; + @Getter + public final MappedString fusedNameDescriptor; + + private Method javaMethodCache = null; + + public UniversalMethod(@NonNull UniversalClass parent, String[] names, Map stringPool) { + this.parent = parent; + parent.addMethod(this); + name = new MappedString(names, 0, 2, (str) -> str.substring(str.lastIndexOf('/') + 1), stringPool); + descriptor = new MappedString(names, 1, 2, (str) -> str, stringPool); + fusedNameDescriptor = MappedString.fuse(name, descriptor, "", stringPool); + } + + public String getName(MappingType mappingType) { + return name.get(mappingType); + } + + public String getDescriptor(MappingType mappingType) { + return descriptor.get(mappingType); + } + + public Method asJavaMethod() throws ClassNotFoundException, NoSuchMethodException { + if (javaMethodCache != null) { + return javaMethodCache; + } + val parentClass = parent.asJavaClass(); + javaMethodCache = parentClass.getDeclaredMethod(getName(parent.realClassMapping()), decodeMethodDescriptor(getDescriptor(parent.realClassMapping()))); + ReflectionUtil.jailBreak(javaMethodCache); + return javaMethodCache; + } + + private static Class[] decodeMethodDescriptor(String desc) throws ClassNotFoundException { + val result = new ArrayList>(); + val buf = new StringBuilder(); + boolean readingReference = false; + int arrayDimensions = 0; + for (val c: desc.toCharArray()) { + Class parsedClass = null; + if (readingReference) { + if (c == ';') { + parsedClass = Class.forName(buf.toString().replace('/', '.')); + buf.setLength(0); + readingReference = false; + } else { + buf.append(c); + continue; + } + } else if (c == '(') continue; + else if (c == ')') break; + else switch (c) { + case 'B': + parsedClass = byte.class; + break; + case 'C': + parsedClass = char.class; + break; + case 'D': + parsedClass = double.class; + break; + case 'F': + parsedClass = float.class; + break; + case 'I': + parsedClass = int.class; + break; + case 'J': + parsedClass = long.class; + break; + case 'L': + readingReference = true; + continue; + case 'S': + parsedClass = short.class; + break; + case 'Z': + parsedClass = boolean.class; + break; + case '[': + arrayDimensions++; + continue; + } + for (int i = 0; i < arrayDimensions; i++) { + parsedClass = Array.newInstance(parsedClass, 0).getClass(); + } + arrayDimensions = 0; + result.add(parsedClass); + } + return result.toArray(new Class[0]); + } + + /** + * This is only here for completeness' sake, given that MethodInsnNode itself also has a deprecated yet functional constructor. + */ + @Deprecated + public MethodInsnNode asInstruction(int opcode, MappingType mapping) { + return new MethodInsnNode(opcode, parent.getName(NameType.Internal, mapping), getName(mapping), getDescriptor(mapping)); + } + + public MethodInsnNode asInstruction(int opcode, MappingType mapping, boolean itf) { + return new MethodInsnNode(opcode, parent.getName(NameType.Internal, mapping), getName(mapping), getDescriptor(mapping), itf); + } +}