Skip to content
This repository has been archived by the owner on Jun 3, 2024. It is now read-only.

Commit

Permalink
Runtime Remapping (#234)
Browse files Browse the repository at this point in the history
* Runtime remapping

* Clean up, and throw exception when it's the issue.

* Message when reflection is accessed but not loaded
  • Loading branch information
cittyinthecloud authored Feb 16, 2021
1 parent 2c314bf commit 4e3d2a3
Show file tree
Hide file tree
Showing 11 changed files with 620 additions and 0 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ dependencies {
include 'com.electronwill.night-config:core:3.6.2'
include 'com.electronwill.night-config:toml:3.6.2'
include 'net.patchworkmc:event-racecar:1.0.1:with-typetools'
include 'org.cadixdev:lorenz:0.5.4'
}

loom {
Expand Down
6 changes: 6 additions & 0 deletions patchwork-mappings/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
archivesBaseName = "patchwork-mappings"
version = getSubprojectVersion(project, "0.1.0")

dependencies {
implementation "org.cadixdev:lorenz:0.5.4"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Minecraft Forge, Patchwork Project
* Copyright (c) 2016-2020, 2019-2020
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

package cpw.mods.modlauncher.api;

@SuppressWarnings("unused")
public interface INameMappingService {
enum Domain { CLASS, METHOD, FIELD }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
/*
* Minecraft Forge, Patchwork Project
* Copyright (c) 2016-2020, 2019-2020
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

package net.minecraftforge.fml.common;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.StringJoiner;

import com.google.common.base.Preconditions;
import cpw.mods.modlauncher.api.INameMappingService;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;

import net.patchworkmc.api.mappings.PatchworkRemappingService;

/**
* A reimplementation of MinecraftForge's ObfuscationReflectionHelper in a way
* that will make sure that the names are remapped.
* <p>
* If you're a Fabric mod that depends on Patchwork API, this class will not do
* what you expect. Use {@link PatchworkRemappingService} instead.
*/
@SuppressWarnings("unused") // Since our implementation is different from Forge we don't use all of the parameters
@Deprecated
public class ObfuscationReflectionHelper {
private static final Logger LOGGER = LogManager.getLogger();
private static final Marker REFLECTION = MarkerManager.getMarker("REFLECTION");

/**
* Remaps a name using the SRG naming function
*
* @param domain The {@link INameMappingService.Domain} to use to remap the name.
* @param name The name to try and remap.
* @return The remapped name, or the original name if it couldn't be remapped.
*/
public static String remapName(INameMappingService.Domain domain, String name) {
// Patchwork intentionally always returns the input, we need an actual
// reference to the Class because intermediary doesn't have the unique
// name guarantee like SRG does.
return name;
}

/**
* Gets the value a field with the specified name in the given class.
* Note: For performance, use {@link #findField(Class, String)} if you are getting the value more than once.
* <p>
* Throws an exception if the field is not found or the value of the field cannot be gotten.
*
* @param classToAccess The class to find the field on.
* @param instance The instance of the {@code classToAccess}.
* @param fieldName The SRG (unmapped) name of the field to find (e.g. "field_181725_a").
* @param <T> The type of the value.
* @param <E> The type of the {@code classToAccess}.
* @return The value of the field with the specified name in the {@code classToAccess}.
* @throws UnableToAccessFieldException If there was a problem getting the field.
* @throws UnableToAccessFieldException If there was a problem getting the value.
*/
public static <T, E> T getPrivateValue(Class<? super E> classToAccess, E instance, String fieldName) {
try {
return (T) findField(classToAccess, fieldName).get(instance);
} catch (UnableToFindFieldException e) {
LOGGER.error(REFLECTION, "Unable to locate field {} ({}) on type {}", fieldName, PatchworkRemappingService.remapFieldName(classToAccess, fieldName), classToAccess.getName(), e);
throw e;
} catch (IllegalAccessException e) {
LOGGER.error(REFLECTION, "Unable to access field {} ({}) on type {}", fieldName, PatchworkRemappingService.remapFieldName(classToAccess, fieldName), classToAccess.getName(), e);
throw new UnableToAccessFieldException(e);
}
}

/**
* Sets the value a field with the specified name in the given class.
* Note: For performance, use {@link #findField(Class, String)} if you are setting the value more than once.
* <p>
* Throws an exception if the field is not found or the value of the field cannot be set.
*
* @param classToAccess The class to find the field on.
* @param instance The instance of the {@code classToAccess}.
* @param value The new value for the field
* @param fieldName The name of the field in the {@code classToAccess}.
* @param <T> The type of the value.
* @param <E> The type of the {@code classToAccess}.
* @throws UnableToFindFieldException If there was a problem getting the field.
* @throws UnableToAccessFieldException If there was a problem setting the value of the field.
*/
public static <T, E> void setPrivateValue(
final Class<? super T> classToAccess,
final T instance,
final E value,
final String fieldName) {
try {
findField(classToAccess, fieldName).set(instance, value);
} catch (UnableToFindFieldException e) {
LOGGER.error("Unable to locate any field {} on type {}", fieldName, classToAccess.getName(), e);
throw e;
} catch (IllegalAccessException e) {
LOGGER.error("Unable to set any field {} on type {}", fieldName, classToAccess.getName(), e);
throw new UnableToAccessFieldException(e);
}
}

/**
* Finds a method with the specified name and parameters in the given class and makes it accessible.
* Note: For performance, store the returned value and avoid calling this repeatedly.
* <p>
* Throws an exception if the method is not found.
*
* @param clazz The class to find the method on.
* @param methodName The SRG (unmapped) name of the method to find (e.g. "func_12820_D").
* @param parameterTypes The parameter types of the method to find.
* @return The method with the specified name and parameters in the given class.
* @throws NullPointerException If {@code clazz} is null.
* @throws NullPointerException If {@code methodName} is null.
* @throws IllegalArgumentException If {@code methodName} is empty.
* @throws NullPointerException If {@code parameterTypes} is null.
* @throws UnableToFindMethodException If the method could not be found.
*/
public static Method findMethod(final Class<?> clazz, final String methodName, final Class<?>... parameterTypes) {
Preconditions.checkNotNull(clazz, "Class to find method on cannot be null.");
Preconditions.checkNotNull(methodName, "Name of method to find cannot be null.");
Preconditions.checkArgument(!methodName.isEmpty(), "Name of method to find cannot be empty.");
Preconditions.checkNotNull(parameterTypes, "Parameter types of method to find cannot be null.");

try {
Method m = clazz.getDeclaredMethod(PatchworkRemappingService.remapMethodName(clazz, methodName), parameterTypes);
m.setAccessible(true);
return m;
} catch (Exception e) {
throw new UnableToFindMethodException(e);
}
}

/**
* Finds a constructor with the specified parameter types in the given class and makes it accessible.
* Note: For performance, store the returned value and avoid calling this repeatedly.
* <p>
* Throws an exception if the constructor is not found.
*
* @param clazz The class to find the constructor in.
* @param parameterTypes The parameter types of the constructor.
* @param <T> The type.
* @return The constructor with the specified parameters in the given class.
* @throws NullPointerException If {@code clazz} is null.
* @throws NullPointerException If {@code parameterTypes} is null.
* @throws UnknownConstructorException If the constructor could not be found.
*/
public static <T> Constructor<T> findConstructor(final Class<T> clazz, final Class<?>... parameterTypes) {
Preconditions.checkNotNull(clazz, "Class to find constructor on cannot be null.");
Preconditions.checkNotNull(parameterTypes, "Parameter types of constructor to find cannot be null.");

try {
Constructor<T> constructor = clazz.getDeclaredConstructor(parameterTypes);
constructor.setAccessible(true);
return constructor;
} catch (final NoSuchMethodException e) {
final StringBuilder desc = new StringBuilder();
desc.append(clazz.getSimpleName());

StringJoiner joiner = new StringJoiner(", ", "(", ")");

for (Class<?> type : parameterTypes) {
joiner.add(type.getSimpleName());
}

desc.append(joiner);
throw new UnknownConstructorException("Could not find constructor '" + desc.toString() + "' in " + clazz);
}
}

/**
* Finds a field with the specified name in the given class and makes it accessible.
* Note: For performance, store the returned value and avoid calling this repeatedly.
* <p>
* Throws an exception if the field is not found.
*
* @param clazz The class to find the field on.
* @param fieldName The SRG (unmapped) name of the field to find (e.g. "field_181725_a").
* @param <T> The type.
* @return The constructor with the specified parameters in the given class.
* @throws NullPointerException If {@code clazz} is null.
* @throws NullPointerException If {@code fieldName} is null.
* @throws IllegalArgumentException If {@code fieldName} is empty.
* @throws UnableToFindFieldException If the field could not be found.
*/
public static <T> Field findField(
final Class<? super T> clazz,
final String fieldName) {
Preconditions.checkNotNull(clazz, "Class to find field on cannot be null.");
Preconditions.checkNotNull(fieldName, "Name of field to find cannot be null.");
Preconditions.checkArgument(!fieldName.isEmpty(), "Name of field to find cannot be empty.");

try {
Field f = clazz.getDeclaredField(PatchworkRemappingService.remapFieldName(clazz, fieldName));
f.setAccessible(true);
return f;
} catch (Exception e) {
throw new UnableToFindFieldException(e);
}
}

public static class UnableToAccessFieldException extends RuntimeException {
private UnableToAccessFieldException(Exception e) {
super(e);
}
}

public static class UnableToFindFieldException extends RuntimeException {
private UnableToFindFieldException(Exception e) {
super(e);
}
}

public static class UnableToFindMethodException extends RuntimeException {
public UnableToFindMethodException(Throwable failed) {
super(failed);
}
}

public static class UnknownConstructorException extends RuntimeException {
public UnknownConstructorException(final String message) {
super(message);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Minecraft Forge, Patchwork Project
* Copyright (c) 2016-2020, 2019-2020
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

package net.patchworkmc.api.mappings;

import java.util.HashMap;

import org.cadixdev.lorenz.MappingSet;
import org.cadixdev.lorenz.model.Mapping;
import org.cadixdev.lorenz.model.MethodMapping;

import net.patchworkmc.impl.mappings.PatchworkMappings;

/**
* A class that Patchwork and mods that depend on it can use to map SRG names to
* whatever the runtime mappings are.
*/
public class PatchworkRemappingService {
/**
* Cache for method lookups so we can avoid doing a search every time.
*/
private static final HashMap<Class<?>, HashMap<String, String>> methodCache = new HashMap<>();

public static String remapMethodName(Class<?> clazz, String srgName) {
HashMap<String, String> cache = methodCache.computeIfAbsent(clazz, ignored -> new HashMap<>());

if (cache.containsKey(srgName)) {
return cache.get(srgName);
}

MappingSet runtime2srg = PatchworkMappings.getMappingGenerator().getRuntimeToSrgMappings();
String runtimeName = runtime2srg.getClassMapping(clazz.getName())
.map(classMapping -> {
for (MethodMapping methodMapping : classMapping.getMethodMappings()) {
if (methodMapping.getDeobfuscatedName().equals(srgName)) {
return methodMapping.getObfuscatedName();
}
}

return srgName;
}).orElse(srgName);
cache.put(srgName, runtimeName);
return runtimeName;
}

public static String remapFieldName(Class<?> clazz, String srgName) {
MappingSet runtime2srg = PatchworkMappings.getMappingGenerator().getRuntimeToSrgMappings();
MappingSet srg2runtime = PatchworkMappings.getMappingGenerator().getSrgToRuntimeMappings();
return runtime2srg.getClassMapping(clazz.getName())
.map(classMapping -> srg2runtime.getClassMapping(classMapping.getObfuscatedName())
.map(srgClassMapping -> srgClassMapping.getFieldMapping(srgName).map(Mapping::getDeobfuscatedName).orElse(srgName))
.orElseThrow(() -> new IllegalStateException("PatchworkRemappingService tried to map class " + clazz.getName()
+ " from runtime -> srg -> runtime, but the last step doesn't exist.")))
.orElse(srgName);
}
}
Loading

0 comments on commit 4e3d2a3

Please sign in to comment.