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

Commit 4e3d2a3

Browse files
Runtime Remapping (#234)
* Runtime remapping * Clean up, and throw exception when it's the issue. * Message when reflection is accessed but not loaded
1 parent 2c314bf commit 4e3d2a3

File tree

11 files changed

+620
-0
lines changed

11 files changed

+620
-0
lines changed

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ dependencies {
267267
include 'com.electronwill.night-config:core:3.6.2'
268268
include 'com.electronwill.night-config:toml:3.6.2'
269269
include 'net.patchworkmc:event-racecar:1.0.1:with-typetools'
270+
include 'org.cadixdev:lorenz:0.5.4'
270271
}
271272

272273
loom {

patchwork-mappings/build.gradle

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
archivesBaseName = "patchwork-mappings"
2+
version = getSubprojectVersion(project, "0.1.0")
3+
4+
dependencies {
5+
implementation "org.cadixdev:lorenz:0.5.4"
6+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Minecraft Forge, Patchwork Project
3+
* Copyright (c) 2016-2020, 2019-2020
4+
*
5+
* This library is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU Lesser General Public
7+
* License as published by the Free Software Foundation version 2.1
8+
* of the License.
9+
*
10+
* This library is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
* Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public
16+
* License along with this library; if not, write to the Free Software
17+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18+
*/
19+
20+
package cpw.mods.modlauncher.api;
21+
22+
@SuppressWarnings("unused")
23+
public interface INameMappingService {
24+
enum Domain { CLASS, METHOD, FIELD }
25+
}
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
/*
2+
* Minecraft Forge, Patchwork Project
3+
* Copyright (c) 2016-2020, 2019-2020
4+
*
5+
* This library is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU Lesser General Public
7+
* License as published by the Free Software Foundation version 2.1
8+
* of the License.
9+
*
10+
* This library is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
* Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public
16+
* License along with this library; if not, write to the Free Software
17+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18+
*/
19+
20+
package net.minecraftforge.fml.common;
21+
22+
import java.lang.reflect.Constructor;
23+
import java.lang.reflect.Field;
24+
import java.lang.reflect.Method;
25+
import java.util.StringJoiner;
26+
27+
import com.google.common.base.Preconditions;
28+
import cpw.mods.modlauncher.api.INameMappingService;
29+
import org.apache.logging.log4j.LogManager;
30+
import org.apache.logging.log4j.Logger;
31+
import org.apache.logging.log4j.Marker;
32+
import org.apache.logging.log4j.MarkerManager;
33+
34+
import net.patchworkmc.api.mappings.PatchworkRemappingService;
35+
36+
/**
37+
* A reimplementation of MinecraftForge's ObfuscationReflectionHelper in a way
38+
* that will make sure that the names are remapped.
39+
* <p>
40+
* If you're a Fabric mod that depends on Patchwork API, this class will not do
41+
* what you expect. Use {@link PatchworkRemappingService} instead.
42+
*/
43+
@SuppressWarnings("unused") // Since our implementation is different from Forge we don't use all of the parameters
44+
@Deprecated
45+
public class ObfuscationReflectionHelper {
46+
private static final Logger LOGGER = LogManager.getLogger();
47+
private static final Marker REFLECTION = MarkerManager.getMarker("REFLECTION");
48+
49+
/**
50+
* Remaps a name using the SRG naming function
51+
*
52+
* @param domain The {@link INameMappingService.Domain} to use to remap the name.
53+
* @param name The name to try and remap.
54+
* @return The remapped name, or the original name if it couldn't be remapped.
55+
*/
56+
public static String remapName(INameMappingService.Domain domain, String name) {
57+
// Patchwork intentionally always returns the input, we need an actual
58+
// reference to the Class because intermediary doesn't have the unique
59+
// name guarantee like SRG does.
60+
return name;
61+
}
62+
63+
/**
64+
* Gets the value a field with the specified name in the given class.
65+
* Note: For performance, use {@link #findField(Class, String)} if you are getting the value more than once.
66+
* <p>
67+
* Throws an exception if the field is not found or the value of the field cannot be gotten.
68+
*
69+
* @param classToAccess The class to find the field on.
70+
* @param instance The instance of the {@code classToAccess}.
71+
* @param fieldName The SRG (unmapped) name of the field to find (e.g. "field_181725_a").
72+
* @param <T> The type of the value.
73+
* @param <E> The type of the {@code classToAccess}.
74+
* @return The value of the field with the specified name in the {@code classToAccess}.
75+
* @throws UnableToAccessFieldException If there was a problem getting the field.
76+
* @throws UnableToAccessFieldException If there was a problem getting the value.
77+
*/
78+
public static <T, E> T getPrivateValue(Class<? super E> classToAccess, E instance, String fieldName) {
79+
try {
80+
return (T) findField(classToAccess, fieldName).get(instance);
81+
} catch (UnableToFindFieldException e) {
82+
LOGGER.error(REFLECTION, "Unable to locate field {} ({}) on type {}", fieldName, PatchworkRemappingService.remapFieldName(classToAccess, fieldName), classToAccess.getName(), e);
83+
throw e;
84+
} catch (IllegalAccessException e) {
85+
LOGGER.error(REFLECTION, "Unable to access field {} ({}) on type {}", fieldName, PatchworkRemappingService.remapFieldName(classToAccess, fieldName), classToAccess.getName(), e);
86+
throw new UnableToAccessFieldException(e);
87+
}
88+
}
89+
90+
/**
91+
* Sets the value a field with the specified name in the given class.
92+
* Note: For performance, use {@link #findField(Class, String)} if you are setting the value more than once.
93+
* <p>
94+
* Throws an exception if the field is not found or the value of the field cannot be set.
95+
*
96+
* @param classToAccess The class to find the field on.
97+
* @param instance The instance of the {@code classToAccess}.
98+
* @param value The new value for the field
99+
* @param fieldName The name of the field in the {@code classToAccess}.
100+
* @param <T> The type of the value.
101+
* @param <E> The type of the {@code classToAccess}.
102+
* @throws UnableToFindFieldException If there was a problem getting the field.
103+
* @throws UnableToAccessFieldException If there was a problem setting the value of the field.
104+
*/
105+
public static <T, E> void setPrivateValue(
106+
final Class<? super T> classToAccess,
107+
final T instance,
108+
final E value,
109+
final String fieldName) {
110+
try {
111+
findField(classToAccess, fieldName).set(instance, value);
112+
} catch (UnableToFindFieldException e) {
113+
LOGGER.error("Unable to locate any field {} on type {}", fieldName, classToAccess.getName(), e);
114+
throw e;
115+
} catch (IllegalAccessException e) {
116+
LOGGER.error("Unable to set any field {} on type {}", fieldName, classToAccess.getName(), e);
117+
throw new UnableToAccessFieldException(e);
118+
}
119+
}
120+
121+
/**
122+
* Finds a method with the specified name and parameters in the given class and makes it accessible.
123+
* Note: For performance, store the returned value and avoid calling this repeatedly.
124+
* <p>
125+
* Throws an exception if the method is not found.
126+
*
127+
* @param clazz The class to find the method on.
128+
* @param methodName The SRG (unmapped) name of the method to find (e.g. "func_12820_D").
129+
* @param parameterTypes The parameter types of the method to find.
130+
* @return The method with the specified name and parameters in the given class.
131+
* @throws NullPointerException If {@code clazz} is null.
132+
* @throws NullPointerException If {@code methodName} is null.
133+
* @throws IllegalArgumentException If {@code methodName} is empty.
134+
* @throws NullPointerException If {@code parameterTypes} is null.
135+
* @throws UnableToFindMethodException If the method could not be found.
136+
*/
137+
public static Method findMethod(final Class<?> clazz, final String methodName, final Class<?>... parameterTypes) {
138+
Preconditions.checkNotNull(clazz, "Class to find method on cannot be null.");
139+
Preconditions.checkNotNull(methodName, "Name of method to find cannot be null.");
140+
Preconditions.checkArgument(!methodName.isEmpty(), "Name of method to find cannot be empty.");
141+
Preconditions.checkNotNull(parameterTypes, "Parameter types of method to find cannot be null.");
142+
143+
try {
144+
Method m = clazz.getDeclaredMethod(PatchworkRemappingService.remapMethodName(clazz, methodName), parameterTypes);
145+
m.setAccessible(true);
146+
return m;
147+
} catch (Exception e) {
148+
throw new UnableToFindMethodException(e);
149+
}
150+
}
151+
152+
/**
153+
* Finds a constructor with the specified parameter types in the given class and makes it accessible.
154+
* Note: For performance, store the returned value and avoid calling this repeatedly.
155+
* <p>
156+
* Throws an exception if the constructor is not found.
157+
*
158+
* @param clazz The class to find the constructor in.
159+
* @param parameterTypes The parameter types of the constructor.
160+
* @param <T> The type.
161+
* @return The constructor with the specified parameters in the given class.
162+
* @throws NullPointerException If {@code clazz} is null.
163+
* @throws NullPointerException If {@code parameterTypes} is null.
164+
* @throws UnknownConstructorException If the constructor could not be found.
165+
*/
166+
public static <T> Constructor<T> findConstructor(final Class<T> clazz, final Class<?>... parameterTypes) {
167+
Preconditions.checkNotNull(clazz, "Class to find constructor on cannot be null.");
168+
Preconditions.checkNotNull(parameterTypes, "Parameter types of constructor to find cannot be null.");
169+
170+
try {
171+
Constructor<T> constructor = clazz.getDeclaredConstructor(parameterTypes);
172+
constructor.setAccessible(true);
173+
return constructor;
174+
} catch (final NoSuchMethodException e) {
175+
final StringBuilder desc = new StringBuilder();
176+
desc.append(clazz.getSimpleName());
177+
178+
StringJoiner joiner = new StringJoiner(", ", "(", ")");
179+
180+
for (Class<?> type : parameterTypes) {
181+
joiner.add(type.getSimpleName());
182+
}
183+
184+
desc.append(joiner);
185+
throw new UnknownConstructorException("Could not find constructor '" + desc.toString() + "' in " + clazz);
186+
}
187+
}
188+
189+
/**
190+
* Finds a field with the specified name in the given class and makes it accessible.
191+
* Note: For performance, store the returned value and avoid calling this repeatedly.
192+
* <p>
193+
* Throws an exception if the field is not found.
194+
*
195+
* @param clazz The class to find the field on.
196+
* @param fieldName The SRG (unmapped) name of the field to find (e.g. "field_181725_a").
197+
* @param <T> The type.
198+
* @return The constructor with the specified parameters in the given class.
199+
* @throws NullPointerException If {@code clazz} is null.
200+
* @throws NullPointerException If {@code fieldName} is null.
201+
* @throws IllegalArgumentException If {@code fieldName} is empty.
202+
* @throws UnableToFindFieldException If the field could not be found.
203+
*/
204+
public static <T> Field findField(
205+
final Class<? super T> clazz,
206+
final String fieldName) {
207+
Preconditions.checkNotNull(clazz, "Class to find field on cannot be null.");
208+
Preconditions.checkNotNull(fieldName, "Name of field to find cannot be null.");
209+
Preconditions.checkArgument(!fieldName.isEmpty(), "Name of field to find cannot be empty.");
210+
211+
try {
212+
Field f = clazz.getDeclaredField(PatchworkRemappingService.remapFieldName(clazz, fieldName));
213+
f.setAccessible(true);
214+
return f;
215+
} catch (Exception e) {
216+
throw new UnableToFindFieldException(e);
217+
}
218+
}
219+
220+
public static class UnableToAccessFieldException extends RuntimeException {
221+
private UnableToAccessFieldException(Exception e) {
222+
super(e);
223+
}
224+
}
225+
226+
public static class UnableToFindFieldException extends RuntimeException {
227+
private UnableToFindFieldException(Exception e) {
228+
super(e);
229+
}
230+
}
231+
232+
public static class UnableToFindMethodException extends RuntimeException {
233+
public UnableToFindMethodException(Throwable failed) {
234+
super(failed);
235+
}
236+
}
237+
238+
public static class UnknownConstructorException extends RuntimeException {
239+
public UnknownConstructorException(final String message) {
240+
super(message);
241+
}
242+
}
243+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Minecraft Forge, Patchwork Project
3+
* Copyright (c) 2016-2020, 2019-2020
4+
*
5+
* This library is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU Lesser General Public
7+
* License as published by the Free Software Foundation version 2.1
8+
* of the License.
9+
*
10+
* This library is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
* Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public
16+
* License along with this library; if not, write to the Free Software
17+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18+
*/
19+
20+
package net.patchworkmc.api.mappings;
21+
22+
import java.util.HashMap;
23+
24+
import org.cadixdev.lorenz.MappingSet;
25+
import org.cadixdev.lorenz.model.Mapping;
26+
import org.cadixdev.lorenz.model.MethodMapping;
27+
28+
import net.patchworkmc.impl.mappings.PatchworkMappings;
29+
30+
/**
31+
* A class that Patchwork and mods that depend on it can use to map SRG names to
32+
* whatever the runtime mappings are.
33+
*/
34+
public class PatchworkRemappingService {
35+
/**
36+
* Cache for method lookups so we can avoid doing a search every time.
37+
*/
38+
private static final HashMap<Class<?>, HashMap<String, String>> methodCache = new HashMap<>();
39+
40+
public static String remapMethodName(Class<?> clazz, String srgName) {
41+
HashMap<String, String> cache = methodCache.computeIfAbsent(clazz, ignored -> new HashMap<>());
42+
43+
if (cache.containsKey(srgName)) {
44+
return cache.get(srgName);
45+
}
46+
47+
MappingSet runtime2srg = PatchworkMappings.getMappingGenerator().getRuntimeToSrgMappings();
48+
String runtimeName = runtime2srg.getClassMapping(clazz.getName())
49+
.map(classMapping -> {
50+
for (MethodMapping methodMapping : classMapping.getMethodMappings()) {
51+
if (methodMapping.getDeobfuscatedName().equals(srgName)) {
52+
return methodMapping.getObfuscatedName();
53+
}
54+
}
55+
56+
return srgName;
57+
}).orElse(srgName);
58+
cache.put(srgName, runtimeName);
59+
return runtimeName;
60+
}
61+
62+
public static String remapFieldName(Class<?> clazz, String srgName) {
63+
MappingSet runtime2srg = PatchworkMappings.getMappingGenerator().getRuntimeToSrgMappings();
64+
MappingSet srg2runtime = PatchworkMappings.getMappingGenerator().getSrgToRuntimeMappings();
65+
return runtime2srg.getClassMapping(clazz.getName())
66+
.map(classMapping -> srg2runtime.getClassMapping(classMapping.getObfuscatedName())
67+
.map(srgClassMapping -> srgClassMapping.getFieldMapping(srgName).map(Mapping::getDeobfuscatedName).orElse(srgName))
68+
.orElseThrow(() -> new IllegalStateException("PatchworkRemappingService tried to map class " + clazz.getName()
69+
+ " from runtime -> srg -> runtime, but the last step doesn't exist.")))
70+
.orElse(srgName);
71+
}
72+
}

0 commit comments

Comments
 (0)