From 0b67968e5fe2de61f70bfb1666d008f9175bc0d6 Mon Sep 17 00:00:00 2001 From: LexManos Date: Thu, 7 May 2026 18:27:38 -0700 Subject: [PATCH 01/12] Short circuit local files in MavenCache --- .../mcmaven/impl/cache/MavenCache.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/cache/MavenCache.java b/src/main/java/net/minecraftforge/mcmaven/impl/cache/MavenCache.java index 2021731..252b8e7 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/cache/MavenCache.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/cache/MavenCache.java @@ -23,6 +23,8 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -41,6 +43,7 @@ public sealed class MavenCache permits MinecraftMavenCache { private final File cache; private final String repo; private final List foreignRepositories; + private final boolean isFileRepo; /** * Initializes a new maven cache with the given name, repository, and cache directory. @@ -72,6 +75,7 @@ public MavenCache(String name, String repo, File root, Map forei this.foreignRepositories.add(new MavenCache(n, r, root)); } this.knownHashes = knownHashes; + this.isFileRepo = this.repo.startsWith("file:"); } public final File getFolder() { @@ -176,6 +180,19 @@ public final File downloadVersionMeta(Artifact artifact) { protected File download(boolean changing, String path) throws IOException { var target = new File(cache, path); + // if we're a local file, let short circuit and just return that file + if (this.isFileRepo) { + try { + var uri = new URI(this.repo + path); + var file = new File(uri.getPath()); + if (file.exists()) + return file; + } catch (URISyntaxException e) { + throw new IOException(e); + } + } + + if (target.exists()) { boolean invalidHash = false; From 2ccdad646fafd80acb321151cc5821e845f3ad4d Mon Sep 17 00:00:00 2001 From: LexManos Date: Thu, 7 May 2026 18:27:38 -0700 Subject: [PATCH 02/12] Add support for FG2.x artifacts Forge 1.8->1.12.2 Add 'srg' mapping channel Refactor mappings to support non-mcpconfig based versions Add support for version ranges in MavenTask Add default mappings for ranged processes --- build.gradle | 1 + settings.gradle | 2 +- .../minecraftforge/mcmaven/cli/MCPTask.java | 26 +- .../mcmaven/impl/MinecraftMaven.java | 106 +- .../mcmaven/impl/cache/JDKCache.java | 8 + .../mcmaven/impl/mappings/MCPMappings.java | 76 +- .../mcmaven/impl/mappings/Mappings.java | 284 +---- .../impl/mappings/OfficialMappings.java | 161 +++ .../impl/mappings/ParchmentMappings.java | 36 +- .../impl/mappings/ResolvedMappings.java | 172 +++ .../mcmaven/impl/mappings/SRGMappings.java | 103 ++ .../mcmaven/impl/repo/Repo.java | 68 +- .../mcmaven/impl/repo/forge/FG2Userdev.java | 653 +++++++++++ .../mcmaven/impl/repo/forge/FGVersion.java | 18 +- .../mcmaven/impl/repo/forge/ForgeRepo.java | 269 +++-- .../impl/repo/forge/ForgeVersionCommon.java | 36 + .../mcmaven/impl/repo/forge/InjectTask.java | 6 +- .../mcmaven/impl/repo/forge/Patcher.java | 88 +- .../impl/repo/mcpconfig/MCPConfigRepo.java | 48 +- .../impl/repo/mcpconfig/MCPLegacy.java | 1033 +++++++++++++++++ .../impl/repo/mcpconfig/MCPTaskFactory.java | 71 +- .../impl/repo/mcpconfig/MinecraftTasks.java | 8 + .../mcmaven/impl/tasks/MCPNames.java | 4 +- .../mcmaven/impl/tasks/RecompileTask.java | 18 +- .../mcmaven/impl/tasks/RenameTask.java | 18 +- .../mcmaven/impl/util/Constants.java | 28 +- .../mcmaven/impl/util/ContextualPatch.java | 680 +++++++++++ .../mcmaven/impl/util/NewLineDetector.java | 80 ++ .../mcmaven/impl/util/ProcessUtils.java | 2 +- .../mcmaven/impl/util/StupidHacks.java | 192 ++- .../mcmaven/impl/util/Util.java | 36 + 31 files changed, 3716 insertions(+), 615 deletions(-) create mode 100644 src/main/java/net/minecraftforge/mcmaven/impl/mappings/OfficialMappings.java create mode 100644 src/main/java/net/minecraftforge/mcmaven/impl/mappings/ResolvedMappings.java create mode 100644 src/main/java/net/minecraftforge/mcmaven/impl/mappings/SRGMappings.java create mode 100644 src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/FG2Userdev.java create mode 100644 src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/ForgeVersionCommon.java create mode 100644 src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MCPLegacy.java create mode 100644 src/main/java/net/minecraftforge/mcmaven/impl/util/ContextualPatch.java create mode 100644 src/main/java/net/minecraftforge/mcmaven/impl/util/NewLineDetector.java diff --git a/build.gradle b/build.gradle index d70fa50..439c211 100644 --- a/build.gradle +++ b/build.gradle @@ -50,6 +50,7 @@ license { header = rootProject.file('LICENSE-header.txt') newLine false exclude '**/ComparableVersion.java' // Apache class used as source-level dep as to not need the hundred+ other classes the library pulls in + exclude '**/ContextualPatch.java' // diff4j single file library } application { diff --git a/settings.gradle b/settings.gradle index 7a21cae..c7b081d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -41,7 +41,7 @@ dependencyResolutionManagement.versionCatalogs.register('libs') { library 'utils-download', 'net.minecraftforge', 'download-utils' version '0.4.0' library 'utils-files', 'net.minecraftforge', 'file-utils' version '0.3.3' library 'utils-hash', 'net.minecraftforge', 'hash-utils' version '0.2.2' - library 'utils-data', 'net.minecraftforge', 'json-data-utils' version '0.4.7' + library 'utils-data', 'net.minecraftforge', 'json-data-utils' version '0.4.9' library 'utils-logging', 'net.minecraftforge', 'log-utils' version '0.5.1' library 'utils-os', 'net.minecraftforge', 'os-utils' version '0.1.0' bundle 'utils', ['utils-download', 'utils-files', 'utils-hash', 'utils-data', 'utils-logging', 'utils-os'] diff --git a/src/main/java/net/minecraftforge/mcmaven/cli/MCPTask.java b/src/main/java/net/minecraftforge/mcmaven/cli/MCPTask.java index b796fd9..6647511 100644 --- a/src/main/java/net/minecraftforge/mcmaven/cli/MCPTask.java +++ b/src/main/java/net/minecraftforge/mcmaven/cli/MCPTask.java @@ -21,6 +21,7 @@ import net.minecraftforge.mcmaven.impl.MinecraftMaven; import net.minecraftforge.mcmaven.impl.cache.Cache; import net.minecraftforge.mcmaven.impl.mappings.Mappings; +import net.minecraftforge.mcmaven.impl.mappings.ResolvedMappings; import net.minecraftforge.mcmaven.impl.repo.forge.Patcher; import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCP; import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCPConfigRepo; @@ -197,12 +198,12 @@ static OptionParser run(String[] args, boolean getParser) throws Exception { Util.filter(LOGGER, " SAS: ", sas); LOGGER.info(); - var task = new MCPTask(outputDir, new Cache(cacheRoot, jdkCacheRoot), artifact, pipeline); + var task = new MCPTask(outputDir, new Cache(cacheRoot, jdkCacheRoot), artifact, pipeline, mappings); var ret = task.classes(); if(mappings != null) - task.mappings(mappings); + task.mappings(); if (!disableDecompile) - ret = task.decompile(mappings, ats, sas); + ret = task.decompile(ats, sas); task.data.put("output", ret); try { @@ -222,13 +223,15 @@ static OptionParser run(String[] args, boolean getParser) throws Exception { private final MCP mcp; private final MCPSide side; private final String prefix; + private final ResolvedMappings mappings; - private MCPTask(@Nullable File outputDir, Cache cache, Artifact artifact, String pipeline) { + private MCPTask(@Nullable File outputDir, Cache cache, Artifact artifact, String pipeline, @Nullable Mappings baseMappings) { this.outputDir = outputDir; this.repo = new MCPConfigRepo(cache, false); this.mcp = repo.get(artifact); this.side = mcp.getSide(pipeline); + this.mappings = baseMappings == null ? null : baseMappings.withContext(side); this.data.put("config", artifact.toString()); this.data.put("pipeline", pipeline); @@ -251,15 +254,15 @@ private String classes() { return this.data.get("classes.srg"); } - private void mappings(@Nullable Mappings mappings) { + private void mappings() { this.data.put("mappings.channel", mappings.channel()); this.data.put("mappings.version", mappings.version()); - this.data.put("mappings.zip", local(mappings.getCsvZip(side).execute(), prefix + "/mappings.zip")); - this.data.put("mappings.map2obf", local(mappings.getMapped2Obf(side).execute(), prefix + "/mappings.map2obf.tsrg.gz")); - this.data.put("mappings.map2srg", local(mappings.getMapped2Srg(side).execute(), prefix + "/mappings.map2srg.tsrg.gz")); + this.data.put("mappings.zip", local(mappings.getCsvZip().execute(), prefix + "/mappings.zip")); + this.data.put("mappings.map2obf", local(mappings.getMapped2Obf().execute(), prefix + "/mappings.map2obf.tsrg.gz")); + this.data.put("mappings.map2srg", local(mappings.getMapped2Srg().execute(), prefix + "/mappings.map2srg.tsrg.gz")); } - private String decompile(@Nullable Mappings mappings, List ats, List sas) { + private String decompile(List ats, List sas) { var sourcesTask = side.getSources(); if (!ats.isEmpty() || !sas.isEmpty()) { @@ -299,7 +302,10 @@ private String decompile(@Nullable Mappings mappings, List ats, List LOGGER.info("Renaming MCP Source Jar"); indent = LOGGER.push(); try { - var renameTask = new RenameTask(side.getBuildFolder(), side.getName(), side, sourcesTask, mappings, false); + var mcVersion = side.getMCP().getMinecraftTasks().getVersion(); + var srgTask = side.getTasks().getMappings(); + + var renameTask = new RenameTask(side.getBuildFolder(), side.getName(), sourcesTask, mappings, false, srgTask, mcVersion); this.data.put("sources.named", local(renameTask.execute(), prefix + "/sources.named.jar")); } finally { LOGGER.pop(indent); diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/MinecraftMaven.java b/src/main/java/net/minecraftforge/mcmaven/impl/MinecraftMaven.java index d825364..5c67560 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/MinecraftMaven.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/MinecraftMaven.java @@ -19,6 +19,7 @@ import net.minecraftforge.mcmaven.impl.util.Constants; import net.minecraftforge.mcmaven.impl.util.POMBuilder; import net.minecraftforge.mcmaven.impl.util.ProcessUtils; +import net.minecraftforge.mcmaven.impl.util.StupidHacks; import net.minecraftforge.mcmaven.impl.util.Task; import net.minecraftforge.mcmaven.impl.util.Util; import net.minecraftforge.srgutils.MinecraftVersion; @@ -79,7 +80,7 @@ public record MinecraftMaven( ) { // Only 1.14.4+ has official mappings, we can support more when we add more mappings private static final MinecraftVersion MIN_OFFICIAL_MAPPINGS = MinecraftVersion.from("1.14.4"); - private static final ComparableVersion MIN_SUPPORTED_FORGE = new ComparableVersion("1.14.4"); + private static final ComparableVersion MIN_SUPPORTED_FORGE = new ComparableVersion("1.8"); public MinecraftMaven { LOGGER.info(" Output: " + output.getAbsolutePath()); @@ -150,28 +151,83 @@ protected void createForge(Artifact artifact, MCPConfigRepo mcprepo, ForgeRepo r if (version == null) throw new IllegalArgumentException("No version specified for Forge"); + ComparableVersion verStart = null; + ComparableVersion verEnd = null; + if (version.charAt(0) == '[' && version.charAt(version.length()-1) == ']') { + var pts = version.split(","); + if (pts[0].length() > 1) + verStart = new ComparableVersion(pts[0].substring(1)); + if (pts[1].length() > 1) + verEnd = new ComparableVersion(pts[1].substring(0, pts[1].length() - 1)); + version = "all"; + } + if ("all".equals(version)) { if (outputJson != null) throw new IllegalArgumentException("Output Json does not support bulk operations"); var versions = this.cache.maven().getVersions(artifact); - var mappingCache = new HashMap(); - for (var ver : versions.reversed()) { - var cver = new ComparableVersion(ver); + var cversions = new ArrayList(versions.size()); + for (var ver : versions) { + cversions.add(new ComparableVersion(ver)); + } + Collections.sort(cversions); + + var mappings = this.mappings; + //var lastMCVersion = ""; + for (var cver : cversions) { if (cver.compareTo(MIN_SUPPORTED_FORGE) < 0) continue; + if (StupidHacks.BLACKLISTED_FORGE_BUILDS.contains(cver.toString())) + continue; + + var ver = cver.toString(); + var mcVersion = Util.forgeToMcVersion(ver); + var fg = FGVersion.fromForge(ver); - if (fg == null || fg.ordinal() < FGVersion.v3.ordinal()) // Unsupported + if (fg == null || fg.ordinal() < FGVersion.v2.ordinal()) // Unsupported continue; - var mappings = mappingCache.computeIfAbsent(forgeToMcVersion(ver), this.mappings::withMCVersion); + if (fg.ordinal() >= FGVersion.v3.ordinal()) + continue; + + if (verStart != null && cver.compareTo(verStart) < 0) + continue; + if (verEnd != null && cver.compareTo(verEnd) > 0) + continue; + + /* + if (mcVersion.equals(lastMCVersion)) + continue; + lastMCVersion = mcVersion; + */ + + // Old versions don't have official mappings, and some of them require specific MCP mappigns due to the sources not being in + // full SRG names (we don't remap static imports) So if we're using official mappings (the default arugment) lets use the + // specific bot mappings that the version of Forge was built for. Any other option is at user's pearly and may not work. + if (this.mappings.channel().equals("official")) { + var newMappings = StupidHacks.getDefaultMappings(cver).withMCVersion(mcVersion); + if (!newMappings.channel().equals(mappings.channel()) || !newMappings.version().equals(mappings.version())) { + mappings = newMappings; + LOGGER.info("Using Mappings: " + mappings); + } + } + var art = artifact.withVersion(ver); var artifacts = repo.process(art, mappings, outputJson); - finalize(art, mappings, artifacts); + LOGGER.push(); + try { + finalize(art, mappings, artifacts); + } finally { + LOGGER.pop(); + } } } else { - var mappings = this.mappings.withMCVersion(forgeToMcVersion(version)); + if (StupidHacks.BLACKLISTED_FORGE_BUILDS.contains(artifact.getVersion())) + throw new IllegalArgumentException("Forge version " + artifact.getVersion() + " has been blacklisted for technical reasons, pick a different Forge version"); + + var mappings = this.mappings.withMCVersion(Util.forgeToMcVersion(version)); var artifacts = repo.process(artifact, mappings, outputJson); finalize(artifact, mappings, artifacts); } @@ -205,10 +261,11 @@ protected void createMinecraft(Artifact artifact, MCPConfigRepo mcprepo, Map artifacts = null; + var mappings = this.mappings.withMCVersion(ver.id); if (hasMcp(mcprepo, ver.id)) - artifacts = mcprepo.process(versioned, mappings.withMCVersion(ver.id), outputJson); + artifacts = mcprepo.process(versioned, mappings, outputJson); else if (mappings.channel().equals("official") && (hasOfficialMappings(mcprepo, ver.id) || !MCPConfigRepo.isObfuscated(ver.id))) - artifacts = mcprepo.processWithoutMcp(versioned, mappings.withMCVersion(ver.id), outputJson); + artifacts = mcprepo.processWithoutMcp(versioned, mappings, outputJson); else { LOGGER.info("Skipping " + versioned + " no mcp config"); continue; @@ -250,17 +307,6 @@ private static boolean hasOfficialMappings(MCPConfigRepo repo, String version) { json.getDownload(MinecraftTasks.MCFile.SERVER_MAPPINGS.key) != null; } - private static String forgeToMcVersion(String version) { - // Save for a few april-fools versions, Minecraft doesn't use _ in their version names. - // So when Forge needs to reference a version of Minecraft that uses - in the name, it replaces - // it with _ - // This could cause issues if we ever support a version with _ in it, but fuck it I don't care right now. - int idx = version.indexOf('-'); - if (idx == -1) - throw new IllegalArgumentException("Invalid Forge version: " + version); - return version.substring(0, idx).replace('_', '-'); - } - public static String mcpToMcVersion(String version) { // MCP names can either be {MCVersion} or {MCVersion}-{Timestamp}, EXA: 1.21.1-20240808.132146 // So lets see if the thing following the last - matches a timestamp @@ -477,7 +523,7 @@ private File facade(Task inputTask, Artifact artifact) { args.add(file.getAbsolutePath()); } - execute("facade ", Constants.FACAD_JAVA_VERSION, tool, args, target); + execute("facade ", Constants.FACADE_JAVA_VERSION, tool, args, target); cache.save(); return target; } @@ -495,25 +541,23 @@ private void execute(String name, int javaVersion, File tool, List args, if (parent != null && !parent.exists()) parent.mkdirs(); - var log = new File(target.getAbsolutePath() + ".log"); var ret = ProcessUtils.runJar(jdk, parent, log, tool, Collections.emptyList(), args); if (ret.exitCode != 0) throw new IllegalStateException("Failed to " + name + " file (exit code " + ret.exitCode + "), See log: " + log.getAbsolutePath()); - } private void updateVariants(Artifact artifact) { var root = new File(this.output, artifact.getFolder()); var inputs = new ArrayList(); for (var file : root.listFiles()) { - if (file.isFile() && file.getName().endsWith(".variants")) { + if (file.isFile() && file.getName().endsWith(".variants")) inputs.add(file); - } } var target = new File(this.output, artifact.withExtension("module").getLocalPath()); var cache = Util.cache(target); + cache.add("codever", "1"); for (var input : inputs) { cache.add(input.getName(), input); } @@ -521,12 +565,18 @@ private void updateVariants(Artifact artifact) { if (Mavenizer.checkCache(target, cache)) return; + var seen = new HashSet(); var module = GradleModule.of(artifact); for (var input : inputs) { try { var data = JsonData.fromJson(input, GradleModule.Variant[].class); - for (var variant : data) - module.variant(variant); + for (var variant : data) { + // Stupid hack to work around a bug in our old system. (MCPMappings was marked as 'primary' so we may run into duplicate variants) + // Variants must have unique attributes + var attributes = JsonData.toJson(variant.attributes); + if (seen.add(attributes)) + module.variant(variant); + } } catch (Throwable t) { throw new RuntimeException("Failed to read artifact variants: %s".formatted(input), t); } diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/cache/JDKCache.java b/src/main/java/net/minecraftforge/mcmaven/impl/cache/JDKCache.java index e335caf..56e784d 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/cache/JDKCache.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/cache/JDKCache.java @@ -41,6 +41,14 @@ public File root() { return this.root; } + public File tryGet(int version) { + try { + return get(version); + } catch (Exception e) { + throw new IllegalStateException("Failed to find JDK for version " + version, e); + } + } + // TODO: [MCMavenizer][JDKCache] Make this thread safe. If this method is accessed concurrently, the same JDK could be downloaded more than once. /** * Gets the JDK for the given version. diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/mappings/MCPMappings.java b/src/main/java/net/minecraftforge/mcmaven/impl/mappings/MCPMappings.java index 0cc698a..dd4024e 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/mappings/MCPMappings.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/mappings/MCPMappings.java @@ -4,40 +4,68 @@ */ package net.minecraftforge.mcmaven.impl.mappings; +import java.io.File; +import java.util.IdentityHashMap; +import java.util.Map; + import org.jetbrains.annotations.Nullable; +import net.minecraftforge.mcmaven.impl.cache.MavenCache; +import net.minecraftforge.mcmaven.impl.repo.forge.FG2Userdev; import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCPSide; import net.minecraftforge.mcmaven.impl.util.Artifact; import net.minecraftforge.mcmaven.impl.util.Task; public class MCPMappings extends Mappings { - public MCPMappings(String channel, @Nullable String version) { - super(channel, version); - if (version == null) - throw new IllegalArgumentException("MCP Mappings can not have a null version"); - } - - @Override - public Task getCsvZip(MCPSide side) { - var key = new Key(Tasks.CSVs, side); - var ret = tasks.get(key); - if (ret != null) - return ret; + private final Map resolved = new IdentityHashMap<>(); - // This is simple because it's the old MCP Bot based zip files - // So it's just a matter of downloading from Forge's maven. - var artifact = Artifact.from("de.oceanlabs.mcp", "mcp_" + this.channel(), this.version(), null, "zip"); - var maven = side.getMCP().getCache().maven(); - - ret = Task.named("srg2names[" + this + ']', - () -> maven.download(artifact) - ); - tasks.put(key, ret); - return ret; + public MCPMappings(String channel, @Nullable String version) { + super(channel, version); + if (version == null) + throw new IllegalArgumentException("MCP Mappings can not have a null version"); } - @Override + @Override public Mappings withMCVersion(String version) { - return this; + return this; + } + + @Override + public boolean isPrimary() { + return false; + } + + @Override + public ResolvedMappings withContext(MCPSide side) { + return this.resolved.computeIfAbsent(side, _ -> withContextImpl(side)); + } + + private ResolvedMappings withContextImpl(MCPSide side) { + var csv = downloadCsv(side.getMCP().getCache().maven()); + var root = new File(side.getMCP().getBuildFolder(), "data/mapings"); + var artifact = this.getArtifact(side); + var mappings = side.getTasks().getMappings(); + return new ResolvedMappings(channel(), version(), artifact, root, mappings, csv, null); + } + + @Override + public ResolvedMappings withContext(FG2Userdev fg2) { + return this.resolved.computeIfAbsent(fg2, _ -> withContextImpl(fg2)); + } + + private ResolvedMappings withContextImpl(FG2Userdev fg2) { + var csv = downloadCsv(fg2.getCache().maven()); + var root = new File(fg2.getBuildFolder(), "data/mapings"); + var artifact = this.getArtifact(fg2); + var mappings = fg2.getMCP().getMappings(); + var extra = fg2.getExtraMappings(); + return new ResolvedMappings(channel(), version(), artifact, root, mappings, csv, extra); + } + + private Task downloadCsv(MavenCache maven) { + // This is simple because it's the old MCP Bot based zip files + // So it's just a matter of downloading from Forge's maven. + var botArtifact = Artifact.from("de.oceanlabs.mcp", "mcp_" + this.channel(), this.version(), null, "zip"); + return Task.named("srg2names[" + this + ']', () -> maven.download(botArtifact)); } } diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/mappings/Mappings.java b/src/main/java/net/minecraftforge/mcmaven/impl/mappings/Mappings.java index 80db428..21bef7d 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/mappings/Mappings.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/mappings/Mappings.java @@ -5,56 +5,17 @@ package net.minecraftforge.mcmaven.impl.mappings; import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; -import java.util.zip.ZipOutputStream; -import net.minecraftforge.mcmaven.impl.Mavenizer; -import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCPConfigRepo; +import net.minecraftforge.mcmaven.impl.repo.forge.FG2Userdev; import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCPSide; -import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MinecraftTasks; import net.minecraftforge.mcmaven.impl.util.Artifact; import net.minecraftforge.mcmaven.impl.util.Constants; -import net.minecraftforge.mcmaven.impl.util.ProcessUtils; -import net.minecraftforge.mcmaven.impl.util.Task; -import net.minecraftforge.mcmaven.impl.util.Util; -import net.minecraftforge.srgutils.IMappingFile; -import net.minecraftforge.srgutils.IRenamer; -import net.minecraftforge.srgutils.IMappingFile.IField; -import net.minecraftforge.srgutils.IMappingFile.IMethod; -import net.minecraftforge.srgutils.IMappingFile.IParameter; import org.jetbrains.annotations.Nullable; -import de.siegmar.fastcsv.reader.CsvReader; - -public class Mappings { +public abstract class Mappings { public static final String CHANNEL_ATTR = "net.minecraftforge.mappings.channel"; public static final String VERSION_ATTR = "net.minecraftforge.mappings.version"; - protected enum Tasks { - CSVs("srg2names"), - MappedToSrg("mapped2srg"), - MappedToObf("mapped2obf"); - - private final String name; - - private Tasks(String name) { - this.name = name; - } - }; - - protected record Key(Tasks type, MCPSide side) {} - - protected final Map tasks = new HashMap<>(); private final String channel; private final @Nullable String version; @@ -67,42 +28,18 @@ public static Mappings of(String mappingsNotation) { case "parchment": return new ParchmentMappings(version); case "official": - return new Mappings(channel, version); + return new OfficialMappings(channel, version); case "snapshot": case "snapshot_nodoc": case "stable": case "stable_nodoc": return new MCPMappings(channel, version); + case "srg": + return new SRGMappings("srg", version); } throw new IllegalArgumentException("Unsupported Mappings: " + mappingsNotation); } - public record Data(Map names, Map docs) { } - public static Data load(File data) throws IOException { - var names = new HashMap(); - var docs = new HashMap(); - try (var zip = new ZipFile(data)) { - var entries = zip.stream().filter(e -> e.getName().endsWith(".csv")).toList(); - for (var entry : entries) { - try (var reader = CsvReader.builder().ofNamedCsvRecord(new InputStreamReader(zip.getInputStream(entry)))) { - for (var row : reader) { - var header = row.getHeader(); - var obf = header.contains("searge") ? "searge" : "param"; - var searge = row.getField(obf); - names.put(searge, row.getField("name")); - if (header.contains("desc")) { - String desc = row.getField("desc"); - if (!desc.isBlank()) - docs.put(searge, desc); - } - } - } - } - } - - return new Data(names, docs); - } - public Mappings(String channel, @Nullable String version) { this.channel = channel; this.version = version; @@ -125,204 +62,41 @@ public File getFolder(File root) { return new File(root, channel() + '/' + version()); } - public boolean isPrimary() { - // This is the 'primary' mapping and thus what we publish as the root artifacts. - // Not as gradle module metadata only variants. - // Basically the thing that looks like a normal maven artifact - return true; - } + // This is the 'primary' mapping and thus what we publish as the root artifacts. + // Not as gradle module metadata only variants. + // Basically the thing that looks like a normal maven artifact + // This is only true for 'official' mappings + public abstract boolean isPrimary(); - public Mappings withMCVersion(String version) { - if (Objects.equals(this.version, version)) - return this; - return new Mappings(channel(), version); - } + public abstract Mappings withMCVersion(String version); - public Artifact getArtifact(MCPSide side) { + public abstract ResolvedMappings withContext(MCPSide side); + public abstract ResolvedMappings withContext(FG2Userdev fg2); + + + protected Artifact getArtifact(MCPSide side) { //net.minecraft:mappings_{CHANNEL}:{MCP_VERSION}[-{VERSION}]@zip + var name = "mappings_" + this.channel; var mcpVersion = side.getMCP().getName().getVersion(); var mcVersion = side.getMCP().getConfig().version; var artifactVersion = mcpVersion; if (this.version() != null && !mcVersion.equals(this.version())) artifactVersion += '-' + this.version(); - return Artifact.from(Constants.MC_GROUP, "mappings_" + this.channel, artifactVersion) - .withExtension("zip"); + return Artifact.from(Constants.MC_GROUP, name, artifactVersion).withExtension("zip"); } - public Task getCsvZip(MCPSide side) { - var key = new Key(Tasks.CSVs, side); - var ret = tasks.get(key); - if (ret != null) - return ret; - - var mc = side.getMCP().getMinecraftTasks(); - var srg = side.getTasks().getMappings(); - - if (MCPConfigRepo.isObfuscated(mc.getVersion())) { - var client = mc.versionFile(MinecraftTasks.MCFile.CLIENT_MAPPINGS); - var server = mc.versionFile(MinecraftTasks.MCFile.SERVER_MAPPINGS); - ret = Task.named("srg2names[" + this + ']', - Task.deps(srg, client, server), - () -> getMappings(side, srg, client, server) - ); - } else { - // Create an empty srg->mapped zip file when requested. This is needed by old MCPConfig setups until we bump it to a version that doesn't require the concept of mappings at all - ret = Task.named("srg2names[" + this + "][Empty]", () -> makeEmptyCsv(side)); - } - - tasks.put(key, ret); - return ret; - } - - public Task getMapped2Srg(MCPSide side) { - return getTsrg(side, Tasks.MappedToSrg); - } - - public Task getMapped2Obf(MCPSide side) { - return getTsrg(side, Tasks.MappedToObf); - } - - private Task getTsrg(MCPSide side, Tasks type) { - var key = new Key(type, side); - var ret = tasks.get(key); - if (ret != null) - return ret; - - var srg = side.getTasks().getMappings(); - var csv = getCsvZip(side); - ret = Task.named(type.name + '[' + this + ']', - Task.deps(srg, csv), - () -> makeTsrg(side, srg, csv, type == Tasks.MappedToObf) - ); - tasks.put(key, ret); - return ret; - } - - private File makeEmptyCsv(MCPSide side) { - var root = getFolder(new File(side.getMCP().getBuildFolder(), "data/mapings")); - var output = new File(root, "official.zip"); - - var cache = Util.cache(output); - - if (Mavenizer.checkCache(output, cache)) - return output; - - if (!output.getParentFile().exists()) - output.getParentFile().mkdirs(); - - byte[] header = String.join(",", "searge", "name", "side", "desc").getBytes(StandardCharsets.UTF_8); - - try (var fos = new FileOutputStream(output); - var out = new ZipOutputStream(fos)) { - out.putNextEntry(new ZipEntry("fields.csv")); - out.write(header); - out.closeEntry(); - out.putNextEntry(new ZipEntry("methods.csv")); - out.write(header); - out.closeEntry(); - } catch (IOException e) { - Util.sneak(e); - } - - cache.save(); - return output; - } - - private File getMappings(MCPSide side, Task srgMappings, Task clientTask, Task serverTask) { - var tool = side.getMCP().getCache().maven().download(Constants.INSTALLER_TOOLS); - - var root = getFolder(new File(side.getMCP().getBuildFolder(), "data/mapings")); - var output = new File(root, "official.zip"); - var log = new File(root, "official.log"); - - var mappings = srgMappings.execute(); - var client = clientTask.execute(); - var server = serverTask.execute(); - - var cache = Util.cache(output); - cache.add("tool", tool); - cache.add("mappings", mappings); - cache.add("client", client); - cache.add("server", server); - - if (Mavenizer.checkCache(output, cache)) - return output; - - var args = List.of( - "--task", - "MAPPINGS_CSV", - "--srg", - mappings.getAbsolutePath(), - "--client", - client.getAbsolutePath(), - "--server", - server.getAbsolutePath(), - "--output", - output.getAbsolutePath() - ); - - File jdk; - try { - jdk = side.getMCP().getCache().jdks().get(Constants.INSTALLER_TOOLS_JAVA_VERSION); - } catch (Exception e) { - throw new IllegalStateException("Failed to find JDK for version " + Constants.INSTALLER_TOOLS_JAVA_VERSION, e); - } - - var ret = ProcessUtils.runJar(jdk, log.getParentFile(), log, tool, Collections.emptyList(), args); - if (ret.exitCode != 0) - throw new IllegalStateException("Failed to run MCP Step (exit code " + ret.exitCode + "), See log: " + log.getAbsolutePath()); - - cache.save(); - return output; - } - - private File makeTsrg(MCPSide side, Task srgTask, Task csvTask, boolean toObf) { - var root = getFolder(new File(side.getMCP().getBuildFolder(), "data/mapings")); - var output = new File(root, channel() + '-' + version + '-' + (toObf ? "srg" : "obf") + ".tsrg.gz"); - - var srg = srgTask.execute(); - var csv = csvTask.execute(); - - var cache = Util.cache(output) - .add("srg", srg) - .add("csv", csv); - - if (Mavenizer.checkCache(output, cache)) - return output; - - try { - var names = Mappings.load(csv).names(); - - var map = IMappingFile.load(srg); // obf2srg - if (!toObf) - map = map.reverse().chain(map); // srg2obf + obf2srg = srg2srg - - // Now we rename target2mapped - map = map.rename(new IRenamer() { - @Override - public String rename(IField value) { - return names.getOrDefault(value.getMapped(), value.getMapped()); - } - - @Override - public String rename(IMethod value) { - return names.getOrDefault(value.getMapped(), value.getMapped()); - } - - @Override - public String rename(IParameter value) { - return names.getOrDefault(value.getMapped(), value.getMapped()); - } - }); - - // Write in reversed == mapped2target - map.write(output.getAbsoluteFile().toPath(), IMappingFile.Format.TSRG2, true); - } catch (IOException e) { - Util.sneak(e); - } + protected Artifact getArtifact(FG2Userdev fg2) { + //net.minecraft:mappings_{CHANNEL}:{MC_VERSION}-{legacy|FORGE_VERSION}[-{VERSION}]@zip + var name = "mappings_" + this.channel; + var version = fg2.getMinecraftVersion(); + if (fg2.getExtraMappings() == null) + version += "-legacy"; + else + version += '-' + fg2.getForgeVersion(); + if (this.version() != null && !fg2.getMinecraftVersion().equals(this.version())) + version += '-' + this.version(); - cache.save(); - return output; + return Artifact.from(Constants.MC_GROUP, name, version).withExtension("zip"); } } diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/mappings/OfficialMappings.java b/src/main/java/net/minecraftforge/mcmaven/impl/mappings/OfficialMappings.java new file mode 100644 index 0000000..985676b --- /dev/null +++ b/src/main/java/net/minecraftforge/mcmaven/impl/mappings/OfficialMappings.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ +package net.minecraftforge.mcmaven.impl.mappings; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import org.jetbrains.annotations.Nullable; + +import net.minecraftforge.mcmaven.impl.Mavenizer; +import net.minecraftforge.mcmaven.impl.repo.forge.FG2Userdev; +import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCPConfigRepo; +import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCPSide; +import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MinecraftTasks; +import net.minecraftforge.mcmaven.impl.util.Constants; +import net.minecraftforge.mcmaven.impl.util.ProcessUtils; +import net.minecraftforge.mcmaven.impl.util.Task; +import net.minecraftforge.mcmaven.impl.util.Util; + +class OfficialMappings extends Mappings { + private final Map resolved = new IdentityHashMap<>(); + + public OfficialMappings(String channel, @Nullable String version) { + super(channel, version); + } + + @Override + public boolean isPrimary() { + return true; + } + + @Override + public Mappings withMCVersion(String version) { + if (Objects.equals(version(), version)) + return this; + return new OfficialMappings(channel(), version); + } + + @Override + public ResolvedMappings withContext(MCPSide side) { + return this.resolved.computeIfAbsent(side, _ -> withContextImpl(side)); + } + + private ResolvedMappings withContextImpl(MCPSide side) { + var csv = makeCsv(side); + var root = new File(side.getMCP().getBuildFolder(), "data/mapings"); + var artifact = this.getArtifact(side); + var mappings = side.getTasks().getMappings(); + return new ResolvedMappings(channel(), version(), artifact, root, mappings, csv, null); + } + + @Override + public ResolvedMappings withContext(FG2Userdev fg2) { + throw new IllegalStateException("Official mappings does not support Legacy: " + fg2.getName()); + } + + private Task makeCsv(MCPSide side) { + var mc = side.getMCP().getMinecraftTasks(); + var srg = side.getTasks().getMappings(); + + // Create an empty srg->mapped zip file when requested. This is needed by old MCPConfig setups until we bump it to a version that doesn't require the concept of mappings at all + if (!MCPConfigRepo.isObfuscated(mc.getVersion())) + return Task.named("srg2names[" + this + "][Empty]", () -> makeEmptyCsv(side)); + + var client = mc.versionFile(MinecraftTasks.MCFile.CLIENT_MAPPINGS); + var server = mc.versionFile(MinecraftTasks.MCFile.SERVER_MAPPINGS); + return Task.named("srg2names[" + this + ']', + Task.deps(srg, client, server), + () -> getMappings(side, srg, client, server) + ); + } + + private File makeEmptyCsv(MCPSide side) { + var root = getFolder(new File(side.getMCP().getBuildFolder(), "data/mapings")); + var output = new File(root, "official.zip"); + + var cache = Util.cache(output); + + if (Mavenizer.checkCache(output, cache)) + return output; + + if (!output.getParentFile().exists()) + output.getParentFile().mkdirs(); + + byte[] header = String.join(",", "searge", "name", "side", "desc").getBytes(StandardCharsets.UTF_8); + + try (var fos = new FileOutputStream(output); + var out = new ZipOutputStream(fos)) { + out.putNextEntry(new ZipEntry("fields.csv")); + out.write(header); + out.closeEntry(); + out.putNextEntry(new ZipEntry("methods.csv")); + out.write(header); + out.closeEntry(); + } catch (IOException e) { + Util.sneak(e); + } + + cache.save(); + return output; + } + + private File getMappings(MCPSide side, Task srgMappings, Task clientTask, Task serverTask) { + var tool = side.getMCP().getCache().maven().download(Constants.INSTALLER_TOOLS); + + var root = getFolder(new File(side.getMCP().getBuildFolder(), "data/mapings")); + var output = new File(root, "official.zip"); + var log = new File(root, "official.log"); + + var mappings = srgMappings.execute(); + var client = clientTask.execute(); + var server = serverTask.execute(); + + var cache = Util.cache(output); + cache.add("tool", tool); + cache.add("mappings", mappings); + cache.add("client", client); + cache.add("server", server); + + if (Mavenizer.checkCache(output, cache)) + return output; + + var args = List.of( + "--task", + "MAPPINGS_CSV", + "--srg", + mappings.getAbsolutePath(), + "--client", + client.getAbsolutePath(), + "--server", + server.getAbsolutePath(), + "--output", + output.getAbsolutePath() + ); + + File jdk; + try { + jdk = side.getMCP().getCache().jdks().get(Constants.INSTALLER_TOOLS_JAVA_VERSION); + } catch (Exception e) { + throw new IllegalStateException("Failed to find JDK for version " + Constants.INSTALLER_TOOLS_JAVA_VERSION, e); + } + + var ret = ProcessUtils.runJar(jdk, log.getParentFile(), log, tool, Collections.emptyList(), args); + if (ret.exitCode != 0) + throw new IllegalStateException("Failed to run MCP Step (exit code " + ret.exitCode + "), See log: " + log.getAbsolutePath()); + + cache.save(); + return output; + } +} diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/mappings/ParchmentMappings.java b/src/main/java/net/minecraftforge/mcmaven/impl/mappings/ParchmentMappings.java index dc8941b..cc71b3e 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/mappings/ParchmentMappings.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/mappings/ParchmentMappings.java @@ -10,6 +10,7 @@ import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -24,16 +25,17 @@ import net.minecraftforge.mcmaven.impl.Mavenizer; import net.minecraftforge.mcmaven.impl.cache.Cache; import net.minecraftforge.mcmaven.impl.cache.MavenCache; +import net.minecraftforge.mcmaven.impl.repo.forge.FG2Userdev; import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCP; import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCPSide; import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MinecraftTasks; -import net.minecraftforge.mcmaven.impl.util.Artifact; import net.minecraftforge.mcmaven.impl.util.Task; import net.minecraftforge.mcmaven.impl.util.Util; import net.minecraftforge.srgutils.IMappingFile; import net.minecraftforge.util.file.FileUtils; public class ParchmentMappings extends Mappings { + private final Map resolved = new IdentityHashMap<>(); private final ParchmentVersion parsedVersion; private Task downloadTask; @@ -63,30 +65,36 @@ public Mappings withMCVersion(String mcVer) { } @Override - public Artifact getArtifact(MCPSide side) { - return this.parsedVersion.getMappingArtifact(side.getMCP().getName().getVersion()); + public ResolvedMappings withContext(MCPSide side) { + return this.resolved.computeIfAbsent(side, _ -> withContextImpl(side)); + } + + private ResolvedMappings withContextImpl(MCPSide side) { + var csv = makeCsv(side); + var root = new File(side.getMCP().getBuildFolder(), "data/mapings"); + var artifact = this.getArtifact(side); + var mappings = side.getTasks().getMappings(); + return new ResolvedMappings(channel(), version(), artifact, root, mappings, csv, null); } @Override - public Task getCsvZip(MCPSide side) { - var key = new Key(Tasks.CSVs, side); - var ret = tasks.get(key); - if (ret != null) - return ret; + public ResolvedMappings withContext(FG2Userdev fg2) { + throw new IllegalStateException("Parchment mappings does not support Legacy: " + fg2.getName()); + } + private Task makeCsv(MCPSide side) { var mc = side.getMCP().getMinecraftTasks(); var srg = side.getTasks().getMappings(); var client = mc.versionFile(MinecraftTasks.MCFile.CLIENT_MAPPINGS); var server = mc.versionFile(MinecraftTasks.MCFile.SERVER_MAPPINGS); var data = downloadTask(side.getMCP()); + var mcpRoot = new File(side.getMCP().getBuildFolder(), "data/mapings"); - ret = Task.named("srg2names[" + this + ']', + return Task.named("srg2names[" + this + ']', Task.deps(Set.of(srg, client, server, data).stream().filter(Objects::nonNull).toList()), - () -> getMappings(side.getMCP(), srg, client, server, data) + () -> getMappings(mcpRoot, srg, client, server, data) ); - tasks.put(key, ret); - return ret; } private Task downloadTask(MCP mcp) { @@ -104,13 +112,13 @@ private File download(Cache cache) { return maven.download(artifact); } - private File getMappings(MCP mcp, Task srgTask, Task clientTask, Task serverTask, Task dataTask) throws IOException { + private File getMappings(File mcpRoot, Task srgTask, Task clientTask, Task serverTask, Task dataTask) throws IOException { var srg = srgTask.execute(); var client = clientTask.execute(); var server = serverTask.execute(); var data = dataTask.execute(); - var root = getFolder(new File(mcp.getBuildFolder(), "data/mapings")); + var root = getFolder(mcpRoot); var output = new File(root, "parchment-" + version() + ".zip"); var cache = Util.cache(output) .add("srg", srg) diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/mappings/ResolvedMappings.java b/src/main/java/net/minecraftforge/mcmaven/impl/mappings/ResolvedMappings.java new file mode 100644 index 0000000..a5218c9 --- /dev/null +++ b/src/main/java/net/minecraftforge/mcmaven/impl/mappings/ResolvedMappings.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ +package net.minecraftforge.mcmaven.impl.mappings; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import org.jetbrains.annotations.Nullable; + +import net.minecraftforge.mcmaven.impl.Mavenizer; +import net.minecraftforge.mcmaven.impl.tasks.MCPNames; +import net.minecraftforge.mcmaven.impl.util.Artifact; +import net.minecraftforge.mcmaven.impl.util.Task; +import net.minecraftforge.mcmaven.impl.util.Util; +import net.minecraftforge.srgutils.IMappingFile; +import net.minecraftforge.srgutils.IRenamer; +import net.minecraftforge.srgutils.IMappingFile.IField; +import net.minecraftforge.srgutils.IMappingFile.IMethod; +import net.minecraftforge.srgutils.IMappingFile.IParameter; + +public class ResolvedMappings { + public static final String CHANNEL_ATTR = "net.minecraftforge.mappings.channel"; + public static final String VERSION_ATTR = "net.minecraftforge.mappings.version"; + + protected enum Tasks { + CSVs("srg2names"), + MappedToSrg("mapped2srg"), + MappedToObf("mapped2obf"); + + private final String name; + + private Tasks(String name) { + this.name = name; + } + }; + + protected final Map tasks = new HashMap<>(); + private final String channel; + private final String version; + private final Artifact artifact; + private final Task baseMappings; + private final Task csvZip; + private final Task mappedToSrg; + private final Task mappedToObf; + + public ResolvedMappings( + String channel, String version, + Artifact artifact, File root, + Task baseMappings, Task csvZip, + @Nullable byte[] extra + ) { + this.channel = channel; + this.version = version; + this.artifact = artifact; + this.baseMappings = baseMappings; + this.csvZip = csvZip; + this.mappedToObf = getTsrg(root, Tasks.MappedToObf, null); + this.mappedToSrg = getTsrg(root, Tasks.MappedToSrg, extra); + } + + public String channel() { + return this.channel; + } + + public String version() { + return this.version; + } + + @Override + public String toString() { + return channel() + '-' + version(); + } + + public File getFolder(File root) { + return new File(root, channel() + '/' + version()); + } + + public Artifact getArtifact() { + return this.artifact; + } + + public Task getCsvZip() { + return this.csvZip; + } + + public Task getMapped2Srg() { + return this.mappedToSrg; + } + + public Task getMapped2Obf() { + return this.mappedToObf; + } + + + + + + private Task getTsrg(File root, Tasks type, @Nullable byte[] extra) { + var csv = getCsvZip(); + + return Task.named(type.name + '[' + this + ']', + Task.deps(this.baseMappings, csv), + () -> makeTsrg(root, this.baseMappings, csv, type == Tasks.MappedToObf, extra) + ); + } + + private File makeTsrg(File mcpRoot, Task srgTask, Task csvTask, boolean toObf, @Nullable byte[] extra) { + var root = getFolder(mcpRoot); + var output = new File(root, channel() + '-' + version + '-' + (toObf ? "srg" : "obf") + ".tsrg.gz"); + + var srg = srgTask.execute(); + var csv = csvTask.execute(); + + var cache = Util.cache(output) + .add("srg", srg) + .add("csv", csv); + + if (extra != null) + cache.add("extra", extra); + + if (Mavenizer.checkCache(output, cache)) + return output; + + try { + var names = MCPNames.load(csv).names(); + + var map = IMappingFile.load(srg); // obf2srg + if (!toObf) + map = map.reverse().chain(map); // srg2obf + obf2srg = srg2srg + + // Now we rename target2mapped + map = map.rename(new IRenamer() { + @Override + public String rename(IField value) { + return names.getOrDefault(value.getMapped(), value.getMapped()); + } + + @Override + public String rename(IMethod value) { + return names.getOrDefault(value.getMapped(), value.getMapped()); + } + + @Override + public String rename(IParameter value) { + return names.getOrDefault(value.getMapped(), value.getMapped()); + } + }); + + // target2mapped -> mapped2target + map = map.reverse(); + + // Extra is for FG2 environements for extra 'reobf' mappings. + // So named2srg + if (extra != null) { + var extraMap = IMappingFile.load(new ByteArrayInputStream(extra)); + map = map.merge(extraMap); + } + + map.write(output.getAbsoluteFile().toPath(), IMappingFile.Format.TSRG2, false); + } catch (IOException e) { + Util.sneak(e); + } + + cache.save(); + return output; + } +} diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/mappings/SRGMappings.java b/src/main/java/net/minecraftforge/mcmaven/impl/mappings/SRGMappings.java new file mode 100644 index 0000000..7aa9ba0 --- /dev/null +++ b/src/main/java/net/minecraftforge/mcmaven/impl/mappings/SRGMappings.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ +package net.minecraftforge.mcmaven.impl.mappings; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import org.jetbrains.annotations.Nullable; + +import net.minecraftforge.mcmaven.impl.Mavenizer; +import net.minecraftforge.mcmaven.impl.repo.forge.FG2Userdev; +import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCPSide; +import net.minecraftforge.mcmaven.impl.util.Task; +import net.minecraftforge.mcmaven.impl.util.Util; + +// The raw mappings, typically named SRG for old versions, but unobfed versions its equivelent to official +public class SRGMappings extends Mappings { + private final Map resolved = new IdentityHashMap<>(); + + public SRGMappings(String channel, @Nullable String version) { + super(channel, version); + } + + @Override + public Mappings withMCVersion(String version) { + if (this.version() != null && this.version().equals(version)) + return this; + return new SRGMappings(channel(), version); + } + + @Override + public boolean isPrimary() { + return false; + } + + @Override + public ResolvedMappings withContext(MCPSide side) { + return this.resolved.computeIfAbsent(side, _ -> withContextImpl(side)); + } + + private ResolvedMappings withContextImpl(MCPSide side) { + var root = new File(side.getMCP().getBuildFolder(), "data/mapings"); + var csv = csvTask(root); + var artifact = this.getArtifact(side); + var mappings = side.getTasks().getMappings(); + return new ResolvedMappings(channel(), version(), artifact, root, mappings, csv, null); + } + + @Override + public ResolvedMappings withContext(FG2Userdev fg2) { + return this.resolved.computeIfAbsent(fg2, _ -> withContextImpl(fg2)); + } + + public ResolvedMappings withContextImpl(FG2Userdev fg2) { + var root = new File(fg2.getBuildFolder(), "data/mapings"); + var csv = csvTask(root); + var artifact = this.getArtifact(fg2); + var mappings = fg2.getMCP().getMappings(); + var extra = fg2.getExtraMappings(); + return new ResolvedMappings(channel(), version(), artifact, root, mappings, csv, extra); + } + + private Task csvTask(File root) { + return Task.named("srg2names[" + this + "][Empty]", () -> makeEmptyCsv(root)); + } + + private File makeEmptyCsv(File root) { + var output = new File(getFolder(root), "srg.zip"); + + var cache = Util.cache(output); + + if (Mavenizer.checkCache(output, cache)) + return output; + + if (!output.getParentFile().exists()) + output.getParentFile().mkdirs(); + + byte[] header = String.join(",", "searge", "name", "side", "desc").getBytes(StandardCharsets.UTF_8); + + try (var fos = new FileOutputStream(output); + var out = new ZipOutputStream(fos)) { + out.putNextEntry(new ZipEntry("fields.csv")); + out.write(header); + out.closeEntry(); + out.putNextEntry(new ZipEntry("methods.csv")); + out.write(header); + out.closeEntry(); + } catch (IOException e) { + Util.sneak(e); + } + + cache.save(); + return output; + } +} diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/repo/Repo.java b/src/main/java/net/minecraftforge/mcmaven/impl/repo/Repo.java index 3d60429..9bef670 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/repo/Repo.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/repo/Repo.java @@ -8,6 +8,7 @@ import net.minecraftforge.mcmaven.impl.cache.Cache; import net.minecraftforge.mcmaven.impl.data.GradleModule; import net.minecraftforge.mcmaven.impl.mappings.Mappings; +import net.minecraftforge.mcmaven.impl.mappings.ResolvedMappings; import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCPConfigRepo; import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCPSide; import net.minecraftforge.mcmaven.impl.util.Artifact; @@ -69,13 +70,13 @@ protected static PendingArtifact pending(String message, Task task, Artifact art protected Supplier sourceVariant(Mappings mappings) { return () -> new GradleModule.Variant[] { GradleModule.Variant.of("sources") - .attribute("org.gradle.status", "release") - .attribute("org.gradle.usage", "java-runtime") - .attribute("org.gradle.category", "documentation") - .attribute("org.gradle.dependency.bundling", "external") - .attribute("org.gradle.docstype", "sources") - .attribute("org.gradle.libraryelements", "jar") - .attribute(Mappings.CHANNEL_ATTR, mappings.channel()) + .attribute("org.gradle.status", "release") + .attribute("org.gradle.usage", "java-runtime") + .attribute("org.gradle.category", "documentation") + .attribute("org.gradle.dependency.bundling", "external") + .attribute("org.gradle.docstype", "sources") + .attribute("org.gradle.libraryelements", "jar") + .attribute(Mappings.CHANNEL_ATTR, mappings.channel()) .attribute(Mappings.VERSION_ATTR, mappings.version()) }; } @@ -112,48 +113,32 @@ protected static Task variantTask(Task parent, Supplier }); } - protected Supplier simpleVariant(String name, Mappings mappings) { + protected Supplier simpleVariant(String name, String mappingChannel, @Nullable String mappingVersion) { return () -> new GradleModule.Variant[] { GradleModule.Variant .of(name) .attribute("org.gradle.status", "release") .attribute("org.gradle.category", "library") .attribute("org.gradle.libraryelements", "jar") - .attribute(Mappings.CHANNEL_ATTR, mappings.channel()) - .attribute(Mappings.VERSION_ATTR, mappings.version()) + .attribute(Mappings.CHANNEL_ATTR, mappingChannel) + .attribute(Mappings.VERSION_ATTR, mappingVersion) }; } protected GradleModule.Variant[] classVariants(Mappings mappings, MCPSide side) { - return classVariants(mappings, side, List.of(), List.of(), List.of()); - } - - // Classes needs a variant for each OS type so that we can have different natives - protected GradleModule.Variant[] classVariants( - Mappings mappings, - MCPSide side, - Collection extraDeps, - Collection extraCompileDeps, - Collection extraRuntimeDeps - ) { var deps = new ArrayList(); deps.addAll(side.getMCLibraries()); deps.addAll(side.getMCPConfigLibraries()); - deps.addAll(extraDeps); - - // ExtraRuntimeDeps is never used.... Not sure where it was meant to go + var jsonTask = side.getMCP().getMinecraftTasks().versionJson; + var json = JsonData.minecraftVersion(jsonTask.execute()); + var java = json.javaVersion != null ? json.javaVersion.majorVersion : null; - return classVariants( - mappings, - side.getMCP().getMinecraftTasks().versionJson, - deps, - extraCompileDeps - ); + return classVariants(mappings, java, deps, List.of()); } protected GradleModule.Variant[] classVariants( Mappings mappings, - Task jsonTask, + @Nullable Integer javaVersion, Collection deps, Collection extraCompileDeps ) { @@ -180,9 +165,6 @@ protected GradleModule.Variant[] classVariants( } } - var json = JsonData.minecraftVersion(jsonTask.execute()); - var java = json.javaVersion != null ? json.javaVersion.majorVersion : null; - Consumer common = v -> { v.attribute("org.gradle.status", "release") .attribute("org.gradle.usage", "java-runtime") @@ -194,8 +176,8 @@ protected GradleModule.Variant[] classVariants( .attribute(Mappings.VERSION_ATTR, mappings.version()) ; - if (java != null) - v.attribute("org.gradle.jvm.version", java); + if (javaVersion != null) + v.attribute("org.gradle.jvm.version", javaVersion); v.deps(all); }; @@ -286,15 +268,15 @@ public PendingArtifact withTask(Task task) { * channel-verson-map2obf.tsrg.gz: gzip compressed tsrg file for mapped names to obf (notch) names * channel-verson-map2srg.tsrg.gz: gzip compressed tsrg file for mapped names to srg (intermediate) names */ - protected List mappingArtifacts(File cache, Mappings mappings, MCPSide side, Map> outputJson) { + protected List mappingArtifacts(File cache, ResolvedMappings mappings, String mcVersion, Map> outputJson) { if (outputJson != null) { outputJson.put("mappings.channel", mappings::channel); outputJson.put("mappings.version", mappings::version); } - var coords = mappings.getArtifact(side); - var csvs = pending("Mappings Zip", mappings.getCsvZip(side), coords, false); - var pom = pending("Mappings POM", simplePom(cache, coords), coords.withExtension("pom"), false); + var coords = mappings.getArtifact(); + var csvs = pending("Mappings Zip", mappings.getCsvZip(), coords, false); + var pom = pending("Mappings POM", simplePom(mappings.getFolder(cache), coords), coords.withExtension("pom"), false); if (outputJson != null) { outputJson.put("mappings.csv.artifact", csvs.artifact()::toString); @@ -302,11 +284,11 @@ protected List mappingArtifacts(File cache, Mappings mappings, } // Just create the zip and pom for unobfuscated versions. - if (!MCPConfigRepo.isObfuscated(side.getMCP().getMinecraftTasks().getVersion())) + if (!MCPConfigRepo.isObfuscated(mcVersion)) return List.of(csvs, pom); - var m2o = pending("Mappings map2obf", mappings.getMapped2Obf(side), coords.withClassifier("map2obf").withExtension("tsrg.gz"), false); - var m2s = pending("Mappings map2srg", mappings.getMapped2Srg(side), coords.withClassifier("map2srg").withExtension("tsrg.gz"), false); + var m2o = pending("Mappings map2obf", mappings.getMapped2Obf(), coords.withClassifier("map2obf").withExtension("tsrg.gz"), false); + var m2s = pending("Mappings map2srg", mappings.getMapped2Srg(), coords.withClassifier("map2srg").withExtension("tsrg.gz"), false); if (outputJson != null) { outputJson.put("mappings.obf.artifact", m2o.artifact()::toString); outputJson.put("mappings.obf.file", m2o.task().filePathSupplier()); diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/FG2Userdev.java b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/FG2Userdev.java new file mode 100644 index 0000000..ca4b604 --- /dev/null +++ b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/FG2Userdev.java @@ -0,0 +1,653 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ +package net.minecraftforge.mcmaven.impl.repo.forge; + +import static net.minecraftforge.mcmaven.impl.Mavenizer.LOGGER; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +import org.jetbrains.annotations.Nullable; + +import io.codechicken.diffpatch.cli.PatchOperation; +import io.codechicken.diffpatch.util.LogLevel; +import io.codechicken.diffpatch.util.PatchMode; +import io.codechicken.diffpatch.util.Input.MultiInput; +import io.codechicken.diffpatch.util.Output.MultiOutput; +import io.codechicken.diffpatch.util.archiver.ArchiveFormat; +import net.minecraftforge.mcmaven.impl.Mavenizer; +import net.minecraftforge.mcmaven.impl.cache.Cache; +import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCPLegacy; +import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MinecraftTasks; +import net.minecraftforge.mcmaven.impl.util.Artifact; +import net.minecraftforge.mcmaven.impl.util.ComparableVersion; +import net.minecraftforge.mcmaven.impl.util.ContextualPatch; +import net.minecraftforge.mcmaven.impl.util.StupidHacks; +import net.minecraftforge.mcmaven.impl.util.Task; +import net.minecraftforge.mcmaven.impl.util.Util; +import net.minecraftforge.util.data.json.JsonData; +import net.minecraftforge.util.data.json.MinecraftVersion; +import net.minecraftforge.util.data.json.RunConfig; +import net.minecraftforge.util.file.FileUtils; +import net.minecraftforge.util.hash.HashFunction; + +public class FG2Userdev implements ForgeVersionCommon { + private final File build; + private final ForgeRepo forge; + private final Artifact name; + private final FGVersion fgVersion; + private final File data; + private final String dataHash; + private final MinecraftVersion config; + private final String mcVersion; + private final String forgeVersion; + private final @Nullable byte[] extraMappings; + private final MCPLegacy mcp; + private final MCPLegacy.Child mcpChild; + private final Task sourcesTask; + private final Map extracts = new HashMap<>(); + private final Task patches; + + FG2Userdev(File build, ForgeRepo forge, Artifact name, FGVersion fgVersion) { + this.build = build; + this.forge = forge; + this.name = name; + this.fgVersion = fgVersion; + + this.data = this.forge.getCache().maven().download(name); + if (!this.data.exists()) + throw new IllegalStateException("Failed to download " + name); + + this.dataHash = Util.sneak(() -> HashFunction.sha1().hash(this.data)); + + try (var zip = new ZipFile(data)) { + var entry = zip.getEntry("dev.json"); + if (entry == null) + throw except("Missing dev.json"); + + var cfg_data = zip.getInputStream(entry).readAllBytes(); + this.config = JsonData.minecraftVersion(cfg_data); + + entry = zip.getEntry("merged.srg"); + this.extraMappings = entry == null ? null : zip.getInputStream(entry).readAllBytes(); + } catch (IOException e) { + throw except("Error reading config", e); + } + + this.mcVersion = Util.forgeToMcVersion(this.name.getVersion()); + + var legacyMCP = StupidHacks.legacyMcp(new ComparableVersion(this.name.getVersion())); + this.mcp = this.forge.mcpconfig.legacy(this.mcVersion, legacyMCP); + this.mcpChild = this.mcp.getChild(this); + + this.forgeVersion = this.name.getVersion().substring(this.name.getVersion().indexOf('-') + 1); + + this.patches = extract("patches.zip"); + this.sourcesTask = patch(); + } + + public File getBuildFolder() { + return this.build; + } + + public Cache getCache() { + return this.forge.getCache(); + } + + public Artifact getName() { + return this.name; + } + + @Override + public String getMinecraftVersion() { + return this.mcVersion; + } + + public String getForgeVersion() { + return this.forgeVersion; + } + + public FGVersion getGradleVersion() { + return this.fgVersion; + } + + public MCPLegacy getMCP() { + return this.mcp; + } + + public @Nullable byte[] getExtraMappings() { + return this.extraMappings; + } + + public Task getSources() { + return this.sourcesTask; + } + + private RuntimeException except(String message) { + return new IllegalArgumentException("Invalid FG2 Dependency: " + this.name + " - " + message); + } + + private RuntimeException except(String message, Throwable e) { + return new IllegalArgumentException("Invalid FG2 Dependency: " + this.name + " - " + message, e); + } + + @Override + public String getDataHash() { + return this.dataHash; + } + + @Override + public int getJavaTarget() { + // Older minecraft supported java 6, but it's not really possible to get a version of that these days, so we've been using 8 for over a decade + return 8; + } + + @Override + public Artifact getMCPArtifact() { + return this.getMCP().getName(); + } + + @Override + public @Nullable List getModules() { + return Collections.emptyList(); + } + + @Override + public List getCompileOnly() { + return Collections.emptyList(); + } + + @Override + public List getRuntimeOnly() { + return Collections.emptyList(); + } + + @Override + public void forAllLibraries(Consumer consumer, Predicate filter) { + for (var library : this.config.getLibs()) { + var artifact = Artifact.from(library.coord).withOS(library.os); + if (filter == null || filter.test(artifact)) + consumer.accept(artifact); + } + for (var library : this.getMCP().getMinecraftTasks().getClientLibraries()) { + if (filter == null || filter.test(library.artifact())) + consumer.accept(library.artifact()); + } + } + + @Override + public List getLibraries() { + var ret = new ArrayList(); + // We don't support OS specific/classified libraries on these old versions because they were made before + // Mojang supported them. So just get them all via the name like FG2 does + for (var lib : this.config.libraries) + ret.add(Artifact.from(lib.name)); + return ret; + } + + @Override + public List getClasspath() { + var classpath = new ArrayList(); + var seen = new HashSet(); + + // minecraft version.json libs + userdev libs + for (var lib : this.getMCP().getMinecraftTasks().getClientLibraries()) { + classpath.add(lib.file()); + // We just want the group:name and clssifier, incase they have upated the version since we were built + seen.add(lib.artifact().withVersion(null).toString()); + } + + var cache = this.forge.getCache(); + if (this.config.inheritsFrom == null) { + // If there is no inherits, this is before Mojang added that feature to the launcher + // So we need to try and strip out any libraries that come from the vanilla launcher. + for (var lib : this.config.libraries) { + var artifact = StupidHacks.fixLegacyForgeDeps(Artifact.from(lib.name)); + var unversioned = artifact.withVersion(null).toString(); + if (!seen.contains(unversioned)) { + if (artifact != null) + classpath.add(Util.getArtifact(cache, artifact)); + } + } + } else { + for (var lib : this.config.libraries) { + var artifact = StupidHacks.fixLegacyForgeDeps(Artifact.from(lib.name)); + if (artifact != null) + classpath.add(Util.getArtifact(cache, artifact)); + } + } + + return classpath; + } + + @Override + public MinecraftTasks getMinecraftTasks() { + return this.getMCP().getMinecraftTasks(); + } + + public Task extract(String name) { + var ret = this.extracts.get(name); + if (ret == null) { + ret = Task.named("extract[" + name + ']', () -> extractImpl(name)); + this.extracts.put(name, ret); + } + return ret; + } + + private File extractImpl(String name) { + var target = new File(new File(this.build, "data"), name); + var cache = Util.cache(target) + .addKnown("data", dataHash); + + if (Mavenizer.checkCache(target, cache)) + return target; + + try (var zip = new ZipFile(data)) { + var entry = zip.getEntry(name); + if (entry == null) + throw except("Missing Data: " + name); + + FileUtils.ensureParent(target); + + try (var os = new FileOutputStream(target)) { + zip.getInputStream(entry).transferTo(os); + } + + target.setLastModified(entry.getLastModifiedTime().toMillis()); + + cache.save(); + return target; + } catch (IOException e) { + throw except("Failed to extract `" + name + '`', e); + } + } + + private Task patch() { + var input = inject(); + var patches = StupidHacks.needsPatchFixes(this.name) + ? fixPatches(this.patches) + : this.patches; + + return Task.named("patch", + Task.deps(input, patches), + () -> this.patch(input.execute(), patches.execute()) + ); + } + + private Task fixPatches(Task base) { + var output = new File(this.build, "fixed-patches.zip"); + return Task.named("fix-patches", Task.deps(base), () -> + StupidHacks.fixPatches(this.name, base.execute(), output) + ); + } + + private File patch(File input, File patches) { + var output = new File(this.build, "patched.jar"); + var rejects = new File(this.build, "patches-rejects.jar"); + + var cache = Util.cache(output) + .add("input", input) + .add("patches", patches) + .addKnown("fg", this.fgVersion.name()) + ; + + if (Mavenizer.checkCache(output, cache)) + return output; + + FileUtils.ensureParent(output); + if (output.exists()) + output.delete(); + if (rejects.exists()) + rejects.delete(); + + if (this.fgVersion == FGVersion.v2) + patchUnstable(input, patches, output, rejects); + else + patchStable(input, patches, output, rejects); + + cache.save(); + return output; + } + + private void patchUnstable(File input, File patchesArchive, File output, File rejectOutput) { + final var patches = new HashMap(); + + // Gather all of our patches + try (ZipInputStream zin = new ZipInputStream(new FileInputStream(patchesArchive))) { + for (ZipEntry entry; (entry = zin.getNextEntry()) != null;) { + String name = entry.getName(); + if (!name.endsWith(".patch")) + continue; + + var filename = name.substring(0, name.length() - 6); + var patch = ContextualPatch.create(zin) + .setAccessC14N(true) + .setMaxFuzz(2); + + patches.put(filename, patch); + } + } catch (IOException e) { + Util.sneak(e); + } + + + // Apply them + try (var zin = new ZipInputStream(new FileInputStream(input)); + var zout = new ZipOutputStream(new FileOutputStream(output)) + ) { + for (ZipEntry entry; (entry = zin.getNextEntry()) != null; ) { + var name = entry.getName(); + var patch = patches.get(name); + var newEntry = new ZipEntry(name); + newEntry.setTime(entry.getTime()); + zout.putNextEntry(newEntry); + + if (patch == null) { + zin.transferTo(zout); + } else { + var ctx = ContextualPatch.context(zin); + var result = patch.patchSingle(false, ctx); + + if (!result.getStatus().isSuccess()) { + LOGGER.error("Fialed to patch " + name); + LOGGER.error(" Input: " + input.getAbsolutePath()); + LOGGER.error(" Patches: " + patchesArchive.getAbsolutePath()); + LOGGER.error(" Output: " + output.getAbsolutePath()); + + for (var hunk : result.getHunks()) + LOGGER.error(" Hunk #" + hunk.getHunkID() + ": " + hunk.getStatus().name()); + + Util.sneak(result.getFailure()); + } + for (var itr = ctx.getData().iterator(); itr.hasNext();) { + var line = itr.next(); + zout.write(line.getBytes(StandardCharsets.UTF_8)); + if (itr.hasNext()) + zout.write('\n'); + } + } + zout.closeEntry(); + } + } catch (IOException e) { + Util.sneak(e); + } + } + + // Modern versions that use out Fernflower fork produce stabelized output so we can use + // DiffPatch. But on older verions we need both ACCESS and OFFSET changes at the same time + // And if we tried to do that with DiffPatch we would need to use FUZZY which is TOO Fuzzy. + // it causes patches to be injected half-way and in weird places. + private void patchStable(File input, File patches, File output, File rejects) { + var builder = PatchOperation.builder() + .logTo(LOGGER::error) + .baseInput(MultiInput.archive(ArchiveFormat.ZIP, input.toPath())) + .patchesInput(MultiInput.archive(ArchiveFormat.ZIP, patches.toPath())) + .patchedOutput(MultiOutput.archive(ArchiveFormat.ZIP, output.toPath())) + .rejectsOutput(MultiOutput.archive(ArchiveFormat.ZIP, rejects.toPath())) + .level(LogLevel.ERROR) + .mode(PatchMode.ACCESS) + .aPrefix("/") + .bPrefix("/") + ; + + try { + var result = builder.build().operate(); + + boolean success = result.exit == 0; + if (!success) { + LOGGER.error("Fialed to apply patches"); + LOGGER.error(" Input: " + input.getAbsolutePath()); + LOGGER.error(" Patches: " + patches.getAbsolutePath()); + LOGGER.error(" Output: " + output.getAbsolutePath()); + LOGGER.error(" Rejects: " + rejects.getAbsolutePath()); + if (result.summary != null) + result.summary.print(LOGGER.getError(), true); + else + LOGGER.error("Failed to apply patches, no summary available"); + + throw except("Failed to apply patches, rejects saved to: " + rejects.getAbsolutePath()); + } + } catch (IOException e) { + Util.sneak(e); + } + } + + private Task inject() { + var input = this.mcpChild.getFinalStep(); + var sources = extract("sources.zip"); + var resources = extract("resources.zip"); + return Task.named("inject", + Task.deps(input, sources, resources), + () -> this.inject(input.execute(), sources.execute(), resources.execute()) + ); + } + + private File inject(File input, File sources, File resources) { + var output = new File(this.build, "injected.jar"); + var cache = Util.cache(output) + .add("input", input) + .add("sources", sources) + .add("resources", resources); + + if (Mavenizer.checkCache(output, cache)) + return output; + + try { + FileUtils.mergeJars(output, false, resources, sources, input); + cache.save(); + return output; + } catch (IOException e) { + return Util.sneak(e); + } + } + + /* + private Task mcpPatch() { + var input = decompileCleanup(); + return Task.named("mcp-patch", + Task.deps(input), () -> mcpPatch(input) + ); + } + + private File mcpPatch(Task inputTask) { + var input = inputTask.execute(); + var output = new File(this.build, "mcp-patched.jar"); + var cache = Util.cache(output) + .add("input", input) + .addKnown("mcp", this.getMCP().getDataHash()); + + //if (Mavenizer.checkCache(output, cache)) + // return output; + + var patchesMap = new HashMap>(); + try (var zin = new ZipInputStream(new FileInputStream(this.getMCP().getData()))) { + final var prefix = "patches/minecraft_merged_ff/"; + for (ZipEntry entry; (entry = zin.getNextEntry()) != null;) { + var name = entry.getName(); + if (!name.startsWith(prefix)) + continue; + + int patchIndex = name.indexOf(".patch"); + // 6 is the length of ".patch" + 3 to account for .## at the end of the file. + if (patchIndex < 0 || patchIndex < name.length() - 9) + continue; + + var filename = name.substring(prefix.length(), patchIndex); + var lines = new ArrayList(); + boolean first = true; + @SuppressWarnings("resource") + var reader = new NewLineDetector(new InputStreamReader(zin, StandardCharsets.UTF_8)); + for (String line; (line = reader.readLine()) != null; ) { + // Old patches are generated with the 'diff' comand as the first line + // And DiffPatch doesn't support that so we need to skip it + if (!(first && line.startsWith("diff "))) + lines.add(line); + first = false; + } + + var patch = PatchFile.fromLines(name, lines, true); + + // Some old patches were trimmed, so the last empty line of context was removed. So lets try and detect that case + var last = patch.patches.getLast(); + if (last.getContextLines().size() != last.length1) + last.recalculateLength(); + + System.out.println("Patch: " + name); + patchesMap.computeIfAbsent(filename, _ -> new ArrayList<>()).add(patch); + } + } catch (IOException e) { + return Util.sneak(e); + } + + FileUtils.ensureParent(output); + try (var zin = new ZipInputStream(new FileInputStream(input)); + var zout = new ZipOutputStream(new FileOutputStream(output))) { + + for (ZipEntry entry; (entry = zin.getNextEntry()) != null; ) { + var name = entry.getName(); + var newEntry = new ZipEntry(name); + newEntry.setTime(entry.getTime()); + zout.putNextEntry(newEntry); + + if (!name.endsWith(".java")) { + zin.transferTo(zout); + zout.closeEntry(); + continue; + } + + List lines = NewLineDetector.readLines(zin); + var patches = patchesMap.get(name.replace('/', '.')); + if (patches != null && !patches.isEmpty()) { + List results = null; + boolean success = false; + for (var patch : patches) { + var patcher = new io.codechicken.diffpatch.patch.Patcher(patch, lines, 1.0F, FuzzyLineMatcher.MatchMatrix.DEFAULT_MAX_OFFSET); + results = patcher.patch(PatchMode.FUZZY); // We need to use FUZZ because we inject annotations which causes an OFFSET, and apply ATs which requires ACCESS + success = results.stream().allMatch(r -> r.success); + if (success) { + lines = patcher.lines; + break; + } + } + + if (!success) { + LOGGER.push(); + LOGGER.error("Input: " + input.getAbsolutePath()); + LOGGER.error("Output: " + output.getAbsolutePath()); + LOGGER.error("MCP: " + this.getMCP().getData().getAbsolutePath()); + LOGGER.error("Failed to apply patch to " + name); + for (int x = 0; x < results.size(); x++) + LOGGER.error(" #" + x + ": " + results.get(x).summary()); + LOGGER.pop(); + throw new IllegalStateException("Failed to apply patch to " + name); + } + } + + for (var line : lines) { + zout.write(line.getBytes(StandardCharsets.UTF_8)); + zout.write('\n'); + } + + zout.closeEntry(); + } + } catch (IOException e) { + return Util.sneak(e); + } + + cache.save(); + return output; + } + + private Task decompileCleanup() { + var input = decompile(); + return Task.named("decompile-cleanup", + Task.deps(input), () -> decompileCleanup(input.execute()) + ); + } + + private File decompileCleanup(File input) { + var tool = this.forge.getCache().maven().download(Constants.MCPCLEANUP); + var output = new File(this.build, "decompile-cleaned.jar"); + var log = new File(this.build, "decompile-cleaned.log"); + var cache = Util.cache(output) + .add("tool", tool) + .add("input", input) + .addKnown("fg", this.fgVersion.name()); + + if (Mavenizer.checkCache(output, cache)) + return output; + + var args = new ArrayList(5); + args.addAll(List.of( + "--input", input.getAbsolutePath(), + "--output", output.getAbsolutePath(), + "--pre-patch" + )); + if (this.fgVersion == FGVersion.v2) + args.add("--fernflower"); // Version 2 is before we used our own fernflower fork, so we did a lot of pre-mcp cleanup + + var jdk = jdk(Constants.MCPCLEANUP_JAVA_VERSION); + var ret = ProcessUtils.runJar(jdk, this.globalBase, log, tool, Collections.emptyList(), args); + if (ret.exitCode != 0) + throw new IllegalStateException("Failed to run MCPCleanup (exit code " + ret.exitCode + "), See log: " + log.getAbsolutePath()); + + cache.save(); + return output; + } + */ + + public Map getRuns() { + // Use LinkedHashMap to keep things stable + var serverEnv = new LinkedHashMap(); + serverEnv.put("MCP_TO_SRG", "{mcp_to_srg}"); + serverEnv.put("mainClass", "net.minecraft.launchwrapper.Launch"); + serverEnv.put("MCP_MAPPINGS", "{mcp_mappings}"); + serverEnv.put("FORGE_VERSION", this.getForgeVersion()); + serverEnv.put("FORGE_GROUP", "net.minecraftforge"); + serverEnv.put("MC_VERSION", this.getMinecraftVersion()); + serverEnv.put("tweakClass", "net.minecraftforge.fml.common.launcher.FMLServerTweaker"); + + var clientEnv = new LinkedHashMap<>(serverEnv); + clientEnv.put("tweakClass", "net.minecraftforge.fml.common.launcher.FMLTweaker"); + clientEnv.put("assetIndex", "{asset_index}"); + clientEnv.put("assetDirectory", "{assets_root}"); + clientEnv.put("nativesDirectory", "{natives}"); + + var ret = new LinkedHashMap(); + ret.put("client", run(true, clientEnv)); + ret.put("server", run(false, serverEnv)); + return ret; + } + + private static RunConfig run(boolean client, Map env) { + var ret = new RunConfig(); + if (client) { + ret.name = "client"; + ret.main = "net.minecraftforge.legacydev.MainClient"; + } else { + ret.name = "server"; + ret.main = "net.minecraftforge.legacydev.MainServer"; + } + ret.client = client; + ret.env = env; + return ret; + } +} diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/FGVersion.java b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/FGVersion.java index 7c77d79..5e2f9ff 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/FGVersion.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/FGVersion.java @@ -20,9 +20,23 @@ public enum FGVersion { v1_1("1.1"), v1_2("1.2"), + /** + * Gradle 2.14, Fernflower closed source with some 'fixes', technically lower then '2.0' because we re-used 2.0-SNAPSHOT... + */ + v2_0_1("2.0.1"), + v2_0_2("2.0.2"), + /** + * Gradle 2.5, fernflower closed sourced 'fixed' https://github.com/LexManos/FernFlowerFixer/ + */ v2 ("2.0"), + /** + * Gradle 2.14, fernflower 2.0-SNAPSHOT + */ v2_1("2.1"), v2_2("2.2"), + /** + * ForgeFlower 1.0.342-SNAPSHOT + */ v2_3("2.3"), /** * Gradle 3.9, MCPConfig v1 @@ -75,8 +89,8 @@ private static void forge(FGVersion fg, String forge) { forge(v1_1, "1.7.2-10.12.0.967"); forge(v1_2, "1.7.2-10.12.0.1048"); forge(v2, "1.8-11.14.3.1503"); - //forge(v2_0_1, "1.8-11.14.3.1506"); - //forge(v2_0_2, "1.8-11.14.3.1535"); + forge(v2_0_1, "1.8-11.14.3.1506"); + forge(v2_0_2, "1.8-11.14.3.1535"); forge(v2_1, "1.8.8-11.14.4.1583"); //-1.8.8 forge(v2_2, "1.9.4-12.17.0.1908"); //-1.9.4 forge(v2_3, "1.12-14.21.0.2320"); diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/ForgeRepo.java b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/ForgeRepo.java index eb45770..2403925 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/ForgeRepo.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/ForgeRepo.java @@ -17,11 +17,11 @@ import net.minecraftforge.mcmaven.impl.util.Constants; import net.minecraftforge.mcmaven.impl.Mavenizer; import net.minecraftforge.mcmaven.impl.util.POMBuilder; -import net.minecraftforge.mcmaven.impl.util.POMBuilder.Dependencies; import net.minecraftforge.mcmaven.impl.util.POMBuilder.Dependencies.Dependency; import net.minecraftforge.mcmaven.impl.util.Task; import net.minecraftforge.mcmaven.impl.util.Util; import net.minecraftforge.util.data.json.JsonData; +import net.minecraftforge.util.data.json.RunConfig; import net.minecraftforge.util.file.FileUtils; import static net.minecraftforge.mcmaven.impl.Mavenizer.LOGGER; @@ -40,6 +40,7 @@ import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -96,13 +97,28 @@ public List process(Artifact artifact, Mappings mappings, Map2.3 artifacts + /// + /// We need to generate the following artifacts: + /// - `net.minecraftforge:forge:{version}` + /// - default: + /// - The default jar contains the recompiled class files, patcher assets + /// - sources: + /// - Source files used to recompile the default jar. + /// - metadata.zip: + /// - Metadata about the version, such as runs.json and version.json. + /// - `net.minecraft:{mcp-version}:client` + /// - extra: + /// - This is the client jar file with class files removed. This is for legacy versions which expect it to + /// exist. + /// - `net.minecraft:mappings_{CHANNEL}:{MCP_VERSION}[-{VERSION}]@zip` + /// - A zip file containing fields, methods, and params.csv files mapping SRG->MCP names. + private List processV2(String version, Mappings baseMappings, Map> outputJson, FGVersion fgVersion) { + var name = Artifact.from(Constants.FORGE_GROUP, Constants.FORGE_NAME, version); + var userdev = getUserdev(version); + + var build = new File(this.cache.root(), "forge/" + userdev.getFolder()); + var jdks = this.cache.jdks(); + + var dev = new FG2Userdev(build, this, userdev, fgVersion); + var mcVersion = Util.forgeToMcVersion(version); + var srgTask = dev.getMCP().getMappings(); + var srgSources = dev.getSources(); + + var mappings = baseMappings.withContext(dev); + var sourcesTask = new RenameTask(build, userdev.getName(), srgSources, mappings, true, srgTask, mcVersion); + var classesTask = new RecompileTask(build, name, jdks, dev.getJavaTarget(), dev::getClasspath, sourcesTask, mappings); + + var mappingCoords = mappings.getArtifact(); + + var mappingArtifacts = mappingArtifacts(build, mappings, mcVersion, outputJson); + + var sources = pending("Sources", sourcesTask, name.withClassifier("sources"), true, sourceVariant(baseMappings)); + var classes = pending("Classes", classesTask, name, false, () -> classVariants(baseMappings, dev, mappingCoords)); + var metadata = pending("Metadata", metadata(build, dev, dev.getRuns()), name.withClassifier("metadata").withExtension("zip"), false, metadataVariant()); + + var pom = pending("Maven POM", pom(mappings.getFolder(build), dev, version, null /*extraCoords*/, mappingCoords), name.withExtension("pom"), false); + + // Gradle only allows downloading artifacts from one repo, so we need to pull in any classifers that we reference + var classifiers = getClassifieres(name, dev.getLibraries(), new HashMap<>()); + + addJsonData(outputJson, dev); + + var ret = new ArrayList(); + ret.addAll(mappingArtifacts); + //ret.addAll(extraOutput); + ret.addAll(List.of(sources, classes, pom, metadata)); + ret.addAll(classifiers.values()); + return ret; + } + + /// This handles UserDev3 artifacts, which are anything created using FG 3->6 /// /// We need to generate the following artifacts: @@ -142,69 +214,45 @@ private static Artifact getUserdev(String forge) { /// - pom: /// - Standard maven pom file that contains all dependency information. // Made this an MD comment to make it easier to read in IDE - Jonathan - private List processV3(String version, Mappings mappings, Map> outputJson) { + private List processV3(String version, Mappings baseMappings, Map> outputJson) { var name = Artifact.from(Constants.FORGE_GROUP, Constants.FORGE_NAME, version); var userdev = getUserdev(version); var build = new File(this.cache.root(), "forge/" + userdev.getFolder()); + var jdks = this.cache.jdks(); var patcher = new Patcher(build, this, userdev); var joined = patcher.getMCP().getSide(MCPSide.JOINED); - var sourcesTask = new RenameTask(build, userdev.getName(), joined, patcher.get(), mappings, true); - var recompile = new RecompileTask(build, name, patcher.getMCP(), patcher::getClasspath, sourcesTask, mappings); + var mcVersion = joined.getMCP().getMinecraftTasks().getVersion(); + var mappings = baseMappings.withContext(joined); + var srgTask = joined.getTasks().getMappings(); + + var sourcesTask = new RenameTask(build, userdev.getName(), patcher.get(), mappings, true, srgTask, mcVersion); + var recompile = new RecompileTask(build, name, jdks, patcher.getJavaTarget(), patcher::getClasspath, sourcesTask, mappings); var classesTask = new InjectTask(build, this.cache, name, patcher, recompile, mappings); var extraCoords = Artifact.from(Constants.MC_GROUP, Constants.MC_CLIENT + "-extra", patcher.getMCP().getName().getVersion()); // If we are not obfuscated, don't add the csv zip as a extra artifact - var mappingCoords = patcher.isObfuscated() ? mappings.getArtifact(joined) : null; + var mappingCoords = patcher.isObfuscated() ? mappings.getArtifact() : null; - var mappingArtifacts = mappingArtifacts(build, mappings, joined, outputJson); + var mappingArtifacts = mappingArtifacts(build, mappings, mcVersion, outputJson); + var mappingFolder = mappingCoords == null ? build : mappings.getFolder(build); - var sources = pending("Sources", sourcesTask, name.withClassifier("sources"), true, sourceVariant(mappings)); - var classes = pending("Classes", classesTask, name, false, () -> classVariants(mappings, patcher, extraCoords, mappingCoords)); - var metadata = pending("Metadata", metadata(build, patcher), name.withClassifier("metadata").withExtension("zip"), false, metadataVariant()); + var sources = pending("Sources", sourcesTask, name.withClassifier("sources"), true, sourceVariant(baseMappings)); + var classes = pending("Classes", classesTask, name, false, () -> classVariants(baseMappings, patcher, extraCoords, mappingCoords)); + var metadata = pending("Metadata", metadata(build, patcher, patcher.config.runs), name.withClassifier("metadata").withExtension("zip"), false, metadataVariant()); - var pom = pending("Maven POM", pom(build, patcher, version, extraCoords, mappingCoords), name.withExtension("pom"), false); + var pom = pending("Maven POM", pom(mappingFolder, patcher, version, extraCoords, mappingCoords), name.withExtension("pom"), false); var extraOutput = this.mcpconfig.processExtra(Constants.MC_GROUP + ':' + Constants.MC_CLIENT, patcher.getMCP().getName().getVersion()); // Gradle only allows downloading artifacts from one repo, so we need to pull in any classifers that we reference var classifiers = new HashMap(); - for (var parent : patcher.getStack()) { - for (var artifact : parent.getArtifacts()) { - if (!name.getGroup().equals(artifact.getGroup()) - || !name.getName().equals(artifact.getName()) - || !name.getVersion().equals(artifact.getVersion()) - || classifiers.containsKey(artifact) - ) - continue; - // Classifers can not have variants in gradle, so we just need to download them - classifiers.put(artifact, - pending("Classifier-" + artifact.getClassifier(), - Task.named( - "classifier[" + artifact.getClassifier() + '@' + artifact.getExtension() + ']', - () -> this.cache.maven().download(artifact) - ), - artifact, - false - ) - ); - } - } + for (var parent : patcher.getStack()) + getClassifieres(name, parent.getLibraries(), classifiers); - // Add some extra metadata for FG to consume: - if (outputJson != null) { - outputJson.put("mcp.version", patcher.getMCP().getName()::getVersion); - outputJson.put("mcp.artifact", patcher.getMCP().getName()::toString); - outputJson.put("mc.version", patcher.getMCP().getMinecraftTasks()::getVersion); - - var modules = patcher.config.modules; - if (modules == null || modules.isEmpty()) - outputJson.put("patcher.modules", () -> ""); - else - outputJson.put("patcher.modules", () -> String.join(",", modules)); - } + addJsonData(outputJson, patcher); var ret = new ArrayList(); ret.addAll(mappingArtifacts); @@ -214,8 +262,49 @@ private List processV3(String version, Mappings mappings, Map { + // Gradle only allows downloading artifacts from one repo, so we need to pull in any classifers that we reference + private Map getClassifieres(Artifact name, Collection artifacts, Map classifiers) { + for (var artifact : artifacts) { + if (!name.getGroup().equals(artifact.getGroup()) + || !name.getName().equals(artifact.getName()) + || !name.getVersion().equals(artifact.getVersion()) + || classifiers.containsKey(artifact) + ) + continue; + // Classifers can not have variants in gradle, so we just need to download them + classifiers.put(artifact, + pending("Classifier-" + artifact.getClassifier(), + Task.named( + "classifier[" + artifact.getClassifier() + '@' + artifact.getExtension() + ']', + () -> this.cache.maven().download(artifact) + ), + artifact, + false + ) + ); + } + return classifiers; + } + + private void addJsonData(@Nullable Map> outputJson, ForgeVersionCommon info) { + // Add some extra metadata for FG to consume: + if (outputJson == null) + return; + + var mcp = info.getMCPArtifact(); + outputJson.put("mcp.version", mcp::getVersion); + outputJson.put("mcp.artifact", mcp::toString); + outputJson.put("mc.version", info::getMinecraftVersion); + + var modules = info.getModules(); + if (modules == null || modules.isEmpty()) + outputJson.put("patcher.modules", () -> ""); + else + outputJson.put("patcher.modules", () -> String.join(",", modules)); + } + + private static Task metadata(File build, ForgeVersionCommon forge, Map runs) { + return Task.named("metadata[forge]", Task.deps(forge.getMinecraftTasks().versionJson), () -> { var output = new File(build, "metadata.zip"); // metadata @@ -224,17 +313,19 @@ private static Task metadata(File build, Patcher patcher) { // metadata/launcher var launcherDir = new File(metadataDir, "launcher"); - var runsJsonStr = JsonData.toJson(patcher.config.runs); + var runsJsonStr = JsonData.toJson(runs); // metadata/minecraft var minecraftDir = new File(metadataDir, "minecraft"); - var versionJson = patcher.getMCP().getMinecraftTasks().versionJson.execute(); + var versionJson = forge.getMinecraftTasks().versionJson.execute(); var cache = Util.cache(output) - .add(versionJson) - .add(versionProperties) - .add("data", patcher.getDataHash()) + .add("json", versionJson) + .add("properties", versionProperties) + .add("runs", runsJsonStr) + .addKnown("data", forge.getDataHash()) .addKnown("version", "1"); + if (Mavenizer.checkCache(output, cache)) return output; @@ -262,7 +353,6 @@ private static Task metadata(File build, Patcher patcher) { new File(minecraftDir, "version.json").toPath(), StandardCopyOption.REPLACE_EXISTING ); - cache.add(versionProperties); // metadata.zip FileUtils.makeZip(metadataDir, output); @@ -275,11 +365,12 @@ private static Task metadata(File build, Patcher patcher) { }); } - private static Task pom(File build, Patcher patcher, String version, @Nullable Artifact clientExtra, @Nullable Artifact mappings) { + private static Task pom(File build, ForgeVersionCommon forge, String version, @Nullable Artifact clientExtra, @Nullable Artifact mappings) { return Task.named("pom[forge]", () -> { var output = new File(build, "forge.pom"); + var cache = Util.cache(output) - .addKnown("data", patcher.getDataHash()) + .addKnown("data", forge.getDataHash()) .addKnown("code-version", "1"); if (clientExtra != null) @@ -291,16 +382,23 @@ private static Task pom(File build, Patcher patcher, String version, @Nullable A if (Mavenizer.checkCache(output, cache)) return output; - var builder = new POMBuilder("net.minecraftforge", "forge", version).preferGradleModule().dependencies(dependencies -> { + var builder = new POMBuilder(Constants.FORGE_GROUP, Constants.FORGE_NAME, version).preferGradleModule().dependencies(dependencies -> { if (clientExtra != null) dependencies.add(clientExtra); if (mappings != null) dependencies.add(mappings); - patcher.forAllLibraries(dependencies::add, Artifact::hasNoOs); + forge.forAllLibraries(dependencies::add, Artifact::hasNoOs); + + // Following https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#dependency-scope + // PROVIDED == 'compileOnly' + for (var descriptor : forge.getCompileOnly()) + dependencies.add(Artifact.from(descriptor), Dependency.Scope.PROVIDED); - addExtraRuntimeToPom(patcher, dependencies); + // RUNTIME = 'runtimeOnly' + for (var descriptor : forge.getRuntimeOnly()) + dependencies.add(Artifact.from(descriptor), Dependency.Scope.RUNTIME); }); FileUtils.ensureParent(output); @@ -316,47 +414,18 @@ private static Task pom(File build, Patcher patcher, String version, @Nullable A } @SuppressWarnings("deprecation") - private static void addExtraRuntimeToPom(Patcher patcher, Dependencies deps) { - if (patcher.config.extraDependencies == null) - return; - - // Following https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#dependency-scope - // PROVIDED == 'compileOnly' - if (patcher.config.extraDependencies.compileOnly != null) { - for (var descriptor : patcher.config.extraDependencies.compileOnly) { - deps.add(Artifact.from(descriptor), Dependency.Scope.PROVIDED); - } - } - - // RUNTIME = 'runtimeOnly' - if (patcher.config.extraDependencies.runtimeOnly != null) { - for (var descriptor : patcher.config.extraDependencies.runtimeOnly) { - deps.add(Artifact.from(descriptor), Dependency.Scope.RUNTIME); - } - } - } - - @SuppressWarnings("deprecation") - protected GradleModule.Variant[] classVariants(Mappings mappings, Patcher patcher, Artifact... extraDeps) { - var extra = new ArrayList<>(Arrays.asList(extraDeps)); - extra.addAll(patcher.getArtifacts()); + protected GradleModule.Variant[] classVariants(Mappings mappings, ForgeVersionCommon patcher, Artifact... extraDeps) { + var libs = new ArrayList<>(Arrays.asList(extraDeps)); + patcher.forAllLibraries(libs::add); var extraCompile = new ArrayList(); + for (var descriptor : patcher.getCompileOnly()) + extraCompile.add(Artifact.from(descriptor)); var extraRuntime = new ArrayList(); - if (patcher.config.extraDependencies != null) { - if (patcher.config.extraDependencies.compileOnly != null) { - for (var descriptor : patcher.config.extraDependencies.compileOnly) { - extraCompile.add(Artifact.from(descriptor)); - } - } + for (var descriptor : patcher.getRuntimeOnly()) + extraRuntime.add(Artifact.from(descriptor)); - if (patcher.config.extraDependencies.runtimeOnly != null) { - for (var descriptor : patcher.config.extraDependencies.runtimeOnly) { - extraRuntime.add(Artifact.from(descriptor)); - } - } - } - return super.classVariants(mappings, patcher.getMCPSide(), extra, extraCompile, extraRuntime); + return super.classVariants(mappings, patcher.getMinecraftTasks().getJavaVersion(), libs, extraCompile); } } diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/ForgeVersionCommon.java b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/ForgeVersionCommon.java new file mode 100644 index 0000000..407d187 --- /dev/null +++ b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/ForgeVersionCommon.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ +package net.minecraftforge.mcmaven.impl.repo.forge; + +import java.io.File; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Predicate; + +import org.jetbrains.annotations.Nullable; + +import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MinecraftTasks; +import net.minecraftforge.mcmaven.impl.util.Artifact; + +public interface ForgeVersionCommon { + String getDataHash(); + + int getJavaTarget(); + List getClasspath(); + + Artifact getMCPArtifact(); + String getMinecraftVersion(); + @Nullable List getModules(); + + default void forAllLibraries(Consumer consumer) { + this.forAllLibraries(consumer, null); + } + void forAllLibraries(Consumer consumer, Predicate filter); + List getLibraries(); + List getCompileOnly(); + List getRuntimeOnly(); + + MinecraftTasks getMinecraftTasks(); +} \ No newline at end of file diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/InjectTask.java b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/InjectTask.java index da8e216..3f117a6 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/InjectTask.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/InjectTask.java @@ -6,7 +6,7 @@ import net.minecraftforge.mcmaven.impl.Mavenizer; import net.minecraftforge.mcmaven.impl.cache.Cache; -import net.minecraftforge.mcmaven.impl.mappings.Mappings; +import net.minecraftforge.mcmaven.impl.mappings.ResolvedMappings; import net.minecraftforge.mcmaven.impl.util.Artifact; import net.minecraftforge.util.file.FileUtils; import net.minecraftforge.mcmaven.impl.util.Task; @@ -24,10 +24,10 @@ public final class InjectTask implements Task { private final Artifact name; private final Cache cache; private final Patcher patcher; - private final Mappings mappings; + private final ResolvedMappings mappings; private final Task task; - InjectTask(File build, Cache cache, Artifact name, Patcher patcher, Task input, Mappings mappings) { + InjectTask(File build, Cache cache, Artifact name, Patcher patcher, Task input, ResolvedMappings mappings) { this.build = mappings.getFolder(build); this.name = name; this.cache = cache; diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/Patcher.java b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/Patcher.java index 0421c5d..d5ce32e 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/Patcher.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/Patcher.java @@ -6,7 +6,6 @@ import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -40,6 +39,7 @@ import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCP; import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCPConfigRepo; import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCPSide; +import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MinecraftTasks; import net.minecraftforge.mcmaven.impl.util.Artifact; import net.minecraftforge.mcmaven.impl.util.Constants; import net.minecraftforge.util.data.json.JsonData; @@ -63,7 +63,7 @@ * These files may or may not be in SRG names, depending on the patcher configuration. * But the point is this creates source code. */ -public class Patcher implements Supplier { +public class Patcher implements Supplier, ForgeVersionCommon { private final File build; private final ForgeRepo forge; private final Artifact name; @@ -246,14 +246,16 @@ public String getDataHash() { return this.dataHash; } - public void forAllLibraries(Consumer consumer) { - this.forAllLibraries(consumer, null); + @Override + public int getJavaTarget() { + return this.getMCP().getConfig().java_target; } + @Override public void forAllLibraries(Consumer consumer, Predicate filter) { this.forAllLibrariesInternal(consumer, filter, this.getMCPSide().getMCLibraries()); this.forAllLibrariesInternal(consumer, filter, this.getMCPSide().getMCPConfigLibraries()); - this.forAllLibrariesInternal(consumer, filter, this.getArtifacts()); + this.forAllLibrariesInternal(consumer, filter, this.getLibraries()); } // to avoid duplicate code @@ -264,7 +266,8 @@ private void forAllLibrariesInternal(Consumer consumer, @Nulla } } - public List getArtifacts() { + @Override + public List getLibraries() { // TODO MOVE ALL THIS LOGIC TO SOME SORT OF "ARTIFACT LIST GENERATOR" IN ARTIFACT.JAVA var artifacts = new ArrayList() /*{ private final Set distincts = new HashSet<>(); @@ -303,12 +306,14 @@ boolean is(Artifact artifact) { for (var lib : this.config.libraries) { var artifact = Artifact.from(lib); artifact = StupidHacks.fixLegacyForgeDeps(artifact); - artifacts.add(artifact); + if (artifact != null) + artifacts.add(artifact); } return artifacts; } + @Override public List getClasspath() { var classpath = new ArrayList(); @@ -320,37 +325,16 @@ public List getClasspath() { var cache = this.forge.getCache(); for (var lib : this.getMCP().getConfig().getLibraries(MCPSide.JOINED)) { - classpath.add(getArtifact(cache, lib)); + classpath.add(Util.getArtifact(cache, lib)); } for (var lib : this.config.libraries) { - classpath.add(getArtifact(cache, lib)); + classpath.add(Util.getArtifact(cache, lib)); } return classpath; } - private static final File getArtifact(Cache cache, String coords) { - var artifact = Artifact.from(coords); - // Some libraries are on Minecraft's maven. Such as launchwrapper. - // Rather then configure Forge's server to proxy Mojang's I add this check. - if ("net.minecraft".equals(artifact.getGroup())) - return cache.minecraft().download(artifact); - try { - return cache.maven().download(artifact); - } catch (Exception e) { - // If its 404 on Forge's maven, try Mojang's - if (e.getCause() instanceof FileNotFoundException) { - try { - return cache.minecraft().download(artifact); - } catch (Exception e2) { - e.addSuppressed(e2); - } - } - return Util.sneak(e); - } - } - /** @return The final unnamed sources */ public Task get() { return this.last; @@ -389,7 +373,7 @@ private File extractJoinedFiles(String filename, List files) { var output = new File(this.build, filename); var cache = Util.cache(output); - cache.add("data", this.data); + cache.addKnown("data", this.dataHash); if (Mavenizer.checkCache(output, cache)) return output; @@ -435,7 +419,7 @@ private File extractSingleTask(String key, String value) { var target = new File(this.build, "data/" + key + '/' + filename); var cache = Util.cache(target); - cache.add("data", this.data); + cache.addKnown("data", this.dataHash); if (Mavenizer.checkCache(target, cache)) return target; @@ -566,7 +550,7 @@ private File postProcess(Task inputTask, PatcherConfig.V2.DataFunction data, Fil var tool = maven.download(toolA); var cache = Util.cache(output); - cache.add("data", this.data); + cache.addKnown("data", this.dataHash); cache.add("tool", tool); cache.add("input", input); cache.add("jvm-args", data.getJvmArgs().stream().collect(Collectors.joining(" "))); @@ -623,7 +607,7 @@ private File patch(Task inputTask, File output, File rejects) { var cache = Util.cache(output); cache.add("input", input); - cache.add("data", this.data); + cache.addKnown("data", this.dataHash); if (Mavenizer.checkCache(output, cache)) return output; @@ -798,4 +782,40 @@ record Info(File file, Artifact artifact, Predicate filter, Function getModules() { + return this.config.modules; + } + + @SuppressWarnings("deprecation") + @Override + public List getCompileOnly() { + if (this.config.extraDependencies == null || this.config.extraDependencies.compileOnly == null) + return Collections.emptyList(); + return this.config.extraDependencies.compileOnly; + } + + @SuppressWarnings("deprecation") + @Override + public List getRuntimeOnly() { + if (this.config.extraDependencies == null || this.config.extraDependencies.runtimeOnly == null) + return Collections.emptyList(); + return this.config.extraDependencies.runtimeOnly; + } + + @Override + public MinecraftTasks getMinecraftTasks() { + return this.getMCP().getMinecraftTasks(); + } } \ No newline at end of file diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MCPConfigRepo.java b/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MCPConfigRepo.java index 5d192ff..3dfa28d 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MCPConfigRepo.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MCPConfigRepo.java @@ -12,6 +12,7 @@ import net.minecraftforge.mcmaven.impl.cache.Cache; import net.minecraftforge.mcmaven.impl.data.GradleModule; import net.minecraftforge.mcmaven.impl.mappings.Mappings; +import net.minecraftforge.mcmaven.impl.mappings.ResolvedMappings; import net.minecraftforge.mcmaven.impl.util.Artifact; import net.minecraftforge.mcmaven.impl.util.ComparableVersion; import net.minecraftforge.mcmaven.impl.util.Constants; @@ -24,6 +25,9 @@ import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; + +import org.jetbrains.annotations.Nullable; + import java.io.File; import java.io.FileOutputStream; import java.io.FileWriter; @@ -69,6 +73,8 @@ // TODO [MCMavenizer][Documentation] Document public final class MCPConfigRepo extends Repo { private final Map versions = new HashMap<>(); + private record LegacyKey(Artifact artifact, @Nullable String python) {} + private final Map legacy = new HashMap<>(); private final Map mcTasks = new HashMap<>(); private final Task downloadLauncherManifest = Task.named("downloadLauncherManifest", this::downloadLauncherManifest); @@ -91,6 +97,16 @@ private MCP download(Artifact artifact) { return new MCP(this, artifact); } + public MCPLegacy legacy(String mcVersion, @Nullable String python) { + var artifact = MCPLegacy.artifact(mcVersion); + var key = new LegacyKey(artifact, python); + return this.legacy.computeIfAbsent(key, this::downloadLegacy); + } + + private MCPLegacy downloadLegacy(LegacyKey key) { + return new MCPLegacy(this, key.artifact(), key.python()); + } + public MinecraftTasks getMCTasks(String version) { return this.mcTasks.computeIfAbsent(version, _ -> new MinecraftTasks(this.cache, version, this.downloadLauncherManifest)); } @@ -140,7 +156,7 @@ private void validate(Artifact artifact) { } @Override - public List process(Artifact artifact, Mappings mappings, Map> outputJson) { + public List process(Artifact artifact, Mappings baseMappings, Map> outputJson) { validate(artifact); var version = artifact.getVersion(); @@ -154,6 +170,7 @@ public List process(Artifact artifact, Mappings mappings, Map process(Artifact artifact, Mappings mappings, Map classVariants(mappings, mcpSide)), + pom.withVariants(() -> classVariants(baseMappings, mcpSide)), metadata ); } - var mappingArtifacts = mappingArtifacts(build, mappings, mcpSide, outputJson); + var mappingArtifacts = mappingArtifacts(build, mappings, mcVersion, outputJson); if (isMappings) return mappingArtifacts; return switch (mappings.channel()) { - case "notch" -> List.of(pending("Classes", mcpTasks.getRawJar(), name.withClassifier("raw"), false, simpleVariant("obf-notch", new Mappings("notch", null)))); - case "srg", "searge" -> List.of(pending("Classes", mcpTasks.getSrgJar(), name.withClassifier("srg"), false, simpleVariant("obf-searge", new Mappings("searge", null)))); + case "notch" -> List.of(pending("Classes", mcpTasks.getRawJar(), name.withClassifier("raw"), false, simpleVariant("obf-notch", "notch", null))); + case "srg", "searge" -> List.of(pending("Classes", mcpTasks.getSrgJar(), name.withClassifier("srg"), false, simpleVariant("obf-searge", "searge", null))); default -> { var pending = new ArrayList(); + var srgTask = mcpSide.getTasks().getMappings(); + var jdks = this.getCache().jdks(); + var javaTarget = mcpSide.getMCP().getConfig().java_target; - var sourcesTask = new RenameTask(build, name.getName(), mcpSide, mcpSide.getSources(), mappings, true); - var recompile = new RecompileTask(build, name, mcpSide.getMCP(), mcpSide::getClasspath, sourcesTask, mappings); + var sourcesTask = new RenameTask(build, name.getName(), mcpSide.getSources(), mappings, true, srgTask, mcVersion); + var recompile = new RecompileTask(build, name, jdks, javaTarget, mcpSide::getClasspath, sourcesTask, mappings); var classesTask = mergeExtra(build, side, recompile, mcpSide.getTasks().getExtra(), mappings); - var sources = pending("Sources", sourcesTask, name.withClassifier("sources"), true, sourceVariant(mappings)); - var classes = pending("Classes", classesTask, name, false, () -> classVariants(mappings, mcpSide)); + var sources = pending("Sources", sourcesTask, name.withClassifier("sources"), true, sourceVariant(baseMappings)); + var classes = pending("Classes", classesTask, name, false, () -> classVariants(baseMappings, mcpSide)); pending.addAll(List.of( sources, classes, metadata, pom @@ -232,7 +254,7 @@ public List processWithoutMcp(Artifact artifact, Mappings mappi //net.minecraft:mappings_{CHANNEL}:{MCP_VERSION}[-{VERSION}]@zip var mapCoords = Artifact.from(Constants.MC_GROUP, "mappings_official", artifact.getVersion()).withExtension("zip"); - var mapPom = pending("Mappings POM", simplePom(cache, mapCoords), mapCoords.withExtension("pom"), false); + var mapPom = pending("Mappings POM", simplePom(mappings.getFolder(cache), mapCoords), mapCoords.withExtension("pom"), false); var m2o = pending("Mappings map2obf", tasks.mergeMappings(), mapCoords.withClassifier("map2obf").withExtension("tsrg.gz"), false); if (outputJson != null) { @@ -277,7 +299,7 @@ public List processExtra(String module, String version) { } // TODO [MCMavenizer][client-extra] Band-aid fix for merging for clean! Remove later. - private static Task mergeExtra(File build, String side, Task recompiled, Task extra, Mappings mappings) { + private static Task mergeExtra(File build, String side, Task recompiled, Task extra, ResolvedMappings mappings) { return Task.named("mergeExtra[" + side + "][" + mappings + ']', Task.deps(extra, recompiled), () -> { var output = new File(mappings.getFolder(build), "recompiled-extra.jar"); var recompiledF = recompiled.execute(); @@ -395,7 +417,7 @@ private GradleModule.Variant[] classVariantsClient(Mappings mappings, MinecraftT var json = JsonData.minecraftVersion(tasks.versionJson.execute()); for (var lib : json.getLibs()) deps.add(Artifact.from(lib.coord).withOS(lib.os)); - return classVariants(mappings, tasks.versionJson, deps, List.of()); + return classVariants(mappings, tasks.getJavaVersion(), deps, List.of()); } private GradleModule.Variant[] classVariantsServer(Mappings mappings, MinecraftTasks tasks) { @@ -412,6 +434,6 @@ private GradleModule.Variant[] classVariantsServer(Mappings mappings, MinecraftT } catch (IOException e) { return Util.sneak(e); } - return classVariants(mappings, tasks.versionJson, deps, List.of()); + return classVariants(mappings, tasks.getJavaVersion(), deps, List.of()); } } diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MCPLegacy.java b/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MCPLegacy.java new file mode 100644 index 0000000..9ea0c7a --- /dev/null +++ b/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MCPLegacy.java @@ -0,0 +1,1033 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ +package net.minecraftforge.mcmaven.impl.repo.mcpconfig; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +import org.jetbrains.annotations.Nullable; + +import com.google.gson.reflect.TypeToken; + +import net.minecraftforge.mcmaven.impl.Mavenizer; +import net.minecraftforge.mcmaven.impl.cache.JDKCache; +import net.minecraftforge.mcmaven.impl.cache.MavenCache; +import net.minecraftforge.mcmaven.impl.repo.forge.FGVersion; +import net.minecraftforge.mcmaven.impl.repo.forge.FG2Userdev; +import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MinecraftTasks.MCFile; +import net.minecraftforge.mcmaven.impl.util.Artifact; +import net.minecraftforge.mcmaven.impl.util.ComparableVersion; +import net.minecraftforge.mcmaven.impl.util.Constants; +import net.minecraftforge.mcmaven.impl.util.ProcessUtils; +import net.minecraftforge.mcmaven.impl.util.StupidHacks; +import net.minecraftforge.mcmaven.impl.util.Task; +import net.minecraftforge.mcmaven.impl.util.Util; +import net.minecraftforge.srgutils.IMappingFile; +import net.minecraftforge.util.data.json.JsonData; +import net.minecraftforge.util.download.DownloadUtils; +import net.minecraftforge.util.file.FileUtils; +import net.minecraftforge.util.hash.HashFunction; + +public class MCPLegacy { + public static Artifact artifact(String version) { + return Artifact.from("de.oceanlabs.mcp", "mcp", version, "srg", "zip"); + } + + private final MCPConfigRepo repo; + private final Artifact name; + private final boolean isPython; + private final String mcVersion; + private final File data; + private final String dataHash; + private final File build; + private final MinecraftTasks mcTasks; + + private final Map children = new HashMap<>(); + private final Map extracts = new HashMap<>(); + private final Task mappings; + private final Task exceptorJson; + private final Task exceptorConfig; + private final Task statics; + private final Task listLibraries; + + private Child child; + + public MCPLegacy(MCPConfigRepo repo, Artifact name, @Nullable String python) { + this.repo = repo; + this.isPython = python != null; + this.mcVersion = name.getVersion(); + + if (python != null) { + this.name = name.withVersion(name.getVersion() + '-' + python.replace(".zip", "")).withClassifier(null); + this.data = new File(this.repo.getCache().root(), "mcp/legacy/" + python); + if (!this.data.exists()) { + if (!DownloadUtils.tryDownloadFile(this.data, Constants.MCP_DOWNLOADS + python)) { + if (this.data.exists()) + this.data.delete(); + throw new IllegalStateException("Failed to download legacy MCP " + Constants.MCP_DOWNLOADS + python); + } + } + } else { + this.name = name; + this.data = this.repo.getCache().maven().download(name); + if (!this.data.exists()) + throw new IllegalStateException("Failed to download " + name); + } + + this.dataHash = Util.sneak(() -> HashFunction.sha1().hash(this.data)); + + this.build = new File(this.repo.getCache().root(), "mcp/" + this.name.getFolder()); + this.mcTasks = this.repo.getMCTasks(this.mcVersion); // Legacy stuff, there is no variant so use the full version + + // TODO: [Mavenizer] Maybe write a task to convert the old python to newer formated artifacts? + var prefix = this.isPython ? "conf/" : ""; + this.mappings = extract(prefix + "joined.srg"); + this.exceptorJson = extract(prefix + "exceptor.json"); // Possibly non-existant? + this.exceptorConfig = extract(prefix + "joined.exc"); + this.statics = extract(this.isPython ? "conf/STATIC_METHODS.txt" : "static_methods.txt"); // may not exist + this.listLibraries = listLibraries(); + } + + public Artifact getName() { + return this.name; + } + + public MinecraftTasks getMinecraftTasks() { + return this.mcTasks; + } + + public File getData() { + return this.data; + } + + public String getDataHash() { + return this.dataHash; + } + + public Task getMappings() { + return this.mappings; + } + + public Task getExceptorJson() { + return this.exceptorJson; + } + + public Child getChild() { + if (this.child == null) + this.child = new Child(this.build, null, FGVersion.v2_3); + return this.child; + } + + public Child getChild(FG2Userdev forge) { + var accessTransformer = forge.extract("merged_at.cfg").execute(); + var hash = Util.hash(HashFunction.sha1(), accessTransformer); + var key = forge.getName().getName() + '/' + hash; + var ret = this.children.get(key); + if (ret == null) { + var base = new File(this.build, key); + ret = new Child(base, accessTransformer, forge.getGradleVersion()); + this.children.put(key, ret); + } + return ret; + } + + private String prefix() { + return '[' + this.name.getVersion() + ']'; + } + + private Task extract(String name) { + var ret = this.extracts.get(name); + if (ret == null) { + ret = Task.named(prefix() + "extract[" + name + ']', () -> extractImpl(name)); + this.extracts.put(name, ret); + } + return ret; + } + + private File extractImpl(String name) { + var target = new File(this.build, name); + var cache = Util.cache(target) + .addKnown("data", dataHash); + + if (Mavenizer.checkCache(target, cache)) + return target; + + try (var zip = new ZipFile(data)) { + var entry = zip.getEntry(name); + if (entry == null) + throw except("Missing Data: " + name); + + FileUtils.ensureParent(target); + + try (var os = new FileOutputStream(target)) { + zip.getInputStream(entry).transferTo(os); + } + + target.setLastModified(entry.getLastModifiedTime().toMillis()); + + cache.save(); + return target; + } catch (IOException e) { + throw except("Failed to extract `" + name + '`', e); + } + } + + private RuntimeException except(String message) { + return new IllegalArgumentException("Invalid MCP Dependency: " + this.name + " - " + message); + } + private RuntimeException except(String message, Throwable t) { + return new IllegalArgumentException("Invalid MCP Dependency: " + this.name + " - " + message, t); + } + + private Task listLibraries() { + var json = this.getMinecraftTasks().versionJson; + var output = new File(this.build, "libraries.txt"); + var maven = this.repo.getCache().minecraft(); + return Task.named(prefix() + "list-libraries", + Task.deps(json), () -> MCPTaskFactory.listLibraries(json, output, maven) + ); + } + + private Task exceptorConfig() { + var base = this.exceptorConfig; + var statics = this.statics; + var mappings = getMappings(); + return Task.named(prefix() + "exceptor-config", + Task.deps(base, statics, mappings), () -> + exceptorConfig(base.execute(), statics.execute(), mappings.execute()) + ); + } + + private File exceptorConfig(File base, File statics, File mappings) { + var output = new File(this.build, "exceptor.exc"); + var cache = Util.cache(output) + .add("base", base) + .add("statics", statics) + .add("mappings", mappings); + + if (Mavenizer.checkCache(output, cache)) + return output; + + var excMap = new HashMap(); + try (var reader = new BufferedReader(new FileReader(base, StandardCharsets.UTF_8))) { + for (String line; (line = reader.readLine()) != null; ) { + if (line.startsWith("#")) + excMap.put(line, null); + else { + var pts = line.split("=", 2); + excMap.put(pts[0], pts[1]); + } + } + } catch (IOException e) { + return Util.sneak(e); + } + + var staticsSet = new HashSet(); + try (var reader = new BufferedReader(new FileReader(statics, StandardCharsets.UTF_8))) { + for (String line; (line = reader.readLine()) != null; ) + staticsSet.add(line); + } catch (IOException e) { + return Util.sneak(e); + } + + // Generate default exc lines from srg + try { + var map = IMappingFile.load(mappings); + for (var cls : map.getClasses()) { + for (var mtd : cls.getMethods()) { + var name = mtd.getMapped(); + if (!name.startsWith("func_")) + continue; + + var prefix = "p_" + name.split("_", 3)[1]; + var args = new ArrayList(); + int idx = staticsSet.contains(name) ? 0 : 1; // Static methods don't have 'this' + + var chrs = mtd.getDescriptor().toCharArray(); + for (int x = 1; x < chrs.length; x++) { + var c = chrs[x]; + if (c == ')') + break; + + // its prefix + bytecode index, so need to double increment for doubles/longs + args.add(prefix + "_" + idx++ + "_"); + switch (c) { + case '[': // Arrays + while (x < chrs.length && chrs[x] == '[') + x++; + if (chrs[x] == 'L') { + while (x < chrs.length && chrs[x] != ';') + x++; + } + break; + case 'D': // double + case 'J': // long + idx++; + break; + case 'L': // object + while (x < chrs.length && chrs[x] != ';') + x++; + } + } + + if (!args.isEmpty()) { + var key = cls.getMapped() + "." + name + mtd.getMappedDescriptor(); + var info = excMap.get(key); + if (info == null) + info = ""; + else { + var offset = info.indexOf('|'); + if (offset != -1) + info = info.substring(0, offset); + } + excMap.put(key, info + '|' + String.join(",", args)); + } + } + } + } catch (IOException e) { + return Util.sneak(e); + } + + FileUtils.ensureParent(output); + try (var out = new BufferedWriter(new FileWriter(output, StandardCharsets.UTF_8))) { + var keys = new ArrayList<>(excMap.keySet()); + Collections.sort(keys); + + for (var key : keys) { + out.write(key); + var value = excMap.get(key); + if (value != null) { + out.write('='); + out.write(value); + } + out.write('\n'); + } + } catch (IOException e) { + return Util.sneak(e); + } + + cache.save(); + return output; + } + + private Task rename() { + var merged = merge(); + var mappings = this.getMappings(); + return Task.named(prefix() + "rename", + Task.deps(merged, mappings), () -> this.rename(merged.execute(), mappings.execute()) + ); + } + + // We used to use SpecialSource, but im going to try with renamer, it should be fine... + private File rename(File input, File mappings) { + var tool = this.repo.getCache().maven().download(Constants.RENAMER); + var output = new File(this.build, "joined-renamed.jar"); + var log = new File(this.build, "joined-renamed.log"); + var cache = Util.cache(output) + .add("tool", tool) + .add("input", input) + .add("mappings", mappings); + + if (Mavenizer.checkCache(output, cache)) + return output; + + var args = List.of( + "--input", input.getAbsolutePath(), + "--output", output.getAbsolutePath(), + "--map", mappings.getAbsolutePath(), + "--disable-abstract-param" // this is taken care of by the cleanup task + ); + + var jdk = this.repo.getCache().jdks().tryGet(Constants.RENAMER_JAVA_VERSION); + var ret = ProcessUtils.runJar(jdk, this.build, log, tool, Collections.emptyList(), args); + if (ret.exitCode != 0) + throw new IllegalStateException("Failed to run Renamer (exit code " + ret.exitCode + "), See log: " + log.getAbsolutePath()); + + cache.save(); + return output; + } + + private Task merge() { + var client = this.getMinecraftTasks().versionFile(MCFile.CLIENT_JAR); + var server = splitServer(); + return Task.named(prefix() + "merge", + Task.deps(client, server), () -> this.merge(client, server) + ); + } + + private File merge(Task clientTask, Task serverTask) { + var tool = this.repo.getCache().maven().download(Constants.LEGACY_MERGETOOL); + var client = clientTask.execute(); + var server = serverTask.execute(); + var output = new File(this.build, "joined.jar"); + var log = new File(this.build, "joined.log"); + var cache = Util.cache(output) + .add("tool", tool) + .add("client", client) + .add("server", server); + + if (Mavenizer.checkCache(output, cache)) + return output; + + var args = List.of( + "--merge", + "--client", client.getAbsolutePath(), + "--server", server.getAbsolutePath(), + "--output", output.getAbsolutePath(), + "--inject", + "--ann", this.mcVersion, // Pick based on MC version + //"--ann", "NMF", // net.minecraftforge.fml.relauncher + "--keep-data" + ); + + var jdk = this.repo.getCache().jdks().tryGet(Constants.LEGACY_MERGETOOL_JAVA_VERSION); + var ret = ProcessUtils.runJar(jdk, this.build, log, tool, Collections.emptyList(), args); + if (ret.exitCode != 0) + throw new IllegalStateException("Failed to run MergeTool (exit code " + ret.exitCode + "), See log: " + log.getAbsolutePath()); + + cache.save(); + return output; + } + + private Task splitServer() { + var input = this.getMinecraftTasks().extractServer(); + return Task.named(prefix() + "splitServer", + Task.deps(input), () -> this.splitServer(input.execute()) + ); + } + + private static final String[] LEGACY_SPLIT_SERVER = new String[] { + "org/bouncycastle/", + "org/apache/", + "com/google/", + "com/mojang/authlib/", + "com/mojang/util/", + "gnu/trove/", + "io/netty/", + "javax/annotation/", + "argo/", + "it/" + }; + private static final String LEGACY_SPLIT_SERVER_HASH = HashFunction.sha1().hash(String.join("\0", LEGACY_SPLIT_SERVER)); + /// Typically we would want to split the server based on the whitelist from the mapping file + /// But I haven't written the unit tests to verify that all the old versions are correct with that + /// And FG2 just uses wildcards. + /// So i'm being lazy and just copying the whildcards + private File splitServer(File input) { + var output = new File(this.build, "server-split.jar"); + var cache = Util.cache(output) + .add("input", input) + .addKnown("filter", LEGACY_SPLIT_SERVER_HASH); + + if (Mavenizer.checkCache(output, cache)) + return output; + + FileUtils.ensureParent(output); + + try (var zin = new ZipInputStream(new FileInputStream(input)); + var zout = new ZipOutputStream(new FileOutputStream(output))) { + for (ZipEntry entry; (entry = zin.getNextEntry()) != null; ) { + boolean filtered = false; + for (var filter : LEGACY_SPLIT_SERVER) { + if (entry.getName().startsWith(filter)) { + filtered = true; + break; + } + } + if (filtered) + continue; + + var newEntry = new ZipEntry(entry.getName()); + newEntry.setSize(entry.getSize()); + newEntry.setTime(entry.getTime()); + zout.putNextEntry(newEntry); + zin.transferTo(zout); + zout.closeEntry(); + } + } catch (IOException e) { + return Util.sneak(e); + } + + cache.save(); + return output; + } + + public class Child { + private final MavenCache maven; + private final JDKCache jdks; + private final File build; + private final File accessTransformer; + private final FGVersion fgVersion; + private final String prefix; + private final MCPCfg cfg; + private final Task inject; + + public Child(File build, File accessTransformer, FGVersion fg) { + this.maven = MCPLegacy.this.repo.getCache().maven(); + this.jdks = MCPLegacy.this.repo.getCache().jdks(); + this.build = build; + this.accessTransformer = accessTransformer; + this.fgVersion = fg; + this.prefix = MCPLegacy.this.prefix(); + this.cfg = MCPCfg.get(fg, MCPLegacy.this.mcVersion); + this.inject = mcpInject(); + } + + public Task getFinalStep() { + return this.inject; + } + + private Task mcpInject() { + var input = cleanup(); + return Task.named(prefix + "mcp-inject", + Task.deps(input), () -> mcpInject(input.execute()) + ); + } + + private File mcpInject(File input) { + var output = new File(this.build, "mcp-inject.jar"); + var cache = Util.cache(output) + .add("input", input) + .addKnown("mcp", MCPLegacy.this.dataHash); + + if (Mavenizer.checkCache(output, cache)) + return output; + + FileUtils.ensureParent(output); + if (output.exists()) + output.delete(); + + try (var zout = new ZipOutputStream(new FileOutputStream(output))) { + + var packages = new HashSet(); + // Copy everything from one jar to the other, gathering java packages + try (var zin = new ZipInputStream(new FileInputStream(input))) { + for (ZipEntry entry; (entry = zin.getNextEntry()) != null; ) { + var name = entry.getName(); + + var newEntry = new ZipEntry(name); + newEntry.setTime(entry.getTime()); + zout.putNextEntry(newEntry); + zin.transferTo(zout); + zout.closeEntry(); + + if (name.endsWith(".java")) { + var idx = name.lastIndexOf('/'); + packages.add(name.substring(0, idx)); + } + } + } + + var prefix = MCPLegacy.this.isPython ? "conf/patches/inject/" : "patches/inject/"; + try (var zin = new ZipInputStream(new FileInputStream(MCPLegacy.this.data))) { + for (ZipEntry entry; (entry = zin.getNextEntry()) != null; ) { + if ((prefix + "package-info-template.java").equals(entry.getName())) { + var template = new String(zin.readAllBytes(), StandardCharsets.UTF_8); + for (var pkg : packages) { + var name = pkg + "/package-info.java"; + zout.putNextEntry(new ZipEntry(FileUtils.getStableEntry(name))); + zout.write(template.replaceAll("\\{PACKAGE\\}", pkg.replace('/', '.')).getBytes(StandardCharsets.UTF_8)); + zout.closeEntry(); + } + } else if (entry.getName().startsWith(prefix + "common/")) { + if (entry.isDirectory()) + continue; + var name = entry.getName().substring(22); + zout.putNextEntry(new ZipEntry(FileUtils.getStableEntry(name))); + zin.transferTo(zout); + zout.closeEntry(); + } + } + } + } catch (IOException e) { + return Util.sneak(e); + } + + cache.save(); + return output; + } + + private Task cleanup() { + var input = this.decompile(); + return Task.named(prefix + "cleanup", + Task.deps(input), () -> cleanup(input.execute()) + ); + } + + private File cleanup(File input) { + var cleanup = this.cfg.cleanup; + var tool = maven.download(cleanup.artifact); + var output = new File(this.build, "cleaned.jar"); + var log = new File(this.build, "cleaned.log"); + var cache = Util.cache(output) + .add("tool", tool) + .add("input", input) + .add("args", String.join(", ", cleanup.args)) + .addKnown("fg", this.fgVersion.name()) + .addKnown("mcp", MCPLegacy.this.dataHash); + + if (Mavenizer.checkCache(output, cache)) + return output; + + var args = new ArrayList(List.of( + "--input", input.getAbsolutePath(), + "--output", output.getAbsolutePath(), + "--patches", MCPLegacy.this.data.getAbsolutePath(), + "--patches-prefix", (MCPLegacy.this.isPython ? "conf/" : "") + "patches/minecraft_merged_ff/" + )); + /* + if (MCPLegacy.this.child != this) // We're on a Forge version, so pull out the side annotations and inject it later + args.add("--filter-fml"); + */ + args.addAll(cleanup.args); + + var jdk = jdks.tryGet(Constants.MCPCLEANUP_JAVA_VERSION); + var ret = ProcessUtils.runJar(jdk, this.build, log, tool, List.of(), args); + if (ret.exitCode != 0) + throw new IllegalStateException("Failed to run MCPCleanup (exit code " + ret.exitCode + "), See log: " + log.getAbsolutePath()); + + cache.save(); + return output; + } + + private Task decompile() { + var input = this.exceptor(); + var libraries = MCPLegacy.this.listLibraries; + return Task.named(prefix + "decompile", + Task.deps(input, libraries), () -> decompile(input.execute(), libraries.execute()) + ); + } + + private File decompile(File input, File libraries) { + var output = new File(this.build, "decompiled.jar"); + var log = new File(this.build, "decompiled.log"); + + var decompiler = this.cfg.decompiler; + + var tool = maven.download(decompiler.artifact); + + int java_version = this.fgVersion == FGVersion.v2_3 ? 8 : 6; + + var cache = Util.cache(output) + .add("input", input) + .add("tool", tool) + .add("args", String.join(", ", decompiler.args)) + .addKnown("fg", this.fgVersion.name()) + .addKnown("java", Integer.toString(java_version)); + + if (Mavenizer.checkCache(output, cache)) + return output; + + var jvm = List.of("-Xmx3G"); // This is what FG2.3 has, older versions have 512M, we're on modern machines 3G should be fine + + var temp = output; + List run; + if (decompiler.legacy) { + // Older versions don't support directly outputting to a single jar file, they require a directory and will output to dir\{input_file_name} + var dir = new File(this.build, "decompile-legacy-temp"); + if (!dir.exists()) + dir.mkdir(); + temp = new File(dir, input.getName()); + + run = new ArrayList(decompiler.args.size()); + for (var arg : decompiler.args) { + if ("{libraries}".equals(arg)) { + // This is a version that doens't support the -cfg arg + // So we have to specify all the libraries via command line + // But this could easily explode to more then the max command size + // So we need to move all libraries into a common directory and use relative names + var workDir = log.getParentFile(); + var libsDir = new File(workDir, "decompile-libs"); + try { + var libs = Files.readAllLines(libraries.toPath()); + if (!libsDir.exists()) + libsDir.mkdirs(); + for (var lib : libs) { + if (lib.startsWith("-e=")) { + var source = new File(lib.substring(3)); + var target = new File(libsDir, source.getName()); + if (!target.exists()) // This doesn't check hashes, but IDGF + Files.copy(source.toPath(), target.toPath()); + run.add("-e=decompile-libs/" + target.getName()); + } else { + run.add(lib); // Shouldn't happen, but just in case + } + } + } catch (IOException e) { + return Util.sneak(e); + } + } else { + run.add(arg); + } + } + run.add(input.getAbsolutePath()); + run.add(dir.getAbsolutePath()); + } else { + run = new ArrayList(decompiler.args); + run.addAll(List.of( + "-cfg", libraries.getAbsolutePath(), + input.getAbsolutePath(), + output.getAbsolutePath() + )); + } + + if (temp.exists()) + temp.delete(); + + var jdk = jdks.tryGet(java_version); + var ret = StupidHacks.runDecompiler(jdk, log, tool, jvm, run); + if (ret.exitCode != 0 || !temp.exists()) + throw new IllegalStateException("Failed to run decompiler (exit code " + ret.exitCode + "), See log: " + log.getAbsolutePath()); + + // Move the decompiled code to the output file if we're using legacy code + if (temp != output) { + try { + Files.move(temp.toPath(), output.toPath(), StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + return Util.sneak(e); + } + } + + cache.save(); + return output; + } + + private Task exceptor() { + var input = this.modifyAccess(); + var config = MCPLegacy.this.exceptorConfig(); + var json = this.cfg.exceptor.json ? this.modifyExceptorAccess(input) : null; + return Task.named(prefix + "exceptor", + Task.deps(input, config, json), () -> this.exceptor(input.execute(), config.execute(), json) + ); + } + + // We used to use SpecialSource, but im going to try with renamer, it should be fine... + private File exceptor(File input, File config, Task jsonTask) { + var except = this.cfg.exceptor; + var json = jsonTask == null ? null : jsonTask.execute(); + var tool = maven.download(Constants.MCINJECTOR); + var output = new File(this.build, "joined-mci.jar"); + var log = new File(this.build, "joined-mci.log"); + var internalLog = new File(this.build, "joined-mci-internal.log"); + var cache = Util.cache(output) + .add("tool", tool) + .add("input", input) + .add("config", config); + if (json != null) + cache.add("json", json); + + if (Mavenizer.checkCache(output, cache)) + return output; + + var args = new ArrayList<>(List.of( + "--jarIn", input.getAbsolutePath(), + "--jarOut", output.getAbsolutePath(), + "--mapIn", config.getAbsolutePath(), + "--log", internalLog.getAbsolutePath(), // Annoying but we have to have two logs to actually have the logs be written + "--generateParams" + )); + if (json != null) + args.addAll(List.of("--jsonIn", json.getAbsolutePath())); + if (except.markers) + args.add("--applyMarkers"); + if (except.lvt) + args.addAll(List.of("--lvt", "LVT")); + + var jdk = jdks.tryGet(Constants.MCINJECTOR_JAVA_VERSION); + var ret = ProcessUtils.runJar(jdk, this.build, log, tool, Collections.emptyList(), args); + if (ret.exitCode != 0) + throw new IllegalStateException("Failed to run MCInjector (exit code " + ret.exitCode + "), See log: " + log.getAbsolutePath()); + + internalLog.delete(); // this is a duplicate file, everything is in the main log + + cache.save(); + return output; + } + + private Task modifyExceptorAccess(Task inputTask) { + var config = MCPLegacy.this.exceptorJson; + return Task.named("modifyExceptorAccess", + Task.deps(inputTask, config), () -> this.modifyExceptorAccess(inputTask.execute(), config.execute()) + ); + } + + private File modifyExceptorAccess(File input, File config) { + var output = new File(this.build, "exceptor-ated.json"); + var cache = Util.cache(output) + .add("input", input) + .add("config", config); + + if (this.accessTransformer != null) + cache.add("at", this.accessTransformer); + + if (Mavenizer.checkCache(output, cache)) + return output; + + try { + var struct = JsonData.fromJson(config, MCI_CONFIG); + + // Fix the inner class access that we modify + if (this.accessTransformer != null) { + for (var line : Files.readAllLines(this.accessTransformer.toPath(), StandardCharsets.UTF_8)) { + var idx = line.indexOf('#'); + if (idx != -1) + line = line.substring(0, idx); + line = line.trim().replace('.', '/'); + if (line.isEmpty()) + continue; + + var pts = line.split(" "); + idx = pts[1].indexOf('$'); + if (pts.length != 2 || idx == -1) + continue; + + var access = pts[0]; + var cls = pts[1]; + + var parent = pts[1].substring(0, idx); + for (var cfg : new MCInjectorConfig[] { struct.get(parent), struct.get(pts[1]) }) { + if (cfg == null || cfg.innerClasses == null) + continue; + for (var inner : cfg.innerClasses) { + if (cls.equals(inner.inner_class)) + inner.fixAccess(access); + } + } + } + } + + // remove any inner classes that doesn't exist.. Not sure why we do this but old code is old + try (var zip = new ZipFile(input)) { + for (var itr = struct.entrySet().iterator(); itr.hasNext(); ) { + var entry = itr.next(); + var outerName = entry.getKey(); + var cfg = entry.getValue(); + + if (zip.getEntry(outerName + ".class") == null) { + itr.remove(); + continue; + } + + if (cfg.innerClasses == null) + continue; + + for (var inner = cfg.innerClasses.iterator(); inner.hasNext(); ) { + var innerName = inner.next().inner_class; + if (zip.getEntry(innerName + ".class") == null) + inner.remove(); + } + } + } + + JsonData.toJson(struct, output); + cache.save(); + return output; + } catch (IOException e) { + return Util.sneak(e); + } + } + + private Task modifyAccess() { + var renamed = MCPLegacy.this.rename(); + if (this.accessTransformer == null) + return renamed; + return Task.named(prefix + "modifyAccess", + Task.deps(renamed), () -> this.modifyAccess(renamed.execute()) + ); + } + + private File modifyAccess(File input) { + var tool = maven.download(Constants.ACCESS_TRANSFORMER); + var output = new File(this.build, "modify-access.jar"); + var log = new File(this.build, "modify-access.log"); + var cache = Util.cache(output) + .add("tool", tool) + .add("input", input) + .add("config", this.accessTransformer); + + if (Mavenizer.checkCache(output, cache)) + return output; + + var args = List.of( + "--inJar", input.getAbsolutePath(), + "--outJar", output.getAbsolutePath(), + "--atfile", this.accessTransformer.getAbsolutePath() + ); + + var jdk = jdks.tryGet(Constants.ACCESS_TRANSFORMER_JAVA_VERSION); + var ret = ProcessUtils.runJar(jdk, this.build, log, tool, Collections.emptyList(), args); + if (ret.exitCode != 0) + throw new IllegalStateException("Failed to run Access Transformer (exit code " + ret.exitCode + "), See log: " + log.getAbsolutePath()); + + cache.save(); + return output; + } + } + + private static final TypeToken> MCI_CONFIG = new TypeToken<>() {}; + public record MCInjectorConfig(@Nullable EnclosingMethod enclosingMethod, @Nullable ArrayList innerClasses) { + public record EnclosingMethod(String desc, String name, String owner) { } + public static class InnerClass { + public @Nullable String access; + public String inner_class; + public String inner_name; + public String outer_class; + public @Nullable String start; + + private static final int ACC_DEFAULT = 0x0000; + private static final int ACC_PUBLIC = 0x0001; + private static final int ACC_PRIVATE = 0x0002; + private static final int ACC_PROTECTED = 0x0004; + private static final int ACC_FINAL = 0x0010; + + void fixAccess(String wanted) { + int access = this.access == null ? 0 : Integer.parseInt(this.access, 16); + int ret = access & ~7; + int t = 0; + + if (wanted.startsWith("public")) + t = ACC_PUBLIC; + else if (wanted.startsWith("private")) + t = ACC_PRIVATE; + else if (wanted.startsWith("protected")) + t = ACC_PROTECTED; + + ret |= switch (access & 7) { + case ACC_PRIVATE -> t; + case ACC_DEFAULT -> (t != ACC_PRIVATE ? t : ACC_DEFAULT); + case ACC_PROTECTED -> (t != ACC_PRIVATE && t != ACC_DEFAULT ? t : ACC_PROTECTED); + case ACC_PUBLIC -> ACC_PUBLIC; + default -> access & 7; + }; + + if (wanted.endsWith("-f")) + ret &= ~ACC_FINAL; + else if (wanted.endsWith("+f")) + ret |= ACC_FINAL; + this.access = ret == 0 ? null : Integer.toHexString(ret); + } + } + } + + // The main difference between FG versions was the decompiler we invoked/embeded + private static final ComparableVersion MC_1_8_8 = new ComparableVersion("1.8.8"); + private static final ComparableVersion MC_1_9 = new ComparableVersion("1.9"); + private static final ComparableVersion MC_1_9_4 = new ComparableVersion("1.9.4"); + + private record Exceptor(boolean json, boolean markers, boolean lvt) { + private static Exceptor get(ComparableVersion mc) { + var json = true; + var markers = true; + var lvt = false; + if (mc.compareTo(MC_1_8_8) >= 0) { + json = false; + lvt = true; + } + if (mc.compareTo(MC_1_9) >= 0) + markers = false; + return new Exceptor(json, markers, lvt); + } + } + private record Cleanup(Artifact artifact, List args) { + private static Cleanup get(String mc) { + var args = List.of(); + if ("1.8".equals(mc)) + args = List.of("--fix-generic-params", "--fml", "--fernflower", + // These patches are enum sythetic arg fixes which cleaup does in a generic way + "--ignore-patch", "net/minecraft/network/EnumConnectionState.java", + "--ignore-patch", "net/minecraft/util/EnumChatFormatting.java", + "--ignore-patch", "net/minecraft/util/EnumParticleTypes.java" + ); + else if ("1.8.8".equals(mc)) + args = List.of("--fix-generic-params");//, "--fernflower", "--enum-args=false", "--enum-constructors=false"); + return new Cleanup(Constants.MCPCLEANUP, args); + } + } + private record Decompiler(Artifact artifact, boolean legacy, List args) { + + private static final List DECOMPILER_ARGS = List.of( + "-din=1", // DECOMPILE_INNER + "-dgs=1", // DECOMPILE_GENERIC_SIGNATURES + "-asc=1", // ASCII_STRING_CHARACTERS + "-iec=1", // INCLUDE_ENTIRE_CLASSPATH + "-rsy=1", // REMOVE_SYNTHETIC + "-rbr=1", // REMOVE_BRIDGE + "-lit=0", // LITERALS_AS_IS + // UNIT_TEST_MODE = 0 + "-mpm=0", // MAX_PROCESSING_METHOD + //"-jvn=1", // Use JAD Renamer, FG3 has an 'advanced' renamer which renames abstract names, but we can do that in MCPCleanup + "--var-renamer", "mcp", // This is the 'Advanced' renamer from FG3 + "--disable-buffer-casts" // Disables the java9 fixes for Buffer methods, it should be fine because we recompile with java 8. + ); + private static final List DECOMPILER_ARGS_18 = List.of( + "-din=1", // DECOMPILE_INNER + "-rbr=0", // REMOVE_BRIDGE + "-dgs=1", // DECOMPILE_GENERIC_SIGNATURES + "-asc=1", // ASCII_STRING_CHARACTERS + "-log=WARN" + ); + private static final List DECOMPILER_ARGS_188 = List.of( + "-din=1", // DECOMPILE_INNER + "-rbr=1", // REMOVE_BRIDGE + "-dgs=1", // DECOMPILE_GENERIC_SIGNATURES + "-asc=1", // ASCII_STRING_CHARACTERS + "-rsy=1", // REMOVE_SYNTHETIC + "-iec=1", // INCLUDE_ENTIRE_CLASSPATH + "-jvn=1", // Use JAD Renamer + "-log=WARN", + "{libraries}" // Will be expanded to a list of all libraries using -e= + ); + private static Decompiler get(FGVersion fg, ComparableVersion mc) { + if (fg == FGVersion.v2_3) { + return new Decompiler(Constants.FERNFLOWER_FG_2_3, false, DECOMPILER_ARGS); + } else if (fg == FGVersion.v2_2 || fg == FGVersion.v2_1) { + return new Decompiler(Constants.FERNFLOWER_FG_2_2, false, DECOMPILER_ARGS); + } else { + var decompTool = Constants.FERNFLOWER_FG_2_0_LEGACY; + var decompArgs = DECOMPILER_ARGS_188; + if (mc.compareTo(MC_1_9_4) >= 0) + decompTool = Constants.FERNFLOWER_FG_2_0_194; + else if (mc.compareTo(MC_1_8_8) >= 0) + decompTool = Constants.FERNFLOWER_FG_2_0_194; + else // 1.8 + decompArgs = DECOMPILER_ARGS_18; + return new Decompiler(decompTool, true, decompArgs); + } + } + } + private record MCPCfg( + Exceptor exceptor, + Cleanup cleanup, + Decompiler decompiler + ) { + private static MCPCfg get(FGVersion fg, String mc) { + var comp = new ComparableVersion(mc); + var except = Exceptor.get(comp); + var cleanup = Cleanup.get(mc); + var decomp = Decompiler.get(fg, comp); + return new MCPCfg(except, cleanup, decomp); + } + } +} diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MCPTaskFactory.java b/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MCPTaskFactory.java index 0eeea26..7f6e8a2 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MCPTaskFactory.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MCPTaskFactory.java @@ -20,7 +20,6 @@ import java.util.Set; import java.util.TreeSet; import java.util.function.BiPredicate; -import java.util.function.ToIntFunction; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarInputStream; @@ -39,6 +38,7 @@ import io.codechicken.diffpatch.util.archiver.ArchiveFormat; import net.minecraftforge.mcmaven.impl.Mavenizer; import net.minecraftforge.mcmaven.impl.cache.MavenCache; +import net.minecraftforge.mcmaven.impl.cache.MinecraftMavenCache; import net.minecraftforge.mcmaven.impl.util.Artifact; import net.minecraftforge.mcmaven.impl.util.Constants; import net.minecraftforge.util.data.json.JsonData; @@ -609,11 +609,11 @@ private Task listLibraries(String name, Map step) { var json = this.side.getMCP().getMinecraftTasks().versionJson; return Task.named(name, Task.deps(json), - () -> listLibraries(json, output) + () -> listLibraries(json, output, this.side.getMCP().getCache().minecraft()) ); } - private File listLibraries(Task jsonTask, File output) { + public static File listLibraries(Task jsonTask, File output, MinecraftMavenCache minecraft) { var jsonF = jsonTask.execute(); var json = JsonData.minecraftVersion(jsonF); @@ -641,7 +641,6 @@ private File listLibraries(Task jsonTask, File output) { return output; var buf = new StringBuilder(20_000); - var minecraft = this.side.getMCP().getCache().minecraft(); for (var lib : libs) { // Sometimes natives don't have a main download if (lib.dl == null) @@ -869,31 +868,12 @@ private File execute(String name, List jvmArgs, List runAr throw new IllegalStateException("Failed to find JDK for version " + java_version, e); } - ToIntFunction logHandler = null; - if (isDecompile) { - jvm = Mavenizer.fillDecompileJvmArgs(jvm, true, true); - logHandler = MCPTaskFactory::parseDecompileLog; - } + ProcessUtils.Result ret; + if (isDecompile) + ret = StupidHacks.runDecompiler(jdk, log, tool, jvm, run); + else + ret = ProcessUtils.runJar(jdk, log.getParentFile(), log, tool, jvm, run); - var ret = ProcessUtils.runJar(jdk, log.getParentFile(), log, tool, jvm, run, logHandler); - if (isDecompile) { - if (ret.exitCode == NOT_ENOUGH_MEMORY) { - LOGGER.error("Failed to create JVM with Not Enough Memory issue, Modern minecraft requires atleast 4GB to decompile. Run it on a system with more ram."); - } else if (ret.exitCode == INVALID_INITAL_HEAP) { - LOGGER.error("Attempted to run decompile with JVM args: " + jvm + " resulted in Invalid Inital and Max heap settings."); - LOGGER.error("This is typically caused by you having a environement variable setting the global memory options, remove or set those variables to values higher then 4GB."); - } - if (ret.exitCode == OUT_OF_MEMORY || ret.exitCode == INVALID_INITAL_HEAP) { - var newJvm = Mavenizer.fillDecompileJvmArgs(resolveArgs(cache, tasks, jvmArgs), false, false); - if (!newJvm.equals(jvm)) { - LOGGER.error("First decompile failed with OutOfMemory using JVM Args: " + jvm); - LOGGER.error("Attempting again with: " + newJvm); - ret = ProcessUtils.runJar(jdk, log.getParentFile(), log, tool, newJvm, run, logHandler); - if (ret.exitCode == OUT_OF_MEMORY) - LOGGER.error("Ran out of memory again, you can specify more manually using the --decompile-memory Mavenizer argument"); - } - } - } if (ret.exitCode != 0) throw new IllegalStateException("Failed to run MCP Step (exit code " + ret.exitCode + "), See log: " + log.getAbsolutePath()); @@ -946,39 +926,4 @@ private List resolveArgs(HashStore cache, Map tasks, List< } return ret; } - - private static final int OUT_OF_MEMORY = -1001; - private static final int FAILED_DECOMPILE = -1002; - private static final int INVALID_INITAL_HEAP = -1003; - private static final int NOT_ENOUGH_MEMORY = -1004; - // Yes this is slow as fuck, but this is only run during a decompile run which is already slow, - // This is to check if Fernflower is broken and returning success when it really failed. - // It also eagerly exits the process when something fails so as to not waste time. - private static int parseDecompileLog(String line) { - if (line.startsWith("Initial heap size set to a larger value than the maximum heap size")) - return INVALID_INITAL_HEAP; - if (line.startsWith("Could not reserve enough space for object heap")) - return NOT_ENOUGH_MEMORY; - if (line.startsWith("java.lang.OutOfMemoryError:")) - return OUT_OF_MEMORY; - if (line.startsWith("Exception in thread") && line.contains("java.lang.OutOfMemoryError")) - return OUT_OF_MEMORY; - if (line.contains("ERROR:")) { - // String message = "Method " + mt.getName() + " " + mt.getDescriptor() + " in class " + cl.qualifiedName + " couldn't be written."; - // String message = "Method " + mt.getName() + " " + mt.getDescriptor() + " in class " + classWrapper.getClassStruct().qualifiedName + " couldn't be written."; - // String message = "Method " + mt.getName() + " " + mt.getDescriptor() + " in class " + node.classStruct.qualifiedName + " couldn't be written."; - if (line.endsWith(" couldn't be written.")) - return FAILED_DECOMPILE; - // DecompilerContext.getLogger().logError("Class " + cl.qualifiedName + " couldn't be processed.", t); - if (line.endsWith(" couldn't be processed.")) - return FAILED_DECOMPILE; - // DecompilerContext.getLogger().logError("Class " + cl.qualifiedName + " couldn't be fully decompiled.", t); - if (line.endsWith(" couldn't be fully decompiled.")) - return FAILED_DECOMPILE; - // String message = "Method " + mt.getName() + " " + mt.getDescriptor() + " in class " + classStruct.qualifiedName + " couldn't be decompiled."; - if (line.endsWith(" couldn't be decompiled.")) - return FAILED_DECOMPILE; - } - return 0; - } } diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MinecraftTasks.java b/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MinecraftTasks.java index e8d3dec..e08caf9 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MinecraftTasks.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MinecraftTasks.java @@ -20,6 +20,8 @@ import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; +import org.jetbrains.annotations.Nullable; + import net.minecraftforge.mcmaven.impl.Mavenizer; import net.minecraftforge.mcmaven.impl.cache.Cache; import net.minecraftforge.util.data.json.JsonData; @@ -431,4 +433,10 @@ private List getServerLibrariesBundle() { } return ret; } + + public @Nullable Integer getJavaVersion() { + var jsonTask = this.versionJson; + var json = JsonData.minecraftVersion(jsonTask.execute()); + return json.javaVersion != null ? json.javaVersion.majorVersion : null; + } } diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/tasks/MCPNames.java b/src/main/java/net/minecraftforge/mcmaven/impl/tasks/MCPNames.java index da9f6a2..f52e1ec 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/tasks/MCPNames.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/tasks/MCPNames.java @@ -36,7 +36,7 @@ // TODO [Mavenizer][MCPNames] This is also in ForgeDev! Consolidate this! // TODO [Mavenizer][MCPNames] GARBAGE GARBAGE GARBAGE, CLEAN UP OR RE-IMPLEMENT -record MCPNames(String hash, Map names, Map docs) { +public record MCPNames(String hash, Map names, Map docs) { // We use \n here because we want to normalize line endings no matter the operating system we are running on. private static final String LINE_SEPERATOR = "\n"; //System.lineSeparator(); //@formatter:off @@ -86,7 +86,7 @@ private static Data loadData(File data) throws IOException { return new Data(names, docs); } - static MCPNames load(File data) throws IOException { + public static MCPNames load(File data) throws IOException { var loaded = loadData(data); return new MCPNames(HashFunction.sha1().hash(data), loaded.names, loaded.docs); } diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/tasks/RecompileTask.java b/src/main/java/net/minecraftforge/mcmaven/impl/tasks/RecompileTask.java index f3f43b7..89e1d37 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/tasks/RecompileTask.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/tasks/RecompileTask.java @@ -5,8 +5,8 @@ package net.minecraftforge.mcmaven.impl.tasks; import net.minecraftforge.mcmaven.impl.Mavenizer; -import net.minecraftforge.mcmaven.impl.mappings.Mappings; -import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCP; +import net.minecraftforge.mcmaven.impl.cache.JDKCache; +import net.minecraftforge.mcmaven.impl.mappings.ResolvedMappings; import net.minecraftforge.mcmaven.impl.util.Artifact; import net.minecraftforge.mcmaven.impl.util.ProcessUtils; import net.minecraftforge.mcmaven.impl.util.Task; @@ -24,15 +24,18 @@ public final class RecompileTask implements Task { private final File build; private final Artifact name; - private final MCP mcp; + private final JDKCache jdks; + private final int javaTarget; private final Supplier> classpath; - private final Mappings mappings; + private final ResolvedMappings mappings; private final Task task; - public RecompileTask(File build, Artifact name, MCP mcp, Supplier> classpath, Task sources, Mappings mappings) { + public RecompileTask(File build, Artifact name, + JDKCache jdks, int javaTarget, Supplier> classpath, Task sources, ResolvedMappings mappings) { this.build = mappings.getFolder(build); this.name = name; - this.mcp = mcp; + this.jdks = jdks; + this.javaTarget = javaTarget; this.classpath = classpath; this.mappings = mappings; this.task = this.recompileSources(sources); @@ -63,7 +66,6 @@ protected Task recompileSources(Task input) { private File recompileSourcesImpl(Task inputTask, File output) { var cache = Util.cache(output); - var javaTarget = this.mcp.getConfig().java_target; var sourcesJar = inputTask.execute(); cache.add("sources", sourcesJar); @@ -74,7 +76,7 @@ private File recompileSourcesImpl(Task inputTask, File output) { File jdk; try { - jdk = this.mcp.getCache().jdks().get(javaTarget); + jdk = this.jdks.get(javaTarget); } catch (Exception e) { throw new IllegalStateException("JDK not found: " + javaTarget, e); } diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/tasks/RenameTask.java b/src/main/java/net/minecraftforge/mcmaven/impl/tasks/RenameTask.java index e4894ed..22a69fb 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/tasks/RenameTask.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/tasks/RenameTask.java @@ -5,8 +5,7 @@ package net.minecraftforge.mcmaven.impl.tasks; import net.minecraftforge.mcmaven.impl.Mavenizer; -import net.minecraftforge.mcmaven.impl.mappings.Mappings; -import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCPSide; +import net.minecraftforge.mcmaven.impl.mappings.ResolvedMappings; import net.minecraftforge.util.file.FileUtils; import net.minecraftforge.util.hash.HashFunction; import net.minecraftforge.mcmaven.impl.util.StupidHacks; @@ -31,9 +30,10 @@ */ public final class RenameTask implements Task { private final String name; - private final MCPSide side; private final boolean javadocs; + private final Task srgTask; private final Task task; + private final boolean legacy; /** * Creates a new renamer for the given patcher. @@ -43,11 +43,12 @@ public final class RenameTask implements Task { * @param sources The task that creates the unnamed sources * @param javadocs Wither to inject javadocs and rename lambda parameters, false is used for ForgeDev as we remap to SRG patches when making userdev */ - public RenameTask(File build, String name, MCPSide side, Task sources, Mappings mappings, boolean javadocs) { + public RenameTask(File build, String name, Task sources, ResolvedMappings mappings, boolean javadocs, Task srgTask, String mcVersion) { this.name = name; - this.side = side; this.javadocs = javadocs; + this.srgTask = srgTask; this.task = this.remapSources(sources, mappings.getFolder(build), mappings); + this.legacy = StupidHacks.isLegacyRenamer(mcVersion); } @Override @@ -65,11 +66,10 @@ public String name() { return this.task.name(); } - private Task remapSources(Task input, File outputDir, Mappings provider) { + private Task remapSources(Task input, File outputDir, ResolvedMappings provider) { var output = new File(outputDir, !this.javadocs ? "remapped.jar" : "remapped-javadoc.jar"); - var mappings = provider.getCsvZip(side); - var srg = this.javadocs ? side.getTasks().getMappings() : null; - var legacy = StupidHacks.isLegacyRenamer(side.getMCP().getMinecraftTasks().getVersion()); + var mappings = provider.getCsvZip(); + var srg = this.javadocs ? this.srgTask : null; return Task.named("remap[" + this.name + "][" + provider + ']' + (!this.javadocs ? "" : "[javadoc]"), Task.deps(input, mappings, srg), () -> remapSourcesImpl(input, mappings, output, srg, legacy) diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/util/Constants.java b/src/main/java/net/minecraftforge/mcmaven/impl/util/Constants.java index 15f6ebe..4f72b46 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/util/Constants.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/util/Constants.java @@ -14,6 +14,21 @@ public final class Constants { public static final String FORGE_NAME = "forge"; public static final String FORGE_ARTIFACT = FORGE_GROUP + ':' + FORGE_NAME; public static final String FORGE_MAVEN = "https://maven.minecraftforge.net/"; + public static final String MCP_DOWNLOADS = FORGE_FILES + "mcp/"; + + // Legacy FG2 Decompilers + // This was https://files.minecraftforge.net/fernflower-fix-1.0.zip!/fernflower.jar but I extracted it to make it easier to download + // This is the old obfusicated fernflower decompiler, with just the inner class NPE fix, tehcnially this wasn't used in 1.0, but its just a NPE fix so use it for everything + public static final Artifact FERNFLOWER_FG_1_0 = Artifact.from("net.minecraftforge:fernflower:0.1.0.0"); + // This was https://files.minecraftforge.net/fernflower-fix-1.0.zip!/fernflower.jar but I extracted it to make it easier to download + // This is the old obfusicated fernflower decompiler, with all of my fixes from: https://github.com/LexManos/FernFlowerFixer/ + public static final Artifact FERNFLOWER_FG_2_0_LEGACY = Artifact.from("net.minecraftforge:fernflower:0.2.0.0"); + // 2.0-SNAPSHOT used for MC 1.8.8 + public static final Artifact FERNFLOWER_FG_2_0_188 = Artifact.from("net.minecraftforge:fernflower:2.0.9"); + // 2.0-SNAPSHOT used for MC 1.9.4 + public static final Artifact FERNFLOWER_FG_2_0_194 = Artifact.from("net.minecraftforge:fernflower:2.0.17"); + public static final Artifact FERNFLOWER_FG_2_2 = Artifact.from("net.minecraftforge:forgeflower:0.2.2.0"); + public static final Artifact FERNFLOWER_FG_2_3 = Artifact.from("net.minecraftforge:forgeflower:0.2.3.1"); // TODO Other toolchains such as FMLOnly (not required, but would be useful so we have the framework to use other toolchains) public static final String FMLONLY_NAME = "fmlonly"; @@ -25,7 +40,7 @@ public final class Constants { public static final String LAUNCHER_MANIFEST = "https://launchermeta.mojang.com/mc/game/version_manifest_v2.json"; public static final String MOJANG_MAVEN = "https://libraries.minecraft.net/"; - public static final Artifact ACCESS_TRANSFORMER = Artifact.from("net.minecraftforge:accesstransformers:8.2.14:fatjar"); + public static final Artifact ACCESS_TRANSFORMER = Artifact.from("net.minecraftforge:accesstransformers:8.2.16:fatjar"); public static final int ACCESS_TRANSFORMER_JAVA_VERSION = 8; public static final Artifact SIDE_STRIPPER = Artifact.from("net.minecraftforge:mergetool:1.2.5:fatjar"); @@ -41,5 +56,14 @@ public final class Constants { public static final int STUBIFY_JAVA_VERSION = 25; public static final Artifact FACADE = Artifact.from("net.minecraftforge:facade:1.0.2"); - public static final int FACAD_JAVA_VERSION = 25; + public static final int FACADE_JAVA_VERSION = 25; + + public static final Artifact MCINJECTOR = Artifact.from("de.oceanlabs.mcp:mcinjector:3.4.5:fatjar"); + public static final int MCINJECTOR_JAVA_VERSION = 8; + + public static final Artifact MCPCLEANUP = Artifact.from("net.minecraftforge:mcpcleanup:2.4.2:fatjar"); + public static final int MCPCLEANUP_JAVA_VERSION = 8;; + + public static final Artifact LEGACY_MERGETOOL = Artifact.from("net.minecraftforge:mergetool:0.2.3.4:fatjar"); + public static final int LEGACY_MERGETOOL_JAVA_VERSION = 8; } diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/util/ContextualPatch.java b/src/main/java/net/minecraftforge/mcmaven/impl/util/ContextualPatch.java new file mode 100644 index 0000000..e45c6f1 --- /dev/null +++ b/src/main/java/net/minecraftforge/mcmaven/impl/util/ContextualPatch.java @@ -0,0 +1,680 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun + * Microsystems, Inc. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package net.minecraftforge.mcmaven.impl.util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A stripped down version of https://github.com/cloudbees/diff4j/blob/master/src/main/java/com/cloudbees/diff/ContextualPatch.java + * With support for access based fuzzing and some helpers + */ +public final class ContextualPatch { + @SuppressWarnings("unchecked") + private static R sneak(Throwable t) throws E { + throw (E) t; + } + + // first seen in mercurial diffs: characters after the second @@ - ignore them + private final Pattern unifiedRangePattern = Pattern.compile("@@ -(\\d+)(,\\d+)? \\+(\\d+)(,\\d+)? @@(\\s.*)?"); + private final Pattern normalChangeRangePattern = Pattern.compile("(\\d+),(\\d+)c(\\d+),(\\d+)"); + private final Pattern normalAddRangePattern = Pattern.compile("(\\d+)a(\\d+),(\\d+)"); + private final Pattern normalDeleteRangePattern = Pattern.compile("(\\d+),(\\d+)d(\\d+)"); + private final Pattern binaryHeaderPattern = Pattern.compile("MIME: (.*?); encoding: (.*?); length: (-?\\d+?)"); + + private final List patches = new ArrayList(); + + private int maxFuzz = 0; + private boolean c14nWhitespace = false; + private boolean c14nAccess = false; + + private BufferedReader patchReader; + private String patchLine; + private boolean patchLineRead = true; + private int lastPatchedLine; // the last line that was successfuly patched + + public static SingleFileContext context(String data) { + return new SingleFileContext(data); + } + + public static SingleFileContext context(InputStream data) throws IOException { + return new SingleFileContext(data); + } + + public static ContextualPatch create(String patchString) { + try { + return new ContextualPatch(new BufferedReader(new StringReader(patchString))); + } catch (Throwable e) { + return sneak(e); + } + } + public static ContextualPatch create(InputStream input) throws IOException { + try { + return new ContextualPatch(new InputStreamReader(input, StandardCharsets.UTF_8)); + } catch (PatchException e) { + return sneak(e); + } + } + + private ContextualPatch(Reader reader) throws IOException, PatchException { + patchReader = new BufferedReader(reader); + for (SinglePatch patch; (patch = getNextPatch()) != null; ) + patches.add(patch); + } + + public ContextualPatch setMaxFuzz(int maxFuzz) { + this.maxFuzz = maxFuzz; + return this; + } + + public ContextualPatch setWhitespaceC14N(boolean canonicalize) { + this.c14nWhitespace = canonicalize; + return this; + } + + public ContextualPatch setAccessC14N(boolean canonicalize) { + this.c14nAccess = canonicalize; + return this; + } + + public PatchReport patchSingle(boolean dryRun, IContextProvider context) { + if (this.patches.size() != 1) + throw new IllegalStateException("Unexpected multi-patch file"); + var patch = this.patches.getFirst(); + try { + return applyPatch(patch, dryRun, context); + } catch (Exception e) { + return new PatchReport(patch.targetPath, patch.binary, PatchStatus.Failure, e, new ArrayList()); + } + } + + /** + * @param dryRun true if the method should not make any modifications to files, false otherwise + * @return List of patch reports. Never null, may be empty. + */ + public List patch(boolean dryRun, IContextProvider context) { + List report = new ArrayList(); + for (SinglePatch patch : patches) { + try { + report.add(applyPatch(patch, dryRun, context)); + //report.add(new PatchReport(patch.targetFile, computeBackup(patch.targetFile), patch.binary, PatchStatus.Patched, null)); + } catch (Exception e) { + report.add(new PatchReport(patch.targetPath, patch.binary, PatchStatus.Failure, e, new ArrayList())); + } + } + return report; + } + + private PatchReport applyPatch(SinglePatch patch, boolean dryRun, IContextProvider context) { + lastPatchedLine = 1; + List ret = new ArrayList(); + + List target = context.getData(patch.targetPath); + + if (target != null && !patch.binary) { + try { + if (patchCreatesNewFileThatAlreadyExists(patch, target)) { //Check if the patch doesn't need to be applied... + for (int x = 0; x < patch.hunks.length; x++) + ret.add(new HunkReport(PatchStatus.Skipped, null, 0, 0, x)); + return new PatchReport(patch.targetPath, patch.binary, PatchStatus.Skipped, null, ret); + } + } catch (Exception e) { + // something went wrong, try doing normal patch + } + } else if (target == null) { + target = new ArrayList(); + } + + if (patch.mode == Mode.DELETE) { + target = new ArrayList(); + } else { + if (!patch.binary) { + int x = 0; + for (Hunk hunk : patch.hunks) { + x++; + try { + ret.add(applyHunk(target, hunk, x)); + } catch (Exception e) { + ret.add(new HunkReport(PatchStatus.Failure, e, 0, 0, x, hunk)); + } + } + } + } + + if (!dryRun) + context.setData(patch.targetPath, target); + + for (HunkReport hunk : ret) { + if (hunk.getStatus() == PatchStatus.Failure) + return new PatchReport(patch.targetPath, patch.binary, PatchStatus.Failure, hunk.getFailure(), ret); + } + return new PatchReport(patch.targetPath, patch.binary, PatchStatus.Patched, null, ret); + } + + private boolean patchCreatesNewFileThatAlreadyExists(SinglePatch patch, List originalFile) throws PatchException { + if (patch.hunks.length != 1) + return false; + Hunk hunk = patch.hunks[0]; + if (hunk.baseStart != 0 || hunk.baseCount != 0 || hunk.modifiedStart != 1 || hunk.modifiedCount != originalFile.size()) + return false; + + List target = new ArrayList(hunk.modifiedCount); + applyHunk(target, hunk, 0); + return target.equals(originalFile); + } + + private HunkReport applyHunk(List target, Hunk hunk, int hunkID) throws PatchException { + int idx = -1; + int fuzz = 0; + for (; idx == -1 && fuzz <= this.maxFuzz; fuzz++) { + idx = findHunkIndex(target, hunk, fuzz, hunkID); + if (idx != -1) + break; + } + if (idx == -1) + throw new PatchException("Cannot find hunk target"); + return applyHunk(target, hunk, idx, false, fuzz, hunkID); + } + + private int findHunkIndex(List target, Hunk hunk, int fuzz, int hunkID) throws PatchException { + int idx = hunk.modifiedStart; // first guess from the hunk range specification + if (idx >= lastPatchedLine && applyHunk(target, hunk, idx, true, fuzz, hunkID).getStatus().isSuccess()) + return idx; + + // try to search for the context + for (int i = idx - 1; i >= lastPatchedLine; i--) { + if (applyHunk(target, hunk, i, true, fuzz, hunkID).getStatus().isSuccess()) + return i; + } + for (int i = idx + 1; i < target.size(); i++) { + if (applyHunk(target, hunk, i, true, fuzz, hunkID).getStatus().isSuccess()) + return i; + } + return -1; + } + + /** + * @return true if the application succeeded + */ + private HunkReport applyHunk(List target, Hunk hunk, int idx, boolean dryRun, int fuzz, int hunkID) throws PatchException { + int startIdx = idx; + idx--; // indices in the target list are 0-based + int hunkIdx = -1; + for (String hunkLine : hunk.lines) { + hunkIdx++; + boolean isAddition = isAdditionLine(hunkLine); + if (!isAddition) { + if (idx >= target.size()) { + if (dryRun) + return new HunkReport(PatchStatus.Failure, null, idx, fuzz, hunkID); + throw new PatchException("Unapplicable hunk #" + hunkID + " @@ " + startIdx); + } + var clean = target.get(idx); + boolean match = similar(clean, hunkLine.substring(1), hunkLine.charAt(0)); + if (!match && fuzz != 0 && !isRemovalLine(hunkLine)) + match = (hunkIdx < fuzz || hunkIdx >= hunk.lines.size() - fuzz ? true : match); + if (!match) { + if (dryRun) + return new HunkReport(PatchStatus.Failure, null, idx, fuzz, hunkID); + throw new PatchException("Unapplicable hunk #" + hunkID + " @@ " + startIdx); + } + } + if (dryRun) { + if (isAddition) + idx--; + } else { + if (isAddition) { + target.add(idx, hunkLine.substring(1)); + } else if (isRemovalLine(hunkLine)) { + target.remove(idx); + idx--; + } + } + idx++; + } + idx++; // indices in the target list are 0-based + lastPatchedLine = idx; + return new HunkReport((fuzz != 0 ? PatchStatus.Fuzzed : PatchStatus.Patched), null, startIdx, fuzz, hunkID); + } + + private boolean isAdditionLine(String hunkLine) { + return hunkLine.charAt(0) == '+'; + } + + private boolean isRemovalLine(String hunkLine) { + return hunkLine.charAt(0) == '-'; + } + + private SinglePatch getNextPatch() throws IOException, PatchException { + SinglePatch patch = new SinglePatch(); + for (; ; ) { + String line = readPatchLine(); + if (line == null) + return null; + + if (line.startsWith("Index:")) { + patch.targetPath = line.substring(6).trim(); + } else if (line.startsWith("MIME: application/octet-stream;")) { + unreadPatchLine(); + readBinaryPatchContent(patch); + break; + } else if (line.startsWith("--- ")) { + unreadPatchLine(); + readPatchContent(patch); + break; + } else if (line.startsWith("*** ")) { + throw new IllegalArgumentException("Invalid patch format, Context Format not supported"); + } else if (isNormalDiffRange(line)) { + throw new IllegalArgumentException("Invalid patch format, Normal Format not supported"); + } + } + return patch; + } + + private boolean isNormalDiffRange(String line) { + return normalAddRangePattern.matcher(line).matches() + || normalChangeRangePattern.matcher(line).matches() + || normalDeleteRangePattern.matcher(line).matches(); + } + + /** + * Reads binary diff hunk. + */ + private void readBinaryPatchContent(SinglePatch patch) throws PatchException, IOException { + List hunks = new ArrayList(); + Hunk hunk = new Hunk(0, 0, 0, 0, new ArrayList<>()); + for (; ; ) { + String line = readPatchLine(); + if (line == null || line.startsWith("Index:") || line.length() == 0) { + unreadPatchLine(); + break; + } + if (patch.binary) { + hunk.lines.add(line); + } else { + Matcher m = binaryHeaderPattern.matcher(line); + if (m.matches()) { + patch.binary = true; + int length = Integer.parseInt(m.group(3)); + if (length == -1) + break; + hunks.add(hunk); + } + } + } + patch.hunks = hunks.toArray(new Hunk[hunks.size()]); + } + + /** + * Reads unified diff hunks. + */ + private void readPatchContent(SinglePatch patch) throws IOException, PatchException { + String base = readPatchLine(); + if (base == null || !base.startsWith("--- ")) + throw new PatchException("Invalid unified diff header: " + base); + String modified = readPatchLine(); + if (modified == null || !modified.startsWith("+++ ")) + throw new PatchException("Invalid unified diff header: " + modified); + if (patch.targetPath == null) + computeTargetPath(base, modified, patch); + + List hunks = new ArrayList(); + Hunk hunk = null; + + for (; ; ) { + String line = readPatchLine(); + if (line == null || line.length() == 0 || line.startsWith("Index:")) { + unreadPatchLine(); + break; + } + char c = line.charAt(0); + if (c == '@') { + hunk = parseRange(line); + hunks.add(hunk); + } else if (c == ' ' || c == '+' || c == '-') { + hunk.lines.add(line); + } else if (line.equals(Hunk.ENDING_NEWLINE)) { + patch.noEndingNewline = true; + } else { + // first seen in mercurial diffs: be optimistic, this is probably the end of this patch + unreadPatchLine(); + break; + } + } + patch.hunks = hunks.toArray(new Hunk[hunks.size()]); + } + + private void computeTargetPath(String base, String modified, SinglePatch patch) { + base = base.substring("+++ ".length()); + modified = modified.substring("--- ".length()); + // first seen in mercurial diffs: base and modified paths are different: base starts with "a/" and modified starts with "b/" + if ((base.equals("/dev/null") || base.startsWith("a/")) && (modified.equals("/dev/null") || modified.startsWith("b/"))) { + if (base.startsWith("a/")) + base = base.substring(2); + if (modified.startsWith("b/")) + modified = modified.substring(2); + } + base = untilTab(base).trim(); + if (base.equals("/dev/null")) { + // "/dev/null" in base indicates a new file + patch.targetPath = untilTab(modified).trim(); + patch.mode = Mode.ADD; + } else { + patch.targetPath = base; + patch.mode = modified.equals("/dev/null") ? Mode.DELETE : Mode.CHANGE; + } + } + + private String untilTab(String base) { + int pathEndIdx = base.indexOf('\t'); + if (pathEndIdx > 0) + base = base.substring(0, pathEndIdx); + return base; + } + + private Hunk parseRange(String range) throws PatchException { + Matcher m = unifiedRangePattern.matcher(range); + if (!m.matches()) + throw new PatchException("Invalid unified diff range: " + range); + + int baseStart = Integer.parseInt(m.group(1)); + int baseCount = m.group(2) != null ? Integer.parseInt(m.group(2).substring(1)) : 1; + int modifiedStart = Integer.parseInt(m.group(3)); + int modifiedCount = m.group(4) != null ? Integer.parseInt(m.group(4).substring(1)) : 1; + return new Hunk(baseStart, baseCount, modifiedStart, modifiedCount, new ArrayList<>()); + } + + private String readPatchLine() throws IOException { + if (patchLineRead) + patchLine = patchReader.readLine(); + else + patchLineRead = true; + return patchLine; + } + + private void unreadPatchLine() { + patchLineRead = false; + } + + private static class SinglePatch { + //String targetIndex; + String targetPath; + Hunk[] hunks; + //boolean targetMustExist = true; // == false if the patch contains one hunk with just additions ('+' lines) + @SuppressWarnings("unused") + boolean noEndingNewline; // resulting file should not end with a newline + boolean binary; // binary patches contain one encoded Hunk + Mode mode; + } + + enum Mode { + /** + * Update to existing file + */ + CHANGE, + /** + * Adding a new file + */ + ADD, + /** + * Deleting an existing file + */ + DELETE + } + + public static enum PatchStatus { + Patched(true), + Missing(false), + Failure(false), + Skipped(true), + Fuzzed(true); + + private boolean success; + + PatchStatus(boolean success) { + this.success = success; + } + + public boolean isSuccess() { + return success; + } + } + + public static final class PatchReport { + private String target; + private boolean binary; + private PatchStatus status; + private Throwable failure; + private List hunks; + + PatchReport(String target, boolean binary, PatchStatus status, Throwable failure, List hunks) { + this.target = target; + this.binary = binary; + this.status = status; + this.failure = failure; + this.hunks = hunks; + } + + public String getTarget() { + return target; + } + + public boolean isBinary() { + return binary; + } + + public PatchStatus getStatus() { + return status; + } + + public Throwable getFailure() { + return failure; + } + + public List getHunks() { + return hunks; + } + } + + public static interface IContextProvider { + public List getData(String target); + + public void setData(String target, List data); + } + + public static class HunkReport { + private PatchStatus status; + private Throwable failure; + private int index; + private int fuzz; + private int hunkID; + public Hunk hunk; + + public HunkReport(PatchStatus status, Throwable failure, int index, int fuzz, int hunkID) { + this.status = status; + this.failure = failure; + this.index = index; + this.fuzz = fuzz; + this.hunkID = hunkID; + } + + public HunkReport(PatchStatus status, Throwable failure, int index, int fuzz, int hunkID, Hunk hunk) { + this(status, failure, index, fuzz, hunkID); + this.hunk = hunk; + } + + public PatchStatus getStatus() { + return status; + } + + public Throwable getFailure() { + return failure; + } + + public int getIndex() { + return index; + } + + public int getFuzz() { + return fuzz; + } + + public int getHunkID() { + return hunkID; + } + } + + private boolean similar(String target, String hunk, char lineType) { + if (c14nAccess) { + if (c14nWhitespace) { + target = target.replaceAll("[\t| ]+", " "); + hunk = hunk.replaceAll("[\t| ]+", " "); + } + String[] t = target.split(" "); + String[] h = hunk.split(" "); + + //don't check length, changing any modifier to default (removing it) will change length + int targetIndex = 0; + int hunkIndex = 0; + while (targetIndex < t.length && hunkIndex < h.length) { + boolean isTargetAccess = isAccess(t[targetIndex]); + boolean isHunkAccess = isAccess(h[hunkIndex]); + if (isTargetAccess || isHunkAccess) { + //Skip access modifiers + if (isTargetAccess) + targetIndex++; + if (isHunkAccess) + hunkIndex++; + continue; + } + String hunkPart = h[hunkIndex]; + String targetPart = t[targetIndex]; + boolean labels = isLabel(targetPart) && isLabel(hunkPart); + if (!labels && !targetPart.equals(hunkPart)) + return false; + hunkIndex++; + targetIndex++; + } + return h.length == hunkIndex && t.length == targetIndex; + } + + if (c14nWhitespace) + return target.replaceAll("[\t| ]+", " ").equals(hunk.replaceAll("[\t| ]+", " ")); + else + return target.equals(hunk); + } + + private boolean isAccess(String data) { + return data.equalsIgnoreCase("public") || + data.equalsIgnoreCase("private") || + data.equalsIgnoreCase("protected") || + data.equalsIgnoreCase("final"); + } + + private boolean isLabel(String data) //Damn FernFlower + { + return data.startsWith("label"); + } + + /** + * The patch is invalid or cannot be applied on the specified file. + * + * @author Maros Sandor + */ + @SuppressWarnings("serial") + public static final class PatchException extends Exception { + public PatchException(String msg) { + super(msg); + } + } + + /** + * One unidiff or context hunk. + * + * @author Maros Sandor + */ + public record Hunk( + int baseStart, + int baseCount, + int modifiedStart, + int modifiedCount, + List lines + ) { + public static final String ENDING_NEWLINE = "\\ No newline at end of file"; + } + + public static class SingleFileContext implements ContextualPatch.IContextProvider { + private List data; + + private SingleFileContext(String base) { + data = NewLineDetector.readLines(base); + } + + private SingleFileContext(InputStream base) throws IOException { + data = NewLineDetector.readLines(base); + } + + @Override + public List getData(String target) { + List out = new ArrayList(data.size() + 5); + out.addAll(data); + return out; + } + + @Override + public void setData(String target, List data) { + this.data = data; + } + + public List getData() { + return this.data; + } + } +} diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/util/NewLineDetector.java b/src/main/java/net/minecraftforge/mcmaven/impl/util/NewLineDetector.java new file mode 100644 index 0000000..1f42f3e --- /dev/null +++ b/src/main/java/net/minecraftforge/mcmaven/impl/util/NewLineDetector.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ +package net.minecraftforge.mcmaven.impl.util; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; + +import java.io.BufferedReader; +import java.io.StringReader; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +// The normal BufferedReader doesn't have a way to determine between a +// file with a newline at the end and a file with no newline at the end. +// So we detect that and add an extra empty result from 'readLine' +public class NewLineDetector { + public static List readLines(String text) { + try { + return readLines(new StringReader(text)); + } catch (IOException e) { // Should never happen as we're reading from a string + return Util.sneak(e); + } + } + public static List readLines(InputStream input) throws IOException { + return readLines(new InputStreamReader(input, StandardCharsets.UTF_8)); + } + private static List readLines(Reader input) throws IOException { + var reader = new NewLineDetector(input); + List lines = new ArrayList(); + for (String line; (line = reader.readLine()) != null; ) + lines.add(line); + return lines; + } + + private final BufferedReader buffered; + private final Detector detector; + + public NewLineDetector(Reader reader) { + this.detector = new Detector(reader); + this.buffered = new BufferedReader(this.detector); + } + + public String readLine() throws IOException { + String ret = this.buffered.readLine(); + if (ret == null && this.detector.lastWasNewline) { + this.detector.lastWasNewline = false; + return ""; + } + return ret; + } + + private static class Detector extends Reader { + private final Reader wrapped; + private boolean lastWasNewline = false; + + public Detector(Reader wrapped) { + this.wrapped = wrapped; + } + + @Override + public int read(char[] cbuf, int off, int len) throws IOException { + int ret = this.wrapped.read(cbuf, off, len); + if (ret > 0) { + int c = cbuf[off + ret - 1]; + lastWasNewline = c == '\n'; + } + return ret; + } + + @Override + public void close() throws IOException { + this.wrapped.close(); + } + } +} \ No newline at end of file diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/util/ProcessUtils.java b/src/main/java/net/minecraftforge/mcmaven/impl/util/ProcessUtils.java index 688486a..b8dc072 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/util/ProcessUtils.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/util/ProcessUtils.java @@ -284,7 +284,7 @@ public static File recompileJar(File javaHome, List classpath, File source var sourcesOutput = new File(temp, "sources").getAbsoluteFile(); try { // Ensure the output directory exists - FileUtils.ensureParent(sourcesOutput); + FileUtils.ensure(sourcesOutput); try (ZipInputStream zin = new ZipInputStream(new FileInputStream(sourcesJar))) { ZipEntry entry; diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/util/StupidHacks.java b/src/main/java/net/minecraftforge/mcmaven/impl/util/StupidHacks.java index a012229..f3e9fb4 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/util/StupidHacks.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/util/StupidHacks.java @@ -4,9 +4,27 @@ */ package net.minecraftforge.mcmaven.impl.util; +import static net.minecraftforge.mcmaven.impl.Mavenizer.LOGGER; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.function.ToIntFunction; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +import org.jetbrains.annotations.Nullable; +import net.minecraftforge.mcmaven.impl.Mavenizer; +import net.minecraftforge.mcmaven.impl.mappings.Mappings; import net.minecraftforge.srgutils.MinecraftVersion; +import net.minecraftforge.util.file.FileUtils; /* * Here be stupid hacks that help us support old versions, @@ -16,7 +34,7 @@ public class StupidHacks { public static Artifact fixLegacyTools(Artifact artifact) { - // Some MCPConfig versions reference a merge tool that has an invlaid 'BUKKIT' side. + // Some MCPConfig versions reference a merge tool that has an invalid 'BUKKIT' side. // This was fixed in 0.2.3.3 https://github.com/MinecraftForge/MergeTool/commit/e68e1b06ba87c68bc1a5c922395286b53a17dddf if ("net.minecraftforge".equals(artifact.getGroup()) && "mergetool".equals(artifact.getName()) && "0.2.3.2".equals(artifact.getVersion())) return artifact.withVersion("0.2.3.3"); @@ -32,10 +50,18 @@ public static Artifact fixLegacyTools(Artifact artifact) { // They moved then from root to modules "org.scala-lang:scala-swing_2.11:1.0.1", "org.scala-lang.modules:scala-swing_2.11:1.0.1", "org.scala-lang:scala-xml_2.11:1.0.2", "org.scala-lang.modules:scala-xml_2.11:1.0.2", - "org.scala-lang:scala-parser-combinators_2.11:1.0.1", "org.scala-lang.modules:scala-parser-combinators_2.11:1.0.1" + "org.scala-lang:scala-parser-combinators_2.11:1.0.1", "org.scala-lang.modules:scala-parser-combinators_2.11:1.0.1", + // They renamed the artifact + "tv.twitch:twitch-external-platform:4.5", "tv.twitch:twitch-platform:6.5" ); + // these are artifacts that Mojang referenced in the past, but now are deleted. + //private static Set DELETED_ARTIFACTS = Set.of( + // "tv.twitch:twitch-external-platform" + //); - public static Artifact fixLegacyForgeDeps(Artifact artifact) { + public static @Nullable Artifact fixLegacyForgeDeps(Artifact artifact) { + //if (DELETED_ARTIFACTS.contains(artifact.withVersion(null).toString())) + // return null; // scala did some releases overwriting their own files, so we host them ourselves because of the hashes, but during dev it should be fine to just use the official var newCoords = FORGE_FIXES.get(artifact.toString()); if (newCoords != null) @@ -54,4 +80,164 @@ public static boolean isLegacyRenamer(String version) { } + private static final int OUT_OF_MEMORY = -1001; + private static final int FAILED_DECOMPILE = -1002; + private static final int INVALID_INITAL_HEAP = -1003; + private static final int NOT_ENOUGH_MEMORY = -1004; + // Yes this is slow as fuck, but this is only run during a decompile run which is already slow, + // This is to check if Fernflower is broken and returning success when it really failed. + // It also eagerly exits the process when something fails so as to not waste time. + private static int parseDecompileLog(String line) { + if (line.startsWith("Initial heap size set to a larger value than the maximum heap size")) + return INVALID_INITAL_HEAP; + if (line.startsWith("Could not reserve enough space for object heap")) + return NOT_ENOUGH_MEMORY; + if (line.startsWith("java.lang.OutOfMemoryError:")) + return OUT_OF_MEMORY; + if (line.startsWith("Exception in thread") && line.contains("java.lang.OutOfMemoryError")) + return OUT_OF_MEMORY; + if (line.contains("ERROR:")) { + // String message = "Method " + mt.getName() + " " + mt.getDescriptor() + " in class " + cl.qualifiedName + " couldn't be written."; + // String message = "Method " + mt.getName() + " " + mt.getDescriptor() + " in class " + classWrapper.getClassStruct().qualifiedName + " couldn't be written."; + // String message = "Method " + mt.getName() + " " + mt.getDescriptor() + " in class " + node.classStruct.qualifiedName + " couldn't be written."; + if (line.endsWith(" couldn't be written.")) + return FAILED_DECOMPILE; + // DecompilerContext.getLogger().logError("Class " + cl.qualifiedName + " couldn't be processed.", t); + if (line.endsWith(" couldn't be processed.")) + return FAILED_DECOMPILE; + // DecompilerContext.getLogger().logError("Class " + cl.qualifiedName + " couldn't be fully decompiled.", t); + if (line.endsWith(" couldn't be fully decompiled.")) + return FAILED_DECOMPILE; + // String message = "Method " + mt.getName() + " " + mt.getDescriptor() + " in class " + classStruct.qualifiedName + " couldn't be decompiled."; + if (line.endsWith(" couldn't be decompiled.")) + return FAILED_DECOMPILE; + } + return 0; + } + + public static ProcessUtils.Result runDecompiler(File jdk, File log, File tool, List defaultJvm, List run) { + ToIntFunction logHandler = StupidHacks::parseDecompileLog; + var jvm = Mavenizer.fillDecompileJvmArgs(defaultJvm, true, true); + + var ret = ProcessUtils.runJar(jdk, log.getParentFile(), log, tool, jvm, run, logHandler); + if (ret.exitCode == NOT_ENOUGH_MEMORY) { + LOGGER.error("Failed to create JVM with Not Enough Memory issue, Modern minecraft requires atleast 4GB to decompile. Run it on a system with more ram."); + } else if (ret.exitCode == INVALID_INITAL_HEAP) { + LOGGER.error("Attempted to run decompile with JVM args: " + jvm + " resulted in Invalid Inital and Max heap settings."); + LOGGER.error("This is typically caused by you having a environement variable setting the global memory options, remove or set those variables to values higher then 4GB."); + } + if (ret.exitCode == OUT_OF_MEMORY || ret.exitCode == INVALID_INITAL_HEAP) { + var newJvm = Mavenizer.fillDecompileJvmArgs(defaultJvm, false, false); + if (!newJvm.equals(jvm)) { + LOGGER.error("First decompile failed with OutOfMemory using JVM Args: " + jvm); + LOGGER.error("Attempting again with: " + newJvm); + ret = ProcessUtils.runJar(jdk, log.getParentFile(), log, tool, newJvm, run, logHandler); + if (ret.exitCode == OUT_OF_MEMORY) + LOGGER.error("Ran out of memory again, you can specify more manually using the --decompile-memory Mavenizer argument"); + } + } + return ret; + } + + // Forge patches sometimes arn't in full SRG names so we need to pick known 'good' mappings + // So I scanned Forge's git history and found each mapping change. + private record ForgeMappings(ComparableVersion start, String mappings) {} + private static final List FORGE_MAPPINGS = new ArrayList(); + private static void mappings(String forge, String mappings) { + FORGE_MAPPINGS.add(new ForgeMappings(new ComparableVersion(forge), mappings)); + } + static { + mappings("1.7.2-10.12.0.1024", "snapshot:20140205-1.7.2"); + mappings("1.8-11.14.3.1503", "snapshot:20141130-1.8"); + mappings("1.8.8-11.14.4.1575", "snapshot:20151122-1.8"); + mappings("1.8.9-11.15.0.1656", "stable:20-1.8.8"); + mappings("1.9-12.16.0.1766", "snapshot:20160312-1.9"); + mappings("1.9.4-12.17.0.1908", "snapshot:20160518-1.9.4"); + mappings("1.10.2-12.18.2.2125", "snapshot:20161111-1.10.2"); + mappings("1.11-13.19.1.2198", "snapshot:20161220-1.11"); + mappings("1.12-14.21.0.2334", "snapshot:20170617-1.12"); + mappings("1.12-14.21.0.2352", "snapshot:20170624-1.12"); + mappings("1.12.2-14.23.0.2502", "snapshot:20171003-1.12"); + mappings("1.13", "snapshot:20180921-1.13"); + mappings("1.14.2-26.0.0", "snapshot:20190526-1.13.2"); + mappings("1.14.2-26.0.6", "snapshot:20190608-1.14.2"); + mappings("1.14.2-26.0.47", "snapshot:20190621-1.14.2"); + mappings("1.14.3-27.0.60", "snapshot:20190719-1.14.3"); + mappings("1.14.4", "official"); + } + public static Mappings getDefaultMappings(ComparableVersion forgeVersion) { + String version = null; + for (var map : FORGE_MAPPINGS) { + if (forgeVersion.compareTo(map.start) < 0) + break; + version = map.mappings(); + } + if(version == null) + throw new IllegalStateException("Could not determine default mappings for " + forgeVersion); + return Mappings.of(version); + } + + // List of 'bad' forge builds that are just broke for some reason and we explicitly don't want to support + public static final Set BLACKLISTED_FORGE_BUILDS = Set.of( + // Has some fucked up patches with weird generics, I have no idea how they came into existance, they dont match github... + // This is the very first 1.8.8 build, and the next one works fine, so I'm chalking it up to Gradle Snapshot voodoo + "1.8.8-11.14.4.1575-1.8.8", + // Has a reference to scala.actors.threadpool.Arrays which is part of org.scala-lang:scala-actors + // Which we don't ship, but instead is a transitive of org.scala-lang:scala-actors-migration_2.11:1.1.0 + // I could add the library to this version, but i'd rather just skip it + "1.10.2-12.18.2.2103" + ); + + private static final ComparableVersion MC_1_12 = new ComparableVersion("1.12"); + private static final ComparableVersion MC_1_12_FIXED_SRG = new ComparableVersion("1.12-14.21.0.2340"); + public static String legacyMcp(ComparableVersion forge) { + // net/minecraft/command/AdvancementCommand$ActionType$Mode -> AdvancementCommand$Mode + if (forge.compareTo(MC_1_12) >= 0 && forge.compareTo(MC_1_12_FIXED_SRG) < 0) + return "mcp940.zip"; + return null; + } + + // Has a invalid `/cp/MethodsReturnNonnullByDefault.java` patch file. + private static final ComparableVersion NON_NULL_PATCH_START = new ComparableVersion("1.12.2-14.23.5.2839"); + private static final ComparableVersion NON_NULL_PATCH_END = new ComparableVersion("1.12.2-14.23.5.2844"); + public static boolean needsPatchFixes(Artifact name) { + if (!Constants.FORGE_GROUP.equals(name.getGroup()) || !Constants.FORGE_NAME.equals(name.getName())) + return false; + var current = new ComparableVersion(name.getVersion()); + if (current.compareTo(NON_NULL_PATCH_START) >= 0 && current.compareTo(NON_NULL_PATCH_END) <= 0) + return true; + return false; + } + + public static File fixPatches(Artifact artifact, File input, File output) { + var cache = Util.cache(output) + .add("input", output); + + if (Mavenizer.checkCache(output, cache)) + return output; + + // Has a invalid `/cp/MethodsReturnNonnullByDefault.java` patch file. + // Guess it was created by old FG bug, so we can just kill it + FileUtils.ensureParent(output); + try (var zin = new ZipInputStream(new FileInputStream(input)); + var zout = new ZipOutputStream(new FileOutputStream(output))) { + + for (ZipEntry entry; (entry = zin.getNextEntry()) != null; ) { + var name = entry.getName(); + // Good night sweet prince + if ("cp/MethodsReturnNonnullByDefault.java.patch".equals(name)) + continue; + + var newEntry = new ZipEntry(name); + newEntry.setTime(entry.getTime()); + zout.putNextEntry(newEntry); + zin.transferTo(zout); + zout.closeEntry(); + } + } catch (IOException e) { + Util.sneak(e); + } + + return output; + } } diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/util/Util.java b/src/main/java/net/minecraftforge/mcmaven/impl/util/Util.java index 602d65b..02acb47 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/util/Util.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/util/Util.java @@ -6,6 +6,7 @@ import java.io.BufferedReader; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; @@ -20,6 +21,7 @@ import java.util.zip.ZipEntry; import net.minecraftforge.mcmaven.impl.Mavenizer; +import net.minecraftforge.mcmaven.impl.cache.Cache; import net.minecraftforge.util.hash.HashFunction; import net.minecraftforge.util.hash.HashStore; import net.minecraftforge.util.logging.Logger; @@ -235,4 +237,38 @@ public static void filter(Logger logger, String header, List files) { logger.getInfo().println(); } } + + public static String forgeToMcVersion(String version) { + // Save for a few april-fools versions, Minecraft doesn't use _ in their version names. + // So when Forge needs to reference a version of Minecraft that uses - in the name, it replaces + // it with _ + // This could cause issues if we ever support a version with _ in it, but fuck it I don't care right now. + int idx = version.indexOf('-'); + if (idx == -1) + throw new IllegalArgumentException("Invalid Forge version: " + version); + return version.substring(0, idx).replace('_', '-'); + } + + public static final File getArtifact(Cache cache, String coords) { + return getArtifact(cache, Artifact.from(coords)); + } + public static final File getArtifact(Cache cache, Artifact artifact) { + // Some libraries are on Minecraft's maven. Such as launchwrapper. + // Rather then configure Forge's server to proxy Mojang's I add this check. + if ("net.minecraft".equals(artifact.getGroup())) + return cache.minecraft().download(artifact); + try { + return cache.maven().download(artifact); + } catch (Exception e) { + // If its 404 on Forge's maven, try Mojang's + if (e.getCause() instanceof FileNotFoundException) { + try { + return cache.minecraft().download(artifact); + } catch (Exception e2) { + e.addSuppressed(e2); + } + } + return Util.sneak(e); + } + } } From 30ae20396fd7382afd61c86a008ff4d0c00b4758 Mon Sep 17 00:00:00 2001 From: LexManos Date: Thu, 21 May 2026 14:29:30 -0700 Subject: [PATCH 03/12] Make exception failure stack a bit smaller --- .../mcmaven/impl/util/Task.java | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/util/Task.java b/src/main/java/net/minecraftforge/mcmaven/impl/util/Task.java index 529a690..4c95463 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/util/Task.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/util/Task.java @@ -8,12 +8,14 @@ import java.io.File; import java.time.Duration; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.SequencedCollection; import java.util.concurrent.Callable; import java.util.function.Supplier; +import java.util.stream.Collectors; import java.util.stream.StreamSupport; /** Represents a task that can be executed. Tasks in this tool will always provide a file. */ @@ -47,7 +49,7 @@ public interface Task { * Useful for our output json file. */ default Supplier filePathSupplier() { - return () -> this.execute().getAbsolutePath(); + return () -> this.execute().getAbsolutePath(); } static Task named(String name, Callable supplier) { @@ -82,6 +84,23 @@ private static Supplier cast(Object obj) { }; } + static class DependencyException extends RuntimeException { + private static final long serialVersionUID = 1L; + private List deps = new ArrayList<>(); + private final Task erroring; + + public DependencyException(Task erroring, Throwable cause, Task self) { + super("Failed to execute task `%s` which is required by task `%s`".formatted(erroring.name(), self.name()), cause); + this.erroring = erroring; + deps.add(self); + } + + @Override + public String getMessage() { + return "Failed to execute task `%s` which is required by task `%s`".formatted(this.erroring.name(), this.deps.stream().map(Task::name).collect(Collectors.joining("->"))); + } + } + final class Simple implements Task { private final String name; private final SequencedCollection> deps; @@ -97,9 +116,9 @@ private Simple(String name, SequencedCollection Date: Thu, 21 May 2026 14:40:00 -0700 Subject: [PATCH 04/12] Add Legacy and ranged support to vanilla Minecraft Remove FGVersion dependency in MCPLegacy, make decisions purely based on Minecraft Version --- settings.gradle | 2 +- .../minecraftforge/mcmaven/cli/MavenTask.java | 1 - .../mcmaven/impl/MinecraftMaven.java | 68 +++++++---- .../mcmaven/impl/mappings/MCPMappings.java | 14 +++ .../mcmaven/impl/mappings/Mappings.java | 16 ++- .../impl/mappings/OfficialMappings.java | 6 + .../impl/mappings/ParchmentMappings.java | 6 + .../mcmaven/impl/mappings/SRGMappings.java | 14 +++ .../mcmaven/impl/repo/forge/ForgeRepo.java | 9 +- .../impl/repo/mcpconfig/MCPConfigRepo.java | 56 ++++++++- .../impl/repo/mcpconfig/MCPLegacy.java | 106 +++++++++++++----- .../impl/repo/mcpconfig/MinecraftTasks.java | 35 +++--- 12 files changed, 261 insertions(+), 72 deletions(-) diff --git a/settings.gradle b/settings.gradle index c7b081d..7a45c84 100644 --- a/settings.gradle +++ b/settings.gradle @@ -30,7 +30,7 @@ dependencyResolutionManagement.versionCatalogs.register('libs') { library 'gson', 'com.google.code.gson', 'gson' version '2.10.1' library 'jopt', 'net.sf.jopt-simple', 'jopt-simple' version '6.0-alpha-3' - library 'jver', 'net.minecraftforge', 'java-provisioner' version '2.0.4' + library 'jver', 'net.minecraftforge', 'java-provisioner' version '2.0.5' library 'srgutils', 'net.minecraftforge', 'srgutils' version '0.6.3' library 'diff', 'io.codechicken', 'DiffPatch' version '2.0.0.36' // Fuzzy patching diff --git a/src/main/java/net/minecraftforge/mcmaven/cli/MavenTask.java b/src/main/java/net/minecraftforge/mcmaven/cli/MavenTask.java index 9c4d3d5..559e6cb 100644 --- a/src/main/java/net/minecraftforge/mcmaven/cli/MavenTask.java +++ b/src/main/java/net/minecraftforge/mcmaven/cli/MavenTask.java @@ -211,7 +211,6 @@ static OptionParser run(String[] args, boolean getParser) throws Exception { options.has(globalAuxiliaryVariantsO), options.has(disableGradleO), options.has(stubO), - new HashSet<>(), new ArrayList<>(options.valuesOf(accessTransformerO)), new ArrayList<>(options.valuesOf(facadeConfigO)), options.valueOf(outputJsonO) diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/MinecraftMaven.java b/src/main/java/net/minecraftforge/mcmaven/impl/MinecraftMaven.java index 5c67560..817c826 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/MinecraftMaven.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/MinecraftMaven.java @@ -13,6 +13,7 @@ import net.minecraftforge.mcmaven.impl.repo.forge.ForgeRepo; import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCP; import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCPConfigRepo; +import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCPLegacy; import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MinecraftTasks; import net.minecraftforge.mcmaven.impl.util.Artifact; import net.minecraftforge.mcmaven.impl.util.ComparableVersion; @@ -44,7 +45,6 @@ import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.TreeMap; import java.util.function.Supplier; @@ -73,7 +73,6 @@ public record MinecraftMaven( boolean globalAuxiliaryVariants, boolean disableGradle, boolean stubJars, - Set mcpConfigVersions, List accessTransformer, List facadeConfigs, @Nullable File outputJsonFile @@ -241,17 +240,39 @@ protected void createMinecraft(Artifact artifact, MCPConfigRepo mcprepo, Map 1) + verStart = MinecraftVersion.from(pts[0].substring(1)); + if (pts[1].length() > 1) + verEnd = MinecraftVersion.from(pts[1].substring(0, pts[1].length() - 1)); + version = "all"; + } + + // Quick check of maven-metadata.xml to get a list of known MCPConfig versions + var maven = mcprepo.getCache().maven(); + var mcpConfigVersions = new HashSet<>(maven.getVersions(MCP.artifact("1.21.11"))); + mcpConfigVersions.remove("1.12.2"); // Force 1.12.2 to not use MCPConfig, instead using the legacy MCP toolchain + var mcpLegacyVersions = new HashSet<>(maven.getVersions(MCPLegacy.artifact("1.12.2"))); + if ("all".equals(version)) { if (outputJson != null) throw new IllegalArgumentException("Output Json does not support bulk operations"); var manifestFile = mcprepo.getLauncherManifestTask().execute(); var manifest = JsonData.launcherManifest(manifestFile); + // The launcher manifest is normally in reverse release order, so don't worry about sorting them. for (var ver : manifest.versions) { - + MinecraftVersion cver; try { - var cver = MinecraftVersion.from(ver.id); - if (cver.compareTo(MIN_OFFICIAL_MAPPINGS) < 0) + cver = MinecraftVersion.from(ver.id); + if (verStart != null && cver.compareTo(verStart) < 0) + continue; + if (verEnd != null && cver.compareTo(verEnd) > 0) + continue; + if (cver.compareTo(MIN_OFFICIAL_MAPPINGS) < 0 && !mcpConfigVersions.contains(ver.id) && !mcpLegacyVersions.contains(ver.id)) continue; } catch (IllegalArgumentException e) { // Invalid/unknown version, so skip. @@ -261,42 +282,49 @@ protected void createMinecraft(Artifact artifact, MCPConfigRepo mcprepo, Map artifacts = null; + var mappings = this.mappings.withMCVersion(ver.id); - if (hasMcp(mcprepo, ver.id)) + // If we're using official mappings, and are on a version that doesn't have official mappings, lets just use SRG names + if (this.mappings.channel().equals("official")) { + if (cver.compareTo(MIN_OFFICIAL_MAPPINGS) < 0) + mappings = Mappings.of("srg").withMCVersion(ver.id); + } + + if (mcpConfigVersions.contains(ver.id)) artifacts = mcprepo.process(versioned, mappings, outputJson); - else if (mappings.channel().equals("official") && (hasOfficialMappings(mcprepo, ver.id) || !MCPConfigRepo.isObfuscated(ver.id))) + else if (mcpLegacyVersions.contains(ver.id)) + artifacts = mcprepo.processLegacy(versioned, mappings, outputJson); + else if (hasOfficialMappings(mcprepo, ver.id) || !MCPConfigRepo.isObfuscated(ver.id)) artifacts = mcprepo.processWithoutMcp(versioned, mappings, outputJson); else { LOGGER.info("Skipping " + versioned + " no mcp config"); continue; } - finalize(versioned, mappings, artifacts); + LOGGER.push(); + try { + finalize(versioned, mappings, artifacts); + } finally { + LOGGER.pop(); + } } } else { var mcVersion = mcpToMcVersion(version); var mappings = this.mappings.withMCVersion(mcVersion); List artifacts = null; - if (hasMcp(mcprepo, version)) + if (mcpConfigVersions.contains(version)) artifacts = mcprepo.process(artifact, mappings, outputJson); - else if (mappings.channel().equals("official") && (hasOfficialMappings(mcprepo, mcVersion) || !MCPConfigRepo.isObfuscated(mcVersion))) + else if (mcpLegacyVersions.contains(version)) + artifacts = mcprepo.processLegacy(artifact, mappings, outputJson); + else if (hasOfficialMappings(mcprepo, mcVersion) || !MCPConfigRepo.isObfuscated(mcVersion)) artifacts = mcprepo.processWithoutMcp(artifact, mappings, outputJson); else - throw new IllegalStateException("Can not process " + artifact + " as it does not have a MCPConfig ror official mappings"); + throw new IllegalStateException("Can not process " + artifact + " as it does not have a MCPConfig, MCPLegacy, or official mappings"); finalize(artifact, mappings, artifacts); } } - // This is ugly, but there really isn't a good way to check if a file exists. DownlodUtils could probably use a 'exists' that HEAD's and returns false non 404 - private boolean hasMcp(MCPConfigRepo repo, String version) { - if (this.mcpConfigVersions.isEmpty()) { - var versions = repo.getCache().maven().getVersions(MCP.artifact("1.21.11")); - this.mcpConfigVersions.addAll(versions); - } - return this.mcpConfigVersions.contains(version); - } - // No official mappings, we can't do anything private static boolean hasOfficialMappings(MCPConfigRepo repo, String version) { var tasks = repo.getMCTasks(version); diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/mappings/MCPMappings.java b/src/main/java/net/minecraftforge/mcmaven/impl/mappings/MCPMappings.java index dd4024e..b98f95f 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/mappings/MCPMappings.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/mappings/MCPMappings.java @@ -12,6 +12,7 @@ import net.minecraftforge.mcmaven.impl.cache.MavenCache; import net.minecraftforge.mcmaven.impl.repo.forge.FG2Userdev; +import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCPLegacy; import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCPSide; import net.minecraftforge.mcmaven.impl.util.Artifact; import net.minecraftforge.mcmaven.impl.util.Task; @@ -62,6 +63,19 @@ private ResolvedMappings withContextImpl(FG2Userdev fg2) { return new ResolvedMappings(channel(), version(), artifact, root, mappings, csv, extra); } + @Override + public ResolvedMappings withContext(MCPLegacy legacy) { + return this.resolved.computeIfAbsent(legacy, _ -> withContextImpl(legacy)); + } + + private ResolvedMappings withContextImpl(MCPLegacy legacy) { + var csv = downloadCsv(legacy.getCache().maven()); + var root = new File(legacy.getBuildFolder(), "data/mapings"); + var artifact = this.getArtifact(legacy); + var mappings = legacy.getMappings(); + return new ResolvedMappings(channel(), version(), artifact, root, mappings, csv, null); + } + private Task downloadCsv(MavenCache maven) { // This is simple because it's the old MCP Bot based zip files // So it's just a matter of downloading from Forge's maven. diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/mappings/Mappings.java b/src/main/java/net/minecraftforge/mcmaven/impl/mappings/Mappings.java index 21bef7d..81320ba 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/mappings/Mappings.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/mappings/Mappings.java @@ -7,6 +7,7 @@ import java.io.File; import net.minecraftforge.mcmaven.impl.repo.forge.FG2Userdev; +import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCPLegacy; import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCPSide; import net.minecraftforge.mcmaven.impl.util.Artifact; import net.minecraftforge.mcmaven.impl.util.Constants; @@ -72,6 +73,7 @@ public File getFolder(File root) { public abstract ResolvedMappings withContext(MCPSide side); public abstract ResolvedMappings withContext(FG2Userdev fg2); + public abstract ResolvedMappings withContext(MCPLegacy legacy); protected Artifact getArtifact(MCPSide side) { @@ -89,7 +91,8 @@ protected Artifact getArtifact(MCPSide side) { protected Artifact getArtifact(FG2Userdev fg2) { //net.minecraft:mappings_{CHANNEL}:{MC_VERSION}-{legacy|FORGE_VERSION}[-{VERSION}]@zip var name = "mappings_" + this.channel; - var version = fg2.getMinecraftVersion(); + // either the raw mc version, or MC_VERSION-PYTHON_VERSION; + var version = fg2.getMCP().getName().getVersion(); if (fg2.getExtraMappings() == null) version += "-legacy"; else @@ -99,4 +102,15 @@ protected Artifact getArtifact(FG2Userdev fg2) { return Artifact.from(Constants.MC_GROUP, name, version).withExtension("zip"); } + + protected Artifact getArtifact(MCPLegacy legacy) { + //net.minecraft:mappings_{CHANNEL}:{MC_VERSION}-legacy[-{VERSION}]@zip + var name = "mappings_" + this.channel; + // either the raw mc version, or MC_VERSION-PYTHON_VERSION; + var version = legacy.getName().getVersion() + "-legacy"; + if (this.version() != null && !legacy.getMinecraftTasks().getVersion().equals(this.version())) + version += '-' + this.version(); + + return Artifact.from(Constants.MC_GROUP, name, version).withExtension("zip"); + } } diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/mappings/OfficialMappings.java b/src/main/java/net/minecraftforge/mcmaven/impl/mappings/OfficialMappings.java index 985676b..5a398dd 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/mappings/OfficialMappings.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/mappings/OfficialMappings.java @@ -21,6 +21,7 @@ import net.minecraftforge.mcmaven.impl.Mavenizer; import net.minecraftforge.mcmaven.impl.repo.forge.FG2Userdev; import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCPConfigRepo; +import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCPLegacy; import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCPSide; import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MinecraftTasks; import net.minecraftforge.mcmaven.impl.util.Constants; @@ -65,6 +66,11 @@ public ResolvedMappings withContext(FG2Userdev fg2) { throw new IllegalStateException("Official mappings does not support Legacy: " + fg2.getName()); } + @Override + public ResolvedMappings withContext(MCPLegacy legacy) { + throw new IllegalStateException("Official mappings does not support Legacy: " + legacy.getName()); + } + private Task makeCsv(MCPSide side) { var mc = side.getMCP().getMinecraftTasks(); var srg = side.getTasks().getMappings(); diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/mappings/ParchmentMappings.java b/src/main/java/net/minecraftforge/mcmaven/impl/mappings/ParchmentMappings.java index cc71b3e..ff7ebc0 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/mappings/ParchmentMappings.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/mappings/ParchmentMappings.java @@ -27,6 +27,7 @@ import net.minecraftforge.mcmaven.impl.cache.MavenCache; import net.minecraftforge.mcmaven.impl.repo.forge.FG2Userdev; import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCP; +import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCPLegacy; import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCPSide; import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MinecraftTasks; import net.minecraftforge.mcmaven.impl.util.Task; @@ -82,6 +83,11 @@ public ResolvedMappings withContext(FG2Userdev fg2) { throw new IllegalStateException("Parchment mappings does not support Legacy: " + fg2.getName()); } + @Override + public ResolvedMappings withContext(MCPLegacy legacy) { + throw new IllegalStateException("Parchment mappings does not support Legacy: " + legacy.getName()); + } + private Task makeCsv(MCPSide side) { var mc = side.getMCP().getMinecraftTasks(); var srg = side.getTasks().getMappings(); diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/mappings/SRGMappings.java b/src/main/java/net/minecraftforge/mcmaven/impl/mappings/SRGMappings.java index 7aa9ba0..b97d4ed 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/mappings/SRGMappings.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/mappings/SRGMappings.java @@ -17,6 +17,7 @@ import net.minecraftforge.mcmaven.impl.Mavenizer; import net.minecraftforge.mcmaven.impl.repo.forge.FG2Userdev; +import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCPLegacy; import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCPSide; import net.minecraftforge.mcmaven.impl.util.Task; import net.minecraftforge.mcmaven.impl.util.Util; @@ -68,6 +69,19 @@ public ResolvedMappings withContextImpl(FG2Userdev fg2) { return new ResolvedMappings(channel(), version(), artifact, root, mappings, csv, extra); } + @Override + public ResolvedMappings withContext(MCPLegacy legacy) { + return this.resolved.computeIfAbsent(legacy, _ -> withContextImpl(legacy)); + } + + public ResolvedMappings withContextImpl(MCPLegacy legacy) { + var root = new File(legacy.getBuildFolder(), "data/mapings"); + var csv = csvTask(root); + var artifact = this.getArtifact(legacy); + var mappings = legacy.getMappings(); + return new ResolvedMappings(channel(), version(), artifact, root, mappings, csv, null); + } + private Task csvTask(File root) { return Task.named("srg2names[" + this + "][Empty]", () -> makeEmptyCsv(root)); } diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/ForgeRepo.java b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/ForgeRepo.java index 2403925..0bb4e00 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/ForgeRepo.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/ForgeRepo.java @@ -160,7 +160,9 @@ private List processV2(String version, Mappings baseMappings, M var srgSources = dev.getSources(); var mappings = baseMappings.withContext(dev); - var sourcesTask = new RenameTask(build, userdev.getName(), srgSources, mappings, true, srgTask, mcVersion); + var sourcesTask = mappings.channel().equals("srg") + ? srgSources + : new RenameTask(build, userdev.getName(), srgSources, mappings, true, srgTask, mcVersion); var classesTask = new RecompileTask(build, name, jdks, dev.getJavaTarget(), dev::getClasspath, sourcesTask, mappings); var mappingCoords = mappings.getArtifact(); @@ -226,8 +228,11 @@ private List processV3(String version, Mappings baseMappings, M var mcVersion = joined.getMCP().getMinecraftTasks().getVersion(); var mappings = baseMappings.withContext(joined); var srgTask = joined.getTasks().getMappings(); + var srgSources = patcher.get(); - var sourcesTask = new RenameTask(build, userdev.getName(), patcher.get(), mappings, true, srgTask, mcVersion); + var sourcesTask = mappings.channel().equals("srg") + ? srgSources + : new RenameTask(build, userdev.getName(), srgSources, mappings, true, srgTask, mcVersion); var recompile = new RecompileTask(build, name, jdks, patcher.getJavaTarget(), patcher::getClasspath, sourcesTask, mappings); var classesTask = new InjectTask(build, this.cache, name, patcher, recompile, mappings); diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MCPConfigRepo.java b/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MCPConfigRepo.java index 3dfa28d..b872210 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MCPConfigRepo.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MCPConfigRepo.java @@ -6,6 +6,7 @@ import net.minecraftforge.mcmaven.impl.Mavenizer; import net.minecraftforge.mcmaven.impl.repo.Repo; +import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MinecraftTasks.ArtifactFile; import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MinecraftTasks.MCFile; import net.minecraftforge.mcmaven.impl.tasks.RecompileTask; import net.minecraftforge.mcmaven.impl.tasks.RenameTask; @@ -200,8 +201,11 @@ public List process(Artifact artifact, Mappings baseMappings, M var srgTask = mcpSide.getTasks().getMappings(); var jdks = this.getCache().jdks(); var javaTarget = mcpSide.getMCP().getConfig().java_target; + var srgSources = mcpSide.getSources(); - var sourcesTask = new RenameTask(build, name.getName(), mcpSide.getSources(), mappings, true, srgTask, mcVersion); + var sourcesTask = mappings.channel().equals("srg") + ? srgSources + : new RenameTask(build, name.getName(), srgSources, mappings, true, srgTask, mcVersion); var recompile = new RecompileTask(build, name, jdks, javaTarget, mcpSide::getClasspath, sourcesTask, mappings); var classesTask = mergeExtra(build, side, recompile, mcpSide.getTasks().getExtra(), mappings); @@ -277,6 +281,56 @@ public List processWithoutMcp(Artifact artifact, Mappings mappi } } + public List processLegacy(Artifact artifact, Mappings baseMappings, Map> outputJson) { + if (!Constants.MC_GROUP.equals(artifact.getGroup())) + throw new IllegalArgumentException("MCPConfigRepo cannot process modules that aren't for group net.minecraft"); + + var side = artifact.getName(); + var version = artifact.getVersion(); + var name = Artifact.from("net.minecraft", side, version); + switch (side) { + //case "client": // Eventually add sided variants + //case "server": + case "joined": + break; + default: + throw new IllegalArgumentException("MCPConfigRepo does not support artifact: " + artifact); + } + + var jdks = this.cache.jdks(); + var mcp = this.legacy(version, null); + var tasks = mcp.getMinecraftTasks(); + var mcVersion = tasks.getVersion(); + var build = mcp.getBuildFolder(); + var srgTask = mcp.getMappings(); + var srgSources = mcp.getChild().getFinalStep(); + + var mappings = baseMappings.withContext(mcp); + var sourcesTask = mappings.channel().equals("srg") + ? srgSources + : new RenameTask(build, mcp.getName().getName(), srgSources, mappings, true, srgTask, mcVersion); + var javaTarget = mcp.getJavaTarget(); + var classesTask = new RecompileTask(build, name, jdks, javaTarget, mcp::getClasspath, sourcesTask, mappings); + + var mappingArtifacts = mappingArtifacts(build, mappings, mcVersion, outputJson); + + var sources = pending("Sources", sourcesTask, name.withClassifier("sources"), true, sourceVariant(baseMappings)); + var classes = pending("Classes", classesTask, name, false, () -> { + var libs = tasks.getClientLibraries().stream().map(ArtifactFile::artifact).toList(); + return classVariants(baseMappings, javaTarget, libs, List.of()); + }); + var metadata = pending("Metadata", metadata(build, artifact.getName(), tasks), name.withClassifier("metadata").withExtension("zip"), false, metadataVariant()); + var pom = pending("Maven POM", tasks.joinedPom(), name.withExtension("pom"), false); + + if (outputJson != null) + outputJson.put("mc.version", tasks::getVersion); + + var ret = new ArrayList(); + ret.addAll(mappingArtifacts); + ret.addAll(List.of(sources, classes, pom, metadata)); + return ret; + } + public List processExtra(String module, String version) { if (!module.startsWith("net.minecraft:")) throw new IllegalArgumentException("MCPConfigRepo cannot process modules that aren't for group net.minecraft"); diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MCPLegacy.java b/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MCPLegacy.java index 9ea0c7a..7158f0c 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MCPLegacy.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MCPLegacy.java @@ -32,9 +32,9 @@ import com.google.gson.reflect.TypeToken; import net.minecraftforge.mcmaven.impl.Mavenizer; +import net.minecraftforge.mcmaven.impl.cache.Cache; import net.minecraftforge.mcmaven.impl.cache.JDKCache; import net.minecraftforge.mcmaven.impl.cache.MavenCache; -import net.minecraftforge.mcmaven.impl.repo.forge.FGVersion; import net.minecraftforge.mcmaven.impl.repo.forge.FG2Userdev; import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MinecraftTasks.MCFile; import net.minecraftforge.mcmaven.impl.util.Artifact; @@ -118,6 +118,14 @@ public MinecraftTasks getMinecraftTasks() { return this.mcTasks; } + public Cache getCache() { + return this.repo.getCache(); + } + + public File getBuildFolder() { + return this.build; + } + public File getData() { return this.data; } @@ -134,9 +142,40 @@ public Task getExceptorJson() { return this.exceptorJson; } + public int getJavaTarget() { + // The vanilla launcher uses Java 8 for all legacy versions + // And the MCP archives have variant patches that deal with the decompile differences + // Also, modern macs simply do not have java 5 & 6 distros. So we have to use java 8 + // My bulk testing works fine, I do not set the -target version when recompiling, Not + // sure if I care to do that. + /* + var comp = new ComparableVersion(this.mcVersion); + if (comp.compareTo(MC_1_6_1) < 0) + return 5; + if (comp.compareTo(MC_1_12) < 0) + return 6; + */ + return 8; + } + + public List getClasspath() { + var classpath = new ArrayList(); + for (var lib : this.getMinecraftTasks().getClientLibraries()) + classpath.add(lib.file()); + + // They added annotations in 1.7.10 so we need null annotations + var comp = new ComparableVersion(this.mcVersion); + if (comp.compareTo(MC_1_7_10) >= 0) { + var artifact = Artifact.from("com.google.code.findbugs:jsr305:1.3.9"); + classpath.add(this.getCache().maven().download(artifact)); + } + + return classpath; + } + public Child getChild() { if (this.child == null) - this.child = new Child(this.build, null, FGVersion.v2_3); + this.child = new Child(this.build, null); return this.child; } @@ -147,7 +186,7 @@ public Child getChild(FG2Userdev forge) { var ret = this.children.get(key); if (ret == null) { var base = new File(this.build, key); - ret = new Child(base, accessTransformer, forge.getGradleVersion()); + ret = new Child(base, accessTransformer); this.children.put(key, ret); } return ret; @@ -479,19 +518,17 @@ public class Child { private final JDKCache jdks; private final File build; private final File accessTransformer; - private final FGVersion fgVersion; private final String prefix; private final MCPCfg cfg; private final Task inject; - public Child(File build, File accessTransformer, FGVersion fg) { + public Child(File build, File accessTransformer) { this.maven = MCPLegacy.this.repo.getCache().maven(); this.jdks = MCPLegacy.this.repo.getCache().jdks(); this.build = build; this.accessTransformer = accessTransformer; - this.fgVersion = fg; this.prefix = MCPLegacy.this.prefix(); - this.cfg = MCPCfg.get(fg, MCPLegacy.this.mcVersion); + this.cfg = MCPCfg.get(MCPLegacy.this.mcVersion); this.inject = mcpInject(); } @@ -585,7 +622,6 @@ private File cleanup(File input) { .add("tool", tool) .add("input", input) .add("args", String.join(", ", cleanup.args)) - .addKnown("fg", this.fgVersion.name()) .addKnown("mcp", MCPLegacy.this.dataHash); if (Mavenizer.checkCache(output, cache)) @@ -628,13 +664,12 @@ private File decompile(File input, File libraries) { var tool = maven.download(decompiler.artifact); - int java_version = this.fgVersion == FGVersion.v2_3 ? 8 : 6; + int java_version = MCPLegacy.this.getJavaTarget(); var cache = Util.cache(output) .add("input", input) .add("tool", tool) .add("args", String.join(", ", decompiler.args)) - .addKnown("fg", this.fgVersion.name()) .addKnown("java", Integer.toString(java_version)); if (Mavenizer.checkCache(output, cache)) @@ -932,9 +967,12 @@ else if (wanted.endsWith("+f")) } // The main difference between FG versions was the decompiler we invoked/embeded - private static final ComparableVersion MC_1_8_8 = new ComparableVersion("1.8.8"); - private static final ComparableVersion MC_1_9 = new ComparableVersion("1.9"); - private static final ComparableVersion MC_1_9_4 = new ComparableVersion("1.9.4"); + private static final ComparableVersion MC_1_6_1 = new ComparableVersion("1.6.1"); + private static final ComparableVersion MC_1_7_10 = new ComparableVersion("1.7.10"); + private static final ComparableVersion MC_1_8_8 = new ComparableVersion("1.8.8"); + private static final ComparableVersion MC_1_9 = new ComparableVersion("1.9"); + private static final ComparableVersion MC_1_9_4 = new ComparableVersion("1.9.4"); + private static final ComparableVersion MC_1_12 = new ComparableVersion("1.12"); private record Exceptor(boolean json, boolean markers, boolean lvt) { private static Exceptor get(ComparableVersion mc) { @@ -999,22 +1037,30 @@ private record Decompiler(Artifact artifact, boolean legacy, List args) "-log=WARN", "{libraries}" // Will be expanded to a list of all libraries using -e= ); - private static Decompiler get(FGVersion fg, ComparableVersion mc) { - if (fg == FGVersion.v2_3) { - return new Decompiler(Constants.FERNFLOWER_FG_2_3, false, DECOMPILER_ARGS); - } else if (fg == FGVersion.v2_2 || fg == FGVersion.v2_1) { - return new Decompiler(Constants.FERNFLOWER_FG_2_2, false, DECOMPILER_ARGS); - } else { - var decompTool = Constants.FERNFLOWER_FG_2_0_LEGACY; - var decompArgs = DECOMPILER_ARGS_188; - if (mc.compareTo(MC_1_9_4) >= 0) - decompTool = Constants.FERNFLOWER_FG_2_0_194; - else if (mc.compareTo(MC_1_8_8) >= 0) - decompTool = Constants.FERNFLOWER_FG_2_0_194; - else // 1.8 - decompArgs = DECOMPILER_ARGS_18; - return new Decompiler(decompTool, true, decompArgs); + private static Decompiler get(ComparableVersion mc) { + var decompTool = Constants.FERNFLOWER_FG_2_0_LEGACY; + var decompArgs = DECOMPILER_ARGS_188; + var legacy = true; + + if (mc.compareTo(MC_1_12) >= 0) { + decompTool = Constants.FERNFLOWER_FG_2_3; + decompArgs = DECOMPILER_ARGS; + legacy = false; + } else if (mc.compareTo(MC_1_8_8) >= 0) { + decompTool = Constants.FERNFLOWER_FG_2_2; + decompArgs = DECOMPILER_ARGS; + legacy = false; + } + /* + else if (mc.compareTo(MC_1_9_4) >= 0) + decompTool = Constants.FERNFLOWER_FG_2_0_194; + else if (mc.compareTo(MC_1_8_8) >= 0) + decompTool = Constants.FERNFLOWER_FG_2_0_194; + */ + else { // 1.8 + decompArgs = DECOMPILER_ARGS_18; } + return new Decompiler(decompTool, legacy, decompArgs); } } private record MCPCfg( @@ -1022,11 +1068,11 @@ private record MCPCfg( Cleanup cleanup, Decompiler decompiler ) { - private static MCPCfg get(FGVersion fg, String mc) { + private static MCPCfg get(String mc) { var comp = new ComparableVersion(mc); var except = Exceptor.get(comp); var cleanup = Cleanup.get(mc); - var decomp = Decompiler.get(fg, comp); + var decomp = Decompiler.get(comp); return new MCPCfg(except, cleanup, decomp); } } diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MinecraftTasks.java b/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MinecraftTasks.java index e08caf9..12ecd81 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MinecraftTasks.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MinecraftTasks.java @@ -51,6 +51,7 @@ public class MinecraftTasks { private Task renameClient; private Task renameServer; private Task clientPom; + private Task joinedPom; private Task serverPom; private Task extractServer; @@ -263,12 +264,18 @@ private File renameJar(String name, Task jarTask, Task mapTask) { public Task clientPom() { if (this.clientPom == null) - this.clientPom = Task.named("pom[" + this.version + "][client]", Task.deps(this.versionJson), this::clientPomImpl); + this.clientPom = Task.named("pom[" + this.version + "][client]", Task.deps(this.versionJson), () -> clientPomImpl("client")); return this.clientPom; } - private File clientPomImpl() { - var output = new File(this.cacheRoot, "client.pom"); + public Task joinedPom() { + if (this.joinedPom == null) + this.joinedPom = Task.named("pom[" + this.version + "][joined]", Task.deps(this.versionJson), () -> clientPomImpl("joined")); + return this.joinedPom; + } + + private File clientPomImpl(String name) { + var output = new File(this.cacheRoot, name + "pom"); var json = this.versionJson.execute(); var cache = Util.cache(output) @@ -279,7 +286,7 @@ private File clientPomImpl() { var meta = JsonData.minecraftVersion(json); - var builder = new POMBuilder("net.minecraft", "client", version) + var builder = new POMBuilder("net.minecraft", name, version) .preferGradleModule() .dependencies(deps -> { for (var lib : meta.libraries) @@ -287,13 +294,7 @@ private File clientPomImpl() { }); - FileUtils.ensureParent(output); - try (var os = new FileOutputStream(output)) { - os.write(builder.build().getBytes(StandardCharsets.UTF_8)); - } catch (IOException | ParserConfigurationException | TransformerException e) { - Util.sneak(e); - } - + write(builder, output); cache.save(); return output; } @@ -305,7 +306,7 @@ public Task serverPom() { } private File serverPomImpl() { - var output = new File(this.cacheRoot, "client.pom"); + var output = new File(this.cacheRoot, "server.pom"); var jarFile = versionFile(MCFile.SERVER_JAR).execute(); var cache = Util.cache(output) @@ -318,16 +319,18 @@ private File serverPomImpl() { var builder = new POMBuilder("net.minecraft", "server", version) .preferGradleModule() .dependencies(deps -> libs.forEach(deps::add)); + write(builder, output); + cache.save(); + return output; + } + private static void write(POMBuilder pom, File output) { FileUtils.ensureParent(output); try (var os = new FileOutputStream(output)) { - os.write(builder.build().getBytes(StandardCharsets.UTF_8)); + os.write(pom.build().getBytes(StandardCharsets.UTF_8)); } catch (IOException | ParserConfigurationException | TransformerException e) { Util.sneak(e); } - - cache.save(); - return output; } public Task extractServer() { From e01465bc4d5787e5cf1db8dac8eb910b88b30e40 Mon Sep 17 00:00:00 2001 From: LexManos Date: Mon, 1 Jun 2026 17:48:18 -0700 Subject: [PATCH 05/12] Support FG1.1 and 1.2, for Minecraft 1.7.2->1.7.10 --- settings.gradle | 2 +- .../minecraftforge/mcmaven/cli/MavenTask.java | 1 - .../mcmaven/impl/MinecraftMaven.java | 11 +- .../mcmaven/impl/repo/forge/FG2Userdev.java | 306 ++++++++++-------- .../mcmaven/impl/repo/forge/FGVersion.java | 14 +- .../mcmaven/impl/repo/forge/ForgeRepo.java | 159 ++++++++- .../impl/repo/forge/LegacyRenamer.java | 108 +++++++ .../impl/repo/mcpconfig/MCPConfigRepo.java | 7 +- .../impl/repo/mcpconfig/MCPLegacy.java | 201 ++++++++---- .../mcmaven/impl/tasks/MCPNames.java | 16 +- .../mcmaven/impl/util/Constants.java | 11 +- .../mcmaven/impl/util/StupidHacks.java | 20 +- 12 files changed, 624 insertions(+), 232 deletions(-) create mode 100644 src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/LegacyRenamer.java diff --git a/settings.gradle b/settings.gradle index 7a45c84..41995f9 100644 --- a/settings.gradle +++ b/settings.gradle @@ -31,7 +31,7 @@ dependencyResolutionManagement.versionCatalogs.register('libs') { library 'gson', 'com.google.code.gson', 'gson' version '2.10.1' library 'jopt', 'net.sf.jopt-simple', 'jopt-simple' version '6.0-alpha-3' library 'jver', 'net.minecraftforge', 'java-provisioner' version '2.0.5' - library 'srgutils', 'net.minecraftforge', 'srgutils' version '0.6.3' + library 'srgutils', 'net.minecraftforge', 'srgutils' version '0.6.6' library 'diff', 'io.codechicken', 'DiffPatch' version '2.0.0.36' // Fuzzy patching library 'fastcsv', 'de.siegmar', 'fastcsv' version '3.4.0' diff --git a/src/main/java/net/minecraftforge/mcmaven/cli/MavenTask.java b/src/main/java/net/minecraftforge/mcmaven/cli/MavenTask.java index 559e6cb..521d89a 100644 --- a/src/main/java/net/minecraftforge/mcmaven/cli/MavenTask.java +++ b/src/main/java/net/minecraftforge/mcmaven/cli/MavenTask.java @@ -7,7 +7,6 @@ import java.io.File; import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; import joptsimple.OptionParser; diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/MinecraftMaven.java b/src/main/java/net/minecraftforge/mcmaven/impl/MinecraftMaven.java index 817c826..8ff075f 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/MinecraftMaven.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/MinecraftMaven.java @@ -79,7 +79,7 @@ public record MinecraftMaven( ) { // Only 1.14.4+ has official mappings, we can support more when we add more mappings private static final MinecraftVersion MIN_OFFICIAL_MAPPINGS = MinecraftVersion.from("1.14.4"); - private static final ComparableVersion MIN_SUPPORTED_FORGE = new ComparableVersion("1.8"); + private static final ComparableVersion MIN_SUPPORTED_FORGE = new ComparableVersion("1.7.2"); public MinecraftMaven { LOGGER.info(" Output: " + output.getAbsolutePath()); @@ -185,10 +185,7 @@ protected void createForge(Artifact artifact, MCPConfigRepo mcprepo, ForgeRepo r var mcVersion = Util.forgeToMcVersion(ver); var fg = FGVersion.fromForge(ver); - if (fg == null || fg.ordinal() < FGVersion.v2.ordinal()) // Unsupported - continue; - - if (fg.ordinal() >= FGVersion.v3.ordinal()) + if (fg == null) // Unsupported continue; if (verStart != null && cver.compareTo(verStart) < 0) @@ -222,6 +219,8 @@ protected void createForge(Artifact artifact, MCPConfigRepo mcprepo, ForgeRepo r LOGGER.pop(); } } + + ForgeRepo.Info.finish(); } else { if (StupidHacks.BLACKLISTED_FORGE_BUILDS.contains(artifact.getVersion())) throw new IllegalArgumentException("Forge version " + artifact.getVersion() + " has been blacklisted for technical reasons, pick a different Forge version"); @@ -256,6 +255,8 @@ protected void createMinecraft(Artifact artifact, MCPConfigRepo mcprepo, Map(maven.getVersions(MCP.artifact("1.21.11"))); mcpConfigVersions.remove("1.12.2"); // Force 1.12.2 to not use MCPConfig, instead using the legacy MCP toolchain var mcpLegacyVersions = new HashSet<>(maven.getVersions(MCPLegacy.artifact("1.12.2"))); + // Used while developing on maven local + //mcpLegacyVersions.addAll(List.of("1.7.2", "1.7.10-pre4", "1.7.10")); if ("all".equals(version)) { if (outputJson != null) diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/FG2Userdev.java b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/FG2Userdev.java index ca4b604..fa79b2b 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/FG2Userdev.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/FG2Userdev.java @@ -11,6 +11,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -64,7 +65,6 @@ public class FG2Userdev implements ForgeVersionCommon { private final MCPLegacy.Child mcpChild; private final Task sourcesTask; private final Map extracts = new HashMap<>(); - private final Task patches; FG2Userdev(File build, ForgeRepo forge, Artifact name, FGVersion fgVersion) { this.build = build; @@ -94,14 +94,26 @@ public class FG2Userdev implements ForgeVersionCommon { this.mcVersion = Util.forgeToMcVersion(this.name.getVersion()); + // FG 1.x we need to merge the access transformers, FML and Forge patches are seperate, and source/resources are not in a zip + var fg1 = this.fgVersion.ordinal() <= FGVersion.v1_2.ordinal(); + var legacyMCP = StupidHacks.legacyMcp(new ComparableVersion(this.name.getVersion())); this.mcp = this.forge.mcpconfig.legacy(this.mcVersion, legacyMCP); - this.mcpChild = this.mcp.getChild(this); + var atFile = fg1 ? mergeAts() : extract("merged_at.cfg"); + // 1.7.2 Forge switched to FG1.2 in 1.7.2-10.12.0.1048, this changed our decompiler to + // a version that bulk sorts all classes. So produces different decomp. + var legacyFG = "1.7.2".equals(this.mcVersion) && this.fgVersion != FGVersion.v1_2 ? this.fgVersion : null; + this.mcpChild = this.mcp.getChild(this, atFile, legacyFG); this.forgeVersion = this.name.getVersion().substring(this.name.getVersion().indexOf('-') + 1); - this.patches = extract("patches.zip"); - this.sourcesTask = patch(); + // FG 1.1 needs to remap between FML and Forge patches + if (this.fgVersion == FGVersion.v1 || this.fgVersion == FGVersion.v1_1) + this.sourcesTask = patchForgeRenamed(); + else if (this.fgVersion == FGVersion.v1_2) + this.sourcesTask = patchForge(); + else + this.sourcesTask = patch(); } public File getBuildFolder() { @@ -206,23 +218,33 @@ public List getLibraries() { @Override public List getClasspath() { var classpath = new ArrayList(); - var seen = new HashSet(); + var seen = new HashMap(); + var cache = this.forge.getCache(); // minecraft version.json libs + userdev libs for (var lib : this.getMCP().getMinecraftTasks().getClientLibraries()) { - classpath.add(lib.file()); - // We just want the group:name and clssifier, incase they have upated the version since we were built - seen.add(lib.artifact().withVersion(null).toString()); + + // We might have to upgrade a vanilla dependency + var artifact = StupidHacks.fixLegacyForgeDeps(lib.artifact()); + if (artifact == null) + continue; + + if (artifact != lib.artifact()) + classpath.add(Util.getArtifact(cache, artifact)); + else + classpath.add(lib.file()); + + // We just want the group:name and clssifier, in case they have updated the version since we were built + seen.put(artifact.withVersion(null).toString(), artifact); } - var cache = this.forge.getCache(); if (this.config.inheritsFrom == null) { // If there is no inherits, this is before Mojang added that feature to the launcher // So we need to try and strip out any libraries that come from the vanilla launcher. for (var lib : this.config.libraries) { var artifact = StupidHacks.fixLegacyForgeDeps(Artifact.from(lib.name)); var unversioned = artifact.withVersion(null).toString(); - if (!seen.contains(unversioned)) { + if (!seen.containsKey(unversioned)) { if (artifact != null) classpath.add(Util.getArtifact(cache, artifact)); } @@ -231,7 +253,7 @@ public List getClasspath() { for (var lib : this.config.libraries) { var artifact = StupidHacks.fixLegacyForgeDeps(Artifact.from(lib.name)); if (artifact != null) - classpath.add(Util.getArtifact(cache, artifact)); + classpath.addFirst(Util.getArtifact(cache, artifact)); // Add our versions before vanilla's in case we upgrade } } @@ -283,12 +305,12 @@ private File extractImpl(String name) { private Task patch() { var input = inject(); var patches = StupidHacks.needsPatchFixes(this.name) - ? fixPatches(this.patches) - : this.patches; + ? fixPatches(extract("patches.zip")) + : extract("patches.zip"); return Task.named("patch", Task.deps(input, patches), - () -> this.patch(input.execute(), patches.execute()) + () -> this.patch(input.execute(), patches.execute(), "patched") ); } @@ -299,9 +321,9 @@ private Task fixPatches(Task base) { ); } - private File patch(File input, File patches) { - var output = new File(this.build, "patched.jar"); - var rejects = new File(this.build, "patches-rejects.jar"); + private File patch(File input, File patches, String name) { + var output = new File(this.build, name + ".jar"); + var rejects = new File(this.build, name + "-rejects.jar"); var cache = Util.cache(output) .add("input", input) @@ -318,7 +340,7 @@ private File patch(File input, File patches) { if (rejects.exists()) rejects.delete(); - if (this.fgVersion == FGVersion.v2) + if (this.fgVersion.ordinal() <= FGVersion.v2.ordinal()) patchUnstable(input, patches, output, rejects); else patchStable(input, patches, output, rejects); @@ -348,7 +370,7 @@ private void patchUnstable(File input, File patchesArchive, File output, File re Util.sneak(e); } - + Throwable failure = null; // Apply them try (var zin = new ZipInputStream(new FileInputStream(input)); var zout = new ZipOutputStream(new FileOutputStream(output)) @@ -375,13 +397,14 @@ private void patchUnstable(File input, File patchesArchive, File output, File re for (var hunk : result.getHunks()) LOGGER.error(" Hunk #" + hunk.getHunkID() + ": " + hunk.getStatus().name()); - Util.sneak(result.getFailure()); - } - for (var itr = ctx.getData().iterator(); itr.hasNext();) { - var line = itr.next(); - zout.write(line.getBytes(StandardCharsets.UTF_8)); - if (itr.hasNext()) - zout.write('\n'); + failure = result.getFailure(); + } else { + for (var itr = ctx.getData().iterator(); itr.hasNext();) { + var line = itr.next(); + zout.write(line.getBytes(StandardCharsets.UTF_8)); + if (itr.hasNext()) + zout.write('\n'); + } } } zout.closeEntry(); @@ -389,6 +412,9 @@ private void patchUnstable(File input, File patchesArchive, File output, File re } catch (IOException e) { Util.sneak(e); } + + if (failure != null) + Util.sneak(failure); } // Modern versions that use out Fernflower fork produce stabelized output so we can use @@ -459,160 +485,156 @@ private File inject(File input, File sources, File resources) { } } - /* - private Task mcpPatch() { - var input = decompileCleanup(); - return Task.named("mcp-patch", - Task.deps(input), () -> mcpPatch(input) + private Task mergeAts() { + var fml = extract("src/main/resources/fml_at.cfg"); + var forge = extract("src/main/resources/forge_at.cfg"); + return Task.named("merge-ats", + Task.deps(fml, forge), () -> this.mergeAts(fml.execute(), forge.execute()) ); } - private File mcpPatch(Task inputTask) { - var input = inputTask.execute(); - var output = new File(this.build, "mcp-patched.jar"); + private File mergeAts(File fml, File forge) { + var output = new File(this.build, "merged-at.cfg"); var cache = Util.cache(output) - .add("input", input) - .addKnown("mcp", this.getMCP().getDataHash()); - - //if (Mavenizer.checkCache(output, cache)) - // return output; + .add("fml", fml) + .add("forge", forge); - var patchesMap = new HashMap>(); - try (var zin = new ZipInputStream(new FileInputStream(this.getMCP().getData()))) { - final var prefix = "patches/minecraft_merged_ff/"; - for (ZipEntry entry; (entry = zin.getNextEntry()) != null;) { - var name = entry.getName(); - if (!name.startsWith(prefix)) - continue; + if (Mavenizer.checkCache(output, cache)) + return output; - int patchIndex = name.indexOf(".patch"); - // 6 is the length of ".patch" + 3 to account for .## at the end of the file. - if (patchIndex < 0 || patchIndex < name.length() - 9) - continue; + try (var out = new FileOutputStream(output)) { + out.write(Files.readAllBytes(fml.toPath())); + out.write("\n# Forge\n".getBytes(StandardCharsets.UTF_8)); + out.write(Files.readAllBytes(forge.toPath())); + cache.save(); + return output; + } catch (IOException e) { + return Util.sneak(e); + } + } - var filename = name.substring(prefix.length(), patchIndex); - var lines = new ArrayList(); - boolean first = true; - @SuppressWarnings("resource") - var reader = new NewLineDetector(new InputStreamReader(zin, StandardCharsets.UTF_8)); - for (String line; (line = reader.readLine()) != null; ) { - // Old patches are generated with the 'diff' comand as the first line - // And DiffPatch doesn't support that so we need to skip it - if (!(first && line.startsWith("diff "))) - lines.add(line); - first = false; - } + private Task injectLegacy() { + var input = this.mcpChild.getFinalStep(); + return Task.named("inject", + Task.deps(input), + () -> this.injectLegacy(input.execute()) + ); + } - var patch = PatchFile.fromLines(name, lines, true); + private File injectLegacy(File input) { + var output = new File(this.build, "injected.jar"); + var cache = Util.cache(output) + .add("input", input) + .addKnown("data", this.dataHash); - // Some old patches were trimmed, so the last empty line of context was removed. So lets try and detect that case - var last = patch.patches.getLast(); - if (last.getContextLines().size() != last.length1) - last.recalculateLength(); + if (Mavenizer.checkCache(output, cache)) + return output; - System.out.println("Patch: " + name); - patchesMap.computeIfAbsent(filename, _ -> new ArrayList<>()).add(patch); + FileUtils.ensureParent(output); + try(var out = new ZipOutputStream(new FileOutputStream(output))) { + var seen = new HashSet(); + try (var zin = new ZipInputStream(new FileInputStream(input))) { + for (ZipEntry entry = null; (entry = zin.getNextEntry()) != null; ) { + seen.add(entry.getName()); + out.putNextEntry(FileUtils.getStableEntry(entry.getName())); + zin.transferTo(out); + out.closeEntry(); + } } + try (var zin = new ZipInputStream(new FileInputStream(this.data))) { + for (ZipEntry entry = null; (entry = zin.getNextEntry()) != null; ) { + var name = entry.getName(); + if (entry.isDirectory()) + continue; + else if (name.startsWith("src/main/resources/")) + name = name.substring(19); + else if (name.startsWith("src/main/java/")) + name = name.substring(14); + else + continue; + + if (!seen.add(name)) + continue; + + out.putNextEntry(FileUtils.getStableEntry(name)); + zin.transferTo(out); + out.closeEntry(); + } + } + cache.save(); + return output; } catch (IOException e) { return Util.sneak(e); } + } - FileUtils.ensureParent(output); - try (var zin = new ZipInputStream(new FileInputStream(input)); - var zout = new ZipOutputStream(new FileOutputStream(output))) { - - for (ZipEntry entry; (entry = zin.getNextEntry()) != null; ) { - var name = entry.getName(); - var newEntry = new ZipEntry(name); - newEntry.setTime(entry.getTime()); - zout.putNextEntry(newEntry); + private Task patchFml() { + var base = injectLegacy(); + var patches = extract("fmlpatches.zip"); + return Task.named("patch-fml", + Task.deps(base, patches), () -> this.patch(base.execute(), patches.execute(), "patched-fml") + ); + } - if (!name.endsWith(".java")) { - zin.transferTo(zout); - zout.closeEntry(); - continue; - } + private Task patchForge() { + var base = patchFml(); + var patches = extract("forgepatches.zip"); + return Task.named("patch", + Task.deps(base, patches), () -> this.patch(base.execute(), patches.execute(), "patched") + ); + } - List lines = NewLineDetector.readLines(zin); - var patches = patchesMap.get(name.replace('/', '.')); - if (patches != null && !patches.isEmpty()) { - List results = null; - boolean success = false; - for (var patch : patches) { - var patcher = new io.codechicken.diffpatch.patch.Patcher(patch, lines, 1.0F, FuzzyLineMatcher.MatchMatrix.DEFAULT_MAX_OFFSET); - results = patcher.patch(PatchMode.FUZZY); // We need to use FUZZ because we inject annotations which causes an OFFSET, and apply ATs which requires ACCESS - success = results.stream().allMatch(r -> r.success); - if (success) { - lines = patcher.lines; - break; - } - } + private Task rename() { + var input = patchFml(); + return Task.named("rename", + Task.deps(input), () -> this.rename(input.execute()) + ); + } - if (!success) { - LOGGER.push(); - LOGGER.error("Input: " + input.getAbsolutePath()); - LOGGER.error("Output: " + output.getAbsolutePath()); - LOGGER.error("MCP: " + this.getMCP().getData().getAbsolutePath()); - LOGGER.error("Failed to apply patch to " + name); - for (int x = 0; x < results.size(); x++) - LOGGER.error(" #" + x + ": " + results.get(x).summary()); - LOGGER.pop(); - throw new IllegalStateException("Failed to apply patch to " + name); - } - } + private File rename(File input) { + var output = new File(this.build, "renamed.jar"); + var cache = Util.cache(output) + .add("input", input) + .addKnown("mappings", this.dataHash); - for (var line : lines) { - zout.write(line.getBytes(StandardCharsets.UTF_8)); - zout.write('\n'); - } + if (Mavenizer.checkCache(output, cache)) + return output; - zout.closeEntry(); - } - } catch (IOException e) { - return Util.sneak(e); - } + LegacyRenamer.rename(input, this.data, output, true); cache.save(); return output; } - private Task decompileCleanup() { - var input = decompile(); - return Task.named("decompile-cleanup", - Task.deps(input), () -> decompileCleanup(input.execute()) + private Task patchForgeRenamed() { + var base = rename(); + var patches = extract("forgepatches.zip"); + return Task.named("patch", + Task.deps(base, patches), () -> this.patch(base.execute(), patches.execute(), "patched") ); } - private File decompileCleanup(File input) { - var tool = this.forge.getCache().maven().download(Constants.MCPCLEANUP); - var output = new File(this.build, "decompile-cleaned.jar"); - var log = new File(this.build, "decompile-cleaned.log"); + public Task getSourcesWithJavadocs() { + var input = this.getSources(); + return Task.named("javadocs", + Task.deps(input), () -> this.javadocs(input.execute()) + ); + } + + private File javadocs(File input) { + var output = new File(this.build, "patched-javadocs.jar"); var cache = Util.cache(output) - .add("tool", tool) .add("input", input) - .addKnown("fg", this.fgVersion.name()); + .addKnown("mappings", this.dataHash); if (Mavenizer.checkCache(output, cache)) return output; - var args = new ArrayList(5); - args.addAll(List.of( - "--input", input.getAbsolutePath(), - "--output", output.getAbsolutePath(), - "--pre-patch" - )); - if (this.fgVersion == FGVersion.v2) - args.add("--fernflower"); // Version 2 is before we used our own fernflower fork, so we did a lot of pre-mcp cleanup - - var jdk = jdk(Constants.MCPCLEANUP_JAVA_VERSION); - var ret = ProcessUtils.runJar(jdk, this.globalBase, log, tool, Collections.emptyList(), args); - if (ret.exitCode != 0) - throw new IllegalStateException("Failed to run MCPCleanup (exit code " + ret.exitCode + "), See log: " + log.getAbsolutePath()); + LegacyRenamer.rename(input, this.data, output, false); cache.save(); return output; } - */ public Map getRuns() { // Use LinkedHashMap to keep things stable diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/FGVersion.java b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/FGVersion.java index 5e2f9ff..65e232e 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/FGVersion.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/FGVersion.java @@ -18,7 +18,17 @@ * GitHub.com */ public enum FGVersion { + /* + * Gradle 1.8, 'fmlpatches.zip' in SRG names 'forgepatches.zip' in mapped names + * Sources/resources included flat in the userdev jar + * All Mappings/SRG files included in userdev + * Forge and FML ats are seperate files + */ + v1("1.0"), v1_1("1.1"), + /* + * Forge patches moved to SRG names + */ v1_2("1.2"), /** * Gradle 2.14, Fernflower closed source with some 'fixes', technically lower then '2.0' because we re-used 2.0-SNAPSHOT... @@ -86,6 +96,7 @@ private static void forge(FGVersion fg, String forge) { } static { + forge(v1, "1.6.4-9.11.1.960"); forge(v1_1, "1.7.2-10.12.0.967"); forge(v1_2, "1.7.2-10.12.0.1048"); forge(v2, "1.8-11.14.3.1503"); @@ -108,6 +119,7 @@ private static void forge(FGVersion fg, String forge) { // Special versions that do not match the general ranges of historical versions. // This will probably need updating when we back port new ForgeDev toolchain to older versions. private static final List SPECIAL_CASES = List.of( + special(null, "1.6.4-9.11.1.964", "1.6.5"), special(v3, "1.12.2-14.23.5.2851", "1.12.3") ); @@ -131,7 +143,7 @@ private static SpecialCase special(FGVersion fg, String start, String end) { } for (var entry : FORGE_TO_FG.entrySet()) { - if (entry.getValue().compareTo(ver) <= 0) + if (ver.compareTo(entry.getValue()) >= 0) return entry.getKey(); } diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/ForgeRepo.java b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/ForgeRepo.java index 0bb4e00..0ed0eec 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/ForgeRepo.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/ForgeRepo.java @@ -5,6 +5,7 @@ package net.minecraftforge.mcmaven.impl.repo.forge; import net.minecraftforge.mcmaven.impl.cache.Cache; +import net.minecraftforge.mcmaven.impl.cache.MavenCache; import net.minecraftforge.mcmaven.impl.data.GradleModule; import net.minecraftforge.mcmaven.impl.mappings.Mappings; import net.minecraftforge.mcmaven.impl.repo.Repo; @@ -18,11 +19,14 @@ import net.minecraftforge.mcmaven.impl.Mavenizer; import net.minecraftforge.mcmaven.impl.util.POMBuilder; import net.minecraftforge.mcmaven.impl.util.POMBuilder.Dependencies.Dependency; +import net.minecraftforge.mcmaven.impl.util.StupidHacks; import net.minecraftforge.mcmaven.impl.util.Task; import net.minecraftforge.mcmaven.impl.util.Util; +import net.minecraftforge.srgutils.IMappingFile; import net.minecraftforge.util.data.json.JsonData; import net.minecraftforge.util.data.json.RunConfig; import net.minecraftforge.util.file.FileUtils; +import net.minecraftforge.util.hash.HashFunction; import static net.minecraftforge.mcmaven.impl.Mavenizer.LOGGER; @@ -31,20 +35,28 @@ import org.jetbrains.annotations.Nullable; +import java.io.ByteArrayOutputStream; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.function.Supplier; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; // TODO: [MCMavenizer][ForgeRepo] For now, the ForgeRepo needs to be fully complete with everything it has to do. // later, we can worry about refactoring it so that other repositories such as MCP (clean) and FMLOnly can function. @@ -90,20 +102,19 @@ public List process(Artifact artifact, Mappings mappings, MapMCP names. @@ -160,9 +168,18 @@ private List processV2(String version, Mappings baseMappings, M var srgSources = dev.getSources(); var mappings = baseMappings.withContext(dev); - var sourcesTask = mappings.channel().equals("srg") - ? srgSources - : new RenameTask(build, userdev.getName(), srgSources, mappings, true, srgTask, mcVersion); + + Task sourcesTask = srgSources; + // v1 and v1.1 have Forge patches in mapped names, and I haven't implemented converting to SRG yet as it would need to run Srg2Source + // So check if we are using the 'correct' mappings, and if not throw and exception + if (fgVersion == FGVersion.v1 || fgVersion == FGVersion.v1_1) { + var required = StupidHacks.getDefaultMappings(new ComparableVersion(version)); + if (!required.channel().equals(mappings.channel()) || !required.version().equals(mappings.version())) + throw new IllegalStateException("Can not setup " + name + " as it requires you to us the explicit mappings: " + required.toString()); + sourcesTask = dev.getSourcesWithJavadocs(); + } else if (!mappings.channel().equals("srg")) { + sourcesTask = new RenameTask(build, userdev.getName(), srgSources, mappings, true, srgTask, mcVersion); + } var classesTask = new RecompileTask(build, name, jdks, dev.getJavaTarget(), dev::getClasspath, sourcesTask, mappings); var mappingCoords = mappings.getArtifact(); @@ -173,7 +190,7 @@ private List processV2(String version, Mappings baseMappings, M var classes = pending("Classes", classesTask, name, false, () -> classVariants(baseMappings, dev, mappingCoords)); var metadata = pending("Metadata", metadata(build, dev, dev.getRuns()), name.withClassifier("metadata").withExtension("zip"), false, metadataVariant()); - var pom = pending("Maven POM", pom(mappings.getFolder(build), dev, version, null /*extraCoords*/, mappingCoords), name.withExtension("pom"), false); + var pom = pending("Maven POM", pom(mappings.getFolder(build), dev, version, null, mappingCoords), name.withExtension("pom"), false); // Gradle only allows downloading artifacts from one repo, so we need to pull in any classifers that we reference var classifiers = getClassifieres(name, dev.getLibraries(), new HashMap<>()); @@ -182,7 +199,6 @@ private List processV2(String version, Mappings baseMappings, M var ret = new ArrayList(); ret.addAll(mappingArtifacts); - //ret.addAll(extraOutput); ret.addAll(List.of(sources, classes, pom, metadata)); ret.addAll(classifiers.values()); return ret; @@ -433,4 +449,117 @@ protected GradleModule.Variant[] classVariants(Mappings mappings, ForgeVersionCo return super.classVariants(mappings, patcher.getMinecraftTasks().getJavaVersion(), libs, extraCompile); } + + + // Old code that I think may be useful for older versions, so I don't want to delete quite yet, delete when all legacy versions are supported + public static class Info { + public static final Map MAPPINGS = new LinkedHashMap<>(); + public static final Map CONF = new LinkedHashMap<>(); + + private static String hashEntries(ZipFile zip, List entries) { + Collections.sort(entries); + var bos = new ByteArrayOutputStream(); + for (var name : entries) { + var entry = zip.getEntry(name); + try { + var stream = zip.getInputStream(entry); + stream.transferTo(bos); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + return HashFunction.sha1().hash(bos.toByteArray()); + } + private static void zip(ZipFile zip, List entries, String suffix, String version, String hash) { + Collections.sort(entries); + var output = new File(version + '-' + suffix + '-' + hash + ".zip"); + try (var out = new ZipOutputStream(new FileOutputStream(output))) { + for (var ent : entries) { + var entry = zip.getEntry(ent); + var name = entry.getName().replace("conf/", ""); + if (name.isEmpty() || entry.isDirectory()) + continue; + byte[] data = zip.getInputStream(entry).readAllBytes(); + + switch (name) { + case "packaged.srg": + name = "joined.srg"; + var map = IMappingFile.load(new ByteArrayInputStream(data)); + var tmp = Path.of("Z:/test/_test/joined.srg"); + map.write(tmp, IMappingFile.Format.SRG, false); + data = Files.readAllBytes(tmp); + break; + case "packaged.exc": + name = "joined.exc"; + break; + case "Start.java": + name = "patches/Start.java"; + continue; + //break; + } + if (name.endsWith(".csv")) + name = name.substring(name.indexOf('/') + 1); + if (name.startsWith("minecraft_ff/")) + name = "patches/minecraft_merged_ff/" + name.substring(13); + + var newEntry = new ZipEntry(name); + newEntry.setTime(entry.getTime()); + out.putNextEntry(newEntry); + out.write(data); + out.closeEntry(); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + private static List gatherVariants(MavenCache cache, String version) { + var userdev = getUserdev(version); + var mappingsFiles = new ArrayList(); + var confFiles = new ArrayList(); + + var file = cache.download(userdev); + try (var zip = new ZipFile(file)) { + for (var itr = zip.entries().asIterator(); itr.hasNext(); ) { + var entry = itr.next(); + var ename = entry.getName(); + var filename = ename.indexOf('/') == -1 ? ename : ename.substring(ename.indexOf('/') + 1); + + if ("fields.csv".equals(filename) || "methods.csv".equals(filename) || "params.csv".equals(filename)) { + mappingsFiles.add(ename); + } else if (ename.startsWith("conf/")) { + confFiles.add(ename); + } + } + + var hash = hashEntries(zip, mappingsFiles); + if (!MAPPINGS.containsKey(hash)) { + System.out.println("New Mappings: " + hash + " " + version); + MAPPINGS.put(hash, version); + zip(zip, mappingsFiles, "mappings", version, hash); + } + hash = hashEntries(zip, confFiles); + if (!CONF.containsKey(hash)) { + System.out.println("New Conf: " + hash + " " + version); + CONF.put(hash, version); + zip(zip, confFiles, "conf", version, hash); + } + + } catch (IOException e) { + throw new IllegalStateException("Error reading config " + file.getAbsolutePath(), e); + } + return List.of(); + } + public static void finish() { + if (!MAPPINGS.isEmpty()) { + System.out.println("Mappings: "); + for (var entry : MAPPINGS.entrySet()) + System.out.println("\t" + entry.getKey() + ' ' + entry.getValue()); + } + if (!CONF.isEmpty()) { + System.out.println("Conf: "); + for (var entry : CONF.entrySet()) + System.out.println("\t" + entry.getKey() + ' ' + entry.getValue()); + } + } + } } diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/LegacyRenamer.java b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/LegacyRenamer.java new file mode 100644 index 0000000..8e958ba --- /dev/null +++ b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/LegacyRenamer.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ +package net.minecraftforge.mcmaven.impl.repo.forge; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +import net.minecraftforge.mcmaven.impl.tasks.MCPNames; +import net.minecraftforge.mcmaven.impl.tasks.MCPNames.JavadocAdder; +import net.minecraftforge.mcmaven.impl.util.NewLineDetector; +import net.minecraftforge.mcmaven.impl.util.Util; +import net.minecraftforge.util.file.FileUtils; + +/* + * Older versions require renaming between FML and Forge patches, + * and include custom comments for injecting javadocs as a second step. + * So this implements that. Basically just copy pasted from FG 1.0 + */ +class LegacyRenamer { + //private static final Pattern SRG_FINDER = Pattern.compile("func_[0-9]+_[a-zA-Z_]+|field_[0-9]+_[a-zA-Z_]+|p_[\\w]+_\\d+_"); + private static final Pattern METHOD = Pattern.compile("^((?: {4})+|\\t+)(?:[\\w$.\\[\\]]+ )+(func_[0-9]+_[a-zA-Z_]+)\\("); + private static final Pattern FIELD = Pattern.compile("^((?: {4})+|\\t+)(?:[\\w$.\\[\\]]+ )+(field_[0-9]+_[a-zA-Z_]+) *(?:=|;)"); + + public static void rename(File input, File mappings, File output, boolean javadocMarkers) { + try { + var names = MCPNames.load(mappings); + FileUtils.ensureParent(output); + try (var zin = new ZipInputStream(new FileInputStream(input)); + var zout = new ZipOutputStream(new FileOutputStream(output))) { + for (ZipEntry entry; (entry = zin.getNextEntry()) != null; ) { + var newEntry = new ZipEntry(entry.getName()); + newEntry.setTime(entry.getTime()); + zout.putNextEntry(newEntry); + + if (!entry.getName().endsWith(".java")) { + zin.transferTo(zout); + } else { + var lines = NewLineDetector.readLines(zin); + lines = rename(names, lines, javadocMarkers); + for (var itr = lines.iterator(); itr.hasNext();) { + var line = itr.next(); + zout.write(line.getBytes(StandardCharsets.UTF_8)); + if (itr.hasNext()) + zout.write('\n'); + } + } + + zout.closeEntry(); + } + } + } catch (IOException e) { + Util.sneak(e); + } + } + + private static List rename(MCPNames names, List lines, boolean javadocMarkers) { + var newLines = new ArrayList(lines.size()); + Matcher matcher = null; + for (var line : lines) { + if (line.trim().startsWith("// JAVADOC ")) { + var idx = line.indexOf('$'); + if (idx != -1 && idx < line.length() - 2) { + var name = line.substring(idx + 2); + var docs = names.docs().get(name); + if (docs != null && !docs.isEmpty()) + line = JavadocAdder.buildJavadoc(line.substring(line.indexOf('/')), docs, true); + } + } else if ((matcher = METHOD.matcher(line)).find()) { + var name = matcher.group(2); + var docs = names.docs().get(name); + + if (!name.equals(names.names().getOrDefault(name, name)) && docs != null && !docs.isEmpty()) { + String formatted; + if (javadocMarkers) + formatted = matcher.group(1) + "// JAVADOC METHOD $$ " + name; + else + formatted = JavadocAdder.buildJavadoc(matcher.group(1), docs, true); + MCPNames.insertAboveAnnotations(newLines, formatted); + } + } else if ((matcher = FIELD.matcher(line)).find()) { + var name = matcher.group(2); + var docs = names.docs().get(name); + if (!name.equals(names.names().getOrDefault(name, name)) && docs != null && !docs.isEmpty()) { + String formatted; + if (javadocMarkers) + formatted = matcher.group(1) + "// JAVADOC FIELD $$ " + name; + else + formatted = JavadocAdder.buildJavadoc(matcher.group(1), docs, true); + MCPNames.insertAboveAnnotations(newLines, formatted); + } + } + newLines.add(names.replaceInLine(line, null)); + } + return newLines; + } +} diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MCPConfigRepo.java b/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MCPConfigRepo.java index b872210..e0be027 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MCPConfigRepo.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MCPConfigRepo.java @@ -98,6 +98,11 @@ private MCP download(Artifact artifact) { return new MCP(this, artifact); } + public MCPLegacy legacy(String mcVersion) { + return legacy(mcVersion, null); + } + + // Variants used for Forge artifacts public MCPLegacy legacy(String mcVersion, @Nullable String python) { var artifact = MCPLegacy.artifact(mcVersion); var key = new LegacyKey(artifact, python); @@ -298,7 +303,7 @@ public List processLegacy(Artifact artifact, Mappings baseMappi } var jdks = this.cache.jdks(); - var mcp = this.legacy(version, null); + var mcp = this.legacy(version); var tasks = mcp.getMinecraftTasks(); var mcVersion = tasks.getVersion(); var build = mcp.getBuildFolder(); diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MCPLegacy.java b/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MCPLegacy.java index 7158f0c..1b78528 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MCPLegacy.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MCPLegacy.java @@ -36,6 +36,7 @@ import net.minecraftforge.mcmaven.impl.cache.JDKCache; import net.minecraftforge.mcmaven.impl.cache.MavenCache; import net.minecraftforge.mcmaven.impl.repo.forge.FG2Userdev; +import net.minecraftforge.mcmaven.impl.repo.forge.FGVersion; import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MinecraftTasks.MCFile; import net.minecraftforge.mcmaven.impl.util.Artifact; import net.minecraftforge.mcmaven.impl.util.ComparableVersion; @@ -59,6 +60,7 @@ public static Artifact artifact(String version) { private final Artifact name; private final boolean isPython; private final String mcVersion; + private final ComparableVersion mcVersionComp; private final File data; private final String dataHash; private final File build; @@ -78,6 +80,7 @@ public MCPLegacy(MCPConfigRepo repo, Artifact name, @Nullable String python) { this.repo = repo; this.isPython = python != null; this.mcVersion = name.getVersion(); + this.mcVersionComp = new ComparableVersion(this.mcVersion); if (python != null) { this.name = name.withVersion(name.getVersion() + '-' + python.replace(".zip", "")).withClassifier(null); @@ -91,9 +94,10 @@ public MCPLegacy(MCPConfigRepo repo, Artifact name, @Nullable String python) { } } else { this.name = name; - this.data = this.repo.getCache().maven().download(name); + var fixed = StupidHacks.fixMCPArtifact(name); + this.data = this.repo.getCache().maven().download(fixed); if (!this.data.exists()) - throw new IllegalStateException("Failed to download " + name); + throw new IllegalStateException("Failed to download " + fixed); } this.dataHash = Util.sneak(() -> HashFunction.sha1().hash(this.data)); @@ -106,7 +110,11 @@ public MCPLegacy(MCPConfigRepo repo, Artifact name, @Nullable String python) { this.mappings = extract(prefix + "joined.srg"); this.exceptorJson = extract(prefix + "exceptor.json"); // Possibly non-existant? this.exceptorConfig = extract(prefix + "joined.exc"); - this.statics = extract(this.isPython ? "conf/STATIC_METHODS.txt" : "static_methods.txt"); // may not exist + + if (this.mcVersionComp.compareTo(MC_1_8) < 0) + this.statics = null; + else + this.statics = extract(this.isPython ? "conf/STATIC_METHODS.txt" : "static_methods.txt"); // may not exist this.listLibraries = listLibraries(); } @@ -175,18 +183,23 @@ public List getClasspath() { public Child getChild() { if (this.child == null) - this.child = new Child(this.build, null); + this.child = new Child(this.build, null, null); return this.child; } - public Child getChild(FG2Userdev forge) { - var accessTransformer = forge.extract("merged_at.cfg").execute(); + public Child getChild(FG2Userdev forge, Task atTask, @Nullable FGVersion legacyFG) { + var accessTransformer = atTask.execute(); var hash = Util.hash(HashFunction.sha1(), accessTransformer); - var key = forge.getName().getName() + '/' + hash; + + var key = forge.getName().getName(); + if (legacyFG != null) + key += '-' + legacyFG.toString(); + key += '/' + hash; + var ret = this.children.get(key); if (ret == null) { var base = new File(this.build, key); - ret = new Child(base, accessTransformer); + ret = new Child(base, accessTransformer, legacyFG); this.children.put(key, ret); } return ret; @@ -213,13 +226,13 @@ private File extractImpl(String name) { if (Mavenizer.checkCache(target, cache)) return target; + FileUtils.ensureParent(target); + try (var zip = new ZipFile(data)) { var entry = zip.getEntry(name); if (entry == null) throw except("Missing Data: " + name); - FileUtils.ensureParent(target); - try (var os = new FileOutputStream(target)) { zip.getInputStream(entry).transferTo(os); } @@ -251,6 +264,8 @@ private Task listLibraries() { private Task exceptorConfig() { var base = this.exceptorConfig; + if (this.statics == null) + return base; var statics = this.statics; var mappings = getMappings(); return Task.named(prefix() + "exceptor-config", @@ -518,17 +533,19 @@ public class Child { private final JDKCache jdks; private final File build; private final File accessTransformer; + private final @Nullable FGVersion legacyFG; private final String prefix; private final MCPCfg cfg; private final Task inject; - public Child(File build, File accessTransformer) { + public Child(File build, File accessTransformer, @Nullable FGVersion legacyFG) { this.maven = MCPLegacy.this.repo.getCache().maven(); this.jdks = MCPLegacy.this.repo.getCache().jdks(); this.build = build; this.accessTransformer = accessTransformer; + this.legacyFG = legacyFG; this.prefix = MCPLegacy.this.prefix(); - this.cfg = MCPCfg.get(MCPLegacy.this.mcVersion); + this.cfg = MCPCfg.get(MCPLegacy.this.mcVersionComp, legacyFG); this.inject = mcpInject(); } @@ -648,15 +665,47 @@ private File cleanup(File input) { return output; } + private Task writeSorts() { + if (this.cfg.decompiler.sorts == null) + return null; + return Task.named(prefix + "sort", () -> writeSortsImpl()); + } + + private File writeSortsImpl() { + var output = new File(this.build, "sorts.cfg"); + var sorts = new StringBuilder(); + for (var sort : this.cfg.decompiler.sorts) { + sorts.append("-sort=") + .append(sort) + .append('\n'); + } + var cache = Util.cache(output) + .add("sorts", sorts.toString()); + + if (Mavenizer.checkCache(output, cache)) + return output; + + FileUtils.ensureParent(output); + try { + Files.writeString(output.toPath(), sorts); + } catch (IOException e) { + return Util.sneak(e); + } + + cache.save(); + return output; + } + private Task decompile() { var input = this.exceptor(); var libraries = MCPLegacy.this.listLibraries; + var sorts = this.writeSorts(); return Task.named(prefix + "decompile", - Task.deps(input, libraries), () -> decompile(input.execute(), libraries.execute()) + Task.deps(input, libraries, sorts), () -> decompile(input.execute(), libraries.execute(), sorts == null ? null : sorts.execute()) ); } - private File decompile(File input, File libraries) { + private File decompile(File input, File libraries, File sorts) { var output = new File(this.build, "decompiled.jar"); var log = new File(this.build, "decompiled.log"); @@ -672,6 +721,9 @@ private File decompile(File input, File libraries) { .add("args", String.join(", ", decompiler.args)) .addKnown("java", Integer.toString(java_version)); + if (sorts != null) + cache.add("sorts", sorts); + if (Mavenizer.checkCache(output, cache)) return output; @@ -687,6 +739,9 @@ private File decompile(File input, File libraries) { temp = new File(dir, input.getName()); run = new ArrayList(decompiler.args.size()); + if (sorts != null) + run.addAll(List.of("-cfg", sorts.getAbsolutePath())); + for (var arg : decompiler.args) { if ("{libraries}".equals(arg)) { // This is a version that doens't support the -cfg arg @@ -721,6 +776,8 @@ private File decompile(File input, File libraries) { run.add(dir.getAbsolutePath()); } else { run = new ArrayList(decompiler.args); + if (sorts != null) + run.addAll(List.of("-cfg", sorts.getAbsolutePath())); run.addAll(List.of( "-cfg", libraries.getAbsolutePath(), input.getAbsolutePath(), @@ -732,6 +789,7 @@ private File decompile(File input, File libraries) { temp.delete(); var jdk = jdks.tryGet(java_version); + //jdk = new File("C:\\Program Files\\java\\jdk1.7.0_80"); var ret = StupidHacks.runDecompiler(jdk, log, tool, jvm, run); if (ret.exitCode != 0 || !temp.exists()) throw new IllegalStateException("Failed to run decompiler (exit code " + ret.exitCode + "), See log: " + log.getAbsolutePath()); @@ -766,10 +824,19 @@ private File exceptor(File input, File config, Task jsonTask) { var output = new File(this.build, "joined-mci.jar"); var log = new File(this.build, "joined-mci.log"); var internalLog = new File(this.build, "joined-mci-internal.log"); + var customArgs = new ArrayList(); + if (except.generate) + customArgs.add("--generateParams"); + if (except.markers) + customArgs.add("--applyMarkers"); + if (except.lvt) + customArgs.addAll(List.of("--lvt", "LVT")); + var cache = Util.cache(output) .add("tool", tool) .add("input", input) - .add("config", config); + .add("config", config) + .add("args", String.join(", ", customArgs)); if (json != null) cache.add("json", json); @@ -780,15 +847,11 @@ private File exceptor(File input, File config, Task jsonTask) { "--jarIn", input.getAbsolutePath(), "--jarOut", output.getAbsolutePath(), "--mapIn", config.getAbsolutePath(), - "--log", internalLog.getAbsolutePath(), // Annoying but we have to have two logs to actually have the logs be written - "--generateParams" + "--log", internalLog.getAbsolutePath() // Annoying but we have to have two logs to actually have the logs be written )); + args.addAll(customArgs); if (json != null) args.addAll(List.of("--jsonIn", json.getAbsolutePath())); - if (except.markers) - args.add("--applyMarkers"); - if (except.lvt) - args.addAll(List.of("--lvt", "LVT")); var jdk = jdks.tryGet(Constants.MCINJECTOR_JAVA_VERSION); var ret = ProcessUtils.runJar(jdk, this.build, log, tool, Collections.emptyList(), args); @@ -908,7 +971,8 @@ private File modifyAccess(File input) { var args = List.of( "--inJar", input.getAbsolutePath(), "--outJar", output.getAbsolutePath(), - "--atfile", this.accessTransformer.getAbsolutePath() + "--atfile", this.accessTransformer.getAbsolutePath(), + "--ignore-invalid" // Needed for some old Forge versions with invalid lines ); var jdk = jdks.tryGet(Constants.ACCESS_TRANSFORMER_JAVA_VERSION); @@ -968,42 +1032,55 @@ else if (wanted.endsWith("+f")) // The main difference between FG versions was the decompiler we invoked/embeded private static final ComparableVersion MC_1_6_1 = new ComparableVersion("1.6.1"); + private static final ComparableVersion MC_1_7_2 = new ComparableVersion("1.7.2"); private static final ComparableVersion MC_1_7_10 = new ComparableVersion("1.7.10"); + private static final ComparableVersion MC_1_8 = new ComparableVersion("1.8"); private static final ComparableVersion MC_1_8_8 = new ComparableVersion("1.8.8"); + private static final ComparableVersion MC_1_8_9 = new ComparableVersion("1.8.9"); private static final ComparableVersion MC_1_9 = new ComparableVersion("1.9"); private static final ComparableVersion MC_1_9_4 = new ComparableVersion("1.9.4"); private static final ComparableVersion MC_1_12 = new ComparableVersion("1.12"); - private record Exceptor(boolean json, boolean markers, boolean lvt) { + private record Exceptor(boolean json, boolean markers, boolean lvt, boolean generate) { private static Exceptor get(ComparableVersion mc) { var json = true; var markers = true; var lvt = false; + var generate = true; if (mc.compareTo(MC_1_8_8) >= 0) { json = false; lvt = true; } if (mc.compareTo(MC_1_9) >= 0) markers = false; - return new Exceptor(json, markers, lvt); + if (mc.compareTo(MC_1_7_2) <= 0) + generate = false; + return new Exceptor(json, markers, lvt, generate); } } private record Cleanup(Artifact artifact, List args) { - private static Cleanup get(String mc) { + private static Cleanup get(ComparableVersion mc) { var args = List.of(); - if ("1.8".equals(mc)) + if (mc.compareTo(MC_1_8_9) >= 0) { + args = List.of(); + } else if (mc.compareTo(MC_1_8_8) >= 0) { + args = List.of("--fix-generic-params");//, "--fernflower", "--enum-args=false", "--enum-constructors=false"); + } else if (mc.compareTo(MC_1_8) >= 0) { args = List.of("--fix-generic-params", "--fml", "--fernflower", // These patches are enum sythetic arg fixes which cleaup does in a generic way "--ignore-patch", "net/minecraft/network/EnumConnectionState.java", "--ignore-patch", "net/minecraft/util/EnumChatFormatting.java", "--ignore-patch", "net/minecraft/util/EnumParticleTypes.java" ); - else if ("1.8.8".equals(mc)) - args = List.of("--fix-generic-params");//, "--fernflower", "--enum-args=false", "--enum-constructors=false"); + } else if (mc.compareTo(MC_1_7_10) >= 0) { + args = List.of("--fix-generic-params", "--fml", "--fernflower"); + } else { + args = List.of("--mode", "1.7.2"); + } return new Cleanup(Constants.MCPCLEANUP, args); } } - private record Decompiler(Artifact artifact, boolean legacy, List args) { + private record Decompiler(Artifact artifact, boolean legacy, List args, List sorts) { private static final List DECOMPILER_ARGS = List.of( "-din=1", // DECOMPILE_INNER @@ -1026,6 +1103,7 @@ private record Decompiler(Artifact artifact, boolean legacy, List args) "-asc=1", // ASCII_STRING_CHARACTERS "-log=WARN" ); + /* private static final List DECOMPILER_ARGS_188 = List.of( "-din=1", // DECOMPILE_INNER "-rbr=1", // REMOVE_BRIDGE @@ -1037,30 +1115,44 @@ private record Decompiler(Artifact artifact, boolean legacy, List args) "-log=WARN", "{libraries}" // Will be expanded to a list of all libraries using -e= ); - private static Decompiler get(ComparableVersion mc) { - var decompTool = Constants.FERNFLOWER_FG_2_0_LEGACY; - var decompArgs = DECOMPILER_ARGS_188; - var legacy = true; - - if (mc.compareTo(MC_1_12) >= 0) { - decompTool = Constants.FERNFLOWER_FG_2_3; - decompArgs = DECOMPILER_ARGS; - legacy = false; - } else if (mc.compareTo(MC_1_8_8) >= 0) { - decompTool = Constants.FERNFLOWER_FG_2_2; - decompArgs = DECOMPILER_ARGS; - legacy = false; - } - /* - else if (mc.compareTo(MC_1_9_4) >= 0) - decompTool = Constants.FERNFLOWER_FG_2_0_194; + */ + + private static String sort(String... args) { + return String.join(",", args); + } + private static Decompiler get(ComparableVersion mc, @Nullable FGVersion legacyFG) { + if (mc.compareTo(MC_1_12) >= 0) + return new Decompiler(Constants.FERNFLOWER_FG_2_3, false, DECOMPILER_ARGS, null); else if (mc.compareTo(MC_1_8_8) >= 0) - decompTool = Constants.FERNFLOWER_FG_2_0_194; - */ - else { // 1.8 - decompArgs = DECOMPILER_ARGS_18; + return new Decompiler(Constants.FERNFLOWER_FG_2_2, false, DECOMPILER_ARGS, null); + else if (mc.compareTo(MC_1_8) >= 0) + return new Decompiler(Constants.FERNFLOWER_FG_2_0_LEGACY, true, DECOMPILER_ARGS_18, null); + else if (mc.compareTo(MC_1_7_2) >= 0 && legacyFG == null) // Forge 1.7.2 uses FG 1.1 and 1.2 so we need this check + return new Decompiler(Constants.FERNFLOWER_FG_2_0_LEGACY, true, DECOMPILER_ARGS_18, null); + else { + // Old Fernflower uses HashSet to iterate over inner classes + // Which the iteration order has changed a few times between java versions + // So unfortunately, we have to hardcode some specifc sorting to make patches apply + var sorts = switch (mc.toString()) { + case "1.7.2" -> List.of( + sort("net/minecraft/client/gui/inventory/GuiContainerCreative", "CreativeSlot", "ContainerCreative"), + sort("net/minecraft/client/renderer/texture/Stitcher", "Slot", "Holder"), + sort("net/minecraft/client/settings/GameSettings", "SwitchOptions", "Options"), + sort("net/minecraft/enchantment/EnchantmentHelper", "IModifier", "ModifierDamage", "ModifierLiving", "HurtIterator", "DamageIterator"), + sort("net/minecraft/entity/player/EntityPlayer", "EnumStatus", "EnumChatVisibility"), + sort("net/minecraft/world/biome/BiomeGenBase", "SpawnListEntry", "Height", "TempCategory"), + sort("net/minecraft/world/gen/structure/ComponentScatteredFeaturePieces", "Feature", "JunglePyramid", "SwampHut", "DesertPyramid"), + sort("net/minecraft/world/gen/structure/StructureMineshaftPieces", "Cross", "Room", "Corridor", "Stairs"), + sort("net/minecraft/world/gen/structure/StructureStrongholdPieces", "Stairs", "Straight", "Library", "PortalRoom", "ChestCorridor", "RoomCrossing", "StairsStraight", "Stairs2", "Prison", "LeftTurn", "RightTurn", "Stones", "Stronghold", "Stronghold$Door", "Crossing", "Corridor", "SwitchDoor", "PieceWeight"), + sort("net/minecraft/world/gen/structure/StructureVillagePieces", + "Well", "Village", "Hall", "House1", "Church", "House4Garden", "Path", + "House2", "Start", "House3", "WoodHut", "Field1", "Field2", "Road", "Torch", "PieceWeight"), + sort("net/minecraft/world/storage/MapData", "MapCoord", "MapInfo") + ); + default -> null; + }; + return new Decompiler(Constants.FERNFLOWER_FG_1_0, true, DECOMPILER_ARGS_18, sorts); } - return new Decompiler(decompTool, legacy, decompArgs); } } private record MCPCfg( @@ -1068,11 +1160,10 @@ private record MCPCfg( Cleanup cleanup, Decompiler decompiler ) { - private static MCPCfg get(String mc) { - var comp = new ComparableVersion(mc); - var except = Exceptor.get(comp); + private static MCPCfg get(ComparableVersion mc, @Nullable FGVersion legacyFG) { + var except = Exceptor.get(mc); var cleanup = Cleanup.get(mc); - var decomp = Decompiler.get(comp); + var decomp = Decompiler.get(mc, legacyFG); return new MCPCfg(except, cleanup, decomp); } } diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/tasks/MCPNames.java b/src/main/java/net/minecraftforge/mcmaven/impl/tasks/MCPNames.java index f52e1ec..5971151 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/tasks/MCPNames.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/tasks/MCPNames.java @@ -59,11 +59,17 @@ private record Data(Map names, Map docs) { } private static Data loadData(File data) throws IOException { var names = new HashMap(); var docs = new HashMap(); + var candidates = Set.of("methods.csv", "fields.csv", "params.csv"); try (var zip = new ZipFile(data)) { var entries = zip.entries(); while (entries.hasMoreElements()) { var entry = entries.nextElement(); - if (!entry.getName().endsWith(".csv")) continue; + var filename = entry.getName(); + var idx = filename.lastIndexOf('/'); + if (idx != -1) + filename = filename.substring(idx + 1); + if (!candidates.contains(filename)) + continue; try (var reader = CsvReader.builder().ofNamedCsvRecord(new InputStreamReader(zip.getInputStream(entry)))) { for (var row : reader) { @@ -264,7 +270,7 @@ private boolean injectJavadoc(List lines, String line, String _package, } /** Inserts the given javadoc line into the list of lines before any annotations */ - private static void insertAboveAnnotations(List list, String line) { + public static void insertAboveAnnotations(List list, String line) { int back = 0; while (list.get(list.size() - 1 - back).trim().startsWith("@")) back++; @@ -288,7 +294,7 @@ private String getMapped(String srg, @Nullable Set blacklist) { return ret; } - private String replaceInLine(String line, @Nullable Set blacklist) { + public String replaceInLine(String line, @Nullable Set blacklist) { var buf = new StringBuffer(); var matcher = SRG_FINDER.matcher(line); while (matcher.find()) { @@ -299,7 +305,7 @@ private String replaceInLine(String line, @Nullable Set blacklist) { return buf.toString(); } - private interface JavadocAdder { + public static final class JavadocAdder { /** * Converts a raw javadoc string into a nicely formatted, indented, and wrapped string. * @@ -309,7 +315,7 @@ private interface JavadocAdder { * doc * @return A fully formatted javadoc comment string complete with comment characters and newlines. */ - static String buildJavadoc(String indent, String javadoc, boolean multiline) { + public static String buildJavadoc(String indent, String javadoc, boolean multiline) { var builder = new StringBuilder(); // split and wrap. diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/util/Constants.java b/src/main/java/net/minecraftforge/mcmaven/impl/util/Constants.java index 4f72b46..f30870c 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/util/Constants.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/util/Constants.java @@ -17,9 +17,10 @@ public final class Constants { public static final String MCP_DOWNLOADS = FORGE_FILES + "mcp/"; // Legacy FG2 Decompilers - // This was https://files.minecraftforge.net/fernflower-fix-1.0.zip!/fernflower.jar but I extracted it to make it easier to download - // This is the old obfusicated fernflower decompiler, with just the inner class NPE fix, tehcnially this wasn't used in 1.0, but its just a NPE fix so use it for everything - public static final Artifact FERNFLOWER_FG_1_0 = Artifact.from("net.minecraftforge:fernflower:0.1.0.0"); + // This was https://files.minecraftforge.net/fernflower_temporary.zip!/fernflower.jar but that has issues with running on anything higher then java 6 + // Java 6 doesn't exist for OSX, so i've had to implement patches to fix those decompile differences. + // See https://github.com/MinecraftForge/ForgeFlower/tree/legacy-1.0 + public static final Artifact FERNFLOWER_FG_1_0 = Artifact.from("net.minecraftforge:forgeflower:0.1.0.0"); // This was https://files.minecraftforge.net/fernflower-fix-1.0.zip!/fernflower.jar but I extracted it to make it easier to download // This is the old obfusicated fernflower decompiler, with all of my fixes from: https://github.com/LexManos/FernFlowerFixer/ public static final Artifact FERNFLOWER_FG_2_0_LEGACY = Artifact.from("net.minecraftforge:fernflower:0.2.0.0"); @@ -40,7 +41,7 @@ public final class Constants { public static final String LAUNCHER_MANIFEST = "https://launchermeta.mojang.com/mc/game/version_manifest_v2.json"; public static final String MOJANG_MAVEN = "https://libraries.minecraft.net/"; - public static final Artifact ACCESS_TRANSFORMER = Artifact.from("net.minecraftforge:accesstransformers:8.2.16:fatjar"); + public static final Artifact ACCESS_TRANSFORMER = Artifact.from("net.minecraftforge:accesstransformers:8.2.17:fatjar"); public static final int ACCESS_TRANSFORMER_JAVA_VERSION = 8; public static final Artifact SIDE_STRIPPER = Artifact.from("net.minecraftforge:mergetool:1.2.5:fatjar"); @@ -61,7 +62,7 @@ public final class Constants { public static final Artifact MCINJECTOR = Artifact.from("de.oceanlabs.mcp:mcinjector:3.4.5:fatjar"); public static final int MCINJECTOR_JAVA_VERSION = 8; - public static final Artifact MCPCLEANUP = Artifact.from("net.minecraftforge:mcpcleanup:2.4.2:fatjar"); + public static final Artifact MCPCLEANUP = Artifact.from("net.minecraftforge:mcpcleanup:2.4.3:fatjar"); public static final int MCPCLEANUP_JAVA_VERSION = 8;; public static final Artifact LEGACY_MERGETOOL = Artifact.from("net.minecraftforge:mergetool:0.2.3.4:fatjar"); diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/util/StupidHacks.java b/src/main/java/net/minecraftforge/mcmaven/impl/util/StupidHacks.java index f3e9fb4..e9b9958 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/util/StupidHacks.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/util/StupidHacks.java @@ -52,7 +52,9 @@ public static Artifact fixLegacyTools(Artifact artifact) { "org.scala-lang:scala-xml_2.11:1.0.2", "org.scala-lang.modules:scala-xml_2.11:1.0.2", "org.scala-lang:scala-parser-combinators_2.11:1.0.1", "org.scala-lang.modules:scala-parser-combinators_2.11:1.0.1", // They renamed the artifact - "tv.twitch:twitch-external-platform:4.5", "tv.twitch:twitch-platform:6.5" + "tv.twitch:twitch-external-platform:4.5", "tv.twitch:twitch-platform:6.5", + // Bumped transitivly somewhere in 1.7.10-pre4, and relied on the bumped version + "com.google.guava:guava:15.0", "com.google.guava:guava:16.0" ); // these are artifacts that Mojang referenced in the past, but now are deleted. //private static Set DELETED_ARTIFACTS = Set.of( @@ -147,6 +149,12 @@ private static void mappings(String forge, String mappings) { FORGE_MAPPINGS.add(new ForgeMappings(new ComparableVersion(forge), mappings)); } static { + // FG 1.x, these were packed in the userdev. Manually uploaded + mappings("1.7.2-10.12.0.967", "snapshot:20131226-1.7.2"); + mappings("1.7.2-10.12.0.1024", "snapshot:20140205-1.7.2"); + mappings("1.7.10_pre4-10.12.2.1137", "snapshot:20140624-1.7.10-pre4"); + + // FG2, normal published mappings mappings("1.7.2-10.12.0.1024", "snapshot:20140205-1.7.2"); mappings("1.8-11.14.3.1503", "snapshot:20141130-1.8"); mappings("1.8.8-11.14.4.1575", "snapshot:20151122-1.8"); @@ -240,4 +248,14 @@ public static File fixPatches(Artifact artifact, File input, File output) { return output; } + + public static Artifact fixMCPArtifact(Artifact artifact) { + // There is an artifact created in 2014 that doesn't follow the + // same format as the other mcp artifacts, and has bad parameter names + // And rather then update an existing file on the maven that may be used by whatever + // I made a new fixed file + if ("1.7.10".equals(artifact.getVersion())) + return artifact.withVersion("1.7.10-fixed"); + return artifact; + } } From f846fca6841bae02714ba4b96a334a51d079de5c Mon Sep 17 00:00:00 2001 From: LexManos Date: Tue, 2 Jun 2026 20:07:27 -0700 Subject: [PATCH 06/12] Supprt FG1.0 for Minecraft 1.6.4 --- .../mcmaven/impl/MinecraftMaven.java | 12 +- .../mcmaven/impl/repo/forge/FG2Userdev.java | 20 ++- .../mcmaven/impl/repo/forge/FGVersion.java | 2 +- .../mcmaven/impl/repo/forge/ForgeRepo.java | 27 +++- .../impl/repo/forge/LegacyRenamer.java | 56 +++++++- .../impl/repo/mcpconfig/MCPLegacy.java | 126 ++++++++++++++++-- .../mcmaven/impl/util/Constants.java | 2 +- .../mcmaven/impl/util/StupidHacks.java | 3 +- 8 files changed, 213 insertions(+), 35 deletions(-) diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/MinecraftMaven.java b/src/main/java/net/minecraftforge/mcmaven/impl/MinecraftMaven.java index 8ff075f..6d042d7 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/MinecraftMaven.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/MinecraftMaven.java @@ -79,7 +79,7 @@ public record MinecraftMaven( ) { // Only 1.14.4+ has official mappings, we can support more when we add more mappings private static final MinecraftVersion MIN_OFFICIAL_MAPPINGS = MinecraftVersion.from("1.14.4"); - private static final ComparableVersion MIN_SUPPORTED_FORGE = new ComparableVersion("1.7.2"); + private static final ComparableVersion MIN_SUPPORTED_FORGE = new ComparableVersion("1.6.4"); public MinecraftMaven { LOGGER.info(" Output: " + output.getAbsolutePath()); @@ -184,20 +184,14 @@ protected void createForge(Artifact artifact, MCPConfigRepo mcprepo, ForgeRepo r var ver = cver.toString(); var mcVersion = Util.forgeToMcVersion(ver); - var fg = FGVersion.fromForge(ver); - if (fg == null) // Unsupported - continue; - if (verStart != null && cver.compareTo(verStart) < 0) continue; if (verEnd != null && cver.compareTo(verEnd) > 0) continue; - /* - if (mcVersion.equals(lastMCVersion)) + // Python isn't supported yet + if (ForgeRepo.isPython(ver)) continue; - lastMCVersion = mcVersion; - */ // Old versions don't have official mappings, and some of them require specific MCP mappigns due to the sources not being in // full SRG names (we don't remap static imports) So if we're using official mappings (the default arugment) lets use the diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/FG2Userdev.java b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/FG2Userdev.java index fa79b2b..05cb20d 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/FG2Userdev.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/FG2Userdev.java @@ -389,11 +389,13 @@ private void patchUnstable(File input, File patchesArchive, File output, File re var result = patch.patchSingle(false, ctx); if (!result.getStatus().isSuccess()) { - LOGGER.error("Fialed to patch " + name); - LOGGER.error(" Input: " + input.getAbsolutePath()); - LOGGER.error(" Patches: " + patchesArchive.getAbsolutePath()); - LOGGER.error(" Output: " + output.getAbsolutePath()); + if (failure == null) { + LOGGER.error("Input: " + input.getAbsolutePath()); + LOGGER.error("Patches: " + patchesArchive.getAbsolutePath()); + LOGGER.error("Output: " + output.getAbsolutePath()); + } + LOGGER.error("Fialed to patch " + name); for (var hunk : result.getHunks()) LOGGER.error(" Hunk #" + hunk.getHunkID() + ": " + hunk.getStatus().name()); @@ -600,7 +602,10 @@ private File rename(File input) { if (Mavenizer.checkCache(output, cache)) return output; - LegacyRenamer.rename(input, this.data, output, true); + if (this.fgVersion == FGVersion.v1) + LegacyRenamer.renameFG_1_0(input, this.data, output); + else + LegacyRenamer.rename(input, this.data, output, true); cache.save(); return output; @@ -616,6 +621,11 @@ private Task patchForgeRenamed() { public Task getSourcesWithJavadocs() { var input = this.getSources(); + + // FG v1 applied javadocs before Forge patches, so just return the source + if (this.fgVersion == FGVersion.v1) + return input; + return Task.named("javadocs", Task.deps(input), () -> this.javadocs(input.execute()) ); diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/FGVersion.java b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/FGVersion.java index 65e232e..0d04dd0 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/FGVersion.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/FGVersion.java @@ -119,7 +119,7 @@ private static void forge(FGVersion fg, String forge) { // Special versions that do not match the general ranges of historical versions. // This will probably need updating when we back port new ForgeDev toolchain to older versions. private static final List SPECIAL_CASES = List.of( - special(null, "1.6.4-9.11.1.964", "1.6.5"), + special(null, "1.6.4-9.11.1.965", "1.6.5"), special(v3, "1.12.2-14.23.5.2851", "1.12.3") ); diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/ForgeRepo.java b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/ForgeRepo.java index 0ed0eec..a919ac3 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/ForgeRepo.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/ForgeRepo.java @@ -86,7 +86,9 @@ public ForgeRepo(Cache cache, MCPConfigRepo mcpconfig) { this.globalBuild = new File(cache.root(), "forge/.global"); } - private static boolean isPython(String version) { + public static boolean isPython(String version) { + if (FGVersion.fromForge(version) != null) + return false; var ver = new ComparableVersion(version); return ver.compareTo(PYTHON_START) >= 0 && ver.compareTo(PYTHON_END) < 0; } @@ -104,7 +106,7 @@ public List process(Artifact artifact, Mappings mappings, Map entries, String suffix, String throw new RuntimeException(e); } } - private static List gatherVariants(MavenCache cache, String version) { + private static List gatherVariants(MavenCache cache, String version, @Nullable FGVersion fg) { var userdev = getUserdev(version); + if (fg == null) + userdev = userdev.withClassifier("src").withExtension("zip"); var mappingsFiles = new ArrayList(); var confFiles = new ArrayList(); @@ -522,15 +526,26 @@ private static List gatherVariants(MavenCache cache, String ver for (var itr = zip.entries().asIterator(); itr.hasNext(); ) { var entry = itr.next(); var ename = entry.getName(); - var filename = ename.indexOf('/') == -1 ? ename : ename.substring(ename.indexOf('/') + 1); + var filename = ename.lastIndexOf('/') == -1 ? ename : ename.substring(ename.lastIndexOf('/') + 1); - if ("fields.csv".equals(filename) || "methods.csv".equals(filename) || "params.csv".equals(filename)) { + if ("fields.csv".equals(filename) + || "methods.csv".equals(filename) + || "params.csv".equals(filename) + ) { mappingsFiles.add(ename); - } else if (ename.startsWith("conf/")) { + } else if (ename.startsWith("conf/") + || ename.startsWith("forge/conf/") + || ename.startsWith("forge/fml/conf/") + ) { confFiles.add(ename); } } + if (confFiles.isEmpty()) + throw new IllegalStateException("Missing conf files: " + version); + if (mappingsFiles.isEmpty()) + throw new IllegalStateException("Missing mapping files: " + version); + var hash = hashEntries(zip, mappingsFiles); if (!MAPPINGS.containsKey(hash)) { System.out.println("New Mappings: " + hash + " " + version); diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/LegacyRenamer.java b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/LegacyRenamer.java index 8e958ba..d94a673 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/LegacyRenamer.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/LegacyRenamer.java @@ -33,7 +33,18 @@ class LegacyRenamer { private static final Pattern METHOD = Pattern.compile("^((?: {4})+|\\t+)(?:[\\w$.\\[\\]]+ )+(func_[0-9]+_[a-zA-Z_]+)\\("); private static final Pattern FIELD = Pattern.compile("^((?: {4})+|\\t+)(?:[\\w$.\\[\\]]+ )+(field_[0-9]+_[a-zA-Z_]+) *(?:=|;)"); + private static final Pattern METHOD_FG1 = Pattern.compile("^( {4}|\\t)(?:[\\w$.\\[\\]]+ )*(func_[0-9]+_[a-zA-Z_]+)\\("); + private static final Pattern FIELD_FG1 = Pattern.compile("^( {4}|\\t)(?:[\\w$.\\[\\]]+ )*(field_[0-9]+_[a-zA-Z_]+) *(?:=|;)"); + + public static void renameFG_1_0(File input, File mappings, File output) { + rename(input, mappings, output, false, true); + } + public static void rename(File input, File mappings, File output, boolean javadocMarkers) { + rename(input, mappings, output, javadocMarkers, false); + } + + private static void rename(File input, File mappings, File output, boolean javadocMarkers, boolean fg_1_0) { try { var names = MCPNames.load(mappings); FileUtils.ensureParent(output); @@ -47,8 +58,13 @@ public static void rename(File input, File mappings, File output, boolean javado if (!entry.getName().endsWith(".java")) { zin.transferTo(zout); } else { + if (entry.getName().endsWith("ComponentVillageStartPiece.java")) + System.currentTimeMillis(); var lines = NewLineDetector.readLines(zin); - lines = rename(names, lines, javadocMarkers); + if (fg_1_0) + lines = renameFG_1_0(names, lines); + else + lines = rename(names, lines, javadocMarkers); for (var itr = lines.iterator(); itr.hasNext();) { var line = itr.next(); zout.write(line.getBytes(StandardCharsets.UTF_8)); @@ -87,6 +103,7 @@ private static List rename(MCPNames names, List lines, boolean j formatted = matcher.group(1) + "// JAVADOC METHOD $$ " + name; else formatted = JavadocAdder.buildJavadoc(matcher.group(1), docs, true); + MCPNames.insertAboveAnnotations(newLines, formatted); } } else if ((matcher = FIELD.matcher(line)).find()) { @@ -97,7 +114,8 @@ private static List rename(MCPNames names, List lines, boolean j if (javadocMarkers) formatted = matcher.group(1) + "// JAVADOC FIELD $$ " + name; else - formatted = JavadocAdder.buildJavadoc(matcher.group(1), docs, true); + formatted = JavadocAdder.buildJavadoc(matcher.group(1), docs, false); + MCPNames.insertAboveAnnotations(newLines, formatted); } } @@ -105,4 +123,38 @@ private static List rename(MCPNames names, List lines, boolean j } return newLines; } + + + // FG 1.0 didn't use the itnermediate javadoc markers + // And didnt take annotations into account when inserting javadocs + private static List renameFG_1_0(MCPNames names, List lines) { + var newLines = new ArrayList(lines.size()); + Matcher matcher = null; + String lastLine = null; + for (var line : lines) { + if ((matcher = METHOD_FG1.matcher(line)).find()) { + var name = matcher.group(2); + var docs = names.docs().get(name); + + if (docs != null && !docs.isEmpty()) { + if (lastLine != null && !lastLine.isEmpty() && !lastLine.endsWith("{")) + newLines.add(""); + String formatted = JavadocAdder.buildJavadoc(matcher.group(1), docs, true); + newLines.add(formatted); + } + } else if ((matcher = FIELD_FG1.matcher(line)).find()) { + var name = matcher.group(2); + var docs = names.docs().get(name); + if (docs != null && !docs.isEmpty()) { + if (lastLine != null && !lastLine.isEmpty() && !lastLine.endsWith("{")) + newLines.add(""); + String formatted = JavadocAdder.buildJavadoc(matcher.group(1), docs, docs.length() >= 70); + newLines.add(formatted); + } + } + newLines.add(names.replaceInLine(line, null)); + lastLine = line; + } + return newLines; + } } diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MCPLegacy.java b/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MCPLegacy.java index 1b78528..1d24002 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MCPLegacy.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MCPLegacy.java @@ -12,6 +12,7 @@ import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; +import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.StandardCopyOption; @@ -41,6 +42,7 @@ import net.minecraftforge.mcmaven.impl.util.Artifact; import net.minecraftforge.mcmaven.impl.util.ComparableVersion; import net.minecraftforge.mcmaven.impl.util.Constants; +import net.minecraftforge.mcmaven.impl.util.NewLineDetector; import net.minecraftforge.mcmaven.impl.util.ProcessUtils; import net.minecraftforge.mcmaven.impl.util.StupidHacks; import net.minecraftforge.mcmaven.impl.util.Task; @@ -108,7 +110,7 @@ public MCPLegacy(MCPConfigRepo repo, Artifact name, @Nullable String python) { // TODO: [Mavenizer] Maybe write a task to convert the old python to newer formated artifacts? var prefix = this.isPython ? "conf/" : ""; this.mappings = extract(prefix + "joined.srg"); - this.exceptorJson = extract(prefix + "exceptor.json"); // Possibly non-existant? + this.exceptorJson = extract(prefix + "exceptor.json"); this.exceptorConfig = extract(prefix + "joined.exc"); if (this.mcVersionComp.compareTo(MC_1_8) < 0) @@ -146,10 +148,6 @@ public Task getMappings() { return this.mappings; } - public Task getExceptorJson() { - return this.exceptorJson; - } - public int getJavaTarget() { // The vanilla launcher uses Java 8 for all legacy versions // And the MCP archives have variant patches that deal with the decompile differences @@ -951,19 +949,28 @@ private Task modifyAccess() { var renamed = MCPLegacy.this.rename(); if (this.accessTransformer == null) return renamed; + + // Obfed access transformers, we need to remap them + var config = MCPLegacy.this.mcVersionComp.compareTo(MC_1_6_4) <= 0 + ? renameAts() + : null; + return Task.named(prefix + "modifyAccess", - Task.deps(renamed), () -> this.modifyAccess(renamed.execute()) + Task.deps(renamed, config), () -> this.modifyAccess(renamed.execute(), config == null ? null : config.execute()) ); } - private File modifyAccess(File input) { + private File modifyAccess(File input, @Nullable File config) { var tool = maven.download(Constants.ACCESS_TRANSFORMER); var output = new File(this.build, "modify-access.jar"); var log = new File(this.build, "modify-access.log"); + if (config == null) + config = this.accessTransformer; + var cache = Util.cache(output) .add("tool", tool) .add("input", input) - .add("config", this.accessTransformer); + .add("config", config); if (Mavenizer.checkCache(output, cache)) return output; @@ -971,7 +978,7 @@ private File modifyAccess(File input) { var args = List.of( "--inJar", input.getAbsolutePath(), "--outJar", output.getAbsolutePath(), - "--atfile", this.accessTransformer.getAbsolutePath(), + "--atfile", config.getAbsolutePath(), "--ignore-invalid" // Needed for some old Forge versions with invalid lines ); @@ -983,6 +990,97 @@ private File modifyAccess(File input) { cache.save(); return output; } + + private Task renameAts() { + var mappings = MCPLegacy.this.getMappings(); + + return Task.named(prefix + "renameAts", + Task.deps(mappings), () -> this.renameAts(mappings.execute()) + ); + } + + // Obfusciated access transformers for 1.6.4 and below + // Uses an old format that AccessTransformer doesn't support, so I transform it here, fairly easy to do + // https://github.com/MinecraftForge/FML/blob/f1b3381e61fac1a0ae90f521223c6bc613eb4888/common/cpw/mods/fml/common/asm/transformers/AccessTransformer.java#L120 + // + // If we reworked the process to apply ATs before renaming, then all that really needs to be done is to replace the first . with a space + // But This is simple enough to do and should work + private File renameAts(File mappings) { + var output = new File(this.build, "renamed-at.cfg"); + var cache = Util.cache(output) + .add("input", this.accessTransformer) + .add("mappings", mappings); + + if (Mavenizer.checkCache(output, cache)) + return output; + + FileUtils.ensureParent(output); + try (var out = new FileOutputStream(output); + var inf = new FileInputStream(this.accessTransformer) + ) { + var map = IMappingFile.load(mappings); + var reader = new NewLineDetector(new InputStreamReader(inf)); + + String line; + while ((line = reader.readLine()) != null) { + String comment = null; + int idx = line.indexOf('#'); + if (idx != -1) { + comment = line.substring(idx); + line = idx == 0 ? "" : line.substring(0, idx); + } + + line = line.trim(); + if (!line.isEmpty()) { + idx = line.indexOf(' '); + if (idx == -1) { // Must have both access and traget + Mavenizer.LOGGER.debug("Invalid access transformer line: " + line); + continue; + } + + var target = line.substring(idx + 1); + var transformation = line.substring(0, idx + 1); + idx = target.indexOf('.'); + + // Class target + if (idx == -1) { + line = transformation + map.remapClass(target); + } else { + var cls = target.substring(0, idx); + target = target.substring(idx + 1); + var mcls = map.getClass(cls); + + // There are no mappings for this class, so assume its not obfed + if (mcls != null) { + idx = target.indexOf('('); + + if (idx == -1) { // Field + line = transformation + map.remapClass(cls) + ' ' + mcls.remapField(target); + } else { // Method + var name = target.substring(0, idx); + var desc = target.substring(idx); + line = transformation + map.remapClass(cls) + ' ' + mcls.remapMethod(name, desc) + map.remapDescriptor(desc); + } + } + } + } + + + out.write(line.getBytes(StandardCharsets.UTF_8)); + if (comment != null) { + if (!line.isEmpty()) + out.write(' '); + out.write(comment.getBytes(StandardCharsets.UTF_8)); + } + out.write('\n'); + } + } catch (IOException e) { + return Util.sneak(e); + } + + cache.save(); + return output; + } } private static final TypeToken> MCI_CONFIG = new TypeToken<>() {}; @@ -1032,6 +1130,7 @@ else if (wanted.endsWith("+f")) // The main difference between FG versions was the decompiler we invoked/embeded private static final ComparableVersion MC_1_6_1 = new ComparableVersion("1.6.1"); + private static final ComparableVersion MC_1_6_4 = new ComparableVersion("1.6.4"); private static final ComparableVersion MC_1_7_2 = new ComparableVersion("1.7.2"); private static final ComparableVersion MC_1_7_10 = new ComparableVersion("1.7.10"); private static final ComparableVersion MC_1_8 = new ComparableVersion("1.8"); @@ -1047,10 +1146,15 @@ private static Exceptor get(ComparableVersion mc) { var markers = true; var lvt = false; var generate = true; + + if (mc.compareTo(MC_1_6_4) <= 0) + json = false; + if (mc.compareTo(MC_1_8_8) >= 0) { json = false; lvt = true; } + if (mc.compareTo(MC_1_9) >= 0) markers = false; if (mc.compareTo(MC_1_7_2) <= 0) @@ -1074,8 +1178,10 @@ private static Cleanup get(ComparableVersion mc) { ); } else if (mc.compareTo(MC_1_7_10) >= 0) { args = List.of("--fix-generic-params", "--fml", "--fernflower"); - } else { + } else if (mc.compareTo(MC_1_7_2) >= 0) { args = List.of("--mode", "1.7.2"); + } else { + args = List.of("--mode", "1.6.4"); } return new Cleanup(Constants.MCPCLEANUP, args); } diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/util/Constants.java b/src/main/java/net/minecraftforge/mcmaven/impl/util/Constants.java index f30870c..a4a1cc7 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/util/Constants.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/util/Constants.java @@ -62,7 +62,7 @@ public final class Constants { public static final Artifact MCINJECTOR = Artifact.from("de.oceanlabs.mcp:mcinjector:3.4.5:fatjar"); public static final int MCINJECTOR_JAVA_VERSION = 8; - public static final Artifact MCPCLEANUP = Artifact.from("net.minecraftforge:mcpcleanup:2.4.3:fatjar"); + public static final Artifact MCPCLEANUP = Artifact.from("net.minecraftforge:mcpcleanup:2.4.4:fatjar"); public static final int MCPCLEANUP_JAVA_VERSION = 8;; public static final Artifact LEGACY_MERGETOOL = Artifact.from("net.minecraftforge:mergetool:0.2.3.4:fatjar"); diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/util/StupidHacks.java b/src/main/java/net/minecraftforge/mcmaven/impl/util/StupidHacks.java index e9b9958..40ef7a7 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/util/StupidHacks.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/util/StupidHacks.java @@ -150,6 +150,7 @@ private static void mappings(String forge, String mappings) { } static { // FG 1.x, these were packed in the userdev. Manually uploaded + mappings("1.6.3-9.11.0.873", "snapshot:20130918-1.6.3"); mappings("1.7.2-10.12.0.967", "snapshot:20131226-1.7.2"); mappings("1.7.2-10.12.0.1024", "snapshot:20140205-1.7.2"); mappings("1.7.10_pre4-10.12.2.1137", "snapshot:20140624-1.7.10-pre4"); @@ -180,7 +181,7 @@ public static Mappings getDefaultMappings(ComparableVersion forgeVersion) { break; version = map.mappings(); } - if(version == null) + if (version == null) throw new IllegalStateException("Could not determine default mappings for " + forgeVersion); return Mappings.of(version); } From 9910c24a9e6b028d4f4e5615bc6fa91dadcfaa01 Mon Sep 17 00:00:00 2001 From: LexManos Date: Tue, 2 Jun 2026 20:13:39 -0700 Subject: [PATCH 07/12] Add 1.6.4, 1.7.10, and 1.8.9 to Action tests --- .github/workflows/test.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2b75c4e..b892ebb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,9 +17,12 @@ jobs: strategy: matrix: command: [ - "--forge --version 1.21.11-61.1.0", + "--forge --version 1.6.4-9.11.1.964", + "--forge --version 1.7.10-10.13.4.1614-1.7.10", + "--forge --version 1.8.9-11.15.1.2318-1.8.9, + "--forge --version 1.12.2-14.23.5.2864", "--forge --version 1.20.1-47.4.0", - "--forge --version 1.12.2-14.23.5.2859 --mappings snapshot:20171003-1.12", + "--forge --version 1.21.11-61.1.0", "--forge --version 26.1-62.0.0", "--client --version 1.20.1", "--server --version 1.20.1", From e1c41cef9f7920754e6d61cbadaead9b94c2a0a5 Mon Sep 17 00:00:00 2001 From: LexManos Date: Thu, 4 Jun 2026 10:05:07 -0700 Subject: [PATCH 08/12] Fix tests Fix 1.8.8 cleanup Add mappings translation to single invocation Simplify patching. Blacklist broken 1.8.8 builds --- .github/workflows/test.yml | 18 +++---- .../mcmaven/impl/MinecraftMaven.java | 21 ++++++-- .../mcmaven/impl/repo/forge/FG2Userdev.java | 50 +------------------ .../mcmaven/impl/repo/forge/ForgeRepo.java | 25 ++++++++-- .../mcmaven/impl/util/Constants.java | 2 +- .../mcmaven/impl/util/StupidHacks.java | 11 ++++ 6 files changed, 59 insertions(+), 68 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b892ebb..fd8fd33 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,17 +17,17 @@ jobs: strategy: matrix: command: [ - "--forge --version 1.6.4-9.11.1.964", - "--forge --version 1.7.10-10.13.4.1614-1.7.10", - "--forge --version 1.8.9-11.15.1.2318-1.8.9, - "--forge --version 1.12.2-14.23.5.2864", - "--forge --version 1.20.1-47.4.0", - "--forge --version 1.21.11-61.1.0", - "--forge --version 26.1-62.0.0", - "--client --version 1.20.1", + "--forge --version 1.6.4-9.11.1.964", # FG 1.0 + "--forge --version 1.7.10-10.13.4.1614-1.7.10", # FG 1.1 + "--forge --version 1.8.9-11.15.1.2318-1.8.9", # FG 2.1 + "--forge --version 1.12.2-14.23.5.2864", # FG + "--forge --version 1.20.1-47.4.0", # FG 3 + "--forge --version 1.21.11-61.1.0", # FG 6 Official names + "--forge --version 26.1-62.0.0", # FG 6 No obfusation + "--client --version 1.20.1", # MCPConfig, full decompile/recompile poissible "--server --version 1.20.1", "--mc --version 1.20.1", - "--client --version 26.1-snapshot-1", + "--client --version 26.1-snapshot-1", # No MCPConfig, unobfuscated, No Recompile possible "--server --version 26.1-snapshot-1" ] diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/MinecraftMaven.java b/src/main/java/net/minecraftforge/mcmaven/impl/MinecraftMaven.java index 6d042d7..132e4e0 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/MinecraftMaven.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/MinecraftMaven.java @@ -9,7 +9,6 @@ import net.minecraftforge.mcmaven.impl.mappings.Mappings; import net.minecraftforge.mcmaven.impl.repo.Repo; import net.minecraftforge.mcmaven.impl.repo.Repo.PendingArtifact; -import net.minecraftforge.mcmaven.impl.repo.forge.FGVersion; import net.minecraftforge.mcmaven.impl.repo.forge.ForgeRepo; import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCP; import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCPConfigRepo; @@ -188,9 +187,7 @@ protected void createForge(Artifact artifact, MCPConfigRepo mcprepo, ForgeRepo r continue; if (verEnd != null && cver.compareTo(verEnd) > 0) continue; - - // Python isn't supported yet - if (ForgeRepo.isPython(ver)) + if (!ForgeRepo.isSupported(ver)) continue; // Old versions don't have official mappings, and some of them require specific MCP mappigns due to the sources not being in @@ -219,7 +216,21 @@ protected void createForge(Artifact artifact, MCPConfigRepo mcprepo, ForgeRepo r if (StupidHacks.BLACKLISTED_FORGE_BUILDS.contains(artifact.getVersion())) throw new IllegalArgumentException("Forge version " + artifact.getVersion() + " has been blacklisted for technical reasons, pick a different Forge version"); - var mappings = this.mappings.withMCVersion(Util.forgeToMcVersion(version)); + var mcVersion = Util.forgeToMcVersion(version); + var mappings = this.mappings.withMCVersion(mcVersion); + + // Old versions don't have official mappings, and some of them require specific MCP mappigns due to the sources not being in + // full SRG names (we don't remap static imports) So if we're using official mappings (the default arugment) lets use the + // specific bot mappings that the version of Forge was built for. Any other option is at user's pearl and may not work. + if (this.mappings.channel().equals("official") && !hasOfficialMappings(mcprepo, mcVersion)) { + var cver = new ComparableVersion(artifact.getVersion()); + var newMappings = StupidHacks.getDefaultMappings(cver).withMCVersion(mcVersion); + if (!newMappings.channel().equals(mappings.channel()) || !newMappings.version().equals(mappings.version())) { + mappings = newMappings; + LOGGER.info("Using Mappings: " + mappings); + } + } + var artifacts = repo.process(artifact, mappings, outputJson); finalize(artifact, mappings, artifacts); } diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/FG2Userdev.java b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/FG2Userdev.java index 05cb20d..6a28957 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/FG2Userdev.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/FG2Userdev.java @@ -28,12 +28,6 @@ import org.jetbrains.annotations.Nullable; -import io.codechicken.diffpatch.cli.PatchOperation; -import io.codechicken.diffpatch.util.LogLevel; -import io.codechicken.diffpatch.util.PatchMode; -import io.codechicken.diffpatch.util.Input.MultiInput; -import io.codechicken.diffpatch.util.Output.MultiOutput; -import io.codechicken.diffpatch.util.archiver.ArchiveFormat; import net.minecraftforge.mcmaven.impl.Mavenizer; import net.minecraftforge.mcmaven.impl.cache.Cache; import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCPLegacy; @@ -340,10 +334,7 @@ private File patch(File input, File patches, String name) { if (rejects.exists()) rejects.delete(); - if (this.fgVersion.ordinal() <= FGVersion.v2.ordinal()) - patchUnstable(input, patches, output, rejects); - else - patchStable(input, patches, output, rejects); + patchUnstable(input, patches, output, rejects); cache.save(); return output; @@ -419,45 +410,6 @@ private void patchUnstable(File input, File patchesArchive, File output, File re Util.sneak(failure); } - // Modern versions that use out Fernflower fork produce stabelized output so we can use - // DiffPatch. But on older verions we need both ACCESS and OFFSET changes at the same time - // And if we tried to do that with DiffPatch we would need to use FUZZY which is TOO Fuzzy. - // it causes patches to be injected half-way and in weird places. - private void patchStable(File input, File patches, File output, File rejects) { - var builder = PatchOperation.builder() - .logTo(LOGGER::error) - .baseInput(MultiInput.archive(ArchiveFormat.ZIP, input.toPath())) - .patchesInput(MultiInput.archive(ArchiveFormat.ZIP, patches.toPath())) - .patchedOutput(MultiOutput.archive(ArchiveFormat.ZIP, output.toPath())) - .rejectsOutput(MultiOutput.archive(ArchiveFormat.ZIP, rejects.toPath())) - .level(LogLevel.ERROR) - .mode(PatchMode.ACCESS) - .aPrefix("/") - .bPrefix("/") - ; - - try { - var result = builder.build().operate(); - - boolean success = result.exit == 0; - if (!success) { - LOGGER.error("Fialed to apply patches"); - LOGGER.error(" Input: " + input.getAbsolutePath()); - LOGGER.error(" Patches: " + patches.getAbsolutePath()); - LOGGER.error(" Output: " + output.getAbsolutePath()); - LOGGER.error(" Rejects: " + rejects.getAbsolutePath()); - if (result.summary != null) - result.summary.print(LOGGER.getError(), true); - else - LOGGER.error("Failed to apply patches, no summary available"); - - throw except("Failed to apply patches, rejects saved to: " + rejects.getAbsolutePath()); - } - } catch (IOException e) { - Util.sneak(e); - } - } - private Task inject() { var input = this.mcpChild.getFinalStep(); var sources = extract("sources.zip"); diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/ForgeRepo.java b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/ForgeRepo.java index a919ac3..27cf894 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/ForgeRepo.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/ForgeRepo.java @@ -86,9 +86,26 @@ public ForgeRepo(Cache cache, MCPConfigRepo mcpconfig) { this.globalBuild = new File(cache.root(), "forge/.global"); } - public static boolean isPython(String version) { - if (FGVersion.fromForge(version) != null) + public static boolean isSupported(String version) { + // Python isn't supported yet + if (isPython(version)) return false; + + var fg = FGVersion.fromForge(version); + if (fg == null) + return false; + + // Early versions of 1.8.8 use a old '2.0' build with funkey fernflower + // The mcp artifact was also updated behind the scenes, so it won't apply correctly + // I need to rebuild those files in order for this to work + // But we have later builds of 1.8.8 that work so im not worried about it for now + if (version.startsWith("1.8.8-") && fg.ordinal() < FGVersion.v2_1.ordinal()) + return false; + + return true; + } + + private static boolean isPython(String version) { var ver = new ComparableVersion(version); return ver.compareTo(PYTHON_START) >= 0 && ver.compareTo(PYTHON_END) < 0; } @@ -117,8 +134,8 @@ public List process(Artifact artifact, Mappings mappings, Map BLACKLISTED_FORGE_BUILDS = Set.of( + // First 1.8 build, missing FML source files in userdev. No way to make this work + "1.8-11.14.0.1237-1.8", + // Broken access transformer, fixed in 1268 + "1.8-11.14.0.1267-1.8", // Has some fucked up patches with weird generics, I have no idea how they came into existance, they dont match github... // This is the very first 1.8.8 build, and the next one works fine, so I'm chalking it up to Gradle Snapshot voodoo "1.8.8-11.14.4.1575-1.8.8", + // These are early builds of 1.8.8 which use FG 2.0 snapshots, and an old Fernflower + // With a long since deleted mcp zip/patch set + "1.8.8-11.14.4.1576-1.8.8", + "1.8.8-11.14.4.1579-1.8.8", + "1.8.8-11.14.4.1580-1.8.8", + "1.8.8-11.14.4.1581-1.8.8", + "1.8.8-11.14.4.1582-1.8.8", // Has a reference to scala.actors.threadpool.Arrays which is part of org.scala-lang:scala-actors // Which we don't ship, but instead is a transitive of org.scala-lang:scala-actors-migration_2.11:1.1.0 // I could add the library to this version, but i'd rather just skip it From 1e75e67caf1d082461a2d453de47ec0c7f3af190 Mon Sep 17 00:00:00 2001 From: LexManos Date: Thu, 4 Jun 2026 10:51:23 -0700 Subject: [PATCH 09/12] Fix python check, and add friendlier error message for missing mcp mappings --- settings.gradle | 2 +- .../mcmaven/impl/mappings/MCPMappings.java | 49 ++++++++++++++++++- .../mcmaven/impl/repo/forge/ForgeRepo.java | 12 ++--- 3 files changed, 54 insertions(+), 9 deletions(-) diff --git a/settings.gradle b/settings.gradle index 41995f9..97024c7 100644 --- a/settings.gradle +++ b/settings.gradle @@ -38,7 +38,7 @@ dependencyResolutionManagement.versionCatalogs.register('libs') { library 'commonsio', 'commons-io', 'commons-io' version '2.18.0' // Utilities - library 'utils-download', 'net.minecraftforge', 'download-utils' version '0.4.0' + library 'utils-download', 'net.minecraftforge', 'download-utils' version '0.4.1' library 'utils-files', 'net.minecraftforge', 'file-utils' version '0.3.3' library 'utils-hash', 'net.minecraftforge', 'hash-utils' version '0.2.2' library 'utils-data', 'net.minecraftforge', 'json-data-utils' version '0.4.9' diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/mappings/MCPMappings.java b/src/main/java/net/minecraftforge/mcmaven/impl/mappings/MCPMappings.java index b98f95f..1471524 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/mappings/MCPMappings.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/mappings/MCPMappings.java @@ -5,7 +5,11 @@ package net.minecraftforge.mcmaven.impl.mappings; import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; import java.util.IdentityHashMap; +import java.util.List; import java.util.Map; import org.jetbrains.annotations.Nullable; @@ -15,7 +19,9 @@ import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCPLegacy; import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCPSide; import net.minecraftforge.mcmaven.impl.util.Artifact; +import net.minecraftforge.mcmaven.impl.util.Constants; import net.minecraftforge.mcmaven.impl.util.Task; +import net.minecraftforge.mcmaven.impl.util.Util; public class MCPMappings extends Mappings { private final Map resolved = new IdentityHashMap<>(); @@ -79,7 +85,46 @@ private ResolvedMappings withContextImpl(MCPLegacy legacy) { private Task downloadCsv(MavenCache maven) { // This is simple because it's the old MCP Bot based zip files // So it's just a matter of downloading from Forge's maven. - var botArtifact = Artifact.from("de.oceanlabs.mcp", "mcp_" + this.channel(), this.version(), null, "zip"); - return Task.named("srg2names[" + this + ']', () -> maven.download(botArtifact)); + var artifact = Artifact.from("de.oceanlabs.mcp", "mcp_" + this.channel(), this.version(), null, "zip"); + return Task.named("srg2names[" + this + ']', () -> downloadCsv(maven, artifact)); + } + + private File downloadCsv(MavenCache maven, Artifact artifact) { + try { + return maven.download(artifact); + } catch (Exception e) { + // They didnt have a suffix and we couldn't find the mapping + if (this.version().indexOf('-') == -1 && e instanceof FileNotFoundException) { + List versions = null; + try { + versions = maven.getVersions(artifact); + } catch (Exception _) { + // Whelp we couldn't find the version list to provide a useful hint, so just fall back to the normal error + return Util.sneak(e); + } + + var candidates = new ArrayList(); + var prefix = this.version() + '-'; + for (var version : versions) { + if (version.startsWith(prefix)) + candidates.add(version); + } + var buf = new StringBuilder(); + buf.append("Could not find mapping file for ").append(this.toString()); + if (candidates.size() == 1) + buf.append(" Did you mean ").append(candidates.getFirst()).append('?'); + else if (!candidates.isEmpty()) { + buf.append(" Did you mean one of these:"); + for (var candidate : candidates) + buf.append("\n\t").append(candidate); + } + buf.append("\n\tYou can try looking at ") + .append(Constants.FORGE_MAVEN).append(artifact.withVersion(null).getFolder()).append("/maven-metadata.xml") + .append(" for full list of possible versions"); + + throw new IllegalArgumentException(buf.toString(), e); + } + return Util.sneak(e); + } } } diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/ForgeRepo.java b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/ForgeRepo.java index 27cf894..0ef2708 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/ForgeRepo.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/ForgeRepo.java @@ -122,13 +122,13 @@ public List process(Artifact artifact, Mappings mappings, Map Date: Thu, 4 Jun 2026 11:06:53 -0700 Subject: [PATCH 10/12] Fix mojang maven fallback --- src/main/java/net/minecraftforge/mcmaven/impl/util/Util.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/util/Util.java b/src/main/java/net/minecraftforge/mcmaven/impl/util/Util.java index 02acb47..a6e33b4 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/util/Util.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/util/Util.java @@ -261,7 +261,7 @@ public static final File getArtifact(Cache cache, Artifact artifact) { return cache.maven().download(artifact); } catch (Exception e) { // If its 404 on Forge's maven, try Mojang's - if (e.getCause() instanceof FileNotFoundException) { + if (e instanceof FileNotFoundException) { try { return cache.minecraft().download(artifact); } catch (Exception e2) { From 52b712587099ab94b9562caeea1d9f348a75605b Mon Sep 17 00:00:00 2001 From: LexManos Date: Thu, 4 Jun 2026 12:51:16 -0700 Subject: [PATCH 11/12] Fix twitch libraries for 1.7.10 --- settings.gradle | 2 +- .../mcmaven/impl/Mavenizer.java | 2 +- .../mcmaven/impl/repo/forge/FG2Userdev.java | 53 +++++++++++++++---- .../impl/repo/mcpconfig/MinecraftTasks.java | 3 +- .../mcmaven/impl/util/StupidHacks.java | 2 +- .../mcmaven/impl/util/Util.java | 15 +++++- 6 files changed, 61 insertions(+), 16 deletions(-) diff --git a/settings.gradle b/settings.gradle index 97024c7..d0e0083 100644 --- a/settings.gradle +++ b/settings.gradle @@ -41,7 +41,7 @@ dependencyResolutionManagement.versionCatalogs.register('libs') { library 'utils-download', 'net.minecraftforge', 'download-utils' version '0.4.1' library 'utils-files', 'net.minecraftforge', 'file-utils' version '0.3.3' library 'utils-hash', 'net.minecraftforge', 'hash-utils' version '0.2.2' - library 'utils-data', 'net.minecraftforge', 'json-data-utils' version '0.4.9' + library 'utils-data', 'net.minecraftforge', 'json-data-utils' version '0.4.10' library 'utils-logging', 'net.minecraftforge', 'log-utils' version '0.5.1' library 'utils-os', 'net.minecraftforge', 'os-utils' version '0.1.0' bundle 'utils', ['utils-download', 'utils-files', 'utils-hash', 'utils-data', 'utils-logging', 'utils-os'] diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/Mavenizer.java b/src/main/java/net/minecraftforge/mcmaven/impl/Mavenizer.java index 78c5088..0efe4a1 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/Mavenizer.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/Mavenizer.java @@ -21,7 +21,7 @@ private Mavenizer() { } private static boolean offline = false; private static boolean cacheOnly = false; - private static boolean cacheMiss = false; + public static boolean cacheMiss = false; private static boolean ignoreCache = false; public static boolean isOffline() { diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/FG2Userdev.java b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/FG2Userdev.java index 6a28957..db9d639 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/FG2Userdev.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/FG2Userdev.java @@ -32,6 +32,7 @@ import net.minecraftforge.mcmaven.impl.cache.Cache; import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MCPLegacy; import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MinecraftTasks; +import net.minecraftforge.mcmaven.impl.repo.mcpconfig.MinecraftTasks.ArtifactFile; import net.minecraftforge.mcmaven.impl.util.Artifact; import net.minecraftforge.mcmaven.impl.util.ComparableVersion; import net.minecraftforge.mcmaven.impl.util.ContextualPatch; @@ -188,14 +189,30 @@ public List getRuntimeOnly() { @Override public void forAllLibraries(Consumer consumer, Predicate filter) { + + /* for (var library : this.config.getLibs()) { - var artifact = Artifact.from(library.coord).withOS(library.os); - if (filter == null || filter.test(artifact)) + var artifact = StupidHacks.fixLegacyForgeDeps(Artifact.from(library.coord)); + if (artifact == null || library.dl == null) + continue; + artifact = artifact.withOS(library.os); + if (filter == null || filter.test(artifact)) { + System.out.println("Dev: " + artifact + " " + artifact.getOs()); consumer.accept(artifact); + } } for (var library : this.getMCP().getMinecraftTasks().getClientLibraries()) { - if (filter == null || filter.test(library.artifact())) + if (filter == null || filter.test(library.artifact())) { + System.out.println("MC: " + library.artifact() + " " + library.artifact().getOs()); consumer.accept(library.artifact()); + } + } + */ + var libs = getLibraryFiles(); + for (var lib : libs) { + if (filter == null || filter.test(lib.artifact())) { + consumer.accept(lib.artifact()); + } } } @@ -211,12 +228,21 @@ public List getLibraries() { @Override public List getClasspath() { - var classpath = new ArrayList(); + var ret = new ArrayList(); + for (var lib : getLibraryFiles()) + ret.add(lib.file()); + return ret; + + } + public List getLibraryFiles() { + var classpath = new ArrayList(); var seen = new HashMap(); var cache = this.forge.getCache(); // minecraft version.json libs + userdev libs - for (var lib : this.getMCP().getMinecraftTasks().getClientLibraries()) { + var mc = this.getMCP().getMinecraftTasks(); + var libs = mc.getClientLibraries(); + for (var lib : libs) { // We might have to upgrade a vanilla dependency var artifact = StupidHacks.fixLegacyForgeDeps(lib.artifact()); @@ -224,9 +250,9 @@ public List getClasspath() { continue; if (artifact != lib.artifact()) - classpath.add(Util.getArtifact(cache, artifact)); + classpath.add(new ArtifactFile(artifact, Util.getArtifact(cache, artifact, true))); else - classpath.add(lib.file()); + classpath.add(lib); // We just want the group:name and clssifier, in case they have updated the version since we were built seen.put(artifact.withVersion(null).toString(), artifact); @@ -240,14 +266,14 @@ public List getClasspath() { var unversioned = artifact.withVersion(null).toString(); if (!seen.containsKey(unversioned)) { if (artifact != null) - classpath.add(Util.getArtifact(cache, artifact)); + classpath.add(new ArtifactFile(artifact, Util.getArtifact(cache, artifact, true))); } } } else { for (var lib : this.config.libraries) { var artifact = StupidHacks.fixLegacyForgeDeps(Artifact.from(lib.name)); if (artifact != null) - classpath.addFirst(Util.getArtifact(cache, artifact)); // Add our versions before vanilla's in case we upgrade + classpath.addFirst(new ArtifactFile(artifact, Util.getArtifact(cache, artifact, true))); // Add our versions before vanilla's in case we upgrade } } @@ -599,6 +625,11 @@ private File javadocs(File input) { } public Map getRuns() { + var mc = new ComparableVersion(this.mcp.getMinecraftTasks().getVersion()); + var prefix = "cpw.mods."; + if (mc.compareTo(new ComparableVersion("1.8")) >= 0) + prefix = "net.minecraftforge."; + // Use LinkedHashMap to keep things stable var serverEnv = new LinkedHashMap(); serverEnv.put("MCP_TO_SRG", "{mcp_to_srg}"); @@ -607,10 +638,10 @@ public Map getRuns() { serverEnv.put("FORGE_VERSION", this.getForgeVersion()); serverEnv.put("FORGE_GROUP", "net.minecraftforge"); serverEnv.put("MC_VERSION", this.getMinecraftVersion()); - serverEnv.put("tweakClass", "net.minecraftforge.fml.common.launcher.FMLServerTweaker"); + serverEnv.put("tweakClass", prefix + "fml.common.launcher.FMLServerTweaker"); var clientEnv = new LinkedHashMap<>(serverEnv); - clientEnv.put("tweakClass", "net.minecraftforge.fml.common.launcher.FMLTweaker"); + clientEnv.put("tweakClass", prefix + "fml.common.launcher.FMLTweaker"); clientEnv.put("assetIndex", "{asset_index}"); clientEnv.put("assetDirectory", "{assets_root}"); clientEnv.put("nativesDirectory", "{natives}"); diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MinecraftTasks.java b/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MinecraftTasks.java index 12ecd81..9874ecb 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MinecraftTasks.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MinecraftTasks.java @@ -383,7 +383,8 @@ public List getClientLibraries() { if (clientLibraries == null) { var ret = new ArrayList(); var json = JsonData.minecraftVersion(this.versionJson.execute()); - for (var lib : json.getLibs()) { + var libs = json.getLibs(); + for (var lib : libs) { //Natives don't have main download if (lib.dl == null) continue; diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/util/StupidHacks.java b/src/main/java/net/minecraftforge/mcmaven/impl/util/StupidHacks.java index 9581fca..05f7a50 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/util/StupidHacks.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/util/StupidHacks.java @@ -52,7 +52,7 @@ public static Artifact fixLegacyTools(Artifact artifact) { "org.scala-lang:scala-xml_2.11:1.0.2", "org.scala-lang.modules:scala-xml_2.11:1.0.2", "org.scala-lang:scala-parser-combinators_2.11:1.0.1", "org.scala-lang.modules:scala-parser-combinators_2.11:1.0.1", // They renamed the artifact - "tv.twitch:twitch-external-platform:4.5", "tv.twitch:twitch-platform:6.5", + //"tv.twitch:twitch-external-platform:4.5", "tv.twitch:twitch-platform:4.5", // Bumped transitivly somewhere in 1.7.10-pre4, and relied on the bumped version "com.google.guava:guava:15.0", "com.google.guava:guava:16.0" ); diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/util/Util.java b/src/main/java/net/minecraftforge/mcmaven/impl/util/Util.java index a6e33b4..b693a9b 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/util/Util.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/util/Util.java @@ -252,7 +252,20 @@ public static String forgeToMcVersion(String version) { public static final File getArtifact(Cache cache, String coords) { return getArtifact(cache, Artifact.from(coords)); } - public static final File getArtifact(Cache cache, Artifact artifact) { + public static final File getArtifact(Cache cache, Artifact artifact, boolean quiet) { + if (!quiet) + return getArtifact(cache, artifact); + + // This is ugly, but I dont feel like re-working the maven system to not print the stack when doing a fallback + boolean old = Mavenizer.cacheMiss; + Mavenizer.cacheMiss = true; + try { + return getArtifact(cache, artifact); + } finally { + Mavenizer.cacheMiss = old; + } + } + private static final File getArtifact(Cache cache, Artifact artifact) { // Some libraries are on Minecraft's maven. Such as launchwrapper. // Rather then configure Forge's server to proxy Mojang's I add this check. if ("net.minecraft".equals(artifact.getGroup())) From aebaebd23d8a55d4866e9e747aba94bc67691b1f Mon Sep 17 00:00:00 2001 From: LexManos Date: Thu, 4 Jun 2026 18:30:24 -0700 Subject: [PATCH 12/12] Add support for compiling minecraft to bytecode lower then java 8. Fixes Forge 1.6.4 runtime --- .../mcmaven/impl/repo/forge/FG2Userdev.java | 3 +-- .../impl/repo/mcpconfig/MCPLegacy.java | 5 +++-- .../mcmaven/impl/tasks/RecompileTask.java | 11 +++++++--- .../mcmaven/impl/util/ProcessUtils.java | 20 ++++++++++++++----- 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/FG2Userdev.java b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/FG2Userdev.java index db9d639..4efe4be 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/FG2Userdev.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/repo/forge/FG2Userdev.java @@ -163,8 +163,7 @@ public String getDataHash() { @Override public int getJavaTarget() { - // Older minecraft supported java 6, but it's not really possible to get a version of that these days, so we've been using 8 for over a decade - return 8; + return this.mcp.getJavaTarget(); } @Override diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MCPLegacy.java b/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MCPLegacy.java index 1d24002..69fba2a 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MCPLegacy.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/repo/mcpconfig/MCPLegacy.java @@ -154,13 +154,11 @@ public int getJavaTarget() { // Also, modern macs simply do not have java 5 & 6 distros. So we have to use java 8 // My bulk testing works fine, I do not set the -target version when recompiling, Not // sure if I care to do that. - /* var comp = new ComparableVersion(this.mcVersion); if (comp.compareTo(MC_1_6_1) < 0) return 5; if (comp.compareTo(MC_1_12) < 0) return 6; - */ return 8; } @@ -712,6 +710,9 @@ private File decompile(File input, File libraries, File sorts) { var tool = maven.download(decompiler.artifact); int java_version = MCPLegacy.this.getJavaTarget(); + // Mac OSX doesn't have Java below 6, so we must use 8. + if (java_version < 8) + java_version = 8; var cache = Util.cache(output) .add("input", input) diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/tasks/RecompileTask.java b/src/main/java/net/minecraftforge/mcmaven/impl/tasks/RecompileTask.java index 89e1d37..1468cef 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/tasks/RecompileTask.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/tasks/RecompileTask.java @@ -70,18 +70,23 @@ private File recompileSourcesImpl(Task inputTask, File output) { cache.add("sources", sourcesJar); cache.addKnown("debug", "true"); + if (this.javaTarget < 8) + cache.addKnown("java_target", Integer.toString(this.javaTarget)); if (Mavenizer.checkCache(output, cache)) return output; + // OSX doesn't have java < 8, so we need to grab 8 and set the --target argument + int jdkTarget = javaTarget < 8 ? 8 : javaTarget; + File jdk; try { - jdk = this.jdks.get(javaTarget); + jdk = this.jdks.get(jdkTarget); } catch (Exception e) { - throw new IllegalStateException("JDK not found: " + javaTarget, e); + throw new IllegalStateException("JDK not found: " + jdkTarget, e); } - ProcessUtils.recompileJar(jdk, this.classpath.get(), sourcesJar, output, true); + ProcessUtils.recompileJar(jdk, this.classpath.get(), sourcesJar, output, true, javaTarget); cache.save(); return output; diff --git a/src/main/java/net/minecraftforge/mcmaven/impl/util/ProcessUtils.java b/src/main/java/net/minecraftforge/mcmaven/impl/util/ProcessUtils.java index b8dc072..a19e5ab 100644 --- a/src/main/java/net/minecraftforge/mcmaven/impl/util/ProcessUtils.java +++ b/src/main/java/net/minecraftforge/mcmaven/impl/util/ProcessUtils.java @@ -269,7 +269,7 @@ public static Result runJar(File javaHome, File workDir, File logFile, File tool } } - public static File recompileJar(File javaHome, List classpath, File sourcesJar, File outputJar, boolean enableDebug) { + public static File recompileJar(File javaHome, List classpath, File sourcesJar, File outputJar, boolean enableDebug, int javaTarget) { // classpath arg var classpathString = makeClasspathString(classpath); @@ -325,14 +325,24 @@ public static File recompileJar(File javaHome, List classpath, File source var outputClasses = new File(temp, "classes"); FileUtils.ensure(outputClasses); - var args = new ArrayList<>(List.of( - "-nowarn", + var args = new ArrayList(); + args.add("-nowarn"); + + if (enableDebug) + args.add("-g"); + + if (javaTarget < 8) { + args.addAll(List.of( + "-source", "1." + javaTarget, + "-target", "1." + javaTarget + )); + } + + args.addAll(List.of( "-d " + wrap(outputClasses.getAbsolutePath().replace('\\', '/')), "-classpath " + wrap(classpathString), sourcePath.toString() )); - if (enableDebug) - args.add("-g"); var process = ProcessUtils.runJavac(javaHome, temp, new File(outputJar.getAbsolutePath() + ".log"), args, sourcesJar); if (process.exitCode != 0) {