-
Notifications
You must be signed in to change notification settings - Fork 0
/
ReobfHelper.java
186 lines (161 loc) · 7.05 KB
/
ReobfHelper.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
package de.cubeside.nmsutils.util;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import net.fabricmc.mappingio.MappingReader;
import net.fabricmc.mappingio.format.MappingFormat;
import net.fabricmc.mappingio.tree.MappingTree;
import net.fabricmc.mappingio.tree.MappingTree.FieldMapping;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
import org.bukkit.Bukkit;
/**
* Modified from https://github.com/PaperMC/Paper/blob/675d1e3f58ab86d5a3b1bc8bbcf2beefc9696078/patches/server/0420-Deobfuscate-stacktraces-in-log-messages-crash-report.patch
* see there for the license
*/
public enum ReobfHelper {
INSTANCE;
public static final String MOJANG_PLUS_YARN_NAMESPACE = "mojang+yarn";
public static final String SPIGOT_NAMESPACE = "spigot";
private final Map<String, ClassMapping> mappingsByObfName;
private final Map<String, ClassMapping> mappingsByMojangName;
ReobfHelper() {
final Set<ClassMapping> maps = loadMappingsIfPresent();
if (maps != null) {
this.mappingsByObfName = maps.stream().collect(Collectors.toUnmodifiableMap(ClassMapping::obfName, map -> map));
this.mappingsByMojangName = maps.stream().collect(Collectors.toUnmodifiableMap(ClassMapping::mojangName, map -> map));
} else {
this.mappingsByObfName = null;
this.mappingsByMojangName = null;
}
}
public Map<String, ClassMapping> mappingsByObfName() {
return this.mappingsByObfName;
}
public Map<String, ClassMapping> mappingsByMojangName() {
return this.mappingsByMojangName;
}
/**
* Attempts to get the obf name for a given class by its Mojang name. Will
* return the input string if mappings are not present.
*
* @param fullyQualifiedMojangName
* fully qualified class name (dotted)
* @return mapped or original fully qualified (dotted) class name
*/
public String reobfClassName(final String fullyQualifiedMojangName) {
if (this.mappingsByMojangName == null) {
return fullyQualifiedMojangName;
}
final ClassMapping map = this.mappingsByMojangName.get(fullyQualifiedMojangName);
if (map == null) {
return fullyQualifiedMojangName;
}
return map.obfName();
}
/**
* Attempts to get the Mojang name for a given class by its obf name. Will
* return the input string if mappings are not present.
*
* @param fullyQualifiedObfName
* fully qualified class name (dotted)
* @return mapped or original fully qualified (dotted) class name
*/
public String deobfClassName(final String fullyQualifiedObfName) {
if (this.mappingsByObfName == null) {
return fullyQualifiedObfName;
}
final ClassMapping map = this.mappingsByObfName.get(fullyQualifiedObfName);
if (map == null) {
return fullyQualifiedObfName;
}
return map.mojangName();
}
private static Set<ClassMapping> loadMappingsIfPresent() {
try (final InputStream mappingsInputStream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/mappings/reobf.tiny")) {
if (mappingsInputStream == null) {
return null;
}
final MemoryMappingTree tree = new MemoryMappingTree();
MappingFormat format;
try {
format = MappingFormat.valueOf("TINY_2_FILE");
} catch (IllegalArgumentException e) {
format = MappingFormat.valueOf("TINY_2");
}
MappingReader.read(new InputStreamReader(mappingsInputStream, StandardCharsets.UTF_8), format, tree);
final Set<ClassMapping> classes = new HashSet<>();
final StringPool pool = new StringPool();
for (final MappingTree.ClassMapping cls : tree.getClasses()) {
final Map<String, String> methods = new HashMap<>();
final Map<String, String> fields = new HashMap<>();
for (final MappingTree.MethodMapping methodMapping : cls.getMethods()) {
methods.put(
pool.string(methodOrFieldKey(
methodMapping.getName(MOJANG_PLUS_YARN_NAMESPACE),
methodMapping.getDesc(MOJANG_PLUS_YARN_NAMESPACE))),
pool.string(methodMapping.getName(SPIGOT_NAMESPACE)));
}
for (final FieldMapping fieldsMapping : cls.getFields()) {
fields.put(
pool.string(fieldsMapping.getName(MOJANG_PLUS_YARN_NAMESPACE)),
pool.string(fieldsMapping.getName(SPIGOT_NAMESPACE)));
}
final ClassMapping map = new ClassMapping(
cls.getName(SPIGOT_NAMESPACE).replace('/', '.'),
cls.getName(MOJANG_PLUS_YARN_NAMESPACE).replace('/', '.'),
Map.copyOf(methods), Map.copyOf(fields));
classes.add(map);
}
return Set.copyOf(classes);
} catch (final Error | Exception ex) {
System.err.println("Failed to load mappings for stacktrace deobfuscation.");
System.err.println(ex.toString());
ex.printStackTrace();
return null;
}
}
public static String methodOrFieldKey(final String obfName, final String obfDescriptor) {
return obfName + obfDescriptor;
}
private static final class StringPool {
private final Map<String, String> pool = new HashMap<>();
public String string(final String string) {
return this.pool.computeIfAbsent(string, Function.identity());
}
}
public record ClassMapping(
String obfName,
String mojangName,
Map<String, String> methodsByMojang,
Map<String, String> fieldsByMojang) {
}
final static Map<String, ReobfHelper.ClassMapping> mappings = ReobfHelper.INSTANCE.mappingsByObfName();
public static String getObfuscatedFieldName(Class<?> clazz, String field) {
if (mappings != null) {
ClassMapping classMapping = mappings.get(clazz.getName());
if (classMapping != null) {
String fieldName = classMapping.fieldsByMojang().get(field);
if (fieldName != null) {
return fieldName;
}
}
}
return field;
}
public static Field getFieldByMojangName(Class<?> clazz, String fieldName) {
try {
Field field = clazz.getDeclaredField(getObfuscatedFieldName(clazz, fieldName));
field.setAccessible(true);
return field;
} catch (NoSuchFieldException | SecurityException e) {
throw new RuntimeException(e);
}
}
}