aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonas Herzig <jonas@spark-squared.com>2022-02-17 10:55:10 +0100
committerJonas Herzig <jonas@spark-squared.com>2022-02-17 10:55:10 +0100
commitae224ac2a7febc9aa22c5caffad3f747212e1fb5 (patch)
treed0480f5db906aa2ce0ab4b32b20de68e09e134b7
parent234f9ccbfd2e67ba9cbbcc67b28dcfb5f6c4ffef (diff)
parent5f33bfd5edfdeb29e9f4dd90a6c768825b91e56a (diff)
downloadarchitectury-loom-ae224ac2a7febc9aa22c5caffad3f747212e1fb5.tar.gz
architectury-loom-ae224ac2a7febc9aa22c5caffad3f747212e1fb5.tar.bz2
architectury-loom-ae224ac2a7febc9aa22c5caffad3f747212e1fb5.zip
Merge branch 'dev/0.10.0-fg2' into dev/0.10.0
-rw-r--r--build.gradle1
-rw-r--r--src/main/java/net/fabricmc/loom/LoomGradleExtension.java8
-rw-r--r--src/main/java/net/fabricmc/loom/api/ForgeExtensionAPI.java4
-rw-r--r--src/main/java/net/fabricmc/loom/build/ModCompileRemapper.java2
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java2
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/LaunchProvider.java28
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java9
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java79
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java5
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java88
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java98
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java15
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftLegacyPatchedProvider.java393
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/Pack200Provider.java8
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java63
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java12
-rw-r--r--src/main/java/net/fabricmc/loom/extension/ForgeExtensionImpl.java8
-rw-r--r--src/main/java/net/fabricmc/loom/util/Constants.java6
-rw-r--r--src/main/java/net/fabricmc/loom/util/LoggerFilter.java28
-rw-r--r--src/main/java/net/fabricmc/loom/util/srg/AccessTransformSetMapper.java70
-rw-r--r--src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java16
-rw-r--r--src/main/java/net/fabricmc/loom/util/srg/SrgNamedWriter.java32
-rw-r--r--src/test/groovy/net/fabricmc/loom/test/integration/forge/ForgeTest.groovy2
-rw-r--r--src/test/resources/projects/forge/simple/build.gradle7
24 files changed, 907 insertions, 77 deletions
diff --git a/build.gradle b/build.gradle
index df034187..3406ac51 100644
--- a/build.gradle
+++ b/build.gradle
@@ -126,6 +126,7 @@ dependencies {
}
testImplementation 'io.javalin:javalin:3.13.11'
testImplementation 'net.fabricmc:fabric-installer:0.9.0'
+ runtimeOnly 'dev.architectury.architectury-pack200:dev.architectury.architectury-pack200.gradle.plugin:0.1.3'
compileOnly 'org.jetbrains:annotations:22.0.0'
}
diff --git a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java
index 85855067..06208777 100644
--- a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java
+++ b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java
@@ -139,6 +139,14 @@ public interface LoomGradleExtension extends LoomGradleExtensionAPI {
return isForge() && !getMcpConfigProvider().isOfficial();
}
+ default boolean isLegacyForge() {
+ return isForge() && getForgeUserdevProvider().isLegacyForge();
+ }
+
+ default boolean isModernForge() {
+ return isForge() && !isLegacyForge();
+ }
+
boolean supportsInclude();
default SrgProvider getSrgProvider() {
diff --git a/src/main/java/net/fabricmc/loom/api/ForgeExtensionAPI.java b/src/main/java/net/fabricmc/loom/api/ForgeExtensionAPI.java
index e455d742..6b77f842 100644
--- a/src/main/java/net/fabricmc/loom/api/ForgeExtensionAPI.java
+++ b/src/main/java/net/fabricmc/loom/api/ForgeExtensionAPI.java
@@ -33,6 +33,8 @@ import org.gradle.api.provider.Property;
import org.gradle.api.provider.SetProperty;
import org.jetbrains.annotations.ApiStatus;
+import net.fabricmc.loom.configuration.providers.forge.fg2.Pack200Provider;
+
/**
* This is the forge extension api available exposed to build scripts.
*/
@@ -148,4 +150,6 @@ public interface ForgeExtensionAPI {
* @see ForgeLocalMod
*/
NamedDomainObjectContainer<ForgeLocalMod> getLocalMods();
+
+ Property<Pack200Provider> getPack200Provider();
}
diff --git a/src/main/java/net/fabricmc/loom/build/ModCompileRemapper.java b/src/main/java/net/fabricmc/loom/build/ModCompileRemapper.java
index 76475caa..6fe72b18 100644
--- a/src/main/java/net/fabricmc/loom/build/ModCompileRemapper.java
+++ b/src/main/java/net/fabricmc/loom/build/ModCompileRemapper.java
@@ -181,7 +181,7 @@ public class ModCompileRemapper {
}
if (forge) {
- if (zipFile.getEntry("META-INF/mods.toml") != null) {
+ if (zipFile.getEntry("META-INF/mods.toml") != null || zipFile.getEntry("mcmod.info") != null) {
logger.info("Found Forge mod in " + config + ": {}", id);
return true;
}
diff --git a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java
index eaac38ad..50febf4e 100644
--- a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java
+++ b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java
@@ -198,9 +198,9 @@ public final class CompileConfiguration {
}
if (extension.isForge()) {
+ dependencyManager.addProvider(new ForgeUniversalProvider(project));
dependencyManager.addProvider(new McpConfigProvider(project));
dependencyManager.addProvider(new PatchProvider(project));
- dependencyManager.addProvider(new ForgeUniversalProvider(project));
}
dependencyManager.addProvider(extension.isForge() ? new FieldMigratedMappingsProvider(project) : new MappingsProviderImpl(project));
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;
diff --git a/src/main/java/net/fabricmc/loom/extension/ForgeExtensionImpl.java b/src/main/java/net/fabricmc/loom/extension/ForgeExtensionImpl.java
index 25da6717..5a4956d9 100644
--- a/src/main/java/net/fabricmc/loom/extension/ForgeExtensionImpl.java
+++ b/src/main/java/net/fabricmc/loom/extension/ForgeExtensionImpl.java
@@ -42,6 +42,7 @@ import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.ForgeExtensionAPI;
import net.fabricmc.loom.api.ForgeLocalMod;
import net.fabricmc.loom.configuration.ide.RunConfigSettings;
+import net.fabricmc.loom.configuration.providers.forge.fg2.Pack200Provider;
public class ForgeExtensionImpl implements ForgeExtensionAPI {
private final LoomGradleExtension extension;
@@ -52,6 +53,7 @@ public class ForgeExtensionImpl implements ForgeExtensionAPI {
private final Property<Boolean> useCustomMixin;
private final List<String> dataGenMods = new ArrayList<>(); // not a property because it has custom adding logic
private final NamedDomainObjectContainer<ForgeLocalMod> localMods;
+ private final Property<Pack200Provider> pack200Provider;
@Inject
public ForgeExtensionImpl(Project project, LoomGradleExtension extension) {
@@ -63,6 +65,7 @@ public class ForgeExtensionImpl implements ForgeExtensionAPI {
useCustomMixin = project.getObjects().property(Boolean.class).convention(true);
localMods = project.container(ForgeLocalMod.class,
baseName -> new ForgeLocalMod(project, baseName, new ArrayList<>()));
+ pack200Provider = project.getObjects().property(Pack200Provider.class);
// Create default mod from main source set
localMods(mod -> mod.create("main").add("main"));
@@ -133,4 +136,9 @@ public class ForgeExtensionImpl implements ForgeExtensionAPI {
public NamedDomainObjectContainer<ForgeLocalMod> getLocalMods() {
return localMods;
}
+
+ @Override
+ public Property<Pack200Provider> getPack200Provider() {
+ return pack200Provider;
+ }
}
diff --git a/src/main/java/net/fabricmc/loom/util/Constants.java b/src/main/java/net/fabricmc/loom/util/Constants.java
index 0a2d3028..75242026 100644
--- a/src/main/java/net/fabricmc/loom/util/Constants.java
+++ b/src/main/java/net/fabricmc/loom/util/Constants.java
@@ -177,4 +177,10 @@ public class Constants {
private Forge() {
}
}
+
+ public static final class LegacyForge {
+ public static final String LAUNCH_WRAPPER = "net.minecraft.launchwrapper.Launch";
+ public static final String FML_TWEAKER = "net.minecraftforge.fml.common.launcher.FMLTweaker";
+ public static final String FML_SERVER_TWEAKER = "net.minecraftforge.fml.common.launcher.FMLServerTweaker";
+ }
}
diff --git a/src/main/java/net/fabricmc/loom/util/LoggerFilter.java b/src/main/java/net/fabricmc/loom/util/LoggerFilter.java
index 1f9f9b96..712172dc 100644
--- a/src/main/java/net/fabricmc/loom/util/LoggerFilter.java
+++ b/src/main/java/net/fabricmc/loom/util/LoggerFilter.java
@@ -26,6 +26,7 @@ package net.fabricmc.loom.util;
import java.io.PrintStream;
+import org.apache.commons.io.output.NullOutputStream;
import org.jetbrains.annotations.NotNull;
public class LoggerFilter {
@@ -46,4 +47,31 @@ public class LoggerFilter {
// Failed to replace logger filter, just ignore
}
}
+
+ public static <T extends Throwable> void withSystemOutAndErrSuppressed(CheckedRunnable<T> block) throws T {
+ PrintStream previousOut = System.out;
+ PrintStream previousErr = System.err;
+
+ try {
+ System.setOut(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM));
+ System.setErr(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM));
+ } catch (SecurityException ignored) {
+ // Failed to replace logger, just ignore
+ }
+
+ try {
+ block.run();
+ } finally {
+ try {
+ System.setOut(previousOut);
+ System.setErr(previousErr);
+ } catch (SecurityException ignored) {
+ // Failed to replace logger, just ignore
+ }
+ }
+ }
+
+ public interface CheckedRunnable<T extends Throwable> {
+ void run() throws T;
+ }
}
diff --git a/src/main/java/net/fabricmc/loom/util/srg/AccessTransformSetMapper.java b/src/main/java/net/fabricmc/loom/util/srg/AccessTransformSetMapper.java
new file mode 100644
index 00000000..d17eabfb
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/srg/AccessTransformSetMapper.java
@@ -0,0 +1,70 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018 Minecrell (https://github.com/Minecrell)
+ *
+ * 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.util.srg;
+
+import java.util.Objects;
+import java.util.Optional;
+
+import org.cadixdev.at.AccessTransformSet;
+import org.cadixdev.bombe.type.signature.MethodSignature;
+import org.cadixdev.lorenz.MappingSet;
+import org.cadixdev.lorenz.model.ClassMapping;
+import org.cadixdev.lorenz.model.FieldMapping;
+import org.cadixdev.lorenz.model.Mapping;
+import org.cadixdev.lorenz.model.MethodMapping;
+
+// TODO from https://github.com/CadixDev/at/blob/c2b92fc26cf26e64d3ca3b35abf3364d4b95e6c3/src/main/java/org/cadixdev/at/impl/AccessTransformSetMapper.java
+// remove once https://github.com/CadixDev/at/issues/7 is fixed
+public final class AccessTransformSetMapper {
+ private AccessTransformSetMapper() {
+ }
+
+ public static AccessTransformSet remap(AccessTransformSet set, MappingSet mappings) {
+ Objects.requireNonNull(set, "set");
+ Objects.requireNonNull(mappings, "mappings");
+
+ AccessTransformSet remapped = AccessTransformSet.create();
+ set.getClasses().forEach((className, classSet) -> {
+ Optional<? extends ClassMapping<?, ?>> mapping = mappings.getClassMapping(className);
+ remap(mappings, mapping, classSet, remapped.getOrCreateClass(mapping.map(Mapping::getFullDeobfuscatedName).orElse(className)));
+ });
+ return remapped;
+ }
+
+ private static void remap(MappingSet mappings, Optional<? extends ClassMapping<?, ?>> mapping, AccessTransformSet.Class set, AccessTransformSet.Class remapped) {
+ remapped.merge(set.get());
+ remapped.mergeAllFields(set.allFields());
+ remapped.mergeAllMethods(set.allMethods());
+
+ set.getFields().forEach((name, transform) ->
+ remapped.mergeField(mapping.flatMap(m -> m.getFieldMapping(name))
+ .map(FieldMapping::getDeobfuscatedName).orElse(name), transform));
+
+ set.getMethods().forEach((signature, transform) ->
+ remapped.mergeMethod(mapping.flatMap(m -> m.getMethodMapping(signature))
+ .map(MethodMapping::getDeobfuscatedSignature)
+ .orElseGet(() -> new MethodSignature(signature.getName(), mappings.deobfuscate(signature.getDescriptor()))), transform));
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java b/src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java
index ede2dbad..93125b3d 100644
--- a/src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java
+++ b/src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java
@@ -72,15 +72,17 @@ public final class SrgMerger {
private final FlatMappingVisitor flatOutput;
private final List<Runnable> postProcesses = new ArrayList<>();
private final boolean lenient;
+ private final boolean legacy;
private final Set<String> methodSrgNames = new HashSet<>();
- public SrgMerger(Logger logger, Path srg, @Nullable Supplier<Path> mojmap, Path tiny, boolean lenient) throws IOException {
+ public SrgMerger(Logger logger, Path srg, @Nullable Supplier<Path> mojmap, Path tiny, boolean lenient, boolean legacy) throws IOException {
this.logger = logger;
this.srg = readSrg(srg, mojmap);
this.src = new MemoryMappingTree();
this.output = new MemoryMappingTree();
this.flatOutput = new RegularAsFlatMappingVisitor(output);
this.lenient = lenient;
+ this.legacy = legacy;
MappingReader.read(tiny, this.src);
@@ -115,14 +117,15 @@ public final class SrgMerger {
* @param tiny the tiny file
* @param out the output file, will be in tiny v2
* @param lenient whether to ignore missing tiny mapping
+ * @param legacy treat any method as mapped, even when it is lacking the 'func_' prefix
* @throws IOException if an IO error occurs while reading or writing the mappings
* @throws MappingException if the input tiny tree's default namespace is not 'official'
* or if an element mentioned in the SRG file does not have tiny mappings
*/
- public static void mergeSrg(Logger logger, @Nullable Supplier<Path> mojmap, Path srg, Path tiny, Path out, boolean lenient)
+ public static void mergeSrg(Logger logger, @Nullable Supplier<Path> mojmap, Path srg, Path tiny, Path out, boolean lenient, boolean legacy)
throws IOException, MappingException {
Stopwatch stopwatch = Stopwatch.createStarted();
- MemoryMappingTree tree = mergeSrg(logger, mojmap, srg, tiny, lenient);
+ MemoryMappingTree tree = mergeSrg(logger, mojmap, srg, tiny, lenient, legacy);
try (Tiny2Writer writer = new Tiny2Writer(Files.newBufferedWriter(out), false)) {
tree.accept(writer);
@@ -140,14 +143,15 @@ public final class SrgMerger {
* @param mojmap the path to the mojmap file used for generating mojmap+srg names, may be null
* @param tiny the tiny file
* @param lenient whether to ignore missing tiny mapping
+ * @param legacy treat any method as mapped, even when it is lacking the 'func_' prefix
* @return the created mapping tree
* @throws IOException if an IO error occurs while reading or writing the mappings
* @throws MappingException if the input tiny tree's default namespace is not 'official'
* or if an element mentioned in the SRG file does not have tiny mappings
*/
- public static MemoryMappingTree mergeSrg(Logger logger, @Nullable Supplier<Path> mojmap, Path srg, Path tiny, boolean lenient)
+ public static MemoryMappingTree mergeSrg(Logger logger, @Nullable Supplier<Path> mojmap, Path srg, Path tiny, boolean lenient, boolean legacy)
throws IOException, MappingException {
- return new SrgMerger(logger, srg, mojmap, tiny, lenient).merge();
+ return new SrgMerger(logger, srg, mojmap, tiny, lenient, legacy).merge();
}
private MemoryMappingTree readSrg(Path srg, @Nullable Supplier<Path> mojmap) throws IOException {
@@ -256,7 +260,7 @@ public final class SrgMerger {
methodToTiny(obf, method, methodSrgName, def);
- if (methodSrgName.startsWith("func_") || methodSrgName.startsWith("m_")) {
+ if (methodSrgName.startsWith("func_") || methodSrgName.startsWith("m_") || legacy) {
methodSrgNames.add(methodSrgName);
}
}
diff --git a/src/main/java/net/fabricmc/loom/util/srg/SrgNamedWriter.java b/src/main/java/net/fabricmc/loom/util/srg/SrgNamedWriter.java
index 456b8b1b..cf795448 100644
--- a/src/main/java/net/fabricmc/loom/util/srg/SrgNamedWriter.java
+++ b/src/main/java/net/fabricmc/loom/util/srg/SrgNamedWriter.java
@@ -25,23 +25,49 @@
package net.fabricmc.loom.util.srg;
import java.io.IOException;
+import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
+import org.cadixdev.lorenz.MappingSet;
import org.cadixdev.lorenz.io.srg.SrgWriter;
-import org.gradle.api.logging.Logger;
import net.fabricmc.lorenztiny.TinyMappingsReader;
import net.fabricmc.mappingio.tree.MappingTree;
public class SrgNamedWriter {
- public static void writeTo(Logger logger, Path srgFile, MappingTree mappings, String from, String to) throws IOException {
+ public static void writeTo(Path srgFile, MappingTree mappings, String from, String to, boolean includeIdentityMappings) throws IOException {
Files.deleteIfExists(srgFile);
- try (SrgWriter writer = new SrgWriter(Files.newBufferedWriter(srgFile))) {
+ try (SrgWriter writer = newSrgWriter(Files.newBufferedWriter(srgFile), includeIdentityMappings)) {
try (TinyMappingsReader reader = new TinyMappingsReader(mappings, from, to)) {
writer.write(reader.read());
}
}
}
+
+ private static SrgWriter newSrgWriter(Writer writer, boolean includeIdentityMappings) {
+ return includeIdentityMappings ? new SrgWithIdentitiesWriter(writer) : new SrgWriter(writer);
+ }
+
+ /**
+ * Legacy Forge's FMLDeobfuscatingRemapper requires class mappings, even if they are identity maps, but such
+ * mappings are filtered out by the SrgWriter. To get around that, this SrgWriter manually emits identity mappings
+ * before emitting all regular mappings.
+ */
+ private static class SrgWithIdentitiesWriter extends SrgWriter {
+ private SrgWithIdentitiesWriter(Writer writer) {
+ super(writer);
+ }
+
+ @Override
+ public void write(MappingSet mappings) {
+ mappings.getTopLevelClassMappings().stream()
+ .filter(cls -> !cls.hasDeobfuscatedName())
+ .sorted(getConfig().getClassMappingComparator())
+ .forEach(cls -> writer.format("CL: %s %s%n", cls.getFullObfuscatedName(), cls.getFullDeobfuscatedName()));
+
+ super.write(mappings);
+ }
+ }
}
diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/forge/ForgeTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/forge/ForgeTest.groovy
index 28dfde1b..1b4ca4b4 100644
--- a/src/test/groovy/net/fabricmc/loom/test/integration/forge/ForgeTest.groovy
+++ b/src/test/groovy/net/fabricmc/loom/test/integration/forge/ForgeTest.groovy
@@ -54,5 +54,7 @@ class ForgeTest extends Specification implements GradleProjectTestTrait {
'1.16.5' | "36.2.4" | "\"net.fabricmc:yarn:1.16.5+build.5:v2\""
'1.14.4' | "28.2.23" | "loom.officialMojangMappings()"
'1.14.4' | "28.2.23" | "\"net.fabricmc:yarn:1.14.4+build.18:v2\""
+ '1.12.2' | "14.23.0.2486" | "\"de.oceanlabs.mcp:mcp_snapshot:20170615-1.12\""
+ '1.8.9' | "11.15.1.2318-1.8.9" | "\"de.oceanlabs.mcp:mcp_stable:22-1.8.9\""
}
}
diff --git a/src/test/resources/projects/forge/simple/build.gradle b/src/test/resources/projects/forge/simple/build.gradle
index f4824155..aa7501c7 100644
--- a/src/test/resources/projects/forge/simple/build.gradle
+++ b/src/test/resources/projects/forge/simple/build.gradle
@@ -57,6 +57,13 @@ jar {
}
}
+loom {
+ forge {
+ // FIXME should eventually be handled by architectury-pack200 gradle plugin?
+ pack200Provider = new dev.architectury.pack200.java.Pack200Adapter()
+ }
+}
+
// configure the maven publication
publishing {
publications {