aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/net/fabricmc/loom/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/net/fabricmc/loom/util')
-rw-r--r--src/main/java/net/fabricmc/loom/util/Checksum.java11
-rw-r--r--src/main/java/net/fabricmc/loom/util/Constants.java42
-rw-r--r--src/main/java/net/fabricmc/loom/util/DependencyDownloader.java97
-rw-r--r--src/main/java/net/fabricmc/loom/util/DownloadUtil.java12
-rw-r--r--src/main/java/net/fabricmc/loom/util/FileSystemUtil.java2
-rw-r--r--src/main/java/net/fabricmc/loom/util/HashedDownloadUtil.java42
-rw-r--r--src/main/java/net/fabricmc/loom/util/LfWriter.java43
-rw-r--r--src/main/java/net/fabricmc/loom/util/LoggerFilter.java49
-rw-r--r--src/main/java/net/fabricmc/loom/util/MappingsProviderVerbose.java115
-rw-r--r--src/main/java/net/fabricmc/loom/util/ModPlatform.java50
-rw-r--r--src/main/java/net/fabricmc/loom/util/PropertyUtil.java45
-rw-r--r--src/main/java/net/fabricmc/loom/util/SourceRemapper.java53
-rw-r--r--src/main/java/net/fabricmc/loom/util/ThreadingUtils.java192
-rw-r--r--src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java77
-rw-r--r--src/main/java/net/fabricmc/loom/util/aw2at/Aw2At.java118
-rw-r--r--src/main/java/net/fabricmc/loom/util/function/CollectionUtil.java75
-rw-r--r--src/main/java/net/fabricmc/loom/util/function/FsPathConsumer.java39
-rw-r--r--src/main/java/net/fabricmc/loom/util/function/IoConsumer.java38
-rw-r--r--src/main/java/net/fabricmc/loom/util/function/LazyBool.java52
-rw-r--r--src/main/java/net/fabricmc/loom/util/srg/AtRemapper.java140
-rw-r--r--src/main/java/net/fabricmc/loom/util/srg/CoreModClassRemapper.java115
-rw-r--r--src/main/java/net/fabricmc/loom/util/srg/InnerClassRemapper.java101
-rw-r--r--src/main/java/net/fabricmc/loom/util/srg/MCPReader.java363
-rw-r--r--src/main/java/net/fabricmc/loom/util/srg/MappingException.java36
-rw-r--r--src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java148
-rw-r--r--src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java406
-rw-r--r--src/main/java/net/fabricmc/loom/util/srg/SrgNamedWriter.java47
-rw-r--r--src/main/java/net/fabricmc/loom/util/srg/Tsrg2Utils.java144
-rw-r--r--src/main/java/net/fabricmc/loom/util/srg/Tsrg2Writer.java114
29 files changed, 2720 insertions, 46 deletions
diff --git a/src/main/java/net/fabricmc/loom/util/Checksum.java b/src/main/java/net/fabricmc/loom/util/Checksum.java
index e3607d4c..ba9cd2e6 100644
--- a/src/main/java/net/fabricmc/loom/util/Checksum.java
+++ b/src/main/java/net/fabricmc/loom/util/Checksum.java
@@ -27,6 +27,7 @@ package net.fabricmc.loom.util;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
+import java.nio.charset.StandardCharsets;
import com.google.common.hash.HashCode;
import com.google.common.hash.Hashing;
@@ -44,8 +45,9 @@ public class Checksum {
try {
HashCode hash = Files.asByteSource(file).hash(Hashing.sha1());
- log.debug("Checksum check: '" + hash.toString() + "' == '" + checksum + "'?");
- return hash.toString().equals(checksum);
+ String hashString = hash.toString();
+ log.debug("Checksum check: '" + hashString + "' == '" + checksum + "'?");
+ return hashString.equals(checksum);
} catch (IOException e) {
e.printStackTrace();
}
@@ -70,4 +72,9 @@ public class Checksum {
throw new UncheckedIOException("Failed to get file hash of " + file, e);
}
}
+
+ public static byte[] sha256(String string) {
+ HashCode hash = Hashing.sha256().hashString(string, StandardCharsets.UTF_8);
+ return hash.asBytes();
+ }
}
diff --git a/src/main/java/net/fabricmc/loom/util/Constants.java b/src/main/java/net/fabricmc/loom/util/Constants.java
index dae98a87..8a68b0fb 100644
--- a/src/main/java/net/fabricmc/loom/util/Constants.java
+++ b/src/main/java/net/fabricmc/loom/util/Constants.java
@@ -34,6 +34,7 @@ import net.fabricmc.loom.configuration.RemappedConfigurationEntry;
import net.fabricmc.loom.configuration.RemappedConfigurationEntry.PublishingMode;
public class Constants {
+ public static final String PLUGIN_ID = "dev.architectury.loom";
public static final String LIBRARIES_BASE = "https://libraries.minecraft.net/";
public static final String RESOURCES_BASE = "https://resources.download.minecraft.net/";
public static final String VERSION_MANIFESTS = "https://launchermeta.mojang.com/mc/game/version_manifest_v2.json";
@@ -72,6 +73,30 @@ public class Constants {
public static final String MAPPINGS_FINAL = "mappingsFinal";
public static final String LOADER_DEPENDENCIES = "loaderLibraries";
public static final String LOOM_DEVELOPMENT_DEPENDENCIES = "loomDevelopmentDependencies";
+ public static final String SRG = "srg";
+ public static final String MCP_CONFIG = "mcp";
+ public static final String FORGE = "forge";
+ public static final String FORGE_USERDEV = "forgeUserdev";
+ public static final String FORGE_INSTALLER = "forgeInstaller";
+ public static final String FORGE_UNIVERSAL = "forgeUniversal";
+ /**
+ * Forge's own dependencies. Not intended to be used by users,
+ * {@link #FORGE_RUNTIME_LIBRARY forgeRuntimeLibrary} is for that instead.
+ */
+ public static final String FORGE_DEPENDENCIES = "forgeDependencies";
+ public static final String FORGE_NAMED = "forgeNamed";
+ /**
+ * "Extra" runtime dependencies on Forge. Contains the Minecraft resources
+ * and {@linkplain Dependencies#FORGE_RUNTIME the Architectury Loom runtime}.
+ */
+ public static final String FORGE_EXTRA = "forgeExtra";
+ /**
+ * The configuration used to create the Forge runtime classpath file list.
+ * Users can also directly add files to this config.
+ *
+ * @see net.fabricmc.loom.configuration.providers.forge.ForgeUserdevProvider
+ */
+ public static final String FORGE_RUNTIME_LIBRARY = "forgeRuntimeLibrary";
public static final String MAPPING_CONSTANTS = "mappingsConstants";
public static final String UNPICK_CLASSPATH = "unpick";
/**
@@ -94,6 +119,9 @@ public class Constants {
public static final String DEV_LAUNCH_INJECTOR = "net.fabricmc:dev-launch-injector:";
public static final String TERMINAL_CONSOLE_APPENDER = "net.minecrell:terminalconsoleappender:";
public static final String JETBRAINS_ANNOTATIONS = "org.jetbrains:annotations:";
+ public static final String JAVAX_ANNOTATIONS = "com.google.code.findbugs:jsr305:"; // I hate that I have to add these.
+ public static final String FORGE_RUNTIME = "dev.architectury:architectury-loom-runtime:";
+ public static final String ACCESS_TRANSFORMERS = "net.minecraftforge:accesstransformers:";
private Dependencies() {
}
@@ -106,6 +134,10 @@ public class Constants {
public static final String DEV_LAUNCH_INJECTOR = "0.2.1+build.8";
public static final String TERMINAL_CONSOLE_APPENDER = "1.2.0";
public static final String JETBRAINS_ANNOTATIONS = "22.0.0";
+ public static final String JAVAX_ANNOTATIONS = "3.0.2";
+ public static final String FORGE_RUNTIME = "1.1.3";
+ public static final String ACCESS_TRANSFORMERS = "3.0.1";
+ public static final String ACCESS_TRANSFORMERS_NEW = "8.0.5";
private Versions() {
}
@@ -131,10 +163,18 @@ public class Constants {
}
public static final class TaskGroup {
- public static final String FABRIC = "fabric";
+ public static final String FABRIC = "loom";
public static final String IDE = "ide";
private TaskGroup() {
}
}
+
+ public static final class Forge {
+ public static final String LAUNCH_TESTING = "net.minecraftforge.userdev.LaunchTesting";
+ public static final String ACCESS_TRANSFORMER_PATH = "META-INF/accesstransformer.cfg";
+
+ private Forge() {
+ }
+ }
}
diff --git a/src/main/java/net/fabricmc/loom/util/DependencyDownloader.java b/src/main/java/net/fabricmc/loom/util/DependencyDownloader.java
new file mode 100644
index 00000000..329d88bf
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/DependencyDownloader.java
@@ -0,0 +1,97 @@
+/*
+ * 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.util;
+
+import java.io.File;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.file.FileCollection;
+
+/**
+ * Simplified dependency downloading.
+ *
+ * @author Juuz
+ */
+public final class DependencyDownloader {
+ /**
+ * Resolves a dependency as well as its transitive dependencies into a {@link FileCollection}.
+ *
+ * @param project the project needing these files
+ * @param dependencyNotation the dependency notation
+ * @return the resolved files
+ */
+ public static FileCollection download(Project project, String dependencyNotation) {
+ return download(project, dependencyNotation, true, false);
+ }
+
+ public static FileCollection download(Project project, String dependencyNotation, boolean transitive, boolean resolve) {
+ Dependency dependency = project.getDependencies().create(dependencyNotation);
+
+ if (dependency instanceof ModuleDependency) {
+ ((ModuleDependency) dependency).setTransitive(transitive);
+ }
+
+ Configuration config = project.getConfigurations().detachedConfiguration(dependency);
+ config.setTransitive(transitive);
+ FileCollection files = config.fileCollection(dep -> true);
+
+ if (resolve) {
+ files = project.files(files.getFiles());
+ }
+
+ return files;
+ }
+
+ private static Set<File> resolve(Configuration configuration, boolean transitive) {
+ Configuration copy = configuration.copy();
+ copy.setTransitive(transitive);
+ Set<File> files = new LinkedHashSet<>(copy.resolve());
+
+ for (Configuration extendsForm : configuration.getExtendsFrom()) {
+ files.addAll(resolve(extendsForm, transitive));
+ }
+
+ return files;
+ }
+
+ /**
+ * Resolves a configuration and its superconfigurations.
+ *
+ * <p>Note that unlike resolving a {@linkplain Configuration#copyRecursive() recursive copy} of the configuration,
+ * this method overrides the transitivity of all superconfigurations as well.
+ *
+ * @param configuration the configuration to resolve
+ * @param transitive true if transitive dependencies should be included, false otherwise
+ * @return a mutable set containing the resolved files of the configuration
+ */
+ public static Set<File> resolveFiles(Configuration configuration, boolean transitive) {
+ return resolve(configuration, transitive);
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/DownloadUtil.java b/src/main/java/net/fabricmc/loom/util/DownloadUtil.java
index fcc79aa7..56ccf8d3 100644
--- a/src/main/java/net/fabricmc/loom/util/DownloadUtil.java
+++ b/src/main/java/net/fabricmc/loom/util/DownloadUtil.java
@@ -48,8 +48,8 @@ public class DownloadUtil {
* @param logger The logger to print everything to, typically from {@link Project#getLogger()}
* @throws IOException If an exception occurs during the process
*/
- public static void downloadIfChanged(URL from, File to, Logger logger) throws IOException {
- downloadIfChanged(from, to, logger, false);
+ public static boolean downloadIfChanged(URL from, File to, Logger logger) throws IOException {
+ return downloadIfChanged(from, to, logger, false);
}
/**
@@ -61,7 +61,7 @@ public class DownloadUtil {
* @param quiet Whether to only print warnings (when <code>true</code>) or everything
* @throws IOException If an exception occurs during the process
*/
- public static void downloadIfChanged(URL from, File to, Logger logger, boolean quiet) throws IOException {
+ public static boolean downloadIfChanged(URL from, File to, Logger logger, boolean quiet) throws IOException {
HttpURLConnection connection = (HttpURLConnection) from.openConnection();
if (LoomGradlePlugin.refreshDeps) {
@@ -102,7 +102,7 @@ public class DownloadUtil {
logger.info("'{}' Not Modified, skipping.", to);
}
- return; //What we've got is already fine
+ return false; //What we've got is already fine
}
long contentLength = connection.getContentLengthLong();
@@ -140,6 +140,8 @@ public class DownloadUtil {
saveETag(to, eTag, logger);
}
+
+ return true;
}
/**
@@ -228,5 +230,7 @@ public class DownloadUtil {
if (etagFile.exists()) {
etagFile.delete();
}
+
+ HashedDownloadUtil.delete(file);
}
}
diff --git a/src/main/java/net/fabricmc/loom/util/FileSystemUtil.java b/src/main/java/net/fabricmc/loom/util/FileSystemUtil.java
index 377bb8cb..05f0642f 100644
--- a/src/main/java/net/fabricmc/loom/util/FileSystemUtil.java
+++ b/src/main/java/net/fabricmc/loom/util/FileSystemUtil.java
@@ -78,6 +78,8 @@ public final class FileSystemUtil {
return new Delegate(FileSystems.newFileSystem(jarUri, create ? jfsArgsCreate : jfsArgsEmpty), true);
} catch (FileSystemAlreadyExistsException e) {
return new Delegate(FileSystems.getFileSystem(jarUri), false);
+ } catch (IOException e) {
+ throw new IOException("Could not create JAR file system for " + uri + " (create: " + create + ")", e);
}
}
}
diff --git a/src/main/java/net/fabricmc/loom/util/HashedDownloadUtil.java b/src/main/java/net/fabricmc/loom/util/HashedDownloadUtil.java
index 3ca404e2..a9b77ab4 100644
--- a/src/main/java/net/fabricmc/loom/util/HashedDownloadUtil.java
+++ b/src/main/java/net/fabricmc/loom/util/HashedDownloadUtil.java
@@ -43,20 +43,48 @@ import org.gradle.api.logging.Logger;
import net.fabricmc.loom.LoomGradlePlugin;
public class HashedDownloadUtil {
+ public static boolean requiresDownload(File to, String expectedHash, Logger logger) {
+ if (LoomGradlePlugin.refreshDeps) {
+ return true;
+ }
+
+ if (to.exists()) {
+ String sha1 = getSha1(to, logger);
+
+ // The hash in the sha1 file matches
+ return !expectedHash.equals(sha1);
+ }
+
+ return true;
+ }
+
public static void downloadIfInvalid(URL from, File to, String expectedHash, Logger logger, boolean quiet) throws IOException {
- downloadIfInvalid(from, to, expectedHash, logger, quiet, () -> { });
+ downloadIfInvalid(from, to, expectedHash, logger, quiet, true);
+ }
+
+ public static void downloadIfInvalid(URL from, File to, String expectedHash, Logger logger, boolean quiet, boolean strict) throws IOException {
+ downloadIfInvalid(from, to, expectedHash, logger, quiet, strict, () -> { });
}
- public static void downloadIfInvalid(URL from, File to, String expectedHash, Logger logger, boolean quiet, Runnable startDownload) throws IOException {
+ public static void downloadIfInvalid(URL from, File to, String expectedHash, Logger logger, boolean quiet, boolean strict, Runnable startDownload) throws IOException {
if (LoomGradlePlugin.refreshDeps) {
delete(to);
}
- String sha1 = getSha1(to, logger);
-
- if (expectedHash.equals(sha1)) {
- // The hash in the sha1 file matches
- return;
+ if (to.exists()) {
+ if (strict) {
+ if (Checksum.equals(to, expectedHash)) {
+ // The hash matches the target file
+ return;
+ }
+ } else {
+ String sha1 = getSha1(to, logger);
+
+ if (expectedHash.equals(sha1)) {
+ // The hash in the sha1 file matches
+ return;
+ }
+ }
}
startDownload.run();
diff --git a/src/main/java/net/fabricmc/loom/util/LfWriter.java b/src/main/java/net/fabricmc/loom/util/LfWriter.java
new file mode 100644
index 00000000..cbd6540f
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/LfWriter.java
@@ -0,0 +1,43 @@
+/*
+ * 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.util;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * A {@link BufferedWriter} that writes {@code \n} (LF) instead of {@link System#lineSeparator()}.
+ */
+public class LfWriter extends BufferedWriter {
+ public LfWriter(Writer out) {
+ super(out);
+ }
+
+ @Override
+ public void newLine() throws IOException {
+ write('\n');
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/LoggerFilter.java b/src/main/java/net/fabricmc/loom/util/LoggerFilter.java
new file mode 100644
index 00000000..1f9f9b96
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/LoggerFilter.java
@@ -0,0 +1,49 @@
+/*
+ * 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.util;
+
+import java.io.PrintStream;
+
+import org.jetbrains.annotations.NotNull;
+
+public class LoggerFilter {
+ public static void replaceSystemOut() {
+ try {
+ PrintStream previous = System.out;
+ System.setOut(new PrintStream(previous) {
+ @Override
+ public PrintStream printf(@NotNull String format, Object... args) {
+ if (format.equals("unknown invokedynamic bsm: %s/%s%s (tag=%d iif=%b)%n")) {
+ return this;
+ }
+
+ return super.printf(format, args);
+ }
+ });
+ } catch (SecurityException ignored) {
+ // Failed to replace logger filter, just ignore
+ }
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/MappingsProviderVerbose.java b/src/main/java/net/fabricmc/loom/util/MappingsProviderVerbose.java
new file mode 100644
index 00000000..9ddb07ef
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/MappingsProviderVerbose.java
@@ -0,0 +1,115 @@
+/*
+ * 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.util;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.UncheckedIOException;
+import java.lang.reflect.Field;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Set;
+
+import dev.architectury.tinyremapper.IMappingProvider;
+import dev.architectury.tinyremapper.TinyRemapper;
+
+import net.fabricmc.mappingio.adapter.RegularAsFlatMappingVisitor;
+import net.fabricmc.mappingio.format.Tiny2Writer;
+import net.fabricmc.mappingio.tree.MemoryMappingTree;
+
+public class MappingsProviderVerbose {
+ public static void saveFile(TinyRemapper providers) throws IOException {
+ try {
+ Field field = TinyRemapper.class.getDeclaredField("mappingProviders");
+ field.setAccessible(true);
+ Set<IMappingProvider> mappingProviders = (Set<IMappingProvider>) field.get(providers);
+ saveFile(mappingProviders);
+ } catch (IllegalAccessException | NoSuchFieldException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static void saveFile(Iterable<IMappingProvider> providers) throws IOException {
+ MemoryMappingTree tree = new MemoryMappingTree();
+ tree.setSrcNamespace("from");
+ tree.setDstNamespaces(new ArrayList<>(Collections.singletonList("to")));
+ RegularAsFlatMappingVisitor flatVisitor = new RegularAsFlatMappingVisitor(tree);
+
+ for (IMappingProvider provider : providers) {
+ provider.load(new IMappingProvider.MappingAcceptor() {
+ @Override
+ public void acceptClass(String from, String to) {
+ try {
+ flatVisitor.visitClass(from, to);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ @Override
+ public void acceptMethod(IMappingProvider.Member from, String to) {
+ try {
+ flatVisitor.visitMethod(from.owner, from.name, from.desc, to);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ @Override
+ public void acceptMethodArg(IMappingProvider.Member from, int lvIndex, String to) {
+ try {
+ flatVisitor.visitMethodArg(from.owner, from.name, from.desc, lvIndex, lvIndex, "", to);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ @Override
+ public void acceptMethodVar(IMappingProvider.Member from, int i, int i1, int i2, String s) {
+ // NO-OP
+ }
+
+ @Override
+ public void acceptField(IMappingProvider.Member from, String to) {
+ try {
+ flatVisitor.visitField(from.owner, from.name, from.desc, to);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+ });
+ }
+
+ Path check = Files.createTempFile("CHECK", null);
+ StringWriter stringWriter = new StringWriter();
+ Tiny2Writer tiny2Writer = new Tiny2Writer(stringWriter, false);
+ tree.accept(tiny2Writer);
+ Files.writeString(check, stringWriter.toString(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
+ System.out.println("Saved debug check mappings to " + check);
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/ModPlatform.java b/src/main/java/net/fabricmc/loom/util/ModPlatform.java
new file mode 100644
index 00000000..0f7ce897
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/ModPlatform.java
@@ -0,0 +1,50 @@
+/*
+ * 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.util;
+
+import java.util.Locale;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.Project;
+
+import net.fabricmc.loom.LoomGradleExtension;
+import net.fabricmc.loom.api.LoomGradleExtensionAPI;
+
+public enum ModPlatform {
+ FABRIC,
+ FORGE;
+
+ public static void assertPlatform(Project project, ModPlatform platform) {
+ assertPlatform(LoomGradleExtension.get(project), platform);
+ }
+
+ public static void assertPlatform(LoomGradleExtensionAPI extension, ModPlatform platform) {
+ if (extension.getPlatform().get() != platform) {
+ String msg = "Loom is not running on %s.%nYou can switch to it by adding 'loom.platform = %s' to your gradle.properties";
+ String name = platform.name().toLowerCase(Locale.ROOT);
+ throw new GradleException(String.format(msg, name, name));
+ }
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/PropertyUtil.java b/src/main/java/net/fabricmc/loom/util/PropertyUtil.java
new file mode 100644
index 00000000..98e85b68
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/PropertyUtil.java
@@ -0,0 +1,45 @@
+/*
+ * 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.util;
+
+import org.gradle.api.provider.HasConfigurableValue;
+import org.gradle.api.provider.Provider;
+
+/**
+ * Working with properties.
+ */
+public final class PropertyUtil {
+ /**
+ * Gets a property and finalizes its value.
+ *
+ * @param property the property
+ * @param <T> the value type
+ * @return the current property value
+ */
+ public static <T, P extends Provider<T> & HasConfigurableValue> T getAndFinalize(P property) {
+ property.finalizeValue();
+ return property.get();
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/SourceRemapper.java b/src/main/java/net/fabricmc/loom/util/SourceRemapper.java
index f37211be..7873c78f 100644
--- a/src/main/java/net/fabricmc/loom/util/SourceRemapper.java
+++ b/src/main/java/net/fabricmc/loom/util/SourceRemapper.java
@@ -39,7 +39,6 @@ import org.cadixdev.mercury.remapper.MercuryRemapper;
import org.gradle.api.Project;
import net.fabricmc.loom.LoomGradleExtension;
-import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.RemappedConfigurationEntry;
import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl;
import net.fabricmc.loom.util.gradle.ProgressLoggerHelper;
@@ -48,18 +47,29 @@ import net.fabricmc.mappingio.tree.MemoryMappingTree;
public class SourceRemapper {
private final Project project;
- private final boolean toNamed;
+ private String from;
+ private String to;
private final List<Consumer<ProgressLoggerHelper>> remapTasks = new ArrayList<>();
private Mercury mercury;
- public SourceRemapper(Project project, boolean toNamed) {
+ public SourceRemapper(Project project, boolean named) {
+ this(project, named ? intermediary(project) : "named", !named ? intermediary(project) : "named");
+ }
+
+ public static String intermediary(Project project) {
+ LoomGradleExtension extension = LoomGradleExtension.get(project);
+ return extension.isForge() ? "srg" : "intermediary";
+ }
+
+ public SourceRemapper(Project project, String from, String to) {
this.project = project;
- this.toNamed = toNamed;
+ this.from = from;
+ this.to = to;
}
- public static void remapSources(Project project, File input, File output, boolean named, boolean reproducibleFileOrder, boolean preserveFileTimestamps) {
- SourceRemapper sourceRemapper = new SourceRemapper(project, named);
+ public static void remapSources(Project project, File input, File output, String from, String to, boolean reproducibleFileOrder, boolean preserveFileTimestamps) {
+ SourceRemapper sourceRemapper = new SourceRemapper(project, from, to);
sourceRemapper.scheduleRemapSources(input, output, reproducibleFileOrder, preserveFileTimestamps);
sourceRemapper.remapAll();
}
@@ -161,18 +171,27 @@ public class SourceRemapper {
LoomGradleExtension extension = LoomGradleExtension.get(project);
MappingsProviderImpl mappingsProvider = extension.getMappingsProvider();
- MappingSet mappings = extension.getOrCreateSrcMappingCache(toNamed ? 1 : 0, () -> {
+ String intermediary = extension.isForge() ? "srg" : "intermediary";
+ int id = -1;
+
+ if (from.equals(intermediary) && to.equals("named")) {
+ id = 1;
+ } else if (to.equals(intermediary) && from.equals("named")) {
+ id = 0;
+ }
+
+ MappingSet mappings = extension.getOrCreateSrcMappingCache(id, () -> {
try {
- MemoryMappingTree m = mappingsProvider.getMappings();
- project.getLogger().info(":loading " + (toNamed ? "intermediary -> named" : "named -> intermediary") + " source mappings");
- return new TinyMappingsReader(m, toNamed ? MappingsNamespace.INTERMEDIARY.toString() : MappingsNamespace.NAMED.toString(), toNamed ? MappingsNamespace.NAMED.toString() : MappingsNamespace.INTERMEDIARY.toString()).read();
+ MemoryMappingTree m = (from.equals("srg") || to.equals("srg")) && extension.shouldGenerateSrgTiny() ? mappingsProvider.getMappingsWithSrg() : mappingsProvider.getMappings();
+ project.getLogger().info(":loading " + from + " -> " + to + " source mappings");
+ return new TinyMappingsReader(m, from, to).read();
} catch (Exception e) {
throw new RuntimeException(e);
}
});
- Mercury mercury = extension.getOrCreateSrcMercuryCache(toNamed ? 1 : 0, () -> {
- Mercury m = createMercuryWithClassPath(project, toNamed);
+ Mercury mercury = extension.getOrCreateSrcMercuryCache(id, () -> {
+ Mercury m = createMercuryWithClassPath(project, to.equals("named"));
for (File file : extension.getUnmappedModCollection()) {
Path path = file.toPath();
@@ -185,6 +204,16 @@ public class SourceRemapper {
m.getClassPath().add(extension.getMinecraftMappedProvider().getMappedJar().toPath());
m.getClassPath().add(extension.getMinecraftMappedProvider().getIntermediaryJar().toPath());
+ if (extension.isForge()) {
+ m.getClassPath().add(extension.getMinecraftMappedProvider().getSrgJar().toPath());
+
+ if (extension.isForgeAndNotOfficial()) {
+ m.getClassPath().add(extension.getMinecraftMappedProvider().getForgeMappedJar().toPath());
+ m.getClassPath().add(extension.getMinecraftMappedProvider().getForgeIntermediaryJar().toPath());
+ m.getClassPath().add(extension.getMinecraftMappedProvider().getForgeSrgJar().toPath());
+ }
+ }
+
Set<File> files = project.getConfigurations()
.detachedConfiguration(project.getDependencies().create(Constants.Dependencies.JETBRAINS_ANNOTATIONS + Constants.Dependencies.Versions.JETBRAINS_ANNOTATIONS))
.resolve();
diff --git a/src/main/java/net/fabricmc/loom/util/ThreadingUtils.java b/src/main/java/net/fabricmc/loom/util/ThreadingUtils.java
new file mode 100644
index 00000000..d6d06f79
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/ThreadingUtils.java
@@ -0,0 +1,192 @@
+/*
+ * 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.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import com.google.common.base.Stopwatch;
+
+public class ThreadingUtils {
+ public static <T> void run(T[] values, UnsafeConsumer<T> action) {
+ run(Arrays.stream(values)
+ .<UnsafeRunnable>map(t -> () -> action.accept(t))
+ .collect(Collectors.toList()));
+ }
+
+ public static <T> void run(Collection<T> values, UnsafeConsumer<T> action) {
+ run(values.stream()
+ .<UnsafeRunnable>map(t -> () -> action.accept(t))
+ .collect(Collectors.toList()));
+ }
+
+ public static void run(UnsafeRunnable... jobs) {
+ run(Arrays.asList(jobs));
+ }
+
+ public static void run(Collection<UnsafeRunnable> jobs) {
+ try {
+ ExecutorService service = Executors.newFixedThreadPool(Math.max(1, Math.min(jobs.size(), Runtime.getRuntime().availableProcessors())));
+ List<Future<?>> futures = new LinkedList<>();
+
+ for (UnsafeRunnable runnable : jobs) {
+ futures.add(service.submit(() -> {
+ try {
+ runnable.run();
+ } catch (Throwable throwable) {
+ throw new RuntimeException(throwable);
+ }
+ }));
+ }
+
+ for (Future<?> future : futures) {
+ future.get();
+ }
+
+ service.shutdownNow();
+ } catch (InterruptedException | ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static <T, R> List<R> get(Collection<T> values, Function<T, R> action) {
+ return get(values.stream()
+ .<UnsafeCallable<R>>map(t -> () -> action.apply(t))
+ .collect(Collectors.toList()));
+ }
+
+ @SafeVarargs
+ public static <T> List<T> get(UnsafeCallable<T>... jobs) {
+ return get(Arrays.asList(jobs));
+ }
+
+ public static <T> List<T> get(Collection<UnsafeCallable<T>> jobs) {
+ try {
+ ExecutorService service = Executors.newFixedThreadPool(Math.max(1, Math.min(jobs.size(), Runtime.getRuntime().availableProcessors())));
+ List<Future<T>> futures = new LinkedList<>();
+ List<T> result = new ArrayList<>();
+
+ for (UnsafeCallable<T> runnable : jobs) {
+ futures.add(service.submit(() -> {
+ try {
+ return runnable.call();
+ } catch (Throwable throwable) {
+ throw new RuntimeException(throwable);
+ }
+ }));
+ }
+
+ for (Future<T> future : futures) {
+ result.add(future.get());
+ }
+
+ service.shutdownNow();
+ return result;
+ } catch (InterruptedException | ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public interface UnsafeRunnable {
+ void run() throws Throwable;
+ }
+
+ public interface UnsafeCallable<T> {
+ T call() throws Throwable;
+ }
+
+ public interface UnsafeConsumer<T> {
+ void accept(T value) throws Throwable;
+ }
+
+ public static TaskCompleter taskCompleter() {
+ return new TaskCompleter();
+ }
+
+ public static class TaskCompleter implements Function<Throwable, Void> {
+ Stopwatch stopwatch = Stopwatch.createUnstarted();
+ List<CompletableFuture<?>> tasks = new ArrayList<>();
+ ExecutorService service = Executors.newFixedThreadPool(Math.max(1, Runtime.getRuntime().availableProcessors()));
+ List<UnsafeConsumer<Stopwatch>> completionListener = new ArrayList<>();
+
+ public TaskCompleter add(UnsafeRunnable job) {
+ if (!stopwatch.isRunning()) {
+ stopwatch.start();
+ }
+
+ tasks.add(CompletableFuture.runAsync(() -> {
+ try {
+ job.run();
+ } catch (Throwable throwable) {
+ throw new RuntimeException(throwable);
+ }
+ }, service).exceptionally(this));
+
+ return this;
+ }
+
+ public TaskCompleter onComplete(UnsafeConsumer<Stopwatch> consumer) {
+ completionListener.add(consumer);
+ return this;
+ }
+
+ public void complete() {
+ try {
+ CompletableFuture.allOf(tasks.toArray(new CompletableFuture[0])).exceptionally(this).get();
+ service.shutdownNow();
+
+ if (stopwatch.isRunning()) {
+ stopwatch.stop();
+ }
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ } finally {
+ try {
+ for (UnsafeConsumer<Stopwatch> consumer : completionListener) {
+ consumer.accept(stopwatch);
+ }
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ @Override
+ public Void apply(Throwable throwable) {
+ throwable.printStackTrace();
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java b/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java
index d534f972..1dc42878 100644
--- a/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java
+++ b/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java
@@ -27,25 +27,31 @@ package net.fabricmc.loom.util;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.regex.Pattern;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import dev.architectury.tinyremapper.IMappingProvider;
+import dev.architectury.tinyremapper.TinyRemapper;
+import org.apache.commons.lang3.mutable.Mutable;
+import org.apache.commons.lang3.mutable.MutableObject;
+import org.apache.commons.lang3.tuple.Triple;
import org.gradle.api.Project;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.mappingio.tree.MappingTree;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
-import net.fabricmc.tinyremapper.IMappingProvider;
-import net.fabricmc.tinyremapper.TinyRemapper;
/**
* Contains shortcuts to create tiny remappers using the mappings accessibly to the project.
*/
public final class TinyRemapperHelper {
- private static final Map<String, String> JSR_TO_JETBRAINS = new ImmutableMap.Builder<String, String>()
+ public static final Map<String, String> JSR_TO_JETBRAINS = new ImmutableMap.Builder<String, String>()
.put("javax/annotation/Nullable", "org/jetbrains/annotations/Nullable")
.put("javax/annotation/Nonnull", "org/jetbrains/annotations/NotNull")
.put("javax/annotation/concurrent/Immutable", "org/jetbrains/annotations/Unmodifiable")
@@ -65,31 +71,62 @@ public final class TinyRemapperHelper {
public static TinyRemapper getTinyRemapper(Project project, String fromM, String toM, boolean fixRecords, Consumer<TinyRemapper.Builder> builderConsumer) throws IOException {
LoomGradleExtension extension = LoomGradleExtension.get(project);
- MemoryMappingTree mappingTree = extension.getMappingsProvider().getMappings();
- if (fixRecords && !mappingTree.getSrcNamespace().equals(fromM)) {
- throw new IllegalStateException("Mappings src namespace must match remap src namespace");
- }
+ TinyRemapper remapper = _getTinyRemapper(project, fixRecords, builderConsumer).getLeft();
+ remapper.replaceMappings(ImmutableSet.of(
+ TinyRemapperHelper.create((fromM.equals("srg") || toM.equals("srg")) && extension.isForge() ? extension.getMappingsProvider().getMappingsWithSrg() : extension.getMappingsProvider().getMappings(), fromM, toM, true),
+ out -> TinyRemapperHelper.JSR_TO_JETBRAINS.forEach(out::acceptClass)
+ ));
+ return remapper;
+ }
- int intermediaryNsId = mappingTree.getNamespaceId(MappingsNamespace.INTERMEDIARY.toString());
+ public static Triple<TinyRemapper, Mutable<MemoryMappingTree>, List<TinyRemapper.ApplyVisitorProvider>> _getTinyRemapper(Project project, boolean fixRecords, Consumer<TinyRemapper.Builder> builderConsumer) throws IOException {
+ LoomGradleExtension extension = LoomGradleExtension.get(project);
+ Mutable<MemoryMappingTree> mappings = new MutableObject<>();
+ List<TinyRemapper.ApplyVisitorProvider> postApply = new ArrayList<>();
TinyRemapper.Builder builder = TinyRemapper.newRemapper()
- .withMappings(create(mappingTree, fromM, toM, true))
- .withMappings(out -> JSR_TO_JETBRAINS.forEach(out::acceptClass))
.renameInvalidLocals(true)
- .rebuildSourceFilenames(true)
- .invalidLvNamePattern(MC_LV_PATTERN)
- .inferNameFromSameLvIndex(true)
- .extraPreApplyVisitor((cls, next) -> {
- if (fixRecords && !cls.isRecord() && "java/lang/Record".equals(cls.getSuperName())) {
- return new RecordComponentFixVisitor(next, mappingTree, intermediaryNsId);
- }
+ .logUnknownInvokeDynamic(false)
+ .ignoreConflicts(extension.isForge())
+ .cacheMappings(true)
+ .threads(Runtime.getRuntime().availableProcessors())
+ .logger(project.getLogger()::lifecycle)
+ .rebuildSourceFilenames(true);
+
+ if (extension.isForge()) {
+ /* FORGE: Required for classes like aej$OptionalNamedTag (1.16.4) which are added by Forge patches.
+ * They won't get remapped to their proper packages, so IllegalAccessErrors will happen without ._.
+ */
+ builder.fixPackageAccess(true);
+ }
- return next;
- });
+ builder.invalidLvNamePattern(MC_LV_PATTERN);
+ builder.inferNameFromSameLvIndex(true);
+ builder.extraPreApplyVisitor((cls, next) -> {
+ if (fixRecords && !cls.isRecord() && "java/lang/Record".equals(cls.getSuperName()) && mappings.getValue() != null) {
+ return new RecordComponentFixVisitor(next, mappings.getValue(), mappings.getValue().getNamespaceId(MappingsNamespace.INTERMEDIARY.toString()));
+ }
+
+ return next;
+ });
+ builder.extraPostApplyVisitor((trClass, classVisitor) -> {
+ for (TinyRemapper.ApplyVisitorProvider provider : postApply) {
+ classVisitor = provider.insertApplyVisitor(trClass, classVisitor);
+ }
+
+ return classVisitor;
+ });
builderConsumer.accept(builder);
- return builder.build();
+ return Triple.of(builder.build(), mappings, postApply);
+ }
+
+ public static Triple<TinyRemapper, Mutable<MemoryMappingTree>, List<TinyRemapper.ApplyVisitorProvider>> getTinyRemapper(Project project, boolean fixRecords, Consumer<TinyRemapper.Builder> builderConsumer) throws IOException {
+ Triple<TinyRemapper, Mutable<MemoryMappingTree>, List<TinyRemapper.ApplyVisitorProvider>> remapper = _getTinyRemapper(project, fixRecords, builderConsumer);
+ remapper.getLeft().readClassPath(getMinecraftDependencies(project));
+ remapper.getLeft().prepareClasses();
+ return remapper;
}
public static Path[] getMinecraftDependencies(Project project) {
diff --git a/src/main/java/net/fabricmc/loom/util/aw2at/Aw2At.java b/src/main/java/net/fabricmc/loom/util/aw2at/Aw2At.java
new file mode 100644
index 00000000..06fd7e86
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/aw2at/Aw2At.java
@@ -0,0 +1,118 @@
+/*
+ * 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.util.aw2at;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+
+import org.cadixdev.at.AccessChange;
+import org.cadixdev.at.AccessTransform;
+import org.cadixdev.at.AccessTransformSet;
+import org.cadixdev.at.ModifierChange;
+import org.cadixdev.bombe.type.signature.MethodSignature;
+import org.gradle.api.Project;
+import org.gradle.api.tasks.SourceSet;
+import org.gradle.api.tasks.SourceSetContainer;
+
+import net.fabricmc.accesswidener.AccessWidenerReader;
+import net.fabricmc.accesswidener.AccessWidenerVisitor;
+import net.fabricmc.loom.LoomGradleExtension;
+import net.fabricmc.loom.task.RemapJarTask;
+
+/**
+ * Converts AW files to AT files.
+ *
+ * @author Juuz
+ */
+public final class Aw2At {
+ public static void setup(Project project, RemapJarTask remapJar) {
+ LoomGradleExtension extension = LoomGradleExtension.get(project);
+
+ if (extension.getAccessWidenerPath().isPresent()) {
+ // Find the relative AW file name
+ String awName = null;
+ Path awPath = extension.getAccessWidenerPath().get().getAsFile().toPath();
+ SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class);
+ SourceSet main = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
+ boolean found = false;
+
+ for (File srcDir : main.getResources().getSrcDirs()) {
+ Path srcDirPath = srcDir.toPath().toAbsolutePath();
+
+ if (awPath.startsWith(srcDirPath)) {
+ awName = srcDirPath.relativize(awPath).toString().replace(File.separator, "/");
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ awName = awPath.getFileName().toString();
+ }
+
+ remapJar.getAtAccessWideners().add(awName);
+ }
+
+ remapJar.getAtAccessWideners().addAll(extension.getForge().getExtraAccessWideners());
+ }
+
+ public static AccessTransformSet toAccessTransformSet(BufferedReader reader) throws IOException {
+ AccessTransformSet atSet = AccessTransformSet.create();
+
+ new AccessWidenerReader(new AccessWidenerVisitor() {
+ @Override
+ public void visitClass(String name, AccessWidenerReader.AccessType access, boolean transitive) {
+ atSet.getOrCreateClass(name).merge(toAt(access));
+ }
+
+ @Override
+ public void visitMethod(String owner, String name, String descriptor, AccessWidenerReader.AccessType access, boolean transitive) {
+ atSet.getOrCreateClass(owner).mergeMethod(MethodSignature.of(name, descriptor), toAt(access));
+ }
+
+ @Override
+ public void visitField(String owner, String name, String descriptor, AccessWidenerReader.AccessType access, boolean transitive) {
+ atSet.getOrCreateClass(owner).mergeField(name, toAt(access));
+ }
+ }).read(reader);
+
+ return atSet;
+ }
+
+ private static AccessTransform toAt(AccessWidenerReader.AccessType access) {
+ return switch (access) {
+ // FIXME: This behaviour doesn't match what the actual AW does for methods.
+ // - accessible makes the method final if it was private
+ // - extendable makes the method protected if it was (package-)private
+ // Neither of these can be achieved with Forge ATs without using bytecode analysis.
+ // However, this might be good enough for us. (The effects only apply in prod.)
+ case ACCESSIBLE -> AccessTransform.of(AccessChange.PUBLIC);
+ case EXTENDABLE -> AccessTransform.of(AccessChange.PUBLIC, ModifierChange.REMOVE);
+ case MUTABLE -> AccessTransform.of(ModifierChange.REMOVE);
+ };
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/function/CollectionUtil.java b/src/main/java/net/fabricmc/loom/util/function/CollectionUtil.java
new file mode 100644
index 00000000..389dbbef
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/function/CollectionUtil.java
@@ -0,0 +1,75 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2020-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.util.function;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+/**
+ * Stream-like utilities for working with collections.
+ *
+ * @author Juuz
+ */
+public final class CollectionUtil {
+ /**
+ * Finds the first element matching the predicate.
+ *
+ * @param collection the collection to be searched
+ * @param filter the predicate to be matched
+ * @param <E> the element type
+ * @return the first matching element, or empty if none match
+ */
+ public static <E> Optional<E> find(Iterable<? extends E> collection, Predicate<? super E> filter) {
+ for (E e : collection) {
+ if (filter.test(e)) {
+ return Optional.of(e);
+ }
+ }
+
+ return Optional.empty();
+ }
+
+ /**
+ * Transforms the collection with a function.
+ *
+ * @param collection the source collection
+ * @param transform the transformation function
+ * @param <A> the source type
+ * @param <B> the target type
+ * @return a mutable list with the transformed entries
+ */
+ public static <A, B> List<B> map(Iterable<? extends A> collection, Function<? super A, ? extends B> transform) {
+ ArrayList<B> result = new ArrayList<>();
+
+ for (A a : collection) {
+ result.add(transform.apply(a));
+ }
+
+ return result;
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/function/FsPathConsumer.java b/src/main/java/net/fabricmc/loom/util/function/FsPathConsumer.java
new file mode 100644
index 00000000..536721c7
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/function/FsPathConsumer.java
@@ -0,0 +1,39 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2020-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.util.function;
+
+import java.io.IOException;
+import java.nio.file.FileSystem;
+import java.nio.file.Path;
+
+/**
+ * Consumes two file systems and corresponding path objects.
+ *
+ * @author Juuz
+ */
+@FunctionalInterface
+public interface FsPathConsumer {
+ void accept(FileSystem sourceFs, FileSystem targetFs, Path sourcePath, Path targetPath) throws IOException;
+}
diff --git a/src/main/java/net/fabricmc/loom/util/function/IoConsumer.java b/src/main/java/net/fabricmc/loom/util/function/IoConsumer.java
new file mode 100644
index 00000000..379d4293
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/function/IoConsumer.java
@@ -0,0 +1,38 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2020-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.util.function;
+
+import java.io.IOException;
+
+/**
+ * Like Consumer, but can throw IOException.
+ *
+ * @param <A> the result type
+ * @author Juuz
+ */
+@FunctionalInterface
+public interface IoConsumer<A> {
+ void accept(A a) throws IOException;
+}
diff --git a/src/main/java/net/fabricmc/loom/util/function/LazyBool.java b/src/main/java/net/fabricmc/loom/util/function/LazyBool.java
new file mode 100644
index 00000000..7d2933a4
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/function/LazyBool.java
@@ -0,0 +1,52 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2020-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.util.function;
+
+import java.util.Objects;
+import java.util.function.BooleanSupplier;
+
+/**
+ * A lazily computed boolean value.
+ *
+ * @author Juuz
+ */
+public final class LazyBool implements BooleanSupplier {
+ private BooleanSupplier supplier;
+ private Boolean value;
+
+ public LazyBool(BooleanSupplier supplier) {
+ this.supplier = Objects.requireNonNull(supplier, "supplier");
+ }
+
+ @Override
+ public boolean getAsBoolean() {
+ if (value == null) {
+ value = supplier.getAsBoolean();
+ supplier = null; // Release the supplier
+ }
+
+ return value;
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/srg/AtRemapper.java b/src/main/java/net/fabricmc/loom/util/srg/AtRemapper.java
new file mode 100644
index 00000000..8e1d3008
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/srg/AtRemapper.java
@@ -0,0 +1,140 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2020-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.util.srg;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.UnaryOperator;
+
+import com.google.common.collect.ImmutableMap;
+import org.gradle.api.logging.Logger;
+
+import net.fabricmc.loom.util.Constants;
+import net.fabricmc.loom.util.function.CollectionUtil;
+import net.fabricmc.mappingio.tree.MappingTree;
+
+/**
+ * Remaps AT classes from SRG to Yarn.
+ *
+ * @author Juuz
+ */
+public final class AtRemapper {
+ public static void remap(Logger logger, Path jar, MappingTree mappings) throws IOException {
+ try (FileSystem fs = FileSystems.newFileSystem(URI.create("jar:" + jar.toUri()), ImmutableMap.of("create", false))) {
+ Path atPath = fs.getPath(Constants.Forge.ACCESS_TRANSFORMER_PATH);
+
+ if (Files.exists(atPath)) {
+ String atContent = Files.readString(atPath);
+
+ String[] lines = atContent.split("\n");
+ List<String> output = new ArrayList<>(lines.length);
+
+ for (int i = 0; i < lines.length; i++) {
+ String line = lines[i].trim();
+
+ if (line.startsWith("#") || line.isBlank()) {
+ output.add(i, line);
+ continue;
+ }
+
+ String[] parts = line.split("\\s+");
+
+ if (parts.length < 2) {
+ logger.warn("Invalid AT Line: " + line);
+ output.add(i, line);
+ continue;
+ }
+
+ String name = parts[1].replace('.', '/');
+ parts[1] = CollectionUtil.find(
+ mappings.getClasses(),
+ def -> def.getName("srg").equals(name)
+ ).map(def -> def.getName("named")).orElse(name).replace('/', '.');
+
+ if (parts.length >= 3) {
+ if (parts[2].contains("(")) {
+ parts[2] = parts[2].substring(0, parts[2].indexOf('(')) + remapDescriptor(parts[2].substring(parts[2].indexOf('(')), s -> {
+ return CollectionUtil.find(
+ mappings.getClasses(),
+ def -> def.getName("srg").equals(s)
+ ).map(def -> def.getName("named")).orElse(s);
+ });
+ }
+ }
+
+ output.add(i, String.join(" ", parts));
+ }
+
+ Files.write(atPath, String.join("\n", output).getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE);
+ }
+ }
+ }
+
+ private static String remapDescriptor(String original, UnaryOperator<String> classMappings) {
+ try {
+ StringReader reader = new StringReader(original);
+ StringBuilder result = new StringBuilder();
+ boolean insideClassName = false;
+ StringBuilder className = new StringBuilder();
+
+ while (true) {
+ int c = reader.read();
+
+ if (c == -1) {
+ break;
+ }
+
+ if ((char) c == ';') {
+ insideClassName = false;
+ result.append(classMappings.apply(className.toString()));
+ }
+
+ if (insideClassName) {
+ className.append((char) c);
+ } else {
+ result.append((char) c);
+ }
+
+ if (!insideClassName && (char) c == 'L') {
+ insideClassName = true;
+ className.setLength(0);
+ }
+ }
+
+ return result.toString();
+ } catch (IOException e) {
+ throw new AssertionError(e);
+ }
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/srg/CoreModClassRemapper.java b/src/main/java/net/fabricmc/loom/util/srg/CoreModClassRemapper.java
new file mode 100644
index 00000000..535bcb21
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/srg/CoreModClassRemapper.java
@@ -0,0 +1,115 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2020-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.util.srg;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import org.gradle.api.logging.Logger;
+
+import net.fabricmc.loom.util.function.CollectionUtil;
+import net.fabricmc.mappingio.tree.MappingTree;
+
+/**
+ * Remaps coremod class names from SRG to Yarn.
+ *
+ * @author Juuz
+ */
+public final class CoreModClassRemapper {
+ private static final Pattern CLASS_NAME_PATTERN = Pattern.compile("^(.*')((?:com\\.mojang\\.|net\\.minecraft\\.)[A-Za-z0-9.-_$]+)('.*)$");
+
+ public static void remapJar(Path jar, MappingTree mappings, Logger logger) throws IOException {
+ try (FileSystem fs = FileSystems.newFileSystem(URI.create("jar:" + jar.toUri()), ImmutableMap.of("create", false))) {
+ Path coremodsJsonPath = fs.getPath("META-INF", "coremods.json");
+
+ if (Files.notExists(coremodsJsonPath)) {
+ logger.info(":no coremods in " + jar.getFileName());
+ return;
+ }
+
+ JsonObject coremodsJson;
+
+ try (Reader reader = Files.newBufferedReader(coremodsJsonPath)) {
+ coremodsJson = new Gson().fromJson(reader, JsonObject.class);
+ }
+
+ for (Map.Entry<String, JsonElement> nameFileEntry : coremodsJson.entrySet()) {
+ String file = nameFileEntry.getValue().getAsString();
+ Path js = fs.getPath(file);
+
+ if (Files.exists(js)) {
+ logger.info(":remapping coremod '" + file + "'");
+ remap(js, mappings);
+ } else {
+ logger.warn("Coremod '" + file + "' listed in coremods.json but not found");
+ }
+ }
+ }
+ }
+
+ public static void remap(Path js, MappingTree mappings) throws IOException {
+ List<String> lines = Files.readAllLines(js);
+ List<String> output = new ArrayList<>(lines);
+
+ for (int i = 0; i < lines.size(); i++) {
+ String line = lines.get(i);
+ Matcher matcher = CLASS_NAME_PATTERN.matcher(line);
+
+ if (matcher.matches()) {
+ String className = matcher.group(2).replace('.', '/');
+ String remapped = CollectionUtil.find(mappings.getClasses(), def -> def.getName("srg").equals(className))
+ .map(def -> def.getName("named"))
+ .orElse(className);
+
+ if (!className.equals(remapped)) {
+ output.set(i, matcher.group(1) + remapped.replace('/', '.') + matcher.group(3));
+ }
+ }
+ }
+
+ if (!lines.equals(output)) {
+ try (Writer writer = Files.newBufferedWriter(js, StandardCharsets.UTF_8, StandardOpenOption.WRITE)) {
+ writer.write(String.join("\n", output));
+ }
+ }
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/srg/InnerClassRemapper.java b/src/main/java/net/fabricmc/loom/util/srg/InnerClassRemapper.java
new file mode 100644
index 00000000..e769c2f4
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/srg/InnerClassRemapper.java
@@ -0,0 +1,101 @@
+/*
+ * 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.util.srg;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.stream.Collectors;
+
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import dev.architectury.tinyremapper.IMappingProvider;
+
+import net.fabricmc.loom.util.FileSystemUtil;
+import net.fabricmc.mappingio.tree.MappingTree;
+
+public class InnerClassRemapper {
+ public static IMappingProvider of(Set<String> fromClassNames, MappingTree mappingsWithSrg, String from, String to) throws IOException {
+ return sink -> {
+ remapInnerClass(fromClassNames, mappingsWithSrg, from, to, sink::acceptClass);
+ };
+ }
+
+ public static Set<String> readClassNames(Path jar) {
+ Set<String> set = new HashSet<>();
+
+ try (FileSystemUtil.Delegate system = FileSystemUtil.getJarFileSystem(jar, false)) {
+ Iterator<Path> iterator = Files.walk(system.get().getPath("/")).iterator();
+
+ while (iterator.hasNext()) {
+ Path path = iterator.next();
+ String name = path.toString();
+ if (name.startsWith("/")) name = name.substring(1);
+
+ if (!Files.isDirectory(path) && name.contains("$") && name.endsWith(".class")) {
+ String className = name.substring(0, name.length() - 6);
+ set.add(className);
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ return set;
+ }
+
+ private static void remapInnerClass(Set<String> classNames, MappingTree mappingsWithSrg, String from, String to, BiConsumer<String, String> action) {
+ BiMap<String, String> availableClasses = HashBiMap.create(mappingsWithSrg.getClasses().stream()
+ .collect(Collectors.groupingBy(classDef -> classDef.getName(from),
+ Collectors.<MappingTree.ClassMapping, String>reducing(
+ null,
+ classDef -> classDef.getName(to),
+ (first, last) -> last
+ ))
+ ));
+
+ for (String className : classNames) {
+ if (!availableClasses.containsKey(className)) {
+ String parentName = className.substring(0, className.indexOf('$'));
+ String childName = className.substring(className.indexOf('$') + 1);
+ String remappedParentName = availableClasses.getOrDefault(parentName, parentName);
+ String remappedName = remappedParentName + "$" + childName;
+
+ if (!className.equals(remappedName)) {
+ if (availableClasses.containsValue(remappedName)) {
+ // https://github.com/MinecraftForge/MinecraftForge/blob/b027a92dd287d6810a9fdae4d4b1e1432d7dc9cc/patches/minecraft/net/minecraft/Util.java.patch#L8
+ action.accept(className, remappedName + "_UNBREAK");
+ } else {
+ action.accept(className, remappedName);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/srg/MCPReader.java b/src/main/java/net/fabricmc/loom/util/srg/MCPReader.java
new file mode 100644
index 00000000..96382bb5
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/srg/MCPReader.java
@@ -0,0 +1,363 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2020-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.util.srg;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.opencsv.CSVReader;
+import com.opencsv.exceptions.CsvValidationException;
+import org.apache.commons.io.IOUtils;
+import org.cadixdev.lorenz.MappingSet;
+import org.cadixdev.lorenz.io.srg.tsrg.TSrgReader;
+import org.cadixdev.lorenz.model.ClassMapping;
+import org.cadixdev.lorenz.model.FieldMapping;
+import org.cadixdev.lorenz.model.InnerClassMapping;
+import org.cadixdev.lorenz.model.MethodMapping;
+import org.cadixdev.lorenz.model.TopLevelClassMapping;
+import org.jetbrains.annotations.Nullable;
+
+import net.fabricmc.mappingio.MappingReader;
+import net.fabricmc.mappingio.tree.MappingTree;
+import net.fabricmc.mappingio.tree.MemoryMappingTree;
+import net.fabricmc.stitch.commands.tinyv2.TinyClass;
+import net.fabricmc.stitch.commands.tinyv2.TinyField;
+import net.fabricmc.stitch.commands.tinyv2.TinyFile;
+import net.fabricmc.stitch.commands.tinyv2.TinyMethod;
+import net.fabricmc.stitch.commands.tinyv2.TinyMethodParameter;
+import net.fabricmc.stitch.commands.tinyv2.TinyV2Reader;
+
+public class MCPReader {
+ private final Path intermediaryTinyPath;
+ private final Path srgTsrgPath;
+
+ public MCPReader(Path intermediaryTinyPath, Path srgTsrgPath) {
+ this.intermediaryTinyPath = intermediaryTinyPath;
+ this.srgTsrgPath = srgTsrgPath;
+ }
+
+ public TinyFile read(Path mcpJar) throws IOException {
+ Map<MemberToken, String> srgTokens = readSrg();
+ TinyFile intermediaryTiny = TinyV2Reader.read(intermediaryTinyPath);
+ Map<String, String> intermediaryToMCPMap = createIntermediaryToMCPMap(intermediaryTiny, srgTokens);
+ Map<String, String[]> intermediaryToDocsMap = new HashMap<>();
+ Map<String, Map<Integer, String>> intermediaryToParamsMap = new HashMap<>();
+
+ try {
+ injectMcp(mcpJar, intermediaryToMCPMap, intermediaryToDocsMap, intermediaryToParamsMap);
+ } catch (CsvValidationException e) {
+ throw new RuntimeException(e);
+ }
+
+ mergeTokensIntoIntermediary(intermediaryTiny, intermediaryToMCPMap, intermediaryToDocsMap, intermediaryToParamsMap);
+ return intermediaryTiny;
+ }
+
+ private Map<String, String> createIntermediaryToMCPMap(TinyFile tiny, Map<MemberToken, String> officialToMCP) {
+ Map<String, String> map = new HashMap<>();
+
+ for (TinyClass tinyClass : tiny.getClassEntries()) {
+ String classObf = tinyClass.getMapping().get(0);
+ String classIntermediary = tinyClass.getMapping().get(1);
+ MemberToken classTokenObf = MemberToken.ofClass(classObf);
+
+ if (officialToMCP.containsKey(classTokenObf)) {
+ map.put(classIntermediary, officialToMCP.get(classTokenObf));
+ }
+
+ for (TinyField tinyField : tinyClass.getFields()) {
+ String fieldObf = tinyField.getMapping().get(0);
+ String fieldIntermediary = tinyField.getMapping().get(1);
+ MemberToken fieldTokenObf = MemberToken.ofField(classTokenObf, fieldObf);
+
+ if (officialToMCP.containsKey(fieldTokenObf)) {
+ map.put(fieldIntermediary, officialToMCP.get(fieldTokenObf));
+ }
+ }
+
+ for (TinyMethod tinyMethod : tinyClass.getMethods()) {
+ String methodObf = tinyMethod.getMapping().get(0);
+ String methodIntermediary = tinyMethod.getMapping().get(1);
+ MemberToken methodTokenObf = MemberToken.ofMethod(classTokenObf, methodObf, tinyMethod.getMethodDescriptorInFirstNamespace());
+
+ if (officialToMCP.containsKey(methodTokenObf)) {
+ map.put(methodIntermediary, officialToMCP.get(methodTokenObf));
+ }
+ }
+ }
+
+ return map;
+ }
+
+ private void mergeTokensIntoIntermediary(TinyFile tiny, Map<String, String> intermediaryToMCPMap, Map<String, String[]> intermediaryToDocsMap, Map<String, Map<Integer, String>> intermediaryToParamsMap) {
+ stripTinyWithParametersAndLocal(tiny);
+
+ // We will be adding the "named" namespace with MCP
+ tiny.getHeader().getNamespaces().add("named");
+
+ for (TinyClass tinyClass : tiny.getClassEntries()) {
+ String classIntermediary = tinyClass.getMapping().get(1);
+ tinyClass.getMapping().add(intermediaryToMCPMap.getOrDefault(classIntermediary, classIntermediary));
+
+ for (TinyField tinyField : tinyClass.getFields()) {
+ String fieldIntermediary = tinyField.getMapping().get(1);
+ String[] docs = intermediaryToDocsMap.get(fieldIntermediary);
+ tinyField.getMapping().add(intermediaryToMCPMap.getOrDefault(fieldIntermediary, fieldIntermediary));
+
+ if (docs != null) {
+ tinyField.getComments().clear();
+ tinyField.getComments().addAll(Arrays.asList(docs));
+ }
+ }
+
+ for (TinyMethod tinyMethod : tinyClass.getMethods()) {
+ String methodIntermediary = tinyMethod.getMapping().get(1);
+ String[] docs = intermediaryToDocsMap.get(methodIntermediary);
+ tinyMethod.getMapping().add(intermediaryToMCPMap.getOrDefault(methodIntermediary, methodIntermediary));
+
+ if (docs != null) {
+ tinyMethod.getComments().clear();
+ tinyMethod.getComments().addAll(Arrays.asList(docs));
+ }
+
+ Map<Integer, String> params = intermediaryToParamsMap.get(methodIntermediary);
+
+ if (params != null) {
+ for (Map.Entry<Integer, String> entry : params.entrySet()) {
+ int lvIndex = entry.getKey();
+ String paramName = entry.getValue();
+
+ ArrayList<String> mappings = new ArrayList<>();
+ mappings.add("");
+ mappings.add("");
+ mappings.add(paramName);
+ tinyMethod.getParameters().add(new TinyMethodParameter(lvIndex, mappings, new ArrayList<>()));
+ }
+ }
+ }
+ }
+ }
+
+ private void stripTinyWithParametersAndLocal(TinyFile tiny) {
+ for (TinyClass tinyClass : tiny.getClassEntries()) {
+ for (TinyMethod tinyMethod : tinyClass.getMethods()) {
+ tinyMethod.getParameters().clear();
+ tinyMethod.getLocalVariables().clear();
+ }
+ }
+ }
+
+ private Map<MemberToken, String> readSrg() throws IOException {
+ Map<MemberToken, String> tokens = new HashMap<>();
+
+ try (BufferedReader reader = Files.newBufferedReader(srgTsrgPath, StandardCharsets.UTF_8)) {
+ String content = IOUtils.toString(reader);
+
+ if (content.startsWith("tsrg2")) {
+ readTsrg2(tokens, content);
+ } else {
+ MappingSet mappingSet = new TSrgReader(new StringReader(content)).read();
+
+ for (TopLevelClassMapping classMapping : mappingSet.getTopLevelClassMappings()) {
+ appendClass(tokens, classMapping);
+ }
+ }
+ }
+
+ return tokens;
+ }
+
+ private void readTsrg2(Map<MemberToken, String> tokens, String content) throws IOException {
+ MemoryMappingTree tree = new MemoryMappingTree();
+ MappingReader.read(new StringReader(content), tree);
+ int obfIndex = tree.getNamespaceId("obf");
+ int srgIndex = tree.getNamespaceId("srg");
+
+ for (MappingTree.ClassMapping classDef : tree.getClasses()) {
+ MemberToken ofClass = MemberToken.ofClass(classDef.getName(obfIndex));
+ tokens.put(ofClass, classDef.getName(srgIndex));
+
+ for (MappingTree.FieldMapping fieldDef : classDef.getFields()) {
+ tokens.put(MemberToken.ofField(ofClass, fieldDef.getName(obfIndex)), fieldDef.getName(srgIndex));
+ }
+
+ for (MappingTree.MethodMapping methodDef : classDef.getMethods()) {
+ tokens.put(MemberToken.ofMethod(ofClass, methodDef.getName(obfIndex), methodDef.getDesc(obfIndex)),
+ methodDef.getName(srgIndex));
+ }
+ }
+ }
+
+ private void injectMcp(Path mcpJar, Map<String, String> intermediaryToSrgMap, Map<String, String[]> intermediaryToDocsMap, Map<String, Map<Integer, String>> intermediaryToParamsMap)
+ throws IOException, CsvValidationException {
+ Map<String, List<String>> srgToIntermediary = inverseMap(intermediaryToSrgMap);
+ Map<String, List<String>> simpleSrgToIntermediary = new HashMap<>();
+ Pattern methodPattern = Pattern.compile("(func_\\d*)_.*");
+
+ for (Map.Entry<String, List<String>> entry : srgToIntermediary.entrySet()) {
+ Matcher matcher = methodPattern.matcher(entry.getKey());
+
+ if (matcher.matches()) {
+ simpleSrgToIntermediary.put(matcher.group(1), entry.getValue());
+ }
+ }
+
+ try (FileSystem fs = FileSystems.newFileSystem(mcpJar, (ClassLoader) null)) {
+ Path fields = fs.getPath("fields.csv");
+ Path methods = fs.getPath("methods.csv");
+ Path params = fs.getPath("params.csv");
+ Pattern paramsPattern = Pattern.compile("p_[^\\d]*(\\d+)_(\\d)+_?");
+
+ try (CSVReader reader = new CSVReader(Files.newBufferedReader(fields, StandardCharsets.UTF_8))) {
+ reader.readNext();
+ String[] line;
+
+ while ((line = reader.readNext()) != null) {
+ List<String> intermediaryField = srgToIntermediary.get(line[0]);
+ String[] docs = line[3].split("\n");
+
+ if (intermediaryField != null) {
+ for (String s : intermediaryField) {
+ intermediaryToSrgMap.put(s, line[1]);
+
+ if (!line[3].trim().isEmpty() && docs.length > 0) {
+ intermediaryToDocsMap.put(s, docs);
+ }
+ }
+ }
+ }
+ }
+
+ try (CSVReader reader = new CSVReader(Files.newBufferedReader(methods, StandardCharsets.UTF_8))) {
+ reader.readNext();
+ String[] line;
+
+ while ((line = reader.readNext()) != null) {
+ List<String> intermediaryMethod = srgToIntermediary.get(line[0]);
+ String[] docs = line[3].split("\n");
+
+ if (intermediaryMethod != null) {
+ for (String s : intermediaryMethod) {
+ intermediaryToSrgMap.put(s, line[1]);
+
+ if (!line[3].trim().isEmpty() && docs.length > 0) {
+ intermediaryToDocsMap.put(s, docs);
+ }
+ }
+ }
+ }
+ }
+
+ if (Files.exists(params)) {
+ try (CSVReader reader = new CSVReader(Files.newBufferedReader(params, StandardCharsets.UTF_8))) {
+ reader.readNext();
+ String[] line;
+
+ while ((line = reader.readNext()) != null) {
+ Matcher param = paramsPattern.matcher(line[0]);
+
+ if (param.matches()) {
+ String named = line[1];
+ String srgMethodStartWith = "func_" + param.group(1);
+ int lvIndex = Integer.parseInt(param.group(2));
+ List<String> intermediaryMethod = simpleSrgToIntermediary.get(srgMethodStartWith);
+
+ if (intermediaryMethod != null) {
+ for (String s : intermediaryMethod) {
+ intermediaryToParamsMap.computeIfAbsent(s, s1 -> new HashMap<>()).put(lvIndex, named);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private Map<String, List<String>> inverseMap(Map<String, String> intermediaryToMCPMap) {
+ Map<String, List<String>> map = new HashMap<>();
+
+ for (Map.Entry<String, String> token : intermediaryToMCPMap.entrySet()) {
+ map.computeIfAbsent(token.getValue(), s -> new ArrayList<>()).add(token.getKey());
+ }
+
+ return map;
+ }
+
+ private void appendClass(Map<MemberToken, String> tokens, ClassMapping<?, ?> classMapping) {
+ MemberToken ofClass = MemberToken.ofClass(classMapping.getFullObfuscatedName());
+ tokens.put(ofClass, classMapping.getFullDeobfuscatedName());
+
+ for (FieldMapping fieldMapping : classMapping.getFieldMappings()) {
+ tokens.put(MemberToken.ofField(ofClass, fieldMapping.getObfuscatedName()), fieldMapping.getDeobfuscatedName());
+ }
+
+ for (MethodMapping methodMapping : classMapping.getMethodMappings()) {
+ tokens.put(MemberToken.ofMethod(ofClass, methodMapping.getObfuscatedName(), methodMapping.getObfuscatedDescriptor()), methodMapping.getDeobfuscatedName());
+ }
+
+ for (InnerClassMapping mapping : classMapping.getInnerClassMappings()) {
+ appendClass(tokens, mapping);
+ }
+ }
+
+ private record MemberToken(
+ TokenType type,
+ @Nullable MCPReader.MemberToken owner,
+ String name,
+ @Nullable String descriptor
+ ) {
+ static MemberToken ofClass(String name) {
+ return new MemberToken(TokenType.CLASS, null, name, null);
+ }
+
+ static MemberToken ofField(MemberToken owner, String name) {
+ return new MemberToken(TokenType.FIELD, owner, name, null);
+ }
+
+ static MemberToken ofMethod(MemberToken owner, String name, String descriptor) {
+ return new MemberToken(TokenType.METHOD, owner, name, descriptor);
+ }
+ }
+
+ private enum TokenType {
+ CLASS,
+ METHOD,
+ FIELD
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/srg/MappingException.java b/src/main/java/net/fabricmc/loom/util/srg/MappingException.java
new file mode 100644
index 00000000..5bf2a63b
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/srg/MappingException.java
@@ -0,0 +1,36 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2020-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.util.srg;
+
+/**
+ * An exception that occurs when processing obfuscation mappings.
+ *
+ * @author Juuz
+ */
+public class MappingException extends RuntimeException {
+ public MappingException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java b/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java
new file mode 100644
index 00000000..59392e94
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java
@@ -0,0 +1,148 @@
+/*
+ * 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.util.srg;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import com.google.common.base.Stopwatch;
+import org.apache.commons.io.output.NullOutputStream;
+import org.gradle.api.Project;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.configuration.ShowStacktrace;
+
+import net.fabricmc.loom.LoomGradleExtension;
+import net.fabricmc.loom.configuration.providers.forge.McpConfigProvider.RemapAction;
+import net.fabricmc.loom.util.FileSystemUtil;
+import net.fabricmc.loom.util.ThreadingUtils;
+
+public class SpecialSourceExecutor {
+ private static String trimLeadingSlash(String string) {
+ if (string.startsWith(File.separator)) {
+ return string.substring(File.separator.length());
+ } else if (string.startsWith("/")) {
+ return string.substring(1);
+ }
+
+ return string;
+ }
+
+ public static Path produceSrgJar(RemapAction remapAction, Project project, String side, Set<File> mcLibs, Path officialJar, Path mappings)
+ throws Exception {
+ Set<String> filter = Files.readAllLines(mappings, StandardCharsets.UTF_8).stream()
+ .filter(s -> !s.startsWith("\t"))
+ .map(s -> s.split(" ")[0] + ".class")
+ .collect(Collectors.toSet());
+ LoomGradleExtension extension = LoomGradleExtension.get(project.getProject());
+ Path stripped = extension.getFiles().getProjectBuildCache().toPath().resolve(officialJar.getFileName().toString().substring(0, officialJar.getFileName().toString().length() - 4) + "-filtered.jar");
+ Files.deleteIfExists(stripped);
+
+ Stopwatch stopwatch = Stopwatch.createStarted();
+
+ try (FileSystemUtil.Delegate output = FileSystemUtil.getJarFileSystem(stripped, true)) {
+ try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(officialJar, false)) {
+ ThreadingUtils.TaskCompleter completer = ThreadingUtils.taskCompleter();
+
+ for (Path path : (Iterable<? extends Path>) Files.walk(fs.get().getPath("/"))::iterator) {
+ String trimLeadingSlash = trimLeadingSlash(path.toString());
+ if (!trimLeadingSlash.endsWith(".class")) continue;
+ boolean has = filter.contains(trimLeadingSlash);
+ String s = trimLeadingSlash;
+
+ while (s.contains("$") && !has) {
+ s = s.substring(0, s.lastIndexOf("$")) + ".class";
+ has = filter.contains(s);
+ }
+
+ if (!has) continue;
+ Path to = output.get().getPath(trimLeadingSlash);
+ Path parent = to.getParent();
+ if (parent != null) Files.createDirectories(parent);
+
+ completer.add(() -> {
+ Files.copy(path, to, StandardCopyOption.COPY_ATTRIBUTES);
+ });
+ }
+
+ completer.complete();
+ }
+ } finally {
+ project.getLogger().info("Copied class files in " + stopwatch.stop());
+ }
+
+ Path output = extension.getFiles().getProjectBuildCache().toPath().resolve(officialJar.getFileName().toString().substring(0, officialJar.getFileName().toString().length() - 4) + "-srg-output.jar");
+ Files.deleteIfExists(output);
+ stopwatch = Stopwatch.createStarted();
+
+ List<String> args = remapAction.getArgs(stripped, output, mappings, project.files(mcLibs));
+
+ project.getLogger().lifecycle(":remapping minecraft (" + remapAction + ", " + side + ", official -> mojang)");
+
+ Path workingDir = tmpDir();
+
+ project.javaexec(spec -> {
+ spec.setArgs(args);
+ spec.setClasspath(remapAction.getClasspath());
+ spec.workingDir(workingDir.toFile());
+ spec.getMainClass().set(remapAction.getMainClass());
+
+ // if running with INFO or DEBUG logging
+ if (project.getGradle().getStartParameter().getShowStacktrace() != ShowStacktrace.INTERNAL_EXCEPTIONS
+ || project.getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0) {
+ spec.setStandardOutput(System.out);
+ spec.setErrorOutput(System.err);
+ } else {
+ spec.setStandardOutput(NullOutputStream.NULL_OUTPUT_STREAM);
+ spec.setErrorOutput(NullOutputStream.NULL_OUTPUT_STREAM);
+ }
+ }).rethrowFailure().assertNormalExitValue();
+
+ project.getLogger().lifecycle(":remapped minecraft (" + remapAction + ", " + side + ", official -> mojang) in " + stopwatch.stop());
+
+ Files.deleteIfExists(stripped);
+
+ Path tmp = tmpFile();
+ Files.deleteIfExists(tmp);
+ Files.copy(output, tmp);
+
+ Files.deleteIfExists(output);
+ return tmp;
+ }
+
+ private static Path tmpFile() throws IOException {
+ return Files.createTempFile(null, null);
+ }
+
+ private static Path tmpDir() throws IOException {
+ return Files.createTempDirectory(null);
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java b/src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java
new file mode 100644
index 00000000..ede2dbad
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java
@@ -0,0 +1,406 @@
+/*
+ * This file is part of fabric-loom, licensed under the MIT License (MIT).
+ *
+ * Copyright (c) 2020-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.util.srg;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.UncheckedIOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Stopwatch;
+import org.apache.commons.io.IOUtils;
+import org.gradle.api.logging.Logger;
+import org.jetbrains.annotations.Nullable;
+
+import net.fabricmc.loom.util.function.CollectionUtil;
+import net.fabricmc.mappingio.FlatMappingVisitor;
+import net.fabricmc.mappingio.MappingReader;
+import net.fabricmc.mappingio.adapter.RegularAsFlatMappingVisitor;
+import net.fabricmc.mappingio.format.Tiny2Writer;
+import net.fabricmc.mappingio.format.TsrgReader;
+import net.fabricmc.mappingio.tree.MappingTree;
+import net.fabricmc.mappingio.tree.MappingTreeView;
+import net.fabricmc.mappingio.tree.MemoryMappingTree;
+
+/**
+ * Utilities for merging SRG mappings.
+ *
+ * @author Juuz
+ */
+public final class SrgMerger {
+ private final Logger logger;
+ private final Map<String, List<MappingTreeView.MemberMappingView>> addRegardlessSrg = new HashMap<>();
+ private final MemoryMappingTree srg;
+ private final MemoryMappingTree src;
+ private final MemoryMappingTree output;
+ private final FlatMappingVisitor flatOutput;
+ private final List<Runnable> postProcesses = new ArrayList<>();
+ private final boolean lenient;
+ private final Set<String> methodSrgNames = new HashSet<>();
+
+ public SrgMerger(Logger logger, Path srg, @Nullable Supplier<Path> mojmap, Path tiny, boolean lenient) 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;
+
+ MappingReader.read(tiny, this.src);
+
+ if (!"official".equals(this.src.getSrcNamespace())) {
+ throw new MappingException("Mapping file " + tiny + " does not have the 'official' namespace as the default!");
+ }
+
+ this.output.visitNamespaces(this.src.getSrcNamespace(), Stream.concat(Stream.of("srg"), this.src.getDstNamespaces().stream()).collect(Collectors.toList()));
+ }
+
+ public MemoryMappingTree merge() throws IOException {
+ for (MappingTree.ClassMapping klass : this.srg.getClasses()) {
+ classToTiny(klass);
+ }
+
+ try {
+ for (Runnable process : postProcesses) {
+ process.run();
+ }
+ } catch (UncheckedIOException e) {
+ throw e.getCause();
+ }
+
+ return output;
+ }
+
+ /**
+ * Merges SRG mappings with a tiny mappings tree through the obf names.
+ *
+ * @param srg the SRG file in .tsrg format
+ * @param mojmap the path to the mojmap file used for generating mojmap+srg names, may be null
+ * @param tiny the tiny file
+ * @param out the output file, will be in tiny v2
+ * @param lenient whether to ignore missing tiny mapping
+ * @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)
+ throws IOException, MappingException {
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ MemoryMappingTree tree = mergeSrg(logger, mojmap, srg, tiny, lenient);
+
+ try (Tiny2Writer writer = new Tiny2Writer(Files.newBufferedWriter(out), false)) {
+ tree.accept(writer);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ logger.info(":merged srg mappings in " + stopwatch.stop());
+ }
+
+ /**
+ * Merges SRG mappings with a tiny mappings tree through the obf names.
+ *
+ * @param srg the SRG file in .tsrg format
+ * @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
+ * @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)
+ throws IOException, MappingException {
+ return new SrgMerger(logger, srg, mojmap, tiny, lenient).merge();
+ }
+
+ private MemoryMappingTree readSrg(Path srg, @Nullable Supplier<Path> mojmap) throws IOException {
+ try (BufferedReader reader = Files.newBufferedReader(srg)) {
+ String content = IOUtils.toString(reader);
+
+ if (content.startsWith("tsrg2") && mojmap != null) {
+ addRegardlessSrgs(mojmap);
+ }
+
+ MemoryMappingTree tsrg = new MemoryMappingTree();
+ TsrgReader.read(new StringReader(content), tsrg);
+ return tsrg;
+ }
+ }
+
+ private void addRegardlessSrgs(Supplier<Path> mojmap) throws IOException {
+ MemoryMappingTree mojmapTree = readTsrg2ToTinyTree(mojmap.get());
+
+ for (MappingTree.ClassMapping classDef : mojmapTree.getClasses()) {
+ for (MappingTree.MethodMapping methodDef : classDef.getMethods()) {
+ String name = methodDef.getSrcName();
+
+ if (name.indexOf('<') != 0 && name.equals(methodDef.getDstName(0))) {
+ addRegardlessSrg.computeIfAbsent(classDef.getSrcName(), $ -> new ArrayList<>()).add(methodDef);
+ }
+ }
+
+ for (MappingTree.FieldMapping fieldDef : classDef.getFields()) {
+ if (fieldDef.getSrcName().equals(fieldDef.getDstName(0))) {
+ addRegardlessSrg.computeIfAbsent(classDef.getSrcName(), $ -> new ArrayList<>()).add(fieldDef);
+ }
+ }
+ }
+ }
+
+ private static MemoryMappingTree readTsrg2ToTinyTree(Path path) throws IOException {
+ MemoryMappingTree tree = new MemoryMappingTree();
+
+ try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
+ MappingReader.read(reader, tree);
+ }
+
+ return tree;
+ }
+
+ private void classToTiny(MappingTree.ClassMapping klass) throws IOException {
+ String obf = klass.getSrcName();
+ String srg = klass.getDstName(0);
+ MappingTree.ClassMapping classDef = this.src.getClass(obf);
+
+ if (classDef == null) {
+ if (lenient) {
+ return;
+ } else {
+ throw new MappingException("Missing class: " + obf + " (srg: " + srg + ")");
+ }
+ }
+
+ List<String> classNames = CollectionUtil.map(
+ output.getDstNamespaces(),
+ namespace -> "srg".equals(namespace) ? srg : classDef.getName(namespace)
+ );
+
+ flatOutput.visitClass(obf, classNames.toArray(new String[0]));
+
+ if (classDef.getComment() != null) {
+ flatOutput.visitClassComment(obf, classDef.getComment());
+ }
+
+ for (MappingTree.MethodMapping method : klass.getMethods()) {
+ MappingTree.MethodMapping def = CollectionUtil.find(
+ classDef.getMethods(),
+ m -> m.getName("official").equals(method.getSrcName()) && m.getDesc("official").equals(method.getSrcDesc())
+ ).orElse(null);
+
+ String methodSrgName = method.getDstName(0);
+
+ if (def == null) {
+ if (lenient) {
+ // TODO Big Hack!
+ // We are checking if there are methods with the same srg name that are already added, if it is, then we would not fill in these names
+ // This is especially troublesome with methods annotated with @DontObfuscate (e.g. m_129629_)
+ // with environments like yarn where methods with the same srg name may not inherit the same names due to parameter mappings and inheritance
+ // This requires further testing!
+ postProcesses.add(() -> {
+ if (!methodSrgNames.contains(methodSrgName)) {
+ List<String> methodNames = CollectionUtil.map(
+ output.getDstNamespaces(),
+ namespace -> "srg".equals(namespace) ? methodSrgName : method.getSrcName()
+ );
+
+ try {
+ flatOutput.visitMethod(obf, method.getSrcName(), method.getSrcDesc(), methodNames.toArray(new String[0]));
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+ });
+ } else {
+ throw new MappingException("Missing method: " + method.getSrcName() + " (srg: " + methodSrgName + ")");
+ }
+
+ continue;
+ }
+
+ methodToTiny(obf, method, methodSrgName, def);
+
+ if (methodSrgName.startsWith("func_") || methodSrgName.startsWith("m_")) {
+ methodSrgNames.add(methodSrgName);
+ }
+ }
+
+ postProcesses.add(() -> {
+ // TODO: This second iteration seems a bit wasteful.
+ // Is it possible to just iterate this and leave SRG out?
+ for (MappingTree.MethodMapping def : classDef.getMethods()) {
+ // If obf = some other name: some special name that srg might not contain.
+ // This includes constructors and overridden JDK methods.
+ if (!def.getSrcName().equals(def.getDstName(0))) {
+ continue;
+ }
+
+ MappingTree.MethodMapping method = CollectionUtil.find(
+ klass.getMethods(),
+ m -> m.getSrcName().equals(def.getName("official")) && m.getSrcDesc().equals(def.getDesc("official"))
+ ).orElse(null);
+
+ if (method == null) {
+ try {
+ methodToTiny(obf, null, def.getSrcName(), def);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+ }
+ });
+
+ for (MappingTree.FieldMapping field : klass.getFields()) {
+ MappingTree.FieldMapping def = CollectionUtil.find(
+ classDef.getFields(),
+ f -> f.getName("official").equals(field.getSrcName())
+ ).orElse(nullOrThrow(() -> new MappingException("Missing field: " + field.getSrcName() + " (srg: " + field.getDstName(0) + ")")));
+
+ if (def == null) {
+ if (tryMatchRegardlessSrgsField(obf, field)) {
+ List<String> fieldNames = CollectionUtil.map(
+ output.getDstNamespaces(),
+ namespace -> "srg".equals(namespace) ? field.getDstName(0) : field.getSrcName()
+ );
+
+ flatOutput.visitField(obf, field.getSrcName(), field.getSrcDesc(), fieldNames.toArray(new String[0]));
+ }
+
+ continue;
+ }
+
+ List<String> fieldNames = CollectionUtil.map(
+ output.getDstNamespaces(),
+ namespace -> "srg".equals(namespace) ? field.getDstName(0) : def.getName(namespace)
+ );
+
+ flatOutput.visitField(obf, def.getName("official"), def.getDesc("official"), fieldNames.toArray(new String[0]));
+
+ if (def.getComment() != null) {
+ flatOutput.visitFieldComment(obf, def.getName("official"), def.getDesc("official"), def.getComment());
+ }
+ }
+ }
+
+ private void methodToTiny(String obfClassName, @Nullable MappingTree.MethodMapping srgMethod, @Nullable String srgMethodName, MappingTree.MethodMapping actualMethod)
+ throws IOException {
+ if (srgMethod != null && srgMethodName != null) {
+ srgMethodName = srgMethod.getDstName(0);
+ }
+
+ String finalSrgMethodName = srgMethodName;
+ List<String> methodNames = CollectionUtil.map(
+ output.getDstNamespaces(),
+ namespace -> "srg".equals(namespace) ? finalSrgMethodName : actualMethod.getName(namespace)
+ );
+
+ flatOutput.visitMethod(obfClassName, actualMethod.getName("official"), actualMethod.getDesc("official"), methodNames.toArray(new String[0]));
+
+ if (actualMethod.getComment() != null) {
+ flatOutput.visitMethodComment(obfClassName, actualMethod.getName("official"), actualMethod.getDesc("official"), actualMethod.getComment());
+ }
+
+ for (MappingTree.MethodArgMapping arg : actualMethod.getArgs()) {
+ MappingTree.MethodArgMapping srgArg = srgMethod != null ? srgMethod.getArg(arg.getArgPosition(), arg.getLvIndex(), arg.getName("official")) : null;
+ String srgName = srgArg != null ? srgArg.getDstName(0) : null;
+ List<String> argNames = CollectionUtil.map(
+ output.getDstNamespaces(),
+ namespace -> "srg".equals(namespace) ? srgName : arg.getName(namespace)
+ );
+
+ flatOutput.visitMethodArg(obfClassName, actualMethod.getName("official"), actualMethod.getDesc("official"), arg.getArgPosition(), arg.getLvIndex(), arg.getName("official"), argNames.toArray(new String[0]));
+
+ if (arg.getComment() != null) {
+ flatOutput.visitMethodArgComment(obfClassName, actualMethod.getName("official"), actualMethod.getDesc("official"), arg.getArgPosition(), arg.getLvIndex(), arg.getName("official"), arg.getComment());
+ }
+ }
+
+ for (MappingTree.MethodVarMapping var : actualMethod.getVars()) {
+ MappingTree.MethodVarMapping srgVar = srgMethod != null ? srgMethod.getVar(var.getLvtRowIndex(), var.getLvIndex(), var.getStartOpIdx(), var.getName("official")) : null;
+ String srgName = srgVar != null ? srgVar.getDstName(0) : null;
+ List<String> varNames = CollectionUtil.map(
+ output.getDstNamespaces(),
+ namespace -> "srg".equals(namespace) ? srgName : var.getName(namespace)
+ );
+
+ flatOutput.visitMethodVar(obfClassName, actualMethod.getName("official"), actualMethod.getDesc("official"), var.getLvtRowIndex(), var.getLvIndex(), var.getStartOpIdx(), var.getName("official"), varNames.toArray(new String[0]));
+
+ if (var.getComment() != null) {
+ flatOutput.visitMethodVarComment(obfClassName, actualMethod.getName("official"), actualMethod.getDesc("official"), var.getLvtRowIndex(), var.getLvIndex(), var.getStartOpIdx(), var.getName("official"), var.getComment());
+ }
+ }
+ }
+
+ private boolean tryMatchRegardlessSrgsMethod(String obf, MappingTree.MethodMapping method) {
+ List<MappingTreeView.MemberMappingView> mutableDescriptoredList = addRegardlessSrg.get(obf);
+
+ if (!method.getDstName(0).equals(method.getSrcName())) {
+ for (MappingTreeView.MemberMappingView descriptored : MoreObjects.firstNonNull(mutableDescriptoredList, Collections.<MappingTreeView.MemberMappingView>emptyList())) {
+ if (descriptored instanceof MappingTree.MethodMapping && descriptored.getSrcName().equals(method.getSrcName()) && descriptored.getSrcDesc().equals(method.getSrcDesc())) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private boolean tryMatchRegardlessSrgsField(String obf, MappingTree.FieldMapping field) {
+ List<MappingTreeView.MemberMappingView> mutableDescriptoredList = addRegardlessSrg.get(obf);
+
+ if (!field.getDstName(0).equals(field.getSrcName())) {
+ for (MappingTreeView.MemberMappingView descriptored : MoreObjects.firstNonNull(mutableDescriptoredList, Collections.<MappingTreeView.MemberMappingView>emptyList())) {
+ if (descriptored instanceof MappingTree.FieldMapping && descriptored.getSrcName().equals(field.getSrcName())) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ @Nullable
+ private <T, X extends Exception> T nullOrThrow(Supplier<X> exception) throws X {
+ if (lenient) {
+ return null;
+ } else {
+ throw exception.get();
+ }
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/srg/SrgNamedWriter.java b/src/main/java/net/fabricmc/loom/util/srg/SrgNamedWriter.java
new file mode 100644
index 00000000..456b8b1b
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/srg/SrgNamedWriter.java
@@ -0,0 +1,47 @@
+/*
+ * 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.util.srg;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+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 {
+ Files.deleteIfExists(srgFile);
+
+ try (SrgWriter writer = new SrgWriter(Files.newBufferedWriter(srgFile))) {
+ try (TinyMappingsReader reader = new TinyMappingsReader(mappings, from, to)) {
+ writer.write(reader.read());
+ }
+ }
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/srg/Tsrg2Utils.java b/src/main/java/net/fabricmc/loom/util/srg/Tsrg2Utils.java
new file mode 100644
index 00000000..4ed3a884
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/srg/Tsrg2Utils.java
@@ -0,0 +1,144 @@
+/*
+ * 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.util.srg;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+import org.cadixdev.lorenz.MappingSet;
+import org.cadixdev.lorenz.io.srg.tsrg.TSrgWriter;
+import org.cadixdev.lorenz.model.ClassMapping;
+import org.cadixdev.lorenz.model.MethodMapping;
+
+import net.fabricmc.mappingio.MappingVisitor;
+import net.fabricmc.mappingio.MappingWriter;
+import net.fabricmc.mappingio.adapter.ForwardingMappingVisitor;
+import net.fabricmc.mappingio.tree.MappingTree;
+import net.fabricmc.mappingio.tree.MemoryMappingTree;
+
+public class Tsrg2Utils {
+ public static void writeTsrg(Consumer<MappingVisitor> visitorConsumer, String dstNamespace, boolean applyParameterMappings, Writer writer)
+ throws IOException {
+ MappingSet set;
+
+ try (MappingsIO2LorenzWriter lorenzWriter = new MappingsIO2LorenzWriter(dstNamespace, applyParameterMappings)) {
+ visitorConsumer.accept(lorenzWriter);
+ set = lorenzWriter.read();
+ }
+
+ try (TSrgWriter w = new TSrgWriter(writer)) {
+ w.write(set);
+ }
+ }
+
+ // TODO Move this elsewhere
+ public abstract static class MappingsIO2Others extends ForwardingMappingVisitor implements MappingWriter {
+ public MappingsIO2Others() {
+ super(new MemoryMappingTree());
+ }
+
+ public MappingTree tree() {
+ return (MappingTree) next;
+ }
+
+ @Override
+ public void close() throws IOException {
+ MappingTree tree = tree();
+ List<String> names = new ArrayList<>();
+
+ for (MappingTree.ClassMapping aClass : tree.getClasses()) {
+ names.add(aClass.getSrcName());
+ }
+
+ for (String name : names) {
+ tree.removeClass(name);
+ }
+ }
+ }
+
+ public static class MappingsIO2LorenzWriter extends MappingsIO2Others {
+ private final Object dstNamespaceUnresolved;
+ private int dstNamespace;
+ private boolean applyParameterMappings;
+
+ public MappingsIO2LorenzWriter(int dstNamespace, boolean applyParameterMappings) {
+ this.dstNamespaceUnresolved = dstNamespace;
+ this.applyParameterMappings = applyParameterMappings;
+ }
+
+ public MappingsIO2LorenzWriter(String dstNamespace, boolean applyParameterMappings) {
+ this.dstNamespaceUnresolved = dstNamespace;
+ this.applyParameterMappings = applyParameterMappings;
+ }
+
+ @Override
+ public void visitNamespaces(String srcNamespace, List<String> dstNamespaces) throws IOException {
+ super.visitNamespaces(srcNamespace, dstNamespaces);
+ this.dstNamespace = dstNamespaceUnresolved instanceof Integer ? (Integer) dstNamespaceUnresolved : dstNamespaces.indexOf((String) dstNamespaceUnresolved);
+ }
+
+ public MappingSet read() throws IOException {
+ return this.read(MappingSet.create());
+ }
+
+ public MappingSet read(final MappingSet mappings) throws IOException {
+ MappingTree tree = tree();
+
+ for (MappingTree.ClassMapping aClass : tree.getClasses()) {
+ ClassMapping<?, ?> lClass = mappings.getOrCreateClassMapping(aClass.getSrcName())
+ .setDeobfuscatedName(aClass.getDstName(dstNamespace));
+
+ for (MappingTree.FieldMapping aField : aClass.getFields()) {
+ String srcDesc = aField.getSrcDesc();
+
+ if (srcDesc == null || srcDesc.isEmpty()) {
+ lClass.getOrCreateFieldMapping(aField.getSrcName())
+ .setDeobfuscatedName(aField.getDstName(dstNamespace));
+ } else {
+ lClass.getOrCreateFieldMapping(aField.getSrcName(), srcDesc)
+ .setDeobfuscatedName(aField.getDstName(dstNamespace));
+ }
+ }
+
+ for (MappingTree.MethodMapping aMethod : aClass.getMethods()) {
+ MethodMapping lMethod = lClass.getOrCreateMethodMapping(aMethod.getSrcName(), aMethod.getSrcDesc())
+ .setDeobfuscatedName(aMethod.getDstName(dstNamespace));
+
+ if (applyParameterMappings) {
+ for (MappingTree.MethodArgMapping aArg : aMethod.getArgs()) {
+ lMethod.getOrCreateParameterMapping(aArg.getLvIndex())
+ .setDeobfuscatedName(aArg.getDstName(dstNamespace));
+ }
+ }
+ }
+ }
+
+ return mappings;
+ }
+ }
+}
diff --git a/src/main/java/net/fabricmc/loom/util/srg/Tsrg2Writer.java b/src/main/java/net/fabricmc/loom/util/srg/Tsrg2Writer.java
new file mode 100644
index 00000000..7463e447
--- /dev/null
+++ b/src/main/java/net/fabricmc/loom/util/srg/Tsrg2Writer.java
@@ -0,0 +1,114 @@
+/*
+ * 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.util.srg;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import net.fabricmc.mappingio.tree.MappingTree;
+import net.fabricmc.mappingio.tree.MappingTreeView;
+
+public class Tsrg2Writer {
+ public Tsrg2Writer() {
+ }
+
+ public static String serialize(MappingTree tree) {
+ List<String> namespaces = Stream.concat(Stream.of(tree.getSrcNamespace()), tree.getDstNamespaces().stream()).collect(Collectors.toList());
+ StringBuilder builder = new StringBuilder();
+ writeHeader(namespaces, builder);
+
+ for (MappingTree.ClassMapping classMapping : tree.getClasses()) {
+ writeClass(namespaces, classMapping, builder);
+ }
+
+ return builder.toString();
+ }
+
+ private static void writeClass(List<String> namespaces, MappingTree.ClassMapping def, StringBuilder builder) {
+ writeMapped(null, namespaces, def, builder);
+
+ for (MappingTree.MethodMapping method : def.getMethods()) {
+ writeMethod(namespaces, method, builder);
+ }
+
+ for (MappingTree.FieldMapping field : def.getFields()) {
+ writeMapped('\t', namespaces, field, builder);
+ }
+ }
+
+ private static void writeMethod(List<String> namespaces, MappingTree.MethodMapping def, StringBuilder builder) {
+ writeMapped('\t', namespaces, def, builder);
+
+ for (MappingTree.MethodArgMapping arg : def.getArgs()) {
+ builder.append("\t\t").append(arg.getLvIndex());
+ writeMapped(' ', namespaces, arg, builder);
+ }
+ }
+
+ private static void writeField(List<String> namespaces, MappingTree.FieldMapping def, StringBuilder builder) {
+ writeMapped('\t', namespaces, def, builder);
+ }
+
+ private static void writeMapped(Character first, List<String> namespaces, MappingTreeView.ElementMappingView mapped, StringBuilder builder) {
+ String[] names = namespaces.stream().map(mapped::getName).toArray(String[]::new);
+
+ for (int i = 0; i < names.length; ++i) {
+ String name = names[i];
+
+ if (i == 0) {
+ if (first != null) {
+ builder.append(first);
+ }
+ } else {
+ builder.append(' ');
+ }
+
+ builder.append(name);
+
+ if (i == 0 && mapped instanceof MappingTreeView.MemberMappingView) {
+ String descriptor = ((MappingTreeView.MemberMappingView) mapped).getSrcDesc();
+
+ if (descriptor != null && !descriptor.isEmpty()) {
+ builder.append(' ');
+ builder.append(descriptor);
+ }
+ }
+ }
+
+ builder.append('\n');
+ }
+
+ private static void writeHeader(List<String> namespaces, StringBuilder builder) {
+ builder.append("tsrg2");
+
+ for (String namespace : namespaces) {
+ builder.append(' ');
+ builder.append(namespace);
+ }
+
+ builder.append('\n');
+ }
+}