From 1d81d3f2ca94f353441fc66fd15230a7fae0a215 Mon Sep 17 00:00:00 2001 From: Technici4n <13494793+Technici4n@users.noreply.github.com> Date: Fri, 7 Nov 2025 21:29:35 +0100 Subject: [PATCH 1/3] MC resources in jar --- .../actions/MergeWithResourcesAction.java | 51 +++++++++++++++++++ .../neoform/runtime/engine/NeoFormEngine.java | 20 ++++++-- .../neoform/runtime/graph/ExecutionGraph.java | 2 +- 3 files changed, 69 insertions(+), 4 deletions(-) create mode 100644 src/main/java/net/neoforged/neoform/runtime/actions/MergeWithResourcesAction.java diff --git a/src/main/java/net/neoforged/neoform/runtime/actions/MergeWithResourcesAction.java b/src/main/java/net/neoforged/neoform/runtime/actions/MergeWithResourcesAction.java new file mode 100644 index 00000000..cf83b364 --- /dev/null +++ b/src/main/java/net/neoforged/neoform/runtime/actions/MergeWithResourcesAction.java @@ -0,0 +1,51 @@ +package net.neoforged.neoform.runtime.actions; + +import net.neoforged.neoform.runtime.engine.ProcessingEnvironment; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.util.zip.ZipException; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +// TODO: de-duplicate with MergeWithSourcesAction +/** + * Merges the Java sources with the compiled output into a single combined file to facilitate source + * lookup in IntelliJ. + */ +public class MergeWithResourcesAction extends BuiltInAction { + @Override + public void run(ProcessingEnvironment environment) throws IOException, InterruptedException { + + var classesFile = environment.getRequiredInputPath("classes"); + var resourcesFile = environment.getRequiredInputPath("resources"); + var output = environment.getOutputPath("output"); + + try (var os = new ZipOutputStream(new BufferedOutputStream(Files.newOutputStream(output)))) { + + try (var in = new ZipInputStream(new BufferedInputStream(Files.newInputStream(classesFile)))) { + for (var entry = in.getNextEntry(); entry != null; entry = in.getNextEntry()) { + os.putNextEntry(entry); + in.transferTo(os); + os.closeEntry(); + } + } + + try (var in = new ZipInputStream(new BufferedInputStream(Files.newInputStream(resourcesFile)))) { + for (var entry = in.getNextEntry(); entry != null; entry = in.getNextEntry()) { + try { + os.putNextEntry(entry); + in.transferTo(os); + os.closeEntry(); + } catch (ZipException e) { +// if (!e.getMessage().startsWith("duplicate entry:")) { + throw e; +// } + } + } + } + } + } +} diff --git a/src/main/java/net/neoforged/neoform/runtime/engine/NeoFormEngine.java b/src/main/java/net/neoforged/neoform/runtime/engine/NeoFormEngine.java index b900d219..a0d80d28 100644 --- a/src/main/java/net/neoforged/neoform/runtime/engine/NeoFormEngine.java +++ b/src/main/java/net/neoforged/neoform/runtime/engine/NeoFormEngine.java @@ -8,6 +8,7 @@ import net.neoforged.neoform.runtime.actions.ExternalJavaToolAction; import net.neoforged.neoform.runtime.actions.InjectFromZipFileSource; import net.neoforged.neoform.runtime.actions.InjectZipContentAction; +import net.neoforged.neoform.runtime.actions.MergeWithResourcesAction; import net.neoforged.neoform.runtime.actions.MergeWithSourcesAction; import net.neoforged.neoform.runtime.actions.PatchActionFactory; import net.neoforged.neoform.runtime.actions.RecompileSourcesAction; @@ -199,6 +200,10 @@ public void loadNeoFormProcess(NeoFormDistConfig distConfig) { // The split-off resources must also be made available. The steps are not consistently named across dists if (graph.hasOutput("stripClient", "resourcesOutput")) { graph.setResult("clientResources", graph.getRequiredOutput("stripClient", "resourcesOutput")); + + // TODO: normal "resources" are actually not available? + createResultWithResources("compiledWithResources", "compiled"); + createResultWithResources("sourcesAndCompiledWithResources", "sourcesAndCompiled"); } if (graph.hasOutput("stripServer", "resourcesOutput")) { graph.setResult("serverResources", graph.getRequiredOutput("stripServer", "resourcesOutput")); @@ -479,6 +484,18 @@ private void createDownloadFromVersionManifest(ExecutionNodeBuilder builder, Str builder.action(new DownloadFromVersionManifestAction(artifactManager, manifestEntry)); } + private void createResultWithResources(String withResourcesId, String withoutResourcesId) { + var builder = graph.nodeBuilder(withResourcesId); + builder.input("classes", graph.getResult(withoutResourcesId).asInput()); + // TODO: clientResources??? really?? + builder.input("resources", graph.getResult("clientResources").asInput()); + builder.action(new MergeWithResourcesAction()); + var output = builder.output("output", NodeOutputType.JAR, "Result of " + withoutResourcesId + " with Minecraft resources added into the same jar."); + builder.build(); + + graph.setResult(withResourcesId, output); + } + private void triggerAndWait(Collection nodes) throws InterruptedException { record Pair(ExecutionNode node, CompletableFuture future) { } @@ -574,9 +591,6 @@ public Map createResults(String... ids) throws InterruptedExceptio Set nodes = Collections.newSetFromMap(new IdentityHashMap<>()); for (String id : ids) { var nodeOutput = graph.getResult(id); - if (nodeOutput == null) { - throw new IllegalArgumentException("Unknown result: " + id + ". Available results: " + getAvailableResults()); - } nodes.add(nodeOutput.getNode()); } diff --git a/src/main/java/net/neoforged/neoform/runtime/graph/ExecutionGraph.java b/src/main/java/net/neoforged/neoform/runtime/graph/ExecutionGraph.java index 6383148d..f4018d64 100644 --- a/src/main/java/net/neoforged/neoform/runtime/graph/ExecutionGraph.java +++ b/src/main/java/net/neoforged/neoform/runtime/graph/ExecutionGraph.java @@ -55,7 +55,7 @@ public NodeOutput getResult(String id) { var outputId = matcher.group(2); return getRequiredOutput(step, outputId); } - return null; + throw new IllegalArgumentException("Unknown result: " + id + ". Available results: " + results.keySet()); } public Map getResults() { From d91e93bfd2eebc63ac2f95a8d743473f277ab256 Mon Sep 17 00:00:00 2001 From: Sebastian Hartte Date: Wed, 26 Nov 2025 12:49:14 +0100 Subject: [PATCH 2/3] Change to minecraftModJar / minecraftModJarWithSources Add neoforge.mods.toml --- .../actions/CreateMinecraftModJarAction.java | 90 +++++++++++++++++++ .../actions/MergeWithResourcesAction.java | 51 ----------- .../neoform/runtime/engine/NeoFormEngine.java | 29 +++--- 3 files changed, 108 insertions(+), 62 deletions(-) create mode 100644 src/main/java/net/neoforged/neoform/runtime/actions/CreateMinecraftModJarAction.java delete mode 100644 src/main/java/net/neoforged/neoform/runtime/actions/MergeWithResourcesAction.java diff --git a/src/main/java/net/neoforged/neoform/runtime/actions/CreateMinecraftModJarAction.java b/src/main/java/net/neoforged/neoform/runtime/actions/CreateMinecraftModJarAction.java new file mode 100644 index 00000000..d98707aa --- /dev/null +++ b/src/main/java/net/neoforged/neoform/runtime/actions/CreateMinecraftModJarAction.java @@ -0,0 +1,90 @@ +package net.neoforged.neoform.runtime.actions; + +import net.neoforged.neoform.runtime.engine.ProcessingEnvironment; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +/** + * In older NeoForm processes, the output of the process is not a usable Minecraft mod jar. + *

+ * The old process strips resources early (in the strip/stripClient/stripServer step), and then only deals with + * classes. In the new process, resources are never stripped, and the Jar pre-processor will also add both the + * Side-Manifest entries to the Jar Manifest, and add a neoforge.mods.toml to the Jar. + */ +public class CreateMinecraftModJarAction extends BuiltInAction { + private static final String MOD_MANIFEST_NAME = "META-INF/neoforge.mods.toml"; + + static final long STABLE_TIMESTAMP = 0x386D4380; //01/01/2000 00:00:00 java 8 breaks when using 0. + + private final String minecraftVersion; + + public CreateMinecraftModJarAction(String minecraftVersion) { + this.minecraftVersion = minecraftVersion; + } + + @Override + public void run(ProcessingEnvironment environment) throws IOException, InterruptedException { + + var classesFile = environment.getRequiredInputPath("classes"); + var resourcesFile = environment.getInputPath("resources"); + var output = environment.getOutputPath("output"); + + try (var os = new ZipOutputStream(new BufferedOutputStream(Files.newOutputStream(output)))) { + boolean modManifestCopied = false; + + try (var in = new ZipInputStream(new BufferedInputStream(Files.newInputStream(classesFile)))) { + for (var entry = in.getNextEntry(); entry != null; entry = in.getNextEntry()) { + os.putNextEntry(entry); + in.transferTo(os); + os.closeEntry(); + if (entry.getName().equals(MOD_MANIFEST_NAME)) { + modManifestCopied = true; + } + } + } + + if (resourcesFile != null) { + try (var in = new ZipInputStream(new BufferedInputStream(Files.newInputStream(resourcesFile)))) { + for (var entry = in.getNextEntry(); entry != null; entry = in.getNextEntry()) { + os.putNextEntry(entry); + in.transferTo(os); + os.closeEntry(); + if (entry.getName().equals(MOD_MANIFEST_NAME)) { + modManifestCopied = true; + } + } + } + } + + // If no META-INF/neoforge.mods.toml was copied over, we now inject a new one. + if (!modManifestCopied) { + appendModManifest(os); + } + } + } + + // This is based on installertools. + private void appendModManifest(ZipOutputStream os) throws IOException { + String modManifest = "modLoader=\"minecraft\"\n" + + "license=\"Minecraft EULA\"\n" + + "[[mods]]\n" + + "modId=\"minecraft\"\n" + + "version=\"" + minecraftVersion + "\"\n" + + "displayName=\"Minecraft\"\n" + + "authors=\"Mojang Studios\"\n" + + "description=\"\"\n"; + + var entry = new ZipEntry(MOD_MANIFEST_NAME); + entry.setTime(STABLE_TIMESTAMP); + os.putNextEntry(entry); + os.write(modManifest.getBytes(StandardCharsets.UTF_8)); + os.closeEntry(); + } +} diff --git a/src/main/java/net/neoforged/neoform/runtime/actions/MergeWithResourcesAction.java b/src/main/java/net/neoforged/neoform/runtime/actions/MergeWithResourcesAction.java deleted file mode 100644 index cf83b364..00000000 --- a/src/main/java/net/neoforged/neoform/runtime/actions/MergeWithResourcesAction.java +++ /dev/null @@ -1,51 +0,0 @@ -package net.neoforged.neoform.runtime.actions; - -import net.neoforged.neoform.runtime.engine.ProcessingEnvironment; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.IOException; -import java.nio.file.Files; -import java.util.zip.ZipException; -import java.util.zip.ZipInputStream; -import java.util.zip.ZipOutputStream; - -// TODO: de-duplicate with MergeWithSourcesAction -/** - * Merges the Java sources with the compiled output into a single combined file to facilitate source - * lookup in IntelliJ. - */ -public class MergeWithResourcesAction extends BuiltInAction { - @Override - public void run(ProcessingEnvironment environment) throws IOException, InterruptedException { - - var classesFile = environment.getRequiredInputPath("classes"); - var resourcesFile = environment.getRequiredInputPath("resources"); - var output = environment.getOutputPath("output"); - - try (var os = new ZipOutputStream(new BufferedOutputStream(Files.newOutputStream(output)))) { - - try (var in = new ZipInputStream(new BufferedInputStream(Files.newInputStream(classesFile)))) { - for (var entry = in.getNextEntry(); entry != null; entry = in.getNextEntry()) { - os.putNextEntry(entry); - in.transferTo(os); - os.closeEntry(); - } - } - - try (var in = new ZipInputStream(new BufferedInputStream(Files.newInputStream(resourcesFile)))) { - for (var entry = in.getNextEntry(); entry != null; entry = in.getNextEntry()) { - try { - os.putNextEntry(entry); - in.transferTo(os); - os.closeEntry(); - } catch (ZipException e) { -// if (!e.getMessage().startsWith("duplicate entry:")) { - throw e; -// } - } - } - } - } - } -} diff --git a/src/main/java/net/neoforged/neoform/runtime/engine/NeoFormEngine.java b/src/main/java/net/neoforged/neoform/runtime/engine/NeoFormEngine.java index c5cd2359..cd97bc56 100644 --- a/src/main/java/net/neoforged/neoform/runtime/engine/NeoFormEngine.java +++ b/src/main/java/net/neoforged/neoform/runtime/engine/NeoFormEngine.java @@ -8,7 +8,7 @@ import net.neoforged.neoform.runtime.actions.ExternalJavaToolAction; import net.neoforged.neoform.runtime.actions.InjectFromZipFileSource; import net.neoforged.neoform.runtime.actions.InjectZipContentAction; -import net.neoforged.neoform.runtime.actions.MergeWithResourcesAction; +import net.neoforged.neoform.runtime.actions.CreateMinecraftModJarAction; import net.neoforged.neoform.runtime.actions.MergeWithSourcesAction; import net.neoforged.neoform.runtime.actions.PatchActionFactory; import net.neoforged.neoform.runtime.actions.RecompileSourcesAction; @@ -203,11 +203,17 @@ public void loadNeoFormProcess(NeoFormDistConfig distConfig) { // The split-off resources must also be made available. The steps are not consistently named across dists if (graph.hasOutput("stripClient", "resourcesOutput")) { - graph.setResult("clientResources", graph.getRequiredOutput("stripClient", "resourcesOutput")); + var resourceOutput = graph.getRequiredOutput("stripClient", "resourcesOutput"); + graph.setResult("clientResources", resourceOutput); - // TODO: normal "resources" are actually not available? - createResultWithResources("compiledWithResources", "compiled"); - createResultWithResources("sourcesAndCompiledWithResources", "sourcesAndCompiled"); + // In NeoForm versions that use the old process, we manually created nodes that merge the resources back into the jar, and create the neoforge.mods.toml + createMinecraftModJar(distConfig.minecraftVersion(), "minecraftModJar", compiledOutput.asInput(), resourceOutput.asInput()); + createMinecraftModJar(distConfig.minecraftVersion(), "minecraftModJarWithSources", sourcesAndCompiledOutput.asInput(), resourceOutput.asInput()); + } else { + // In newer versions of the process where resources aren't stripped, we're likely to also have a neoforge.mods.toml already + // Then we just alias the outputs + graph.setResult("minecraftModJar", compiledOutput); + graph.setResult("minecraftModJarWithSources", sourcesAndCompiledOutput); } if (graph.hasOutput("stripServer", "resourcesOutput")) { graph.setResult("serverResources", graph.getRequiredOutput("stripServer", "resourcesOutput")); @@ -491,13 +497,14 @@ private void createDownloadFromVersionManifest(ExecutionNodeBuilder builder, Str builder.action(new DownloadFromVersionManifestAction(artifactManager, manifestEntry)); } - private void createResultWithResources(String withResourcesId, String withoutResourcesId) { + private void createMinecraftModJar(String minecraftVersion, String withResourcesId, NodeInput classesInput, @Nullable NodeInput resourceInput) { var builder = graph.nodeBuilder(withResourcesId); - builder.input("classes", graph.getResult(withoutResourcesId).asInput()); - // TODO: clientResources??? really?? - builder.input("resources", graph.getResult("clientResources").asInput()); - builder.action(new MergeWithResourcesAction()); - var output = builder.output("output", NodeOutputType.JAR, "Result of " + withoutResourcesId + " with Minecraft resources added into the same jar."); + builder.input("classes", classesInput); + if (resourceInput != null) { + builder.input("resources", resourceInput); + } + builder.action(new CreateMinecraftModJarAction(minecraftVersion)); + var output = builder.output("output", NodeOutputType.JAR, "Result of " + classesInput.getId() + " with Minecraft resources added into the same jar."); builder.build(); graph.setResult(withResourcesId, output); From c36d687c46d0c82a27648169accc22105b2ce88a Mon Sep 17 00:00:00 2001 From: Sebastian Hartte Date: Wed, 26 Nov 2025 12:54:53 +0100 Subject: [PATCH 3/3] Revert accidental (?) change --- .../net/neoforged/neoform/runtime/engine/NeoFormEngine.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/net/neoforged/neoform/runtime/engine/NeoFormEngine.java b/src/main/java/net/neoforged/neoform/runtime/engine/NeoFormEngine.java index cd97bc56..71a00b62 100644 --- a/src/main/java/net/neoforged/neoform/runtime/engine/NeoFormEngine.java +++ b/src/main/java/net/neoforged/neoform/runtime/engine/NeoFormEngine.java @@ -605,6 +605,9 @@ public Map createResults(String... ids) throws InterruptedExceptio Set nodes = Collections.newSetFromMap(new IdentityHashMap<>()); for (String id : ids) { var nodeOutput = graph.getResult(id); + if (nodeOutput == null) { + throw new IllegalArgumentException("Unknown result: " + id + ". Available results: " + getAvailableResults()); + } nodes.add(nodeOutput.getNode()); }