working on the reflection stuff some more...

This commit is contained in:
FalsePattern committed 2022-07-25 19:17:47 +02:00
1 parent f6faba4ffd
commit 3438b556b3
11 files changed
+439 -131

No files matched your search

@@ -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<String, Object> data) {
obfuscated = (Boolean) data.get("runtimeDeobfuscationEnabled");
}
@Override
@@ -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)");
}
}
}
}
}
@@ -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);
}
}
@@ -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<UniversalField> fields = new Lookup<>();
private final Lookup<UniversalMethod> methods = new Lookup<>();
public UniversalClass(String[] names, Map<String, String> 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);
}
}
}
@@ -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<String, String> stringPool) {
name = new MappedString(names, 0, 1, (str) -> str.substring(str.lastIndexOf('/') + 1), stringPool);
}
public String getName(@NonNull MappingType mappingType) {
return name.get(mappingType);
}
}
@@ -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<String, String> 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);
}
}
@@ -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<T> {
private final Set<T> values = new HashSet<>();
private final Map<String, T> notch = new HashMap<>();
private final Map<String, T> srg = new HashMap<>();
private final Map<String, T> 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 <T> void removeFirst(Iterator<Map.Entry<String, T>> 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);
}
}
}
@@ -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<String, String> 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;
@@ -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<UniversalField> fields = new Lookup<>();
private final Lookup<UniversalMethod> methods = new Lookup<>();
private Class<?> javaClassCache = null;
@Getter
private MappingType realClassMapping = null;
public UniversalClass(String[] names, Map<String, String> 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());
}
}
}
@@ -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<String, String> 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);
}
}
@@ -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<String, String> 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<Class<?>>();
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);
}
}