Skip to content

Commit fce656b

Browse files
committed
Fix reobfuscating for restricted names by using file systems
1 parent 7d9146f commit fce656b

File tree

4 files changed

+203
-232
lines changed

4 files changed

+203
-232
lines changed

gui/src/main/java/org/mcphackers/mcp/main/MainGUI.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@
5555
* GUI implementation of MCP
5656
*/
5757
public class MainGUI extends MCP {
58-
public static final TaskMode[] TASKS = {TaskMode.DECOMPILE, TaskMode.RECOMPILE, TaskMode.REOBFUSCATE, TaskMode.BUILD, TaskMode.UPDATE_MD5, TaskMode.CREATE_PATCH};
59-
public static final String[] TABS = {"task.decompile", "task.recompile", "task.reobfuscate", "task.build", "options.running"};
58+
public static final TaskMode[] TASKS = {TaskMode.DECOMPILE, TaskMode.RECOMPILE, TaskMode.BUILD, TaskMode.UPDATE_MD5, TaskMode.CREATE_PATCH};
59+
public static final String[] TABS = {"task.decompile", "task.recompile", "task.build", "options.running"};
6060
public static final TaskParameter[][] TAB_PARAMETERS = {
6161
{TaskParameter.PATCHES, TaskParameter.FERNFLOWER_OPTIONS, TaskParameter.IGNORED_PACKAGES, TaskParameter.OUTPUT_SRC, TaskParameter.DECOMPILE_RESOURCES, TaskParameter.GUESS_GENERICS, TaskParameter.STRIP_GENERICS},
6262
{TaskParameter.SOURCE_VERSION, TaskParameter.TARGET_VERSION, TaskParameter.JAVA_HOME}, {TaskParameter.OBFUSCATION, TaskParameter.EXCLUDED_CLASSES},

src/main/java/org/mcphackers/mcp/tasks/TaskBuild.java

+201-34
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,31 @@
22

33
import static org.mcphackers.mcp.MCPPaths.*;
44

5+
import java.io.File;
6+
import java.io.IOException;
7+
import java.net.URI;
8+
import java.nio.file.FileSystem;
9+
import java.nio.file.FileSystems;
510
import java.nio.file.Files;
611
import java.nio.file.Path;
12+
import java.util.ArrayList;
13+
import java.util.HashMap;
714
import java.util.List;
15+
import java.util.Map;
16+
import java.util.regex.Pattern;
17+
import java.util.stream.Stream;
818

919
import org.mcphackers.mcp.MCP;
1020
import org.mcphackers.mcp.MCPPaths;
1121
import org.mcphackers.mcp.tasks.mode.TaskParameter;
1222
import org.mcphackers.mcp.tools.FileUtil;
23+
import org.mcphackers.mcp.tools.mappings.MappingUtil;
24+
import org.mcphackers.rdi.injector.data.ClassStorage;
25+
import org.mcphackers.rdi.injector.data.Mappings;
26+
import org.mcphackers.rdi.nio.ClassStorageWriter;
27+
import org.mcphackers.rdi.nio.MappingsIO;
28+
import org.mcphackers.rdi.nio.RDInjector;
29+
import org.objectweb.asm.ClassWriter;
1330

1431
public class TaskBuild extends TaskStaged {
1532
/*
@@ -26,40 +43,7 @@ public TaskBuild(Side side, MCP instance) {
2643
@Override
2744
protected Stage[] setStages() {
2845
Path bin = MCPPaths.get(mcp, BIN, side);
29-
return new Stage[]{
30-
stage(getLocalizedStage("recompile"),
31-
() -> new TaskRecompile(side, mcp, this).doTask()),
32-
stage(getLocalizedStage("reobf"), 50,
33-
() -> new TaskReobfuscate(side, mcp, this).doTask()),
34-
stage(getLocalizedStage("build"), 70,
35-
() -> {
36-
Side[] sides = side == Side.MERGED ? new Side[]{Side.CLIENT, Side.SERVER} : new Side[]{side};
37-
for (Side localSide : sides) {
38-
Path originalJar = MCPPaths.get(mcp, JAR_ORIGINAL, localSide);
39-
Path reobfDir = MCPPaths.get(mcp, REOBF_SIDE, localSide);
40-
Path buildJar = MCPPaths.get(mcp, BUILD_JAR, localSide);
41-
Path buildZip = MCPPaths.get(mcp, BUILD_ZIP, localSide);
42-
FileUtil.createDirectories(MCPPaths.get(mcp, BUILD));
43-
if (mcp.getOptions().getBooleanParameter(TaskParameter.FULL_BUILD)) {
44-
Files.deleteIfExists(buildJar);
45-
Files.copy(originalJar, buildJar);
46-
List<Path> reobfClasses = FileUtil.walkDirectory(reobfDir, path -> !Files.isDirectory(path));
47-
FileUtil.packFilesToZip(buildJar, reobfClasses, reobfDir);
48-
List<Path> assets = FileUtil.walkDirectory(bin, path -> !Files.isDirectory(path) && !path.getFileName().toString().endsWith(".class"));
49-
FileUtil.packFilesToZip(buildJar, assets, bin);
50-
FileUtil.deleteFileInAZip(buildJar, "/META-INF/MOJANG_C.DSA");
51-
FileUtil.deleteFileInAZip(buildJar, "/META-INF/MOJANG_C.SF");
52-
FileUtil.deleteFileInAZip(buildJar, "/META-INF/CODESIGN.DSA");
53-
FileUtil.deleteFileInAZip(buildJar, "/META-INF/CODESIGN.SF");
54-
} else {
55-
Files.deleteIfExists(buildZip);
56-
FileUtil.compress(reobfDir, buildZip);
57-
List<Path> assets = FileUtil.walkDirectory(bin, path -> !Files.isDirectory(path) && !path.getFileName().toString().endsWith(".class"));
58-
FileUtil.packFilesToZip(buildZip, assets, bin);
59-
}
60-
}
61-
})
62-
};
46+
return new Stage[]{stage(getLocalizedStage("recompile", 0), () -> new TaskRecompile(side, mcp, this).doTask()), stage(getLocalizedStage("gathermd5", 25), () -> new TaskUpdateMD5(side, mcp, this).updateMD5(true)), stage(getLocalizedStage("reobf"), 50, this::reobfuscate)};
6347
}
6448

6549
@Override
@@ -80,4 +64,187 @@ public void setProgress(int progress) {
8064
break;
8165
}
8266
}
67+
68+
// Reobfuscation utilities
69+
private Mappings getMappings(ClassStorage storage, Side side) throws IOException {
70+
Path mappingsPath = MCPPaths.get(mcp, MAPPINGS);
71+
if (!Files.exists(mappingsPath)) {
72+
return new Mappings();
73+
}
74+
final boolean enableObfuscation = mcp.getOptions().getBooleanParameter(TaskParameter.OBFUSCATION);
75+
boolean joined = MappingUtil.readNamespaces(mappingsPath).contains("official");
76+
Mappings mappings = MappingsIO.read(mappingsPath, "named", joined ? "official" : side.name);
77+
modifyClassMappings(mappings, storage.getAllClasses(), enableObfuscation);
78+
return mappings;
79+
}
80+
81+
private void modifyClassMappings(Mappings mappings, List<String> classNames, boolean obf) {
82+
Map<String, Integer> obfIndexes = new HashMap<>();
83+
Map<String, String> packageMappings = getPackageMappings(mappings.classes);
84+
for (String className : classNames) {
85+
String reobfName = mappings.classes.get(className);
86+
if (reobfName == null /*&& !hashes.containsKey(className)*/) {
87+
int i1 = className.lastIndexOf('/');
88+
String packageName = i1 == -1 ? "" : className.substring(0, i1 + 1);
89+
String obfPackage = packageMappings.get(packageName);
90+
String clsName = i1 == -1 ? className : className.substring(i1 + 1);
91+
if (obf) {
92+
int obfIndex = obfIndexes.getOrDefault(obfPackage, 0);
93+
String obfName = MappingUtil.getObfuscatedName(obfIndex);
94+
List<String> obfNames = new ArrayList<>();
95+
for (Map.Entry<String, String> entry : mappings.classes.entrySet()) {
96+
obfNames.add(entry.getValue());
97+
}
98+
while (obfNames.contains(obfPackage + obfName)) {
99+
obfIndex++;
100+
obfName = MappingUtil.getObfuscatedName(obfIndex);
101+
}
102+
if (obfIndex > obfIndexes.getOrDefault(obfPackage, 0)) {
103+
obfIndexes.put(obfPackage, obfIndex);
104+
}
105+
clsName = obfName;
106+
}
107+
if (obf || obfPackage != null) {
108+
String className2 = (obfPackage == null ? packageName : obfPackage) + clsName;
109+
mappings.classes.put(className, className2);
110+
}
111+
}
112+
}
113+
}
114+
115+
private Map<String, String> gatherMD5Hashes(boolean reobf) throws IOException {
116+
final Path md5 = MCPPaths.get(mcp, reobf ? MCPPaths.MD5_RO : MCPPaths.MD5, side);
117+
Map<String, String> hashes = new HashMap<>();
118+
119+
try (Stream<String> lines = Files.lines(md5)) {
120+
lines.forEach((line) -> {
121+
String[] tokens = line.split(" ");
122+
hashes.put(tokens[0], tokens[1]);
123+
});
124+
}
125+
return hashes;
126+
}
127+
128+
private static Map<String, String> getPackageMappings(Map<String, String> classMappings) {
129+
Map<String, String> packageMappings = new HashMap<>();
130+
for (Map.Entry<String, String> entry : classMappings.entrySet()) {
131+
int i1 = entry.getKey().lastIndexOf('/');
132+
int i2 = entry.getValue().lastIndexOf('/');
133+
String name1 = i1 == -1 ? "" : entry.getKey().substring(0, i1 + 1);
134+
String name2 = i2 == -1 ? "" : entry.getKey().substring(0, i2 + 1);
135+
packageMappings.put(name1, name2);
136+
}
137+
return packageMappings;
138+
}
139+
140+
private void reobfuscate() throws IOException {
141+
final Path reobfBin = MCPPaths.get(mcp, BIN, side);
142+
143+
Side[] sides = side == Side.MERGED ? new Side[]{Side.CLIENT, Side.SERVER} : new Side[]{side};
144+
145+
Map<String, String> originalHashes = gatherMD5Hashes(false);
146+
Map<String, String> recompHashes = gatherMD5Hashes(true);
147+
148+
for (Side localSide : sides) {
149+
final Path reobfJar = MCPPaths.get(mcp, REOBF_JAR, localSide);
150+
Files.deleteIfExists(reobfJar);
151+
RDInjector injector = new RDInjector(reobfBin);
152+
Mappings mappings = getMappings(injector.getStorage(), localSide);
153+
if (mappings != null) {
154+
injector.applyMappings(mappings);
155+
}
156+
injector.transform();
157+
new ClassStorageWriter(injector.getStorage(), ClassWriter.COMPUTE_MAXS).write(Files.newOutputStream(reobfJar));
158+
159+
Map<String, String> reversedNames = new HashMap<>();
160+
if (mappings != null) {
161+
for (Map.Entry<String, String> entry : mappings.classes.entrySet()) {
162+
reversedNames.put(entry.getValue(), entry.getKey());
163+
}
164+
}
165+
Pattern regexPattern = Pattern.compile(mcp.getOptions().getStringParameter(TaskParameter.EXCLUDED_CLASSES));
166+
List<Path> changedFiles = new ArrayList<>();
167+
List<Path> assets = new ArrayList<>(FileUtil.walkDirectory(reobfBin, p -> !Files.isDirectory(p) && !p.toString().endsWith(".class")));
168+
169+
// Identify differences
170+
try (FileSystem fs = FileSystems.newFileSystem(reobfJar, null)) {
171+
Iterable<Path> rootDirectories = fs.getRootDirectories();
172+
for (Path root : rootDirectories) {
173+
try (Stream<Path> stream = Files.walk(root)) {
174+
stream.forEach(path -> {
175+
if (!Files.isDirectory(path)) {
176+
String obfClassName = root.relativize(path).toString().replace(".class", "");
177+
// Force inner classes to compare outer class hash
178+
String className = obfClassName;
179+
int index = className.indexOf('$');
180+
if (index != -1) {
181+
className = className.substring(0, index);
182+
}
183+
String deobfName = reversedNames.get(className);
184+
if (deobfName == null) {
185+
deobfName = className;
186+
}
187+
String hash = originalHashes.get(deobfName);
188+
String hashModified = recompHashes.get(deobfName);
189+
boolean extract = (hash == null) || !hash.equals(hashModified) && !regexPattern.matcher(deobfName).matches();
190+
if (extract) {
191+
changedFiles.add(path);
192+
System.out.println(reversedNames.get(obfClassName) + " : " + obfClassName);
193+
}
194+
}
195+
});
196+
}
197+
}
198+
}
199+
200+
// Export differences to JAR/ZIP file
201+
Path buildJar = MCPPaths.get(mcp, BUILD_JAR, localSide);
202+
Path buildZip = MCPPaths.get(mcp, BUILD_ZIP, localSide);
203+
try {
204+
FileUtil.createDirectories(MCPPaths.get(mcp, BUILD));
205+
// Simply copy re-obfuscated JAR
206+
if (mcp.getOptions().getBooleanParameter(TaskParameter.FULL_BUILD)) {
207+
Files.deleteIfExists(buildJar);
208+
Files.copy(reobfJar, buildJar);
209+
try (FileSystem fs = FileSystems.newFileSystem(buildJar, null)) {
210+
// Copy assets
211+
for (Path asset : assets) {
212+
Files.copy(Files.newInputStream(asset), fs.getPath(reobfBin.relativize(asset).toString()));
213+
}
214+
}
215+
FileUtil.deleteFileInAZip(buildJar, "/META-INF/MOJANG_C.DSA");
216+
FileUtil.deleteFileInAZip(buildJar, "/META-INF/MOJANG_C.SF");
217+
FileUtil.deleteFileInAZip(buildJar, "/META-INF/CODESIGN.DSA");
218+
FileUtil.deleteFileInAZip(buildJar, "/META-INF/CODESIGN.RSA");
219+
FileUtil.deleteFileInAZip(buildJar, "/META-INF/CODESIGN.SF");
220+
FileUtil.deleteFileInAZip(buildJar, "/META-INF/MOJANGCS.RSA");
221+
FileUtil.deleteFileInAZip(buildJar, "/META-INF/MOJANGCS.SF");
222+
} else {
223+
Files.deleteIfExists(buildZip);
224+
225+
Map<String, String> env = new HashMap<>();
226+
env.put("create", "true");
227+
228+
URI buildZipURI = URI.create("jar:file:" + buildZip.toUri().getPath());
229+
230+
try (FileSystem reobfFs = FileSystems.newFileSystem(reobfJar, null)) {
231+
try (FileSystem buildFs = FileSystems.newFileSystem(buildZipURI, env, null)) {
232+
// Copy different files
233+
for (Path changedFile : changedFiles) {
234+
Path file = reobfFs.getPath(changedFile.toString());
235+
Files.copy(file, buildFs.getPath(changedFile.toString()));
236+
}
237+
238+
// Copy assets
239+
for (Path asset : assets) {
240+
Files.copy(Files.newInputStream(asset), buildFs.getPath(reobfBin.relativize(asset).toString()));
241+
}
242+
}
243+
}
244+
}
245+
} catch (IOException ex) {
246+
ex.printStackTrace();
247+
}
248+
}
249+
}
83250
}

0 commit comments

Comments
 (0)