Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,11 @@ idea {
programParameters = "run --dist joined --neoforge net.neoforged:neoforge:21.0.0-beta:userdev --write-result=compiled:build/minecraft.jar --write-result=clientResources:build/client-extra.jar --write-result=sources:build/minecraft-sources.jar"
moduleRef(project, sourceSets.main)
}
"Run Neoforge 1.21 (joined, binpatch)"(Application) {
mainClass = mainClassName
programParameters = "run --binary-pipeline --dist joined --neoforge net.neoforged:neoforge:21.0.0-beta:userdev --write-result=compiled:build/minecraft.jar --write-result=compiledWithNeoForge:build/minecraft-and-neoforge.jar --write-result=clientResources:build/client-extra.jar"
moduleRef(project, sourceSets.main)
}
"Run Neoform 1.21 (joined)"(Application) {
mainClass = mainClassName
programParameters = "run --dist joined --neoform net.neoforged:neoform:1.21-20240613.152323@zip --write-result=compiled:build/minecraft.jar --write-result=clientResources:build/client-extra.jar --write-result=sources:build/minecraft-sources.jar"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
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.HashSet;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

/**
* Binarypatcher only outputs the classes that were patched,
* and this action will copy the unpatched classes into such a jar.
*/
public class CopyUnpatchedClassesAction extends BuiltInAction {
@Override
public void run(ProcessingEnvironment environment) throws IOException, InterruptedException {

var patchedClassesFile = environment.getRequiredInputPath("patched");
var unpatchedClassesFile = environment.getRequiredInputPath("unpatched");
var output = environment.getOutputPath("output");

try (var os = new ZipOutputStream(new BufferedOutputStream(Files.newOutputStream(output)))) {

var patchedNames = new HashSet<String>();
try (var in = new ZipInputStream(new BufferedInputStream(Files.newInputStream(patchedClassesFile)))) {
for (var entry = in.getNextEntry(); entry != null; entry = in.getNextEntry()) {
patchedNames.add(entry.getName());
os.putNextEntry(entry);
in.transferTo(os);
os.closeEntry();
}
}

try (var in = new ZipInputStream(new BufferedInputStream(Files.newInputStream(unpatchedClassesFile)))) {
for (var entry = in.getNextEntry(); entry != null; entry = in.getNextEntry()) {
if (!entry.getName().endsWith(".class")) {
continue; // Only copy .class files
}
if (patchedNames.contains(entry.getName())) {
continue; // Skip classes that were patched
}

os.putNextEntry(entry);
in.transferTo(os);
os.closeEntry();
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package net.neoforged.neoform.runtime.cli;

import net.neoforged.neoform.runtime.actions.ApplySourceTransformAction;
import net.neoforged.neoform.runtime.actions.CopyUnpatchedClassesAction;
import net.neoforged.neoform.runtime.actions.ExternalJavaToolAction;
import net.neoforged.neoform.runtime.actions.InjectFromZipFileSource;
import net.neoforged.neoform.runtime.actions.InjectZipContentAction;
Expand All @@ -9,6 +10,7 @@
import net.neoforged.neoform.runtime.actions.RecompileSourcesAction;
import net.neoforged.neoform.runtime.actions.StripManifestDigestContentFilter;
import net.neoforged.neoform.runtime.artifacts.ClasspathItem;
import net.neoforged.neoform.runtime.config.neoforge.BinpatcherConfig;
import net.neoforged.neoform.runtime.config.neoforge.NeoForgeConfig;
import net.neoforged.neoform.runtime.config.neoform.NeoFormDistConfig;
import net.neoforged.neoform.runtime.engine.DataSource;
Expand All @@ -23,6 +25,7 @@
import net.neoforged.neoform.runtime.utils.FileUtil;
import net.neoforged.neoform.runtime.utils.HashingUtil;
import net.neoforged.neoform.runtime.utils.Logger;
import net.neoforged.neoform.runtime.utils.MavenCoordinate;
import net.neoforged.neoform.runtime.utils.ToolCoordinate;
import picocli.CommandLine;

Expand Down Expand Up @@ -76,6 +79,9 @@ public class RunNeoFormCommand extends NeoFormEngineCommand {
@CommandLine.Option(names = "--parchment-conflict-prefix", description = "Setting this option enables automatic Parchment parameter conflict resolution and uses this prefix for parameter names that clash.")
String parchmentConflictPrefix;

@CommandLine.Option(names = "--binary-pipeline", description = "Use a pipeline based on binary (.class) files and patches only. The standard source results will not be available, but node outputs depending on sources will be.")
boolean binaryPipeline;

static class SourceArtifacts {
@CommandLine.Option(names = "--neoform")
String neoform;
Expand All @@ -101,7 +107,7 @@ protected void runWithNeoFormEngine(NeoFormEngine engine, List<AutoCloseable> cl
neoformArtifact = artifactManager.get(neoforgeConfig.neoformArtifact()).path();
}

engine.loadNeoFormData(neoformArtifact, dist);
engine.loadNeoFormData(neoformArtifact, dist, binaryPipeline);

// Add NeoForge specific data sources
engine.addDataSource("neoForgeAccessTransformers", neoforgeZipFile, neoforgeConfig.accessTransformersFolder());
Expand Down Expand Up @@ -208,14 +214,36 @@ protected void runWithNeoFormEngine(NeoFormEngine engine, List<AutoCloseable> cl

engine.applyTransforms(transforms);

var graph = engine.getGraph();
var sourcesWithNeoForgeOutput = createSourcesWithNeoForge(engine, neoforgeSourcesZip);
var compiledWithNeoForgeOutput = createCompiledWithNeoForge(engine, neoforgeClassesZip);

createSourcesAndCompiledWithNeoForge(engine.getGraph(), compiledWithNeoForgeOutput, sourcesWithNeoForgeOutput);
var sourcesAndCompiledWithNeoForgeOutput =
createSourcesAndCompiledWithNeoForge(graph, compiledWithNeoForgeOutput, sourcesWithNeoForgeOutput);

if (binaryPipeline) {
var renamedOutput = graph.getResult("compiled");

engine.addDataSource("patch", neoforgeZipFile, neoforgeConfig.binaryPatchesFile());

var binaryPatchOnlyOutput = createBinaryPatch(graph, renamedOutput, neoforgeConfig.binaryPatcherConfig());
var binaryPatchOutput = createBinaryWithUnpatched(graph, renamedOutput, binaryPatchOnlyOutput);

// TODO: apply additional ATs and interface injections

graph.setResult("compiled", binaryPatchOutput);

var binaryWithNeoForgeOutput = createBinaryWithNeoForge(graph, binaryPatchOutput, neoforgeClassesZip);
graph.setResult("compiledWithNeoForge", binaryWithNeoForgeOutput);
} else {
graph.setResult("sourcesWithNeoForge", sourcesWithNeoForgeOutput);
graph.setResult("compiledWithNeoForge", compiledWithNeoForgeOutput);
graph.setResult("sourcesAndCompiledWithNeoForge", sourcesAndCompiledWithNeoForgeOutput);
}
} else {
var neoFormDataPath = artifactManager.get(sourceArtifacts.neoform).path();

engine.loadNeoFormData(neoFormDataPath, dist);
engine.loadNeoFormData(neoFormDataPath, dist, binaryPipeline);
}

applyAdditionalAccessTransformers(engine);
Expand Down Expand Up @@ -275,7 +303,6 @@ private static NodeOutput createCompiledWithNeoForge(NeoFormEngine engine, ZipFi

// In older processes, we already had to inject the sources before recompiling (due to remapping)
if (engine.getProcessGeneration().sourcesUseIntermediaryNames()) {
graph.setResult("compiledWithNeoForge", recompiledClasses);
return recompiledClasses;
}

Expand All @@ -288,7 +315,6 @@ private static NodeOutput createCompiledWithNeoForge(NeoFormEngine engine, ZipFi
)));
builder.build();

graph.setResult("compiledWithNeoForge", output);
return output;
}

Expand All @@ -299,9 +325,7 @@ private static NodeOutput createSourcesWithNeoForge(NeoFormEngine engine, ZipFil
if (engine.getProcessGeneration().sourcesUseIntermediaryNames()) {
// 1.20.1 and below use SRG in production and for ATs, so we cannot use the JST output as it is in SRG
// therefore we must output the renamed sources
var remapSrgSourcesToOfficialOutput = graph.getRequiredOutput("remapSrgSourcesToOfficial", "output");
graph.setResult("sourcesWithNeoForge", remapSrgSourcesToOfficialOutput);
return remapSrgSourcesToOfficialOutput;
return graph.getRequiredOutput("remapSrgSourcesToOfficial", "output");
} else {
var transformedSourceOutput = graph.getRequiredOutput("transformSources", "output");

Expand All @@ -312,20 +336,53 @@ private static NodeOutput createSourcesWithNeoForge(NeoFormEngine engine, ZipFil
new InjectFromZipFileSource(neoforgeSourcesZip, "/")
)));
builder.build();
graph.setResult("sourcesWithNeoForge", output);
return output;
}
}

private static void createSourcesAndCompiledWithNeoForge(ExecutionGraph graph, NodeOutput compiledWithNeoForgeOutput, NodeOutput sourcesWithNeoForgeOutput) {
private static NodeOutput createSourcesAndCompiledWithNeoForge(ExecutionGraph graph, NodeOutput compiledWithNeoForgeOutput, NodeOutput sourcesWithNeoForgeOutput) {
// Add a step that merges sources and compiled classes to satisfy IntelliJ
var builder = graph.nodeBuilder("sourcesAndCompiledWithNeoForge");
builder.input("classes", compiledWithNeoForgeOutput.asInput());
builder.input("sources", sourcesWithNeoForgeOutput.asInput());
var output = builder.output("output", NodeOutputType.JAR, "Combined output of sourcesWithNeoForge and compiledWithNeoForge");
builder.action(new MergeWithSourcesAction());
builder.build();
graph.setResult("sourcesAndCompiledWithNeoForge", output);
return output;
}

private static NodeOutput createBinaryPatch(ExecutionGraph graph, NodeOutput clean, BinpatcherConfig config) {
var builder = graph.nodeBuilder("binaryPatch");
builder.input("clean", clean.asInput());
var output = builder.output("output", NodeOutputType.JAR, "JAR containing the patched Minecraft classes");
var action = new ExternalJavaToolAction(MavenCoordinate.parse(config.version()));
action.setArgs(config.args());
builder.action(action);
builder.build();
return output;
}

private static NodeOutput createBinaryWithUnpatched(ExecutionGraph graph, NodeOutput clean, NodeOutput binaryPatched) {
var builder = graph.nodeBuilder("binaryWithUnpatched");
builder.input("patched", binaryPatched.asInput());
builder.input("unpatched", clean.asInput());
var output = builder.output("output", NodeOutputType.JAR, "JAR containing the patched and clean (if not patched) Minecraft classes");
builder.action(new CopyUnpatchedClassesAction());
builder.build();
return output;
}

private static NodeOutput createBinaryWithNeoForge(ExecutionGraph graph, NodeOutput binary, ZipFile neoforgeClassesZip) {
// Add a step that produces a classes-zip containing both Minecraft and NeoForge classes
var builder = graph.nodeBuilder("binaryWithNeoForge");
builder.input("input", binary.asInput());
var output = builder.output("output", NodeOutputType.JAR, "JAR containing NeoForge classes, resources and Minecraft classes");
builder.action(new InjectZipContentAction(List.of(
new InjectFromZipFileSource(neoforgeClassesZip, "/")
)));
builder.build();

return output;
}

private void execute(NeoFormEngine engine) throws InterruptedException, IOException {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package net.neoforged.neoform.runtime.config.neoforge;

import java.util.List;

public record BinpatcherConfig(
String version,
List<String> args) {}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ public record NeoForgeConfig(
int spec,
@SerializedName("mcp") String neoformArtifact,
@SerializedName("ats") String accessTransformersFolder,
@SerializedName("binpatches") String binaryPatchesFile,
@SerializedName("binpatcher") BinpatcherConfig binaryPatcherConfig,
@SerializedName("patches") String patchesFolder,
@SerializedName("sources") String sourcesArtifact,
@SerializedName("universal") String universalArtifact,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ public void addDataSource(String id, ZipFile zipFile, String sourceFolder) {
dataSources.put(id, new DataSource(zipFile, sourceFolder, fileHashService));
}

public void loadNeoFormData(Path neoFormDataPath, String dist) throws IOException {
public void loadNeoFormData(Path neoFormDataPath, String dist, boolean binaryPipeline) throws IOException {
var zipFile = new ZipFile(neoFormDataPath.toFile());
var config = NeoFormConfig.from(zipFile);
var distConfig = config.getDistConfig(dist);
Expand All @@ -170,10 +170,10 @@ public void loadNeoFormData(Path neoFormDataPath, String dist) throws IOExceptio
addDataSource(entry.getKey(), zipFile, entry.getValue());
}

loadNeoFormProcess(distConfig);
loadNeoFormProcess(distConfig, binaryPipeline);
}

public void loadNeoFormProcess(NeoFormDistConfig distConfig) {
public void loadNeoFormProcess(NeoFormDistConfig distConfig, boolean binaryPipeline) {
processGeneration = ProcessGeneration.fromMinecraftVersion(distConfig.minecraftVersion());

for (var step : distConfig.steps()) {
Expand All @@ -193,12 +193,22 @@ public void loadNeoFormProcess(NeoFormDistConfig distConfig) {
// Register the sources and the compiled binary as results
// Vanilla deobfuscated is equivalent to the input to the decompiler
var decompile = graph.getNode("decompile");
NodeOutput vanillaDeobfuscated = null;
if (decompile != null && decompile.inputs().get("input") instanceof NodeInput.NodeInputForOutput nodeInputForOutput) {
graph.setResult("vanillaDeobfuscated", nodeInputForOutput.getOutput());
vanillaDeobfuscated = nodeInputForOutput.getOutput();
graph.setResult("vanillaDeobfuscated", vanillaDeobfuscated);
}
if (binaryPipeline) {
if (vanillaDeobfuscated == null) {
throw new IllegalStateException("Could not find input of \"decompile\" step, could not obtain binary pipeline output.");
}
graph.setResult("compiled", vanillaDeobfuscated);
// Avoid exposing sources, such that they can't be requested by accident in binary mode.
} else {
graph.setResult("sources", sourcesOutput);
graph.setResult("compiled", compiledOutput);
graph.setResult("sourcesAndCompiled", sourcesAndCompiledOutput);
}
graph.setResult("sources", sourcesOutput);
graph.setResult("compiled", compiledOutput);
graph.setResult("sourcesAndCompiled", sourcesAndCompiledOutput);

// The split-off resources must also be made available. The steps are not consistently named across dists
if (graph.hasOutput("stripClient", "resourcesOutput")) {
Expand Down Expand Up @@ -581,8 +591,8 @@ public Map<String, Path> createResults(String... ids) throws InterruptedExceptio
Set<ExecutionNode> 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());
if (verbose) {
LOG.println(AnsiColor.MUTED + "Requested output " + id + " was mapped to " + nodeOutput + AnsiColor.RESET);
}
nodes.add(nodeOutput.getNode());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, NodeOutput> getResults() {
Expand Down