diff options
Diffstat (limited to 'src/main/java/net/fabricmc/loom/configuration/providers')
11 files changed, 732 insertions, 66 deletions
diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/LaunchProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/LaunchProvider.java index 2ea5c812..27fe1ba5 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/LaunchProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/LaunchProvider.java @@ -31,7 +31,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -66,7 +66,7 @@ public class LaunchProvider extends DependencyProvider { .property("client", "java.library.path", getExtension().getMinecraftProvider().nativesDir().getAbsolutePath()) .property("client", "org.lwjgl.librarypath", getExtension().getMinecraftProvider().nativesDir().getAbsolutePath()); - if (!getExtension().isForge()) { + if (!getExtension().isModernForge()) { launchConfig .argument("client", "--assetIndex") .argument("client", getExtension().getMinecraftProvider().getVersionInfo().assetIndex().fabricId(getExtension().getMinecraftProvider().minecraftVersion())) @@ -74,7 +74,7 @@ public class LaunchProvider extends DependencyProvider { .argument("client", new File(getDirectories().getUserCache(), "assets").getAbsolutePath()); } - if (getExtension().isForge()) { + if (getExtension().isModernForge()) { launchConfig // Should match YarnNamingService.PATH_TO_MAPPINGS in forge-runtime .property("fabric.yarnWithSrg.path", getExtension().getMappingsProvider().tinyMappingsWithSrg.toAbsolutePath().toString()) @@ -103,11 +103,29 @@ public class LaunchProvider extends DependencyProvider { } } + if (getExtension().isLegacyForge()) { + launchConfig + .argument("client", "--tweakClass") + .argument("client", Constants.LegacyForge.FML_TWEAKER) + .argument("server", "--tweakClass") + .argument("server", Constants.LegacyForge.FML_SERVER_TWEAKER) + + .argument("--accessToken") + .argument("undefined") + + .property("net.minecraftforge.gradle.GradleStart.srg.srg-mcp", getExtension().getMappingsProvider().srgToNamedSrg.toAbsolutePath().toString()) + .property("mixin.env.remapRefMap", "true"); + + for (String config : PropertyUtil.getAndFinalize(getExtension().getForge().getMixinConfigs())) { + launchConfig.argument("--mixin").argument(config); + } + } + addDependency(Constants.Dependencies.DEV_LAUNCH_INJECTOR + Constants.Dependencies.Versions.DEV_LAUNCH_INJECTOR, Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES); addDependency(Constants.Dependencies.TERMINAL_CONSOLE_APPENDER + Constants.Dependencies.Versions.TERMINAL_CONSOLE_APPENDER, Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES); addDependency(Constants.Dependencies.JETBRAINS_ANNOTATIONS + Constants.Dependencies.Versions.JETBRAINS_ANNOTATIONS, JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME); - if (getExtension().isForge()) { + if (getExtension().isModernForge()) { addDependency(Constants.Dependencies.FORGE_RUNTIME + Constants.Dependencies.Versions.FORGE_RUNTIME, Constants.Configurations.FORGE_EXTRA); addDependency(Constants.Dependencies.JAVAX_ANNOTATIONS + Constants.Dependencies.Versions.JAVAX_ANNOTATIONS, JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME); } @@ -197,7 +215,7 @@ public class LaunchProvider extends DependencyProvider { } public static class LaunchConfig { - private final Map<String, List<String>> values = new HashMap<>(); + private final Map<String, List<String>> values = new LinkedHashMap<>(); public LaunchConfig property(String key, String value) { return property("common", key, value); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java index b2ebfa71..aa041162 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java @@ -109,6 +109,13 @@ public class FieldMigratedMappingsProvider extends MappingsProviderImpl { public void manipulateMappings(Path mappingsJar) throws IOException { Stopwatch stopwatch = Stopwatch.createStarted(); LoomGradleExtension extension = getExtension(); + + if (extension.isLegacyForge()) { + // Legacy forge patches are in official namespace, so if the type of a field is changed by them, then that + // is effectively a new field and not traceable to any mapping. Therefore this does not apply to it. + return; + } + this.rawTinyMappings = tinyMappings; this.rawTinyMappingsWithSrg = tinyMappingsWithSrg; String mappingsJarName = mappingsJar.getFileName().toString(); @@ -116,7 +123,7 @@ public class FieldMigratedMappingsProvider extends MappingsProviderImpl { if (getExtension().shouldGenerateSrgTiny()) { if (Files.notExists(rawTinyMappingsWithSrg) || isRefreshDeps()) { // Merge tiny mappings with srg - SrgMerger.mergeSrg(getProject().getLogger(), getExtension().getMappingsProvider()::getMojmapSrgFileIfPossible, getRawSrgFile(), rawTinyMappings, rawTinyMappingsWithSrg, true); + SrgMerger.mergeSrg(getProject().getLogger(), getExtension().getMappingsProvider()::getMojmapSrgFileIfPossible, getRawSrgFile(), rawTinyMappings, rawTinyMappingsWithSrg, true, false); } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java index 358fd110..b7436124 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java @@ -50,6 +50,7 @@ import com.google.gson.JsonObject; import org.gradle.api.Project; import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.ModuleDependency; +import org.gradle.api.artifacts.repositories.IvyArtifactRepository; import org.gradle.api.artifacts.transform.InputArtifact; import org.gradle.api.artifacts.transform.TransformAction; import org.gradle.api.artifacts.transform.TransformOutputs; @@ -72,6 +73,7 @@ public class ForgeUserdevProvider extends DependencyProvider { private File userdevJar; private JsonObject json; private Consumer<Runnable> postPopulationScheduler; + private boolean isLegacyForge; public ForgeUserdevProvider(Project project) { super(project); @@ -99,7 +101,14 @@ public class ForgeUserdevProvider extends DependencyProvider { Files.copy(resolved.toPath(), userdevJar.toPath(), StandardCopyOption.REPLACE_EXISTING); try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + resolved.toURI()), ImmutableMap.of("create", false))) { - Files.copy(fs.getPath("config.json"), configJson, StandardCopyOption.REPLACE_EXISTING); + Path configEntry = fs.getPath("config.json"); + + // If we cannot find a modern config json, try the legacy/FG2-era one + if (Files.notExists(configEntry)) { + configEntry = fs.getPath("dev.json"); + } + + Files.copy(configEntry, configJson, StandardCopyOption.REPLACE_EXISTING); } } @@ -107,11 +116,31 @@ public class ForgeUserdevProvider extends DependencyProvider { json = new Gson().fromJson(reader, JsonObject.class); } - addDependency(json.get("mcp").getAsString(), Constants.Configurations.MCP_CONFIG); - addDependency(json.get("mcp").getAsString(), Constants.Configurations.SRG); - addDependency(json.get("universal").getAsString(), Constants.Configurations.FORGE_UNIVERSAL); + isLegacyForge = !json.has("mcp"); + + if (!isLegacyForge) { + addDependency(json.get("mcp").getAsString(), Constants.Configurations.MCP_CONFIG); + addDependency(json.get("mcp").getAsString(), Constants.Configurations.SRG); + addDependency(json.get("universal").getAsString(), Constants.Configurations.FORGE_UNIVERSAL); + } else { + Map<String, String> mcpDep = Map.of( + "group", "de.oceanlabs.mcp", + "name", "mcp", + "version", json.get("inheritsFrom").getAsString(), + "classifier", "srg", + "ext", "zip" + ); + addDependency(mcpDep, Constants.Configurations.MCP_CONFIG); + addDependency(mcpDep, Constants.Configurations.SRG); + addDependency(dependency.getDepString() + ":universal", Constants.Configurations.FORGE_UNIVERSAL); + addLegacyMCPRepo(); + } for (JsonElement lib : json.get("libraries").getAsJsonArray()) { + if (isLegacyForge) { + lib = lib.getAsJsonObject().get("name"); + } + Dependency dep = null; if (lib.getAsString().startsWith("org.spongepowered:mixin:")) { @@ -128,7 +157,7 @@ public class ForgeUserdevProvider extends DependencyProvider { dep = addDependency(lib.getAsString(), Constants.Configurations.FORGE_DEPENDENCIES); } - if (lib.getAsString().split(":").length < 4) { + if (!isLegacyForge && lib.getAsString().split(":").length < 4) { ((ModuleDependency) dep).attributes(attributes -> { attributes.attribute(transformed, true); }); @@ -137,7 +166,16 @@ public class ForgeUserdevProvider extends DependencyProvider { // TODO: Should I copy the patches from here as well? // That'd require me to run the "MCP environment" fully up to merging. - for (Map.Entry<String, JsonElement> entry : json.getAsJsonObject("runs").entrySet()) { + + if (!isLegacyForge) { + configureRuns(json.getAsJsonObject("runs")); + } else { + configureRunsForLegacyForge(); + } + } + + private void configureRuns(JsonObject runs) { + for (Map.Entry<String, JsonElement> entry : runs.entrySet()) { LaunchProviderSettings launchSettings = getExtension().getLaunchConfigs().findByName(entry.getKey()); RunConfigSettings settings = getExtension().getRunConfigs().findByName(entry.getKey()); JsonObject value = entry.getValue().getAsJsonObject(); @@ -184,6 +222,35 @@ public class ForgeUserdevProvider extends DependencyProvider { } } + private void configureRunsForLegacyForge() { + getExtension().getRunConfigs().configureEach(config -> { + if (Constants.Forge.LAUNCH_TESTING.equals(config.getDefaultMainClass())) { + config.setDefaultMainClass(Constants.LegacyForge.LAUNCH_WRAPPER); + } + }); + } + + private void addLegacyMCPRepo() { + getProject().getRepositories().ivy(repo -> { + // Old MCP data does not have POMs + repo.setName("LegacyMCP"); + repo.setUrl("https://maven.minecraftforge.net/"); + repo.patternLayout(layout -> { + layout.artifact("[orgPath]/[artifact]/[revision]/[artifact]-[revision](-[classifier])(.[ext])"); + // also check the zip so people do not have to explicitly specify the extension for older versions + layout.artifact("[orgPath]/[artifact]/[revision]/[artifact]-[revision](-[classifier]).zip"); + }); + repo.content(descriptor -> { + descriptor.includeGroup("de.oceanlabs.mcp"); + }); + repo.metadataSources(IvyArtifactRepository.MetadataSources::artifact); + }); + } + + public boolean isLegacyForge() { + return isLegacyForge; + } + public abstract static class RemoveNameProvider implements TransformAction<TransformParameters.None> { @InputArtifact public abstract Provider<FileSystemLocation> getInput(); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java index 7404cc0a..160f14d6 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java @@ -64,6 +64,11 @@ public class McpConfigProvider extends DependencyProvider { @Override public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception { + if (getExtension().isLegacyForge()) { + official = false; + return; + } + init(dependency.getDependency().getVersion()); Path mcpZip = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve MCPConfig")).toPath(); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java index fadf7d33..f11907dc 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java @@ -120,9 +120,9 @@ public class MinecraftPatchedProvider extends DependencyProvider { private File minecraftClientExtra; private File projectAtHash; - private Set<File> projectAts = new HashSet<>(); - private boolean atDirty = false; - private boolean filesDirty = false; + protected Set<File> projectAts = new HashSet<>(); + protected boolean atDirty = false; + protected boolean filesDirty = false; private Path mcpConfigMappings; private Path[] mergedMojangTsrg2Files; @@ -130,8 +130,7 @@ public class MinecraftPatchedProvider extends DependencyProvider { super(project); } - public void initFiles() throws IOException { - filesDirty = false; + protected void initAts() throws IOException { projectAtHash = new File(getDirectories().getProjectPersistentCache(), "at.sha256"); ConfigurableFileCollection accessTransformers = getExtension().getForge().getAccessTransformers(); accessTransformers.finalizeValue(); @@ -164,6 +163,11 @@ public class MinecraftPatchedProvider extends DependencyProvider { atDirty = mismatched; } + } + + public void initFiles() throws IOException { + filesDirty = false; + initAts(); MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); PatchProvider patchProvider = getExtension().getPatchProvider(); @@ -186,7 +190,7 @@ public class MinecraftPatchedProvider extends DependencyProvider { forgeMergedJar = getExtension().isForgeAndOfficial() ? null : new File(globalCache, "forge-official.jar"); minecraftMergedPatchedSrgAtJar = new File(projectDir, "merged-srg-at-patched.jar"); minecraftMergedPatchedJar = new File(projectDir, "merged-patched.jar"); - minecraftClientExtra = new File(globalCache, "forge-client-extra.jar"); + minecraftClientExtra = getExtension().isForgeAndOfficial() ? new File(globalCache, "forge-client-extra.jar") : null; if (isRefreshDeps() || Stream.of(getGlobalCaches()).anyMatch(((Predicate<File>) File::exists).negate()) || !isPatchedJarUpToDate(minecraftMergedPatchedJar)) { @@ -215,7 +219,7 @@ public class MinecraftPatchedProvider extends DependencyProvider { cleanProjectCache(); } - private File[] getGlobalCaches() { + protected File[] getGlobalCaches() { File[] files = { minecraftClientSrgJar, minecraftServerSrgJar, @@ -223,14 +227,9 @@ public class MinecraftPatchedProvider extends DependencyProvider { minecraftServerPatchedSrgJar, minecraftMergedPatchedSrgJar, minecraftClientExtra, + forgeMergedJar }; - - if (forgeMergedJar != null) { - Arrays.copyOf(files, files.length + 1); - files[files.length - 1] = forgeMergedJar; - } - - return files; + return Arrays.stream(files).filter(Objects::nonNull).toArray(File[]::new); } public void cleanProjectCache() { @@ -239,7 +238,7 @@ public class MinecraftPatchedProvider extends DependencyProvider { } } - private File[] getProjectCache() { + protected File[] getProjectCache() { return new File[] { minecraftMergedPatchedSrgAtJar, minecraftMergedPatchedJar @@ -456,7 +455,7 @@ public class MinecraftPatchedProvider extends DependencyProvider { return getExtension().getForgeUserdevProvider().getUserdevJar(); } - private boolean isPatchedJarUpToDate(File jar) throws IOException { + protected boolean isPatchedJarUpToDate(File jar) throws IOException { if (!jar.exists()) return false; byte[] manifestBytes = ZipUtils.unpackNullable(jar.toPath(), "META-INF/MANIFEST.MF"); @@ -478,6 +477,26 @@ public class MinecraftPatchedProvider extends DependencyProvider { } private void accessTransformForge(Logger logger) throws Exception { + List<byte[]> ats = new ArrayList<>(); + + for (File jar : ImmutableList.of(getForgeJar(), getForgeUserdevJar(), minecraftMergedPatchedSrgJar)) { + byte[] atBytes = ZipUtils.unpackNullable(jar.toPath(), Constants.Forge.ACCESS_TRANSFORMER_PATH); + + if (atBytes != null) { + ats.add(atBytes); + } + } + + if (usesProjectCache()) { + for (File projectAt : projectAts) { + ats.add(Files.readAllBytes(projectAt.toPath())); + } + } + + accessTransformForge(logger, minecraftMergedPatchedSrgJar, minecraftMergedPatchedSrgAtJar, ats); + } + + protected void accessTransformForge(Logger logger, File input, File target, List<byte[]> ats) throws Exception { MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); List<File> toDelete = new ArrayList<>(); String atDependency = Constants.Dependencies.ACCESS_TRANSFORMERS + (minecraftProvider.isNewerThan21w39a() ? Constants.Dependencies.Versions.ACCESS_TRANSFORMERS_NEW : Constants.Dependencies.Versions.ACCESS_TRANSFORMERS); @@ -486,8 +505,6 @@ public class MinecraftPatchedProvider extends DependencyProvider { logger.lifecycle(":access transforming minecraft"); - File input = minecraftMergedPatchedSrgJar; - File target = minecraftMergedPatchedSrgAtJar; Files.deleteIfExists(target.toPath()); List<String> args = new ArrayList<>(); @@ -496,23 +513,12 @@ public class MinecraftPatchedProvider extends DependencyProvider { args.add("--outJar"); args.add(target.getAbsolutePath()); - for (File jar : ImmutableList.of(getForgeJar(), getForgeUserdevJar(), minecraftMergedPatchedSrgJar)) { - byte[] atBytes = ZipUtils.unpackNullable(jar.toPath(), Constants.Forge.ACCESS_TRANSFORMER_PATH); - - if (atBytes != null) { - File tmpFile = File.createTempFile("at-conf", ".cfg"); - toDelete.add(tmpFile); - Files.write(tmpFile.toPath(), atBytes, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); - args.add("--atFile"); - args.add(tmpFile.getAbsolutePath()); - } - } - - if (usesProjectCache()) { - for (File projectAt : projectAts) { - args.add("--atFile"); - args.add(projectAt.getAbsolutePath()); - } + for (byte[] atBytes : ats) { + File tmpFile = File.createTempFile("at-conf", ".cfg"); + toDelete.add(tmpFile); + Files.write(tmpFile.toPath(), atBytes, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + args.add("--atFile"); + args.add(tmpFile.getAbsolutePath()); } getProject().javaexec(spec -> { @@ -599,7 +605,7 @@ public class MinecraftPatchedProvider extends DependencyProvider { applyLoomPatchVersion(mcOutput); } - private void patchJars(Logger logger) throws IOException { + private void patchJars(Logger logger) throws Exception { Stopwatch stopwatch = Stopwatch.createStarted(); logger.lifecycle(":patching jars"); @@ -619,7 +625,7 @@ public class MinecraftPatchedProvider extends DependencyProvider { logger.lifecycle(":patched jars in " + stopwatch.stop()); } - private void patchJars(File clean, File output, Path patches) throws IOException { + protected void patchJars(File clean, File output, Path patches) throws Exception { PrintStream previous = System.out; try { @@ -682,7 +688,7 @@ public class MinecraftPatchedProvider extends DependencyProvider { } } - private void walkFileSystems(File source, File target, Predicate<Path> filter, FsPathConsumer action) throws IOException { + protected void walkFileSystems(File source, File target, Predicate<Path> filter, FsPathConsumer action) throws IOException { walkFileSystems(source, target, filter, FileSystem::getRootDirectories, action); } @@ -690,7 +696,7 @@ public class MinecraftPatchedProvider extends DependencyProvider { walkFileSystems(source, target, it -> true, this::copyReplacing); } - private void copyMissingClasses(File source, File target) throws IOException { + protected void copyMissingClasses(File source, File target) throws IOException { walkFileSystems(source, target, it -> it.toString().endsWith(".class"), (sourceFs, targetFs, sourcePath, targetPath) -> { if (Files.exists(targetPath)) return; Path parent = targetPath.getParent(); @@ -703,7 +709,7 @@ public class MinecraftPatchedProvider extends DependencyProvider { }); } - private void copyNonClassFiles(File source, File target) throws IOException { + protected void copyNonClassFiles(File source, File target) throws IOException { Predicate<Path> filter = getExtension().isForgeAndOfficial() ? file -> { String s = file.toString(); return !s.endsWith(".class"); @@ -715,7 +721,7 @@ public class MinecraftPatchedProvider extends DependencyProvider { walkFileSystems(source, target, filter, this::copyReplacing); } - private void copyReplacing(FileSystem sourceFs, FileSystem targetFs, Path sourcePath, Path targetPath) throws IOException { + protected void copyReplacing(FileSystem sourceFs, FileSystem targetFs, Path sourcePath, Path targetPath) throws IOException { Path parent = targetPath.getParent(); if (parent != null) { diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java index d49ae8b4..bfd36d93 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java @@ -24,7 +24,16 @@ package net.fabricmc.loom.configuration.providers.forge; +import static java.nio.file.StandardOpenOption.CREATE; +import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.io.UncheckedIOException; import java.net.URI; import java.nio.file.FileSystem; @@ -33,11 +42,21 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.function.Consumer; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; +import java.util.jar.JarOutputStream; +import java.util.zip.ZipEntry; import com.google.common.collect.ImmutableMap; +import lzma.sdk.lzma.Decoder; +import lzma.sdk.lzma.Encoder; +import lzma.streams.LzmaInputStream; +import lzma.streams.LzmaOutputStream; +import org.apache.commons.io.IOUtils; import org.gradle.api.Project; import net.fabricmc.loom.configuration.DependencyProvider; +import net.fabricmc.loom.configuration.providers.forge.fg2.Pack200Provider; import net.fabricmc.loom.util.Constants; public class PatchProvider extends DependencyProvider { @@ -56,11 +75,17 @@ public class PatchProvider extends DependencyProvider { if (Files.notExists(clientPatches) || Files.notExists(serverPatches) || isRefreshDeps()) { getProject().getLogger().info(":extracting forge patches"); - Path installerJar = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve Forge installer")).toPath(); + Path installerJar = getExtension().isModernForge() + ? dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve Forge installer")).toPath() + : getExtension().getForgeUniversalProvider().getForge().toPath(); try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + installerJar.toUri()), ImmutableMap.of("create", false))) { - Files.copy(fs.getPath("data", "client.lzma"), clientPatches, StandardCopyOption.REPLACE_EXISTING); - Files.copy(fs.getPath("data", "server.lzma"), serverPatches, StandardCopyOption.REPLACE_EXISTING); + if (getExtension().isModernForge()) { + Files.copy(fs.getPath("data", "client.lzma"), clientPatches, StandardCopyOption.REPLACE_EXISTING); + Files.copy(fs.getPath("data", "server.lzma"), serverPatches, StandardCopyOption.REPLACE_EXISTING); + } else { + splitAndConvertLegacyPatches(fs.getPath("binpatches.pack.lzma")); + } } } } @@ -77,6 +102,73 @@ public class PatchProvider extends DependencyProvider { } } + private void splitAndConvertLegacyPatches(Path joinedLegacyPatches) throws IOException { + try (JarInputStream in = new JarInputStream(new ByteArrayInputStream(unpack200Lzma(joinedLegacyPatches))); + OutputStream clientFileOut = Files.newOutputStream(clientPatches, CREATE, TRUNCATE_EXISTING); + LzmaOutputStream clientLzmaOut = new LzmaOutputStream(clientFileOut, new Encoder()); + JarOutputStream clientJarOut = new JarOutputStream(clientLzmaOut); + OutputStream serverFileOut = Files.newOutputStream(serverPatches, CREATE, TRUNCATE_EXISTING); + LzmaOutputStream serverLzmaOut = new LzmaOutputStream(serverFileOut, new Encoder()); + JarOutputStream serverJarOut = new JarOutputStream(serverLzmaOut); + ) { + for (JarEntry entry; (entry = in.getNextJarEntry()) != null;) { + String name = entry.getName(); + + JarOutputStream out; + + if (name.startsWith("binpatch/client/")) { + out = clientJarOut; + } else if (name.startsWith("binpatch/server/")) { + out = serverJarOut; + } else { + getProject().getLogger().warn("Unexpected file in Forge binpatches archive: " + name); + continue; + } + + out.putNextEntry(new ZipEntry(name)); + + // Converting from legacy format to modern (v1) format + DataInputStream dataIn = new DataInputStream(in); + DataOutputStream dataOut = new DataOutputStream(out); + dataOut.writeByte(1); // version + dataIn.readUTF(); // unused patch name (presumably always the same as the obf class name) + dataOut.writeUTF(dataIn.readUTF().replace('.', '/')); // obf class name + dataOut.writeUTF(dataIn.readUTF().replace('.', '/')); // srg class name + IOUtils.copy(in, out); // remainder is unchanged + + out.closeEntry(); + } + } + } + + private byte[] unpack200(InputStream in) throws IOException { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + + try (JarOutputStream jarOut = new JarOutputStream(bytes)) { + Pack200Provider provider = getExtension().getForge().getPack200Provider().getOrNull(); + + if (provider == null) { + throw new IllegalStateException("No provider for Pack200 has been found. Did you declare a provider?"); + } + + provider.unpack(in, jarOut); + } + + return bytes.toByteArray(); + } + + private byte[] unpack200Lzma(InputStream in) throws IOException { + try (LzmaInputStream lzmaIn = new LzmaInputStream(in, new Decoder())) { + return unpack200(lzmaIn); + } + } + + private byte[] unpack200Lzma(Path path) throws IOException { + try (InputStream in = Files.newInputStream(path)) { + return unpack200Lzma(in); + } + } + public Path getProjectCacheFolder() { return projectCacheFolder; } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java index 55a19fd6..9c24416c 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java @@ -29,8 +29,10 @@ import java.io.BufferedWriter; import java.io.File; import java.io.IOException; import java.io.PrintStream; +import java.io.Reader; import java.io.StringReader; import java.io.UncheckedIOException; +import java.io.Writer; import java.net.URI; import java.nio.charset.StandardCharsets; import java.nio.file.FileSystem; @@ -47,6 +49,8 @@ import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableMap; import org.apache.commons.io.FileUtils; import org.apache.commons.io.output.NullOutputStream; +import org.cadixdev.lorenz.io.srg.SrgReader; +import org.cadixdev.lorenz.io.srg.tsrg.TSrgWriter; import org.gradle.api.Project; import org.gradle.api.logging.LogLevel; @@ -87,7 +91,16 @@ public class SrgProvider extends DependencyProvider { Path srgZip = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve srg")).toPath(); try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + srgZip.toUri()), ImmutableMap.of("create", false))) { - Files.copy(fs.getPath("config", "joined.tsrg"), srg, StandardCopyOption.REPLACE_EXISTING); + Path srgPath = fs.getPath("joined.srg"); + + if (Files.exists(srgPath)) { + // FG2-era MCP uses the older SRG format, convert it on the fly + try (Reader reader = Files.newBufferedReader(srgPath); Writer writer = Files.newBufferedWriter(srg)) { + new TSrgWriter(writer).write(new SrgReader(reader).read()); + } + } else { + Files.copy(fs.getPath("config", "joined.tsrg"), srg, StandardCopyOption.REPLACE_EXISTING); + } } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftLegacyPatchedProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftLegacyPatchedProvider.java new file mode 100644 index 00000000..e74e463e --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftLegacyPatchedProvider.java @@ -0,0 +1,393 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration.providers.forge.fg2; + +import java.io.BufferedWriter; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; +import java.util.Arrays; +import java.util.Collections; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import com.google.common.base.Stopwatch; +import com.google.common.collect.ImmutableMap; +import org.cadixdev.at.AccessTransformSet; +import org.cadixdev.at.io.AccessTransformFormats; +import org.cadixdev.lorenz.MappingSet; +import org.gradle.api.Project; +import org.gradle.api.logging.Logger; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl; +import net.fabricmc.loom.configuration.providers.forge.MinecraftPatchedProvider; +import net.fabricmc.loom.configuration.providers.forge.PatchProvider; +import net.fabricmc.loom.util.FileSystemUtil; +import net.fabricmc.loom.util.ThreadingUtils; +import net.fabricmc.loom.util.TinyRemapperHelper; +import net.fabricmc.loom.util.ZipUtils; +import net.fabricmc.loom.util.srg.AccessTransformSetMapper; +import net.fabricmc.lorenztiny.TinyMappingsReader; +import net.fabricmc.mappingio.tree.MappingTree; +import net.fabricmc.stitch.merge.JarMerger; + +public class MinecraftLegacyPatchedProvider extends MinecraftPatchedProvider { + // Step 1: Binary Patch (global) + private File minecraftClientPatchedJar; + private File minecraftServerPatchedJar; + // Step 2: Merge (global) + private File minecraftMergedPatchedJar; + // Step 4: Access Transform (global or project) + private File minecraftMergedPatchedAtJar; + + private File forgeJar; + + public MinecraftLegacyPatchedProvider(Project project) { + super(project); + } + + @Override + public void initFiles() throws IOException { + filesDirty = false; + initAts(); + + File globalCache = getExtension().getForgeProvider().getGlobalCache(); + File projectDir = usesProjectCache() ? getExtension().getForgeProvider().getProjectCache() : globalCache; + projectDir.mkdirs(); + + minecraftClientPatchedJar = new File(globalCache, "client-patched.jar"); + minecraftServerPatchedJar = new File(globalCache, "server-patched.jar"); + minecraftMergedPatchedJar = new File(globalCache, "merged-patched.jar"); + minecraftMergedPatchedAtJar = new File(projectDir, "merged-at-patched.jar"); + + forgeJar = new File(globalCache, "forge.jar"); + + if (isRefreshDeps() || Stream.of(getGlobalCaches()).anyMatch(((Predicate<File>) File::exists).negate()) + || !isPatchedJarUpToDate(forgeJar) || !isPatchedJarUpToDate(minecraftMergedPatchedAtJar)) { + cleanAllCache(); + } else if (atDirty || Stream.of(getProjectCache()).anyMatch(((Predicate<File>) File::exists).negate())) { + cleanProjectCache(); + } + } + + @Override + protected File[] getGlobalCaches() { + return new File[] { + minecraftClientPatchedJar, + minecraftServerPatchedJar, + minecraftMergedPatchedJar, + forgeJar, + }; + } + + @Override + protected File[] getProjectCache() { + return new File[] { + minecraftMergedPatchedAtJar, + }; + } + + @Override + public void provide(DependencyInfo dependency, Consumer<Runnable> postPopulationScheduler) throws Exception { + initFiles(); + + if (atDirty) { + getProject().getLogger().lifecycle(":found dirty access transformers"); + } + } + + @Override + public void finishProvide() throws Exception { + if (!forgeJar.exists()) { + filesDirty = true; + patchForge(getProject().getLogger()); + applyLoomPatchVersion(forgeJar.toPath()); + } + + if (!minecraftClientPatchedJar.exists() || !minecraftServerPatchedJar.exists()) { + filesDirty = true; + patchJars(getProject().getLogger()); + } + + if (filesDirty || !minecraftMergedPatchedJar.exists()) { + filesDirty = true; + mergeJars(getProject().getLogger()); + } + + if (atDirty || filesDirty || !minecraftMergedPatchedAtJar.exists()) { + filesDirty = true; + accessTransformForge(getProject().getLogger()); + applyLoomPatchVersion(minecraftMergedPatchedAtJar.toPath()); + } + } + + private void patchForge(Logger logger) throws Exception { + Stopwatch stopwatch = Stopwatch.createStarted(); + logger.lifecycle(":patching forge"); + + Files.copy(getExtension().getForgeUniversalProvider().getForge().toPath(), forgeJar.toPath(), StandardCopyOption.REPLACE_EXISTING); + + // For the development environment, we need to remove the binpatches, otherwise forge will try to re-apply them + try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(forgeJar, false)) { + Files.delete(fs.get().getPath("binpatches.pack.lzma")); + } + + // Older versions of Forge rely on utility classes from log4j-core 2.0-beta9 but we'll upgrade the runtime to a + // release version (so we can use the TerminalConsoleAppender) where some of those classes have been moved from + // a `helpers` to a `utils` package. + // To allow Forge to work regardless, we'll re-package those helper classes into the forge jar. + Path log4jBeta9 = Arrays.stream(TinyRemapperHelper.getMinecraftDependencies(getProject())) + .filter(it -> it.getFileName().toString().equals("log4j-core-2.0-beta9.jar")) + .findAny() + .orElse(null); + if (log4jBeta9 != null) { + Predicate<Path> isHelper = path -> path.startsWith("/org/apache/logging/log4j/core/helpers"); + walkFileSystems(log4jBeta9.toFile(), forgeJar, isHelper, this::copyReplacing); + } + + logger.lifecycle(":patched forge in " + stopwatch.stop()); + } + + private void patchJars(Logger logger) throws Exception { + Stopwatch stopwatch = Stopwatch.createStarted(); + logger.lifecycle(":patching jars"); + + MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); + PatchProvider patchProvider = getExtension().getPatchProvider(); + patchJars(minecraftProvider.minecraftServerJar, minecraftServerPatchedJar, patchProvider.serverPatches); + patchJars(minecraftProvider.minecraftClientJar, minecraftClientPatchedJar, patchProvider.clientPatches); + + logger.lifecycle(":patched jars in " + stopwatch.stop()); + } + + @Override + protected void patchJars(File clean, File output, Path patches) throws Exception { + super.patchJars(clean, output, patches); + + // Patching only preserves affected classes, everything else we need to copy manually + copyMissingClasses(clean, output); + copyNonClassFiles(clean, output); + + // Workaround Forge patches apparently violating the JVM spec (see ParameterAnnotationsFixer for details) + modifyClasses(output, ParameterAnnotationsFixer::new); + } + + private void mergeJars(Logger logger) throws Exception { + logger.info(":merging jars"); + Stopwatch stopwatch = Stopwatch.createStarted(); + + try (JarMerger jarMerger = new JarMerger(minecraftClientPatchedJar, minecraftServerPatchedJar, minecraftMergedPatchedJar)) { + jarMerger.enableSyntheticParamsOffset(); + jarMerger.merge(); + } + + // The JarMerger adds Sided annotations but so do the Forge patches. The latter doesn't require extra + // dependencies beyond Forge, so we'll keep those and remove the Fabric ones. + modifyClasses(minecraftMergedPatchedJar, FabricSideStripper::new); + + logger.info(":merged jars in " + stopwatch); + } + + private void accessTransformForge(Logger logger) throws Exception { + // Load all applicable access transformers + AccessTransformSet accessTransformSet = AccessTransformSet.create(); + + byte[] forgeAt = ZipUtils.unpack(forgeJar.toPath(), "forge_at.cfg"); + AccessTransformFormats.FML.read(new InputStreamReader(new ByteArrayInputStream(forgeAt)), accessTransformSet); + + for (File projectAt : projectAts) { + AccessTransformFormats.FML.read(projectAt.toPath(), accessTransformSet); + } + + // Remap them from srg to official mappings + MappingTree mappingTree = getExtension().getMappingsProvider().getMappingsWithSrg(); + MappingSet mappingSet = new TinyMappingsReader(mappingTree, "srg", "official").read(); + accessTransformSet = AccessTransformSetMapper.remap(accessTransformSet, mappingSet); + + ByteArrayOutputStream remappedOut = new ByteArrayOutputStream(); + // TODO the extra BufferedWriter wrapper and closing can be removed once https://github.com/CadixDev/at/issues/6 is fixed + BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(remappedOut)); + AccessTransformFormats.FML.write(writer, accessTransformSet); + writer.close(); + byte[] remappedAt = remappedOut.toByteArray(); + + // And finally, apply them to the merged+patched jar + accessTransformForge(logger, minecraftMergedPatchedJar, minecraftMergedPatchedAtJar, Collections.singletonList(remappedAt)); + } + + private void modifyClasses(File jarFile, Function<ClassVisitor, ClassVisitor> func) throws Exception { + try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + jarFile.toURI()), ImmutableMap.of("create", false))) { + ThreadingUtils.TaskCompleter completer = ThreadingUtils.taskCompleter(); + + for (Path file : (Iterable<? extends Path>) Files.walk(fs.getPath("/"))::iterator) { + if (!file.toString().endsWith(".class")) continue; + + completer.add(() -> { + byte[] original = Files.readAllBytes(file); + + ClassReader reader = new ClassReader(original); + ClassWriter writer = new ClassWriter(reader, 0); + reader.accept(func.apply(writer), 0); + + byte[] modified = writer.toByteArray(); + + if (!Arrays.equals(original, modified)) { + Files.write(file, modified, StandardOpenOption.TRUNCATE_EXISTING); + } + }); + } + + completer.complete(); + } + } + + public File getMergedJar() { + return minecraftMergedPatchedAtJar; + } + + public File getForgeMergedJar() { + return forgeJar; + } + + /** + * It seems that Forge patches produce class files which are in violation of the JVM spec. Specifically, the value + * of Runtime[In]VisibleParameterAnnotation.num_parameters is by SE8 spec required to match the number of formal + * parameters of the method (and may be ignored in favor of directly looking at the method arguments, which is + * indeed what the OpenJDK 8 compiler does). Using a smaller value (possible if e.g. the last parameter has no + * annotations) will cause the compiler to read past the end of the table, throwing an exception and therefore being + * unable to read the class file. + * <br> + * This class visitor fixes that by ignoring the original num_parameters value, letting the MethodVisitor compute a + * new value based on its signature. This will at first produce an invalid count when there are synthetic parameters + * but later, during mergeJars, those will be properly offset (enableSyntheticParamsOffset). + * <br> + * SE8 JVM spec: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.18 + * Example method affected: RenderGlobal.ContainerLocalRenderInformation(RenderChunk, EnumFacing, int) + */ + private static class ParameterAnnotationsFixer extends ClassVisitor { + private ParameterAnnotationsFixer(ClassVisitor classVisitor) { + super(Opcodes.ASM9, classVisitor); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions); + + // This issue has so far only been observed with constructors, so we can skip everything else + if (name.equals("<init>")) { + methodVisitor = new MethodFixer(methodVisitor); + } + + return methodVisitor; + } + + private static class MethodFixer extends MethodVisitor { + private MethodFixer(MethodVisitor methodVisitor) { + super(Opcodes.ASM9, methodVisitor); + } + + @Override + public void visitAnnotableParameterCount(int parameterCount, boolean visible) { + // Not calling visitAnnotableParameterCount will cause it to compute its value from the method signature + } + } + } + + private static class FabricSideStripper extends ClassVisitor { + private static final String SIDED_DESCRIPTOR = "Lnet/fabricmc/api/Environment;"; + + private FabricSideStripper(ClassVisitor classVisitor) { + super(Opcodes.ASM9, classVisitor); + } + + @Override + public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { + if (descriptor.equals(SIDED_DESCRIPTOR)) { + return null; + } + + return super.visitAnnotation(descriptor, visible); + } + + @Override + public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { + return new FieldStripper(super.visitField(access, name, descriptor, signature, value)); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + return new MethodStripper(super.visitMethod(access, name, descriptor, signature, exceptions)); + } + + private static class FieldStripper extends FieldVisitor { + private FieldStripper(FieldVisitor fieldVisitor) { + super(Opcodes.ASM9, fieldVisitor); + } + + @Override + public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { + if (descriptor.equals(SIDED_DESCRIPTOR)) { + return null; + } + + return super.visitAnnotation(descriptor, visible); + } + } + + private static class MethodStripper extends MethodVisitor { + private MethodStripper(MethodVisitor methodVisitor) { + super(Opcodes.ASM9, methodVisitor); + } + + @Override + public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { + if (descriptor.equals(SIDED_DESCRIPTOR)) { + return null; + } + + return super.visitAnnotation(descriptor, visible); + } + } + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/Pack200Provider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/Pack200Provider.java new file mode 100644 index 00000000..8912c42c --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/Pack200Provider.java @@ -0,0 +1,8 @@ +package net.fabricmc.loom.configuration.providers.forge.fg2; + +import java.io.InputStream; +import java.util.jar.JarOutputStream; + +public interface Pack200Provider { + void unpack(InputStream inputStream, JarOutputStream outputStream); +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java index 401d07be..fef7849b 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java @@ -52,6 +52,7 @@ import com.google.gson.JsonObject; import org.apache.tools.ant.util.StringUtils; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; +import org.gradle.api.logging.Logger; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Opcodes; @@ -66,15 +67,18 @@ import net.fabricmc.loom.configuration.processors.MinecraftProcessedProvider; import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl; import net.fabricmc.loom.configuration.providers.forge.MinecraftPatchedProvider; import net.fabricmc.loom.configuration.providers.forge.SrgProvider; +import net.fabricmc.loom.configuration.providers.forge.fg2.MinecraftLegacyPatchedProvider; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvider; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.DeletingFileVisitor; import net.fabricmc.loom.util.DownloadUtil; +import net.fabricmc.loom.util.LoggerFilter; import net.fabricmc.loom.util.ZipUtils; import net.fabricmc.loom.util.srg.MCPReader; import net.fabricmc.loom.util.srg.SrgMerger; import net.fabricmc.loom.util.srg.SrgNamedWriter; import net.fabricmc.mappingio.MappingReader; +import net.fabricmc.mappingio.MappingWriter; import net.fabricmc.mappingio.adapter.MappingNsCompleter; import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch; import net.fabricmc.mappingio.format.MappingFormat; @@ -83,6 +87,7 @@ import net.fabricmc.mappingio.format.Tiny2Writer; import net.fabricmc.mappingio.tree.MappingTree; import net.fabricmc.mappingio.tree.MemoryMappingTree; import net.fabricmc.stitch.Command; +import net.fabricmc.stitch.commands.CommandGenerateIntermediary; import net.fabricmc.stitch.commands.CommandProposeFieldNames; import net.fabricmc.stitch.commands.tinyv2.TinyFile; import net.fabricmc.stitch.commands.tinyv2.TinyV2Writer; @@ -147,7 +152,12 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings } if (getExtension().isForge()) { - patchedProvider = new MinecraftPatchedProvider(getProject()); + if (getExtension().isModernForge()) { + patchedProvider = new MinecraftPatchedProvider(getProject()); + } else { + patchedProvider = new MinecraftLegacyPatchedProvider(getProject()); + } + patchedProvider.provide(dependency, postPopulationScheduler); } @@ -157,7 +167,7 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings if (getExtension().shouldGenerateSrgTiny()) { if (Files.notExists(tinyMappingsWithSrg) || isRefreshDeps()) { // Merge tiny mappings with srg - SrgMerger.mergeSrg(getProject().getLogger(), getExtension().getMappingsProvider()::getMojmapSrgFileIfPossible, getRawSrgFile(), tinyMappings, tinyMappingsWithSrg, true); + SrgMerger.mergeSrg(getProject().getLogger(), getExtension().getMappingsProvider()::getMojmapSrgFileIfPossible, getRawSrgFile(), tinyMappings, tinyMappingsWithSrg, true, getExtension().isLegacyForge()); } mappingTreeWithSrg = readMappings(tinyMappingsWithSrg); @@ -192,7 +202,7 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings } if (Files.notExists(srgToNamedSrg) || isRefreshDeps()) { - SrgNamedWriter.writeTo(getProject().getLogger(), srgToNamedSrg, getMappingsWithSrg(), "srg", "named"); + SrgNamedWriter.writeTo(srgToNamedSrg, getMappingsWithSrg(), "srg", "named", getExtension().isLegacyForge()); } } @@ -340,6 +350,7 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings getProject().getDependencies().add(provider.getTargetConfig(), "de.oceanlabs.mcp:mcp_config:" + getMinecraftProvider().minecraftVersion()); Configuration configuration = getProject().getConfigurations().getByName(provider.getTargetConfig()); provider.provide(DependencyInfo.create(getProject(), configuration.getDependencies().iterator().next(), configuration), postPopulationScheduler); + getExtension().getDependencyManager().addProvider(provider); } Path srgPath = getRawSrgFile(); @@ -577,17 +588,53 @@ public class MappingsProviderImpl extends DependencyProvider implements Mappings hasRefreshed = true; // Download and extract intermediary - String encodedMinecraftVersion = UrlEscapers.urlFragmentEscaper().escape(getMinecraftProvider().minecraftVersion()); - String intermediaryArtifactUrl = getExtension().getIntermediaryUrl(encodedMinecraftVersion); - File intermediaryJar = getMinecraftProvider().file("intermediary-v2.jar"); - DownloadUtil.downloadIfChanged(new URL(intermediaryArtifactUrl), intermediaryJar, getProject().getLogger()); - extractMappings(intermediaryJar.toPath(), intermediaryTiny); + if (!getExtension().isLegacyForge()) { + String encodedMinecraftVersion = UrlEscapers.urlFragmentEscaper().escape(getMinecraftProvider().minecraftVersion()); + String intermediaryArtifactUrl = getExtension().getIntermediaryUrl(encodedMinecraftVersion); + File intermediaryJar = getMinecraftProvider().file("intermediary-v2.jar"); + DownloadUtil.downloadIfChanged(new URL(intermediaryArtifactUrl), intermediaryJar, getProject().getLogger()); + extractMappings(intermediaryJar.toPath(), intermediaryTiny); + } else { + generateDummyIntermediary(getProject().getLogger(), intermediaryTiny); + } } } return intermediaryTiny; } + private void generateDummyIntermediary(Logger logger, Path tinyV2) throws IOException { + Stopwatch stopwatch = Stopwatch.createStarted(); + logger.lifecycle(":generating dummy intermediary"); + + Path minecraftJar = getExtension().getMinecraftProvider().getMergedJar().toPath(); + + // create a temporary folder into which stitch will output the v1 file + // we cannot just create a temporary file directly, cause stitch will try to read it if it exists + Path tmpFolder = Files.createTempDirectory("dummy-intermediary"); + Path tinyV1 = tmpFolder.resolve("intermediary-v1.tiny"); + + CommandGenerateIntermediary command = new CommandGenerateIntermediary(); + LoggerFilter.withSystemOutAndErrSuppressed(() -> { + try { + command.run(new String[]{ minecraftJar.toAbsolutePath().toString(), tinyV1.toAbsolutePath().toString() }); + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new IOException("Failed to generate intermediary", e); + } + }); + + try (MappingWriter writer = MappingWriter.create(tinyV2, MappingFormat.TINY_2)) { + MappingReader.read(tinyV1, writer); + } + + Files.delete(tinyV1); + Files.delete(tmpFolder); + + logger.lifecycle(":generated dummy intermediary in " + stopwatch.stop()); + } + @Override public Path mappingsWorkingDir() { return mappingsWorkingDir; diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java index ec003995..a137aa8b 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java @@ -266,7 +266,17 @@ public class MinecraftMappedProvider extends DependencyProvider { } public void remap(TinyRemapper remapper, Mutable<MemoryMappingTree> mappings, List<TinyRemapper.ApplyVisitorProvider> postApply, Info vanilla, @Nullable Info forge, String fromM) throws IOException { - Set<String> classNames = getExtension().isForge() ? InnerClassRemapper.readClassNames(vanilla.input) : null; + Set<String> classNames; + + if (getExtension().isForge()) { + classNames = InnerClassRemapper.readClassNames(vanilla.input); + + if (forge != null) { + classNames.addAll(InnerClassRemapper.readClassNames(forge.input)); + } + } else { + classNames = null; + } for (String toM : getExtension().isForge() ? Arrays.asList(MappingsNamespace.INTERMEDIARY.toString(), MappingsNamespace.SRG.toString(), MappingsNamespace.NAMED.toString()) : Arrays.asList(MappingsNamespace.INTERMEDIARY.toString(), MappingsNamespace.NAMED.toString())) { Path output = MappingsNamespace.NAMED.toString().equals(toM) ? vanilla.outputMapped : MappingsNamespace.SRG.toString().equals(toM) ? vanilla.outputSrg : vanilla.outputIntermediary; |