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 0000000..d98707a --- /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/engine/NeoFormEngine.java b/src/main/java/net/neoforged/neoform/runtime/engine/NeoFormEngine.java
index 48762ad..71a00b6 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.CreateMinecraftModJarAction;
import net.neoforged.neoform.runtime.actions.MergeWithSourcesAction;
import net.neoforged.neoform.runtime.actions.PatchActionFactory;
import net.neoforged.neoform.runtime.actions.RecompileSourcesAction;
@@ -202,7 +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);
+
+ // 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"));
@@ -486,6 +497,19 @@ private void createDownloadFromVersionManifest(ExecutionNodeBuilder builder, Str
builder.action(new DownloadFromVersionManifestAction(artifactManager, manifestEntry));
}
+ private void createMinecraftModJar(String minecraftVersion, String withResourcesId, NodeInput classesInput, @Nullable NodeInput resourceInput) {
+ var builder = graph.nodeBuilder(withResourcesId);
+ 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);
+ }
+
private void triggerAndWait(Collection