diff --git a/src/main/java/net/fabricmc/loom/build/nesting/IncludedJarFactory.java b/src/main/java/net/fabricmc/loom/build/nesting/IncludedJarFactory.java index 4447d7d3..925f5a7c 100644 --- a/src/main/java/net/fabricmc/loom/build/nesting/IncludedJarFactory.java +++ b/src/main/java/net/fabricmc/loom/build/nesting/IncludedJarFactory.java @@ -53,7 +53,7 @@ import org.jetbrains.annotations.Nullable; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.LoomGradlePlugin; import net.fabricmc.loom.task.RemapTaskConfiguration; -import net.fabricmc.loom.util.ZipUtils; +import net.fabricmc.loom.util.ZipReprocessorUtil; import net.fabricmc.loom.util.fmj.FabricModJsonFactory; public final class IncludedJarFactory { @@ -163,7 +163,7 @@ public final class IncludedJarFactory { try { FileUtils.copyFile(input, tempFile); - ZipUtils.add(tempFile.toPath(), "fabric.mod.json", generateModForDependency(metadata).getBytes(StandardCharsets.UTF_8)); + ZipReprocessorUtil.appendZipEntry(tempFile, "fabric.mod.json", generateModForDependency(metadata).getBytes(StandardCharsets.UTF_8)); } catch (IOException e) { throw new UncheckedIOException("Failed to add dummy mod while including %s".formatted(input), e); } diff --git a/src/main/java/net/fabricmc/loom/util/ZipReprocessorUtil.java b/src/main/java/net/fabricmc/loom/util/ZipReprocessorUtil.java index 79f0c872..90d486bc 100644 --- a/src/main/java/net/fabricmc/loom/util/ZipReprocessorUtil.java +++ b/src/main/java/net/fabricmc/loom/util/ZipReprocessorUtil.java @@ -95,44 +95,80 @@ public class ZipReprocessorUtil { return; } - try (ZipFile zipFile = new ZipFile(file)) { + try (var zipFile = new ZipFile(file)) { ZipEntry[] entries; if (reproducibleFileOrder) { - entries = zipFile.stream().sorted(Comparator.comparing(ZipEntry::getName, ZipReprocessorUtil::specialOrdering)).toArray(ZipEntry[]::new); + entries = zipFile.stream() + .sorted(Comparator.comparing(ZipEntry::getName, ZipReprocessorUtil::specialOrdering)) + .toArray(ZipEntry[]::new); } else { - entries = zipFile.stream().toArray(ZipEntry[]::new); + entries = zipFile.stream() + .toArray(ZipEntry[]::new); } - ByteArrayOutputStream outZip = new ByteArrayOutputStream(zipFile.size()); + final var outZip = new ByteArrayOutputStream(entries.length); - try (ZipOutputStream zipOutputStream = new ZipOutputStream(outZip)) { + try (var zipOutputStream = new ZipOutputStream(outZip)) { for (ZipEntry entry : entries) { ZipEntry newEntry = entry; if (!preserveFileTimestamps) { newEntry = new ZipEntry(entry.getName()); - newEntry.setTime(CONSTANT_TIME_FOR_ZIP_ENTRIES); - newEntry.setLastModifiedTime(FileTime.fromMillis(CONSTANT_TIME_FOR_ZIP_ENTRIES)); - newEntry.setLastAccessTime(FileTime.fromMillis(CONSTANT_TIME_FOR_ZIP_ENTRIES)); + setConstantFileTime(newEntry); } - zipOutputStream.putNextEntry(newEntry); - InputStream inputStream = zipFile.getInputStream(entry); - byte[] buf = new byte[1024]; - int length; - - while ((length = inputStream.read(buf)) > 0) { - zipOutputStream.write(buf, 0, length); - } - - zipOutputStream.closeEntry(); + copyZipEntry(zipOutputStream, newEntry, zipFile.getInputStream(entry)); } } - try (FileOutputStream fileOutputStream = new FileOutputStream(file)) { + try (var fileOutputStream = new FileOutputStream(file)) { outZip.writeTo(fileOutputStream); } } } + + public static void appendZipEntry(File file, String path, byte[] data) throws IOException { + try (var zipFile = new ZipFile(file)) { + ZipEntry[] entries = zipFile.stream().toArray(ZipEntry[]::new); + + final var outZip = new ByteArrayOutputStream(entries.length); + + try (var zipOutputStream = new ZipOutputStream(outZip)) { + // Copy existing entries + for (ZipEntry entry : entries) { + copyZipEntry(zipOutputStream, entry, zipFile.getInputStream(entry)); + } + + // Append the new entry + var entry = new ZipEntry(path); + setConstantFileTime(entry); + zipOutputStream.putNextEntry(entry); + zipOutputStream.write(data, 0, data.length); + zipOutputStream.closeEntry(); + } + + try (var fileOutputStream = new FileOutputStream(file)) { + outZip.writeTo(fileOutputStream); + } + } + } + + private static void copyZipEntry(ZipOutputStream zipOutputStream, ZipEntry entry, InputStream inputStream) throws IOException { + zipOutputStream.putNextEntry(entry); + byte[] buf = new byte[1024]; + int length; + + while ((length = inputStream.read(buf)) > 0) { + zipOutputStream.write(buf, 0, length); + } + + zipOutputStream.closeEntry(); + } + + private static void setConstantFileTime(ZipEntry entry) { + entry.setTime(ZipReprocessorUtil.CONSTANT_TIME_FOR_ZIP_ENTRIES); + entry.setLastModifiedTime(FileTime.fromMillis(ZipReprocessorUtil.CONSTANT_TIME_FOR_ZIP_ENTRIES)); + entry.setLastAccessTime(FileTime.fromMillis(ZipReprocessorUtil.CONSTANT_TIME_FOR_ZIP_ENTRIES)); + } } diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/ZipUtilsTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/ZipUtilsTest.groovy index 1c73bed0..95c46916 100644 --- a/src/test/groovy/net/fabricmc/loom/test/unit/ZipUtilsTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/unit/ZipUtilsTest.groovy @@ -29,7 +29,9 @@ import java.nio.file.Files import spock.lang.Specification +import net.fabricmc.loom.util.Checksum import net.fabricmc.loom.util.Pair +import net.fabricmc.loom.util.ZipReprocessorUtil import net.fabricmc.loom.util.ZipUtils class ZipUtilsTest extends Specification { @@ -150,4 +152,24 @@ class ZipUtilsTest extends Specification { then: !result } + + def "append zip entry"() { + given: + // Create a reproducible input zip + def dir = Files.createTempDirectory("loom-zip-test") + def zip = Files.createTempFile("loom-zip-test", ".zip") + def fileInside = dir.resolve("text.txt") + Files.writeString(fileInside, "hello world") + ZipUtils.pack(dir, zip) + ZipReprocessorUtil.reprocessZip(zip.toFile(), true, false) + + when: + // Add an entry to it + ZipReprocessorUtil.appendZipEntry(zip.toFile(), "fabric.mod.json", "Some text".getBytes(StandardCharsets.UTF_8)) + + then: + ZipUtils.unpack(zip, "text.txt") == "hello world".bytes + ZipUtils.unpack(zip, "fabric.mod.json") == "Some text".bytes + Checksum.sha1Hex(zip) == "232ecda4c770bde8ba618e7a194a4f7b57928dc5" + } }