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
|
package cuchaz.enigma.mapping;
import com.google.common.base.Charsets;
import com.google.common.collect.Maps;
import cuchaz.enigma.mapping.entry.ClassEntry;
import cuchaz.enigma.throwables.MappingConflict;
import cuchaz.enigma.throwables.MappingParseException;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class MappingsTinyReader {
public ClassMapping readClass(String[] parts) {
// Extract the inner naming of the deob form if it have one
String deobName = parts[2].contains("$") ? parts[2].substring(parts[2].lastIndexOf('$') + 1) : parts[2];
return new ClassMapping(parts[1], deobName).setDeobInner(parts[2]);
}
public FieldMapping readField(String[] parts) {
return new FieldMapping(parts[3], new TypeDescriptor(parts[2]), parts[4], Mappings.EntryModifier.UNCHANGED);
}
public MethodMapping readMethod(String[] parts) {
return new MethodMapping(parts[3], new MethodDescriptor(parts[2]), parts[4]);
}
public Mappings read(File file) throws IOException, MappingParseException {
Mappings mappings = new Mappings(Mappings.FormatType.TINY_FILE);
List<String> lines = Files.readAllLines(file.toPath(), Charsets.UTF_8);
Map<String, ClassMapping> classMappingMap = Maps.newHashMap();
lines.remove(0); // TODO: use the header
for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) {
String line = lines.get(lineNumber);
String[] parts = line.split("\t");
try {
String token = parts[0];
ClassMapping classMapping;
switch (token) {
case "CLASS":
// Check for orphan created by field or method entries. It shouldn't be possible but I prefer to handle this case
if (classMappingMap.containsKey(parts[1])) {
classMapping = classMappingMap.get(parts[1]);
// We have the full deob name, Enigma only support simple class name so we extract it.
String deobName = parts[2].contains("$") ?
parts[2].substring(parts[2].lastIndexOf('$') + 1) :
parts[2];
// Add full deob name to the class mapping to handle inner class after this loop
classMappingMap.put(parts[2], classMapping.setDeobInner(parts[2]));
classMapping.setDeobfName(deobName);
// Avoid to make the mapping dirty directly at the startup
classMapping.resetDirty();
} else
classMapping = readClass(parts);
classMappingMap.put(parts[1], classMapping);
break;
case "FIELD":
// We can have missing classes mappings because they don't have a ob name, so we create it and use it
classMapping = classMappingMap.computeIfAbsent(parts[1], k -> new ClassMapping(parts[1]));
classMapping.addFieldMapping(readField(parts));
break;
case "METHOD":
// We can have missing classes mappings because they don't have a ob name, so we create it and use it
classMapping = classMappingMap.computeIfAbsent(parts[1], k -> new ClassMapping(parts[1]));
classMapping.addMethodMapping(readMethod(parts));
break;
case "MTH-ARG":
classMapping = classMappingMap.computeIfAbsent(parts[1], k -> new ClassMapping(parts[1]));
classMapping.setArgumentName(parts[3], new Signature(parts[2]), Integer.parseInt(parts[4]), parts[5]);
break;
default:
throw new MappingParseException(file, lineNumber, "Unknown token '" + token + "' !");
}
} catch (ArrayIndexOutOfBoundsException | IllegalArgumentException ex) {
ex.printStackTrace();
throw new MappingParseException(file, lineNumber, "Malformed line:\n" + line);
}
}
List<ClassMapping> toRegister = new ArrayList<>(classMappingMap.values());
// After having completely parsed the file, we need to register it to the real mapping
for (ClassMapping classMapping : toRegister) {
ClassEntry obEntry = classMapping.getObfEntry();
ClassEntry deobEntry = classMapping.getDeObfEntry();
try {
if (obEntry.isInnerClass()) {
ClassMapping parent = classMappingMap.get(obEntry.getOuterClassName());
// Inner class can miss their parent... So we create it and add it to the mappings
if (parent == null) {
parent = new ClassMapping(obEntry.getOuterClassName()); // FIXME: WE ACTUALLY DON'T MANAGE INNER CLASS OF INNER CLASS
classMappingMap.put(obEntry.getOuterClassName(), parent);
mappings.addClassMapping(parent);
}
// Add the inner class to the parent
parent.addInnerClassMapping(classMapping);
}
// obf class can become deobf inner classs, manage this case.
else if (deobEntry != null && deobEntry.isInnerClass()) {
String outerClassName = deobEntry.getOuterClassName();
ClassMapping parent = classMappingMap.get(outerClassName);
// Only the inner is deob??? Okay
if (parent == null) {
parent = classMappingMap.get(outerClassName);
if (parent == null) {
parent = new ClassMapping(outerClassName); // FIXME: WE ACTUALLY DON'T MANAGE INNER CLASS OF INNER CLASS
classMappingMap.put(outerClassName, parent);
mappings.addClassMapping(parent);
}
}
parent.addInnerClassMapping(classMapping);
} else
mappings.addClassMapping(classMapping);
} catch (MappingConflict e) {
throw new MappingParseException(file, -1, e.getMessage());
}
}
lines.clear();
classMappingMap.clear();
return mappings;
}
}
|