aboutsummaryrefslogtreecommitdiff
path: root/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/net/fabricmc/loom/LoomGradleExtension.java8
-rw-r--r--src/main/java/net/fabricmc/loom/LoomRepositoryPlugin.java14
-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/build/mixin/AnnotationProcessorInvoker.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.java53
-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.java32
-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.java14
-rw-r--r--src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftLegacyPatchedProvider.java447
-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.java55
-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/SrgMerger.java16
-rw-r--r--src/main/java/net/fabricmc/loom/util/srg/SrgNamedWriter.java36
22 files changed, 839 insertions, 48 deletions
diff --git a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java
index 85855067..b5566765 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 isModLauncher() {
+ return isForge() && !isLegacyForge();
+ }
+
boolean supportsInclude();
default SrgProvider getSrgProvider() {
diff --git a/src/main/java/net/fabricmc/loom/LoomRepositoryPlugin.java b/src/main/java/net/fabricmc/loom/LoomRepositoryPlugin.java
index 28045071..3d93bad6 100644
--- a/src/main/java/net/fabricmc/loom/LoomRepositoryPlugin.java
+++ b/src/main/java/net/fabricmc/loom/LoomRepositoryPlugin.java
@@ -90,6 +90,20 @@ public class LoomRepositoryPlugin implements Plugin<PluginAware> {
sources.ignoreGradleMetadataRedirection();
});
});
+ repositories.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);
+ });
repositories.mavenCentral();
repositories.ivy(repo -> {
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/build/mixin/AnnotationProcessorInvoker.java b/src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java
index 33fec4e6..1793ca72 100644
--- a/src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java
+++ b/src/main/java/net/fabricmc/loom/build/mixin/AnnotationProcessorInvoker.java
@@ -101,7 +101,7 @@ public abstract class AnnotationProcessorInvoker<T extends Task> {
ConfigurationContainer configs = project.getConfigurations();
LoomGradleExtension extension = LoomGradleExtension.get(project);
- if (!extension.ideSync()) {
+ if (!extension.ideSync() || extension.isLegacyForge()) {
for (Configuration processorConfig : apConfigurations) {
project.getLogger().info("Adding mixin to classpath of AP config: " + processorConfig.getName());
// Pass named MC classpath to mixin AP classpath
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..ae52c76f 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().isModLauncher()) {
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().isModLauncher()) {
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().isForge() && !getExtension().isLegacyForge()) {
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..ec98da78 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
@@ -72,6 +72,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 +100,13 @@ 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 (!Files.exists(configEntry)) {
+ configEntry = fs.getPath("dev.json");
+ }
+
+ Files.copy(configEntry, configJson, StandardCopyOption.REPLACE_EXISTING);
}
}
@@ -107,11 +114,30 @@ 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) {
+ 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);
+ } else {
+ 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);
+ }
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 +154,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 +163,10 @@ 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()) {
+
+ JsonObject runs = isLegacyForge ? new JsonObject() : json.getAsJsonObject("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();
@@ -182,6 +211,18 @@ public class ForgeUserdevProvider extends DependencyProvider {
});
}
}
+
+ if (isLegacyForge) {
+ getExtension().getRunConfigs().configureEach(config -> {
+ if (Constants.Forge.LAUNCH_TESTING.equals(config.getDefaultMainClass())) {
+ config.setDefaultMainClass(Constants.LegacyForge.LAUNCH_WRAPPER);
+ }
+ });
+ }
+ }
+
+ public boolean isLegacyForge() {
+ return isLegacyForge;
}
public abstract static class RemoveNameProvider implements TransformAction<TransformParameters.None> {
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 0c089e8f..05a216bd 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();
@@ -215,7 +219,7 @@ public class MinecraftPatchedProvider extends DependencyProvider {
cleanProjectCache();
}
- private File[] getGlobalCaches() {
+ protected File[] getGlobalCaches() {
File[] files = {
minecraftClientSrgJar,
minecraftServerSrgJar,
@@ -234,7 +238,7 @@ public class MinecraftPatchedProvider extends DependencyProvider {
}
}
- private File[] getProjectCache() {
+ protected File[] getProjectCache() {
return new File[] {
minecraftMergedPatchedSrgAtJar,
minecraftMergedPatchedJar
@@ -451,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");
@@ -594,7 +598,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");
@@ -614,7 +618,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 {
@@ -677,7 +681,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);
}
@@ -685,7 +689,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();
@@ -698,7 +702,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");
@@ -710,7 +714,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..ca55e8d7 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().isLegacyForge()
+ ? getExtension().getForgeUniversalProvider().getForge().toPath()
+ : dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve Forge installer")).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().isLegacyForge()) {
+ splitAndConvertLegacyPatches(fs.getPath("binpatches.pack.lzma"));
+ } else {
+ Files.copy(fs.getPath("data", "client.lzma"), clientPatches, StandardCopyOption.REPLACE_EXISTING);
+ Files.copy(fs.getPath("data", "server.lzma"), serverPatches, StandardCopyOption.REPLACE_EXISTING);
+ }
}
}
}
@@ -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..920d269e 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,15 @@ 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)) {
+ 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..e87004c2
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftLegacyPatchedProvider.java
@@ -0,0 +1,447 @@
+/*
+ * 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.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+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.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 net.md_5.specialsource.AccessChange;
+import net.md_5.specialsource.AccessMap;
+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.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 {
+ Stopwatch stopwatch = Stopwatch.createStarted();
+
+ logger.lifecycle(":access transforming minecraft");
+
+ MappingTree mappingTree = getExtension().getMappingsProvider().getMappingsWithSrg();
+
+ AccessMap accessMap = new LegacyAccessMap();
+
+ byte[] forgeAt = ZipUtils.unpack(forgeJar.toPath(), "forge_at.cfg");
+ accessMap.loadAccessTransformer(new BufferedReader(new InputStreamReader(new ByteArrayInputStream(forgeAt))));
+
+ for (File projectAt : projectAts) {
+ accessMap.loadAccessTransformer(projectAt);
+ }
+
+ Files.copy(minecraftMergedPatchedJar.toPath(), minecraftMergedPatchedAtJar.toPath(), StandardCopyOption.REPLACE_EXISTING);
+
+ modifyClasses(minecraftMergedPatchedAtJar, writer -> new AccessTransformingVisitor(accessMap, mappingTree, writer));
+
+ logger.lifecycle(":access transformed minecraft in " + stopwatch.stop());
+ }
+
+ 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);
+ }
+ }
+ }
+
+ private static class LegacyAccessMap extends AccessMap {
+ @Override
+ public void addAccessChange(String key, AccessChange accessChange) {
+ // Forge's AT separates fields/methods from their owner by a space but we require a slash
+ int spaceIdx = key.indexOf(' ');
+
+ if (spaceIdx != -1 && key.charAt(spaceIdx + 1) != '(') {
+ key = key.replaceFirst(" ", "/");
+ }
+
+ super.addAccessChange(key, accessChange);
+ }
+ }
+
+ private static class AccessTransformingVisitor extends ClassVisitor {
+ private final AccessMap accessMap;
+ private final MappingTree mappingTree;
+ private final int src;
+ private final int dst;
+
+ private MappingTree.ClassMapping classMapping;
+ private String mappedClassName;
+
+ private AccessTransformingVisitor(AccessMap accessMap, MappingTree mappingTree, ClassVisitor classVisitor) {
+ super(Opcodes.ASM9, classVisitor);
+ this.accessMap = accessMap;
+ this.mappingTree = mappingTree;
+ this.src = mappingTree.getNamespaceId("official");
+ this.dst = mappingTree.getNamespaceId("srg");
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
+ classMapping = mappingTree.getClass(name, src);
+ mappedClassName = classMapping != null ? classMapping.getName(dst) : name;
+ access = accessMap.applyClassAccess(mappedClassName, access);
+ super.visit(version, access, name, signature, superName, interfaces);
+ }
+
+ @Override
+ public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
+ MappingTree.FieldMapping field = classMapping != null ? classMapping.getField(name, descriptor, src) : null;
+ String mappedName = field != null ? field.getName(dst) : name;
+ access = accessMap.applyFieldAccess(mappedClassName, mappedName, access);
+ return super.visitField(access, name, descriptor, signature, value);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+ MappingTree.MethodMapping method = classMapping != null ? classMapping.getMethod(name, desc, src) : null;
+ String mappedName = method != null ? method.getName(dst) : name;
+ String mappedDesc = method != null ? method.getDesc(dst) : mappingTree.mapDesc(desc, src, dst);
+ access = accessMap.applyMethodAccess(mappedClassName, mappedName, mappedDesc, access);
+ return super.visitMethod(access, name, desc, signature, exceptions);
+ }
+
+ @Override
+ public void visitInnerClass(String name, String outerName, String innerName, int access) {
+ MappingTree.ClassMapping classMapping = mappingTree.getClass(name, src);
+ access = accessMap.applyClassAccess(classMapping != null ? classMapping.getName(dst) : name, access);
+ super.visitInnerClass(name, outerName, innerName, access);
+ }
+ }
+}
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 0f4614ce..344071f0 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().isLegacyForge()) {
+ patchedProvider = new MinecraftLegacyPatchedProvider(getProject());
+ } else {
+ patchedProvider = new MinecraftPatchedProvider(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);
@@ -578,17 +588,48 @@ 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()) {
+ Path intermediaryV1Tiny = getMinecraftProvider().file("intermediary-v1.tiny").toPath();
+ generateDummyIntermediary(getProject().getLogger(), intermediaryV1Tiny, intermediaryTiny);
+ } else {
+ 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);
+ }
}
}
return intermediaryTiny;
}
+ private void generateDummyIntermediary(Logger logger, Path tinyV1, Path tinyV2) throws IOException {
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ logger.lifecycle(":generating dummy intermediary");
+
+ Path minecraftJar = getExtension().getMinecraftProvider().getMergedJar().toPath();
+
+ Files.deleteIfExists(tinyV1); // file must not exist, otherwise stitch will try to read it
+
+ 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);
+ }
+
+ 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 8a68b0fb..2ce96c46 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/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..f999f936 100644
--- a/src/main/java/net/fabricmc/loom/util/srg/SrgNamedWriter.java
+++ b/src/main/java/net/fabricmc/loom/util/srg/SrgNamedWriter.java
@@ -28,7 +28,14 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
+import org.cadixdev.lorenz.MappingSet;
+import org.cadixdev.lorenz.impl.MappingSetModelFactoryImpl;
+import org.cadixdev.lorenz.impl.model.InnerClassMappingImpl;
+import org.cadixdev.lorenz.impl.model.TopLevelClassMappingImpl;
import org.cadixdev.lorenz.io.srg.SrgWriter;
+import org.cadixdev.lorenz.model.ClassMapping;
+import org.cadixdev.lorenz.model.InnerClassMapping;
+import org.cadixdev.lorenz.model.TopLevelClassMapping;
import org.gradle.api.logging.Logger;
import net.fabricmc.lorenztiny.TinyMappingsReader;
@@ -40,8 +47,35 @@ public class SrgNamedWriter {
try (SrgWriter writer = new SrgWriter(Files.newBufferedWriter(srgFile))) {
try (TinyMappingsReader reader = new TinyMappingsReader(mappings, from, to)) {
- writer.write(reader.read());
+ writer.write(reader.read(MappingSet.create(new ClassesAlwaysHaveDeobfNameFactory())));
}
}
}
+
+ /**
+ * 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, we create a custom mapping set which always
+ * claims to have deobfuscated names set for classes.
+ */
+ private static class ClassesAlwaysHaveDeobfNameFactory extends MappingSetModelFactoryImpl {
+ @Override
+ public TopLevelClassMapping createTopLevelClassMapping(MappingSet parent, String obfuscatedName, String deobfuscatedName) {
+ return new TopLevelClassMappingImpl(parent, obfuscatedName, deobfuscatedName) {
+ @Override
+ public boolean hasDeobfuscatedName() {
+ return true;
+ }
+ };
+ }
+
+ @Override
+ public InnerClassMapping createInnerClassMapping(ClassMapping parent, String obfuscatedName, String deobfuscatedName) {
+ return new InnerClassMappingImpl(parent, obfuscatedName, deobfuscatedName) {
+ @Override
+ public boolean hasDeobfuscatedName() {
+ return true;
+ }
+ };
+ }
+ }
}