From 8747708ebe50aefe1fdd1445f27fec520fd1753d Mon Sep 17 00:00:00 2001 From: sqersters <109853788+bouclem@users.noreply.github.com> Date: Tue, 26 May 2026 13:50:07 +0200 Subject: [PATCH] fix: don't include untouched files in build output Fixes #237. Two bugs caused unmodified files to leak into the build: TaskReobfuscate's extract filter looked up the obfuscated outer-class name in the deobfuscated-by-obf map, which always missed for inner classes. The hash lookup then returned null and forced extract=true, so unchanged inner classes were always reobfuscated and bundled. Resolve the deobfuscated name first, then strip the inner-class suffix for the hash key. TaskBuild packed every non-class file under bin/ into the build zip without consulting the recorded hashes. Now compares each asset against the original MD5 file and only includes new or modified ones, matching the behavior already used for classes. --- .../org/mcphackers/mcp/tasks/TaskBuild.java | 48 ++++++++++++++++++- .../mcphackers/mcp/tasks/TaskReobfuscate.java | 23 +++++---- 2 files changed, 61 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/mcphackers/mcp/tasks/TaskBuild.java b/src/main/java/org/mcphackers/mcp/tasks/TaskBuild.java index 78a10a6f..654bc187 100644 --- a/src/main/java/org/mcphackers/mcp/tasks/TaskBuild.java +++ b/src/main/java/org/mcphackers/mcp/tasks/TaskBuild.java @@ -2,14 +2,19 @@ import static org.mcphackers.mcp.MCPPaths.*; +import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.stream.Stream; import org.mcphackers.mcp.MCP; import org.mcphackers.mcp.MCPPaths; import org.mcphackers.mcp.tasks.mode.TaskParameter; import org.mcphackers.mcp.tools.FileUtil; +import org.mcphackers.mcp.tools.Util; public class TaskBuild extends TaskStaged { /* @@ -40,12 +45,12 @@ protected Stage[] setStages() { Path buildJar = MCPPaths.get(mcp, BUILD_JAR, localSide); Path buildZip = MCPPaths.get(mcp, BUILD_ZIP, localSide); FileUtil.createDirectories(MCPPaths.get(mcp, BUILD)); + List assets = collectChangedAssets(bin); if (mcp.getOptions().getBooleanParameter(TaskParameter.FULL_BUILD)) { Files.deleteIfExists(buildJar); Files.copy(originalJar, buildJar); List reobfClasses = FileUtil.walkDirectory(reobfDir, path -> !Files.isDirectory(path)); FileUtil.packFilesToZip(buildJar, reobfClasses, reobfDir); - List assets = FileUtil.walkDirectory(bin, path -> !Files.isDirectory(path) && !path.getFileName().toString().endsWith(".class")); FileUtil.packFilesToZip(buildJar, assets, bin); FileUtil.deleteFileInAZip(buildJar, "/META-INF/MOJANG_C.DSA"); FileUtil.deleteFileInAZip(buildJar, "/META-INF/MOJANG_C.SF"); @@ -54,7 +59,6 @@ protected Stage[] setStages() { } else { Files.deleteIfExists(buildZip); FileUtil.compress(reobfDir, buildZip); - List assets = FileUtil.walkDirectory(bin, path -> !Files.isDirectory(path) && !path.getFileName().toString().endsWith(".class")); FileUtil.packFilesToZip(buildZip, assets, bin); } } @@ -62,6 +66,46 @@ protected Stage[] setStages() { }; } + /** + * Collects only assets (non-class resources) whose contents differ from the + * hash recorded by the last MD5 update, or that have no recorded hash at all. + * Avoids packaging untouched resources into the build output. + */ + private List collectChangedAssets(Path bin) throws IOException { + final Map originalHashes = readMD5Hashes(MCPPaths.get(mcp, MD5, side)); + return FileUtil.walkDirectory(bin, path -> { + if (Files.isDirectory(path) || path.getFileName().toString().endsWith(".class")) { + return false; + } + String key = bin.relativize(path).toString().replace("\\", "/"); + String original = originalHashes.get(key); + if (original == null) { + return true; + } + try { + return !original.equals(Util.getMD5(path)); + } catch (IOException e) { + return true; + } + }); + } + + private static Map readMD5Hashes(Path md5File) throws IOException { + Map hashes = new HashMap<>(); + if (!Files.exists(md5File)) { + return hashes; + } + try (Stream lines = Files.lines(md5File)) { + lines.forEach(line -> { + String[] tokens = line.split(" "); + if (tokens.length == 2) { + hashes.put(tokens[0], tokens[1]); + } + }); + } + return hashes; + } + @Override public void setProgress(int progress) { switch (step) { diff --git a/src/main/java/org/mcphackers/mcp/tasks/TaskReobfuscate.java b/src/main/java/org/mcphackers/mcp/tasks/TaskReobfuscate.java index 069cabf7..b7cd9566 100644 --- a/src/main/java/org/mcphackers/mcp/tasks/TaskReobfuscate.java +++ b/src/main/java/org/mcphackers/mcp/tasks/TaskReobfuscate.java @@ -101,15 +101,22 @@ private void reobfuscate() throws IOException { return false; } String obfClassName = entry.getName().replace(".class", ""); - // Force inner classes to compare outer class hash - String className = obfClassName; - int index = className.indexOf('$'); - if (index != -1) { - className = className.substring(0, index); - } - String deobfName = reversedNames.get(className); + // Try to resolve the deobfuscated name. If the inner-class entry + // has no direct mapping, fall back to the outer class. + String deobfName = reversedNames.get(obfClassName); if (deobfName == null) { - deobfName = className; + int dollar = obfClassName.indexOf('$'); + if (dollar != -1) { + deobfName = reversedNames.get(obfClassName.substring(0, dollar)); + } + if (deobfName == null) { + deobfName = obfClassName; + } + } + // MD5 hashes are recorded per outer class, so strip any inner-class suffix. + int innerIdx = deobfName.indexOf('$'); + if (innerIdx != -1) { + deobfName = deobfName.substring(0, innerIdx); } String hash = originalHashes.get(deobfName); String hashModified = recompHashes.get(deobfName);