diff --git a/src/main/java/net/fabricmc/loom/api/ForgeExtensionAPI.java b/src/main/java/net/fabricmc/loom/api/ForgeExtensionAPI.java index e455d742f..6b77f8420 100644 --- a/src/main/java/net/fabricmc/loom/api/ForgeExtensionAPI.java +++ b/src/main/java/net/fabricmc/loom/api/ForgeExtensionAPI.java @@ -33,6 +33,8 @@ import org.gradle.api.provider.SetProperty; import org.jetbrains.annotations.ApiStatus; +import net.fabricmc.loom.configuration.providers.forge.fg2.Pack200Provider; + /** * This is the forge extension api available exposed to build scripts. */ @@ -148,4 +150,6 @@ interface DataGenConsumer { * @see ForgeLocalMod */ NamedDomainObjectContainer getLocalMods(); + + Property getPack200Provider(); } diff --git a/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java b/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java index 1e939f3ce..18a7f2e11 100644 --- a/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java +++ b/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java @@ -270,4 +270,6 @@ default void addTaskBeforeRun(String task) { ForgeExtensionAPI getForge(); void forge(Action action); + + Property getStubIntermediaries(); } diff --git a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java index eaac38ad3..88ac119b2 100644 --- a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java @@ -191,6 +191,7 @@ public static void configureCompile(Project p) { if (extension.isForge()) { dependencyManager.addProvider(new ForgeProvider(project)); dependencyManager.addProvider(new ForgeUserdevProvider(project)); + dependencyManager.addProvider(new ForgeUniversalProvider(project)); } if (extension.shouldGenerateSrgTiny()) { @@ -200,7 +201,6 @@ public static void configureCompile(Project p) { if (extension.isForge()) { dependencyManager.addProvider(new McpConfigProvider(project)); dependencyManager.addProvider(new PatchProvider(project)); - dependencyManager.addProvider(new ForgeUniversalProvider(project)); } dependencyManager.addProvider(extension.isForge() ? new FieldMigratedMappingsProvider(project) : new MappingsProviderImpl(project)); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/LaunchProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/LaunchProvider.java index 2ea5c812f..d05dd9ff0 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/LaunchProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/LaunchProvider.java @@ -104,7 +104,11 @@ public void provide(DependencyInfo dependency, Consumer postPopulation } addDependency(Constants.Dependencies.DEV_LAUNCH_INJECTOR + Constants.Dependencies.Versions.DEV_LAUNCH_INJECTOR, Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES); - addDependency(Constants.Dependencies.TERMINAL_CONSOLE_APPENDER + Constants.Dependencies.Versions.TERMINAL_CONSOLE_APPENDER, Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES); + + if (!getExtension().isForge() || !getExtension().getForgeProvider().isFG2()) { + addDependency(Constants.Dependencies.TERMINAL_CONSOLE_APPENDER + Constants.Dependencies.Versions.TERMINAL_CONSOLE_APPENDER, Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES); + } + addDependency(Constants.Dependencies.JETBRAINS_ANNOTATIONS + Constants.Dependencies.Versions.JETBRAINS_ANNOTATIONS, JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME); if (getExtension().isForge()) { diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java index b2ebfa714..9651769af 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java @@ -56,6 +56,7 @@ import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.LoomGradlePlugin; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; +import net.fabricmc.loom.configuration.providers.forge.fg3.MinecraftPatchedProviderFG3; import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl; import net.fabricmc.loom.util.FileSystemUtil; import net.fabricmc.loom.util.ThreadingUtils; @@ -116,7 +117,7 @@ public void manipulateMappings(Path mappingsJar) throws IOException { if (getExtension().shouldGenerateSrgTiny()) { if (Files.notExists(rawTinyMappingsWithSrg) || isRefreshDeps()) { // Merge tiny mappings with srg - SrgMerger.mergeSrg(getProject().getLogger(), getExtension().getMappingsProvider()::getMojmapSrgFileIfPossible, getRawSrgFile(), rawTinyMappings, rawTinyMappingsWithSrg, true); + SrgMerger.mergeSrg(getProject().getLogger(), getExtension().getMappingsProvider()::getMojmapSrgFileIfPossible, getRawSrgFile(), rawTinyMappings, rawTinyMappingsWithSrg, true, extension.getSrgProvider().isLegacy()); } } @@ -203,20 +204,25 @@ public FieldVisitor visitField(int access, String name, String descriptor, Strin Visitor visitor = new Visitor(Opcodes.ASM9); - for (MinecraftPatchedProvider.Environment environment : MinecraftPatchedProvider.Environment.values()) { - File patchedSrgJar = environment.patchedSrgJar.apply(extension.getMappingsProvider().patchedProvider); - FileSystemUtil.Delegate system = FileSystemUtil.getJarFileSystem(patchedSrgJar, false); - completer.onComplete(value -> system.close()); - - for (Path fsPath : (Iterable) Files.walk(system.get().getPath("/"))::iterator) { - if (Files.isRegularFile(fsPath) && fsPath.toString().endsWith(".class")) { - completer.add(() -> { - byte[] bytes = Files.readAllBytes(fsPath); - new ClassReader(bytes).accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); - }); + if (extension.getMappingsProvider().patchedProvider instanceof MinecraftPatchedProviderFG3 pp3) { + for (MinecraftPatchedProviderFG3.Environment environment : MinecraftPatchedProviderFG3.Environment.values()) { + File patchedSrgJar = environment.patchedSrgJar.apply(pp3); + FileSystemUtil.Delegate system = FileSystemUtil.getJarFileSystem(patchedSrgJar, false); + completer.onComplete(value -> system.close()); + + for (Path fsPath : (Iterable) Files.walk(system.get().getPath("/"))::iterator) { + if (Files.isRegularFile(fsPath) && fsPath.toString().endsWith(".class")) { + completer.add(() -> { + byte[] bytes = Files.readAllBytes(fsPath); + new ClassReader(bytes).accept( + visitor, + ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES + ); + }); + } } } - } + } //TODO: DOES FG2 NEED THIS???? completer.complete(); Map migratedFields = new HashMap<>(); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeProvider.java index 9e1e4a7a5..e734723c6 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeProvider.java @@ -37,6 +37,8 @@ public class ForgeProvider extends DependencyProvider { private File globalCache; private File projectCache; + private boolean fg2 = false; + public ForgeProvider(Project project) { super(project); } @@ -75,6 +77,14 @@ public String getTargetConfig() { return Constants.Configurations.FORGE; } + public boolean isFG2() { + return fg2; + } + + public void setFg2(boolean fg2) { + this.fg2 = fg2; + } + public static final class ForgeVersion { private final String combined; private final String minecraftVersion; diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java index 358fd1109..a42ab20aa 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java @@ -35,6 +35,7 @@ import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Set; @@ -99,7 +100,13 @@ public void provide(DependencyInfo dependency, Consumer postPopulation Files.copy(resolved.toPath(), userdevJar.toPath(), StandardCopyOption.REPLACE_EXISTING); try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + resolved.toURI()), ImmutableMap.of("create", false))) { - Files.copy(fs.getPath("config.json"), configJson, StandardCopyOption.REPLACE_EXISTING); + if (Files.exists(fs.getPath("config.json"))) { + //fg3+ + Files.copy(fs.getPath("config.json"), configJson, StandardCopyOption.REPLACE_EXISTING); + } else { + //fg2 + Files.copy(fs.getPath("dev.json"), configJson, StandardCopyOption.REPLACE_EXISTING); + } } } @@ -107,6 +114,59 @@ public void provide(DependencyInfo dependency, Consumer postPopulation json = new Gson().fromJson(reader, JsonObject.class); } + boolean fg2 = !json.has("mcp"); + getExtension().getForgeProvider().setFg2(fg2); + + if (fg2) { + getProject().getLogger().info("FG2 Userdev, using default mcp_config/universal..."); + + String defaultMCPPath = "de.oceanlabs.mcp:mcp:" + getExtension().getMinecraftProvider().minecraftVersion() + ":srg@zip"; + String defaultUniversalPath = "net.minecraftforge:forge:" + dependency.getResolvedVersion() + ":universal"; + + getProject().getLogger().info("Using default MCP path: " + defaultMCPPath); + getProject().getLogger().info("Using default Universal path: " + defaultUniversalPath); + + addDependency(defaultMCPPath, Constants.Configurations.MCP_CONFIG); + addDependency(defaultMCPPath, Constants.Configurations.SRG); + addDependency(defaultUniversalPath, Constants.Configurations.FORGE_UNIVERSAL); + + for (JsonElement lib : json.getAsJsonArray("libraries")) { + JsonObject libObj = lib.getAsJsonObject(); + + Dependency dep = addDependency(libObj.get("name").getAsString(), Constants.Configurations.FORGE_DEPENDENCIES); + + if (libObj.get("name").getAsString().split(":").length < 4) { + ((ModuleDependency) dep).attributes(attributes -> { + attributes.attribute(transformed, true); + }); + } + } + + for (String name : new String[] {"client", "server"}) { + LaunchProviderSettings launchSettings = getExtension().getLaunchConfigs().findByName(name); + RunConfigSettings settings = getExtension().getRunConfigs().findByName(name); + + if (launchSettings != null) { + launchSettings.evaluateLater(() -> { + launchSettings.arg(Arrays.stream(json.get("minecraftArguments").getAsString().split(" ")).map(this::processTemplates).collect(Collectors.toList())); + + // add missing args + launchSettings.arg("--accessToken", "FML"); + }); + } + + if (settings != null) { + settings.evaluateLater(() -> { + settings.defaultMainClass("net.minecraft.launchwrapper.Launch"); + }); + } + } + + return; + } + + getProject().getLogger().info("FG3+ Userdev"); + addDependency(json.get("mcp").getAsString(), Constants.Configurations.MCP_CONFIG); addDependency(json.get("mcp").getAsString(), Constants.Configurations.SRG); addDependency(json.get("universal").getAsString(), Constants.Configurations.FORGE_UNIVERSAL); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java index 7404cc0a6..a1669c462 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java @@ -68,6 +68,18 @@ public void provide(DependencyInfo dependency, Consumer postPopulation Path mcpZip = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve MCPConfig")).toPath(); + if (getExtension().getForgeProvider().isFG2()) { + official = false; + mappingsPath = ZipUtils.contains(mcpZip, "joined.srg") ? "joined.srg" : "config/joined.tsrg"; + remapAction = null; + + if (!Files.exists(mcp) || isRefreshDeps()) { + Files.copy(mcpZip, mcp, StandardCopyOption.REPLACE_EXISTING); + } + + return; + } + if (!Files.exists(mcp) || !Files.exists(configJson) || isRefreshDeps()) { Files.copy(mcpZip, mcp, StandardCopyOption.REPLACE_EXISTING); Files.write(configJson, ZipUtils.unpack(mcp, "config.json"), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java index fadf7d337..8f1144be3 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java @@ -31,7 +31,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.io.PrintStream; import java.io.UncheckedIOException; import java.net.URI; import java.nio.file.FileSystem; @@ -45,7 +44,6 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; -import java.util.Locale; import java.util.Objects; import java.util.Set; import java.util.concurrent.CompletableFuture; @@ -67,7 +65,6 @@ import dev.architectury.tinyremapper.InputTag; import dev.architectury.tinyremapper.OutputConsumerPath; import dev.architectury.tinyremapper.TinyRemapper; -import net.minecraftforge.binarypatcher.ConsoleTool; import org.apache.commons.io.output.NullOutputStream; import org.gradle.api.Project; import org.gradle.api.file.ConfigurableFileCollection; @@ -96,38 +93,34 @@ import net.fabricmc.loom.util.ZipUtils; import net.fabricmc.loom.util.function.FsPathConsumer; import net.fabricmc.loom.util.srg.InnerClassRemapper; -import net.fabricmc.loom.util.srg.SpecialSourceExecutor; import net.fabricmc.mappingio.tree.MemoryMappingTree; -public class MinecraftPatchedProvider extends DependencyProvider { +public abstract class MinecraftPatchedProvider extends DependencyProvider { private static final String LOOM_PATCH_VERSION_KEY = "Loom-Patch-Version"; private static final String CURRENT_LOOM_PATCH_VERSION = "5"; private static final String NAME_MAPPING_SERVICE_PATH = "/inject/META-INF/services/cpw.mods.modlauncher.api.INameMappingService"; + protected final String accessTransformerPath; - // Step 1: Remap Minecraft to SRG (global) - private File minecraftClientSrgJar; - private File minecraftServerSrgJar; - // Step 2: Binary Patch (global) - private File minecraftClientPatchedSrgJar; - private File minecraftServerPatchedSrgJar; - // Step 3: Merge (global) - private File minecraftMergedPatchedSrgJar; - // Step 4: Access Transform (global or project) - private File minecraftMergedPatchedSrgAtJar; - // Step 5: Remap Patched AT & Forge to Official (global or project) - private File minecraftMergedPatchedJar; - private File forgeMergedJar; - private File minecraftClientExtra; - - private File projectAtHash; - private Set projectAts = new HashSet<>(); - private boolean atDirty = false; - private boolean filesDirty = false; - private Path mcpConfigMappings; - private Path[] mergedMojangTsrg2Files; - - public MinecraftPatchedProvider(Project project) { + // step 3 - fg2: srg transform, fg3: merge + protected File minecraftMergedPatchedSrgJar; + + // step 4 - Access transform + protected File minecraftMergedPatchedSrgAtJar; + + // step 5 - Official Mapped Patched AT & Forge + protected File minecraftMergedPatchedAtJar; + protected File forgeMergedJar; + protected File minecraftClientExtra; + protected File projectAtHash; + + protected Set projectAts = new HashSet<>(); + protected boolean atDirty = false; + protected boolean filesDirty = false; + protected Path mcpConfigMappings; + + protected MinecraftPatchedProvider(Project project, String accessTransformerPath) { super(project); + this.accessTransformerPath = accessTransformerPath; } public void initFiles() throws IOException { @@ -178,22 +171,11 @@ public void initFiles() throws IOException { File projectDir = usesProjectCache() ? getExtension().getForgeProvider().getProjectCache() : globalCache; projectDir.mkdirs(); - minecraftClientSrgJar = new File(globalCache, "minecraft-client-srg.jar"); - minecraftServerSrgJar = new File(globalCache, "minecraft-server-srg.jar"); - minecraftClientPatchedSrgJar = new File(globalCache, "client-srg-patched.jar"); - minecraftServerPatchedSrgJar = new File(globalCache, "server-srg-patched.jar"); - minecraftMergedPatchedSrgJar = new File(globalCache, "merged-srg-patched.jar"); forgeMergedJar = getExtension().isForgeAndOfficial() ? null : new File(globalCache, "forge-official.jar"); - minecraftMergedPatchedSrgAtJar = new File(projectDir, "merged-srg-at-patched.jar"); - minecraftMergedPatchedJar = new File(projectDir, "merged-patched.jar"); + minecraftMergedPatchedAtJar = new File(projectDir, "merged-at-patched.jar"); minecraftClientExtra = new File(globalCache, "forge-client-extra.jar"); - - if (isRefreshDeps() || Stream.of(getGlobalCaches()).anyMatch(((Predicate) File::exists).negate()) - || !isPatchedJarUpToDate(minecraftMergedPatchedJar)) { - cleanAllCache(); - } else if (atDirty || Stream.of(getProjectCache()).anyMatch(((Predicate) File::exists).negate())) { - cleanProjectCache(); - } + minecraftMergedPatchedSrgJar = new File(globalCache, "merged-srg-patched.jar"); + minecraftMergedPatchedSrgAtJar = new File(projectDir, "merged-srg-at-patched.jar"); } private byte[] getProjectAtsHash() throws IOException { @@ -215,18 +197,13 @@ public void cleanAllCache() { cleanProjectCache(); } - private File[] getGlobalCaches() { + protected File[] getGlobalCaches() { File[] files = { - minecraftClientSrgJar, - minecraftServerSrgJar, - minecraftClientPatchedSrgJar, - minecraftServerPatchedSrgJar, - minecraftMergedPatchedSrgJar, - minecraftClientExtra, + minecraftClientExtra }; if (forgeMergedJar != null) { - Arrays.copyOf(files, files.length + 1); + files = Arrays.copyOf(files, files.length + 1); files[files.length - 1] = forgeMergedJar; } @@ -239,83 +216,54 @@ public void cleanProjectCache() { } } - private File[] getProjectCache() { + protected File[] getProjectCache() { return new File[] { minecraftMergedPatchedSrgAtJar, - minecraftMergedPatchedJar + minecraftMergedPatchedAtJar }; } - private boolean dirty; - @Override public void provide(DependencyInfo dependency, Consumer postPopulationScheduler) throws Exception { initFiles(); - - if (atDirty) { - getProject().getLogger().lifecycle(":found dirty access transformers"); - } - - this.dirty = false; - - if (!minecraftClientSrgJar.exists() || !minecraftServerSrgJar.exists()) { - this.dirty = true; - // Remap official jars to MCPConfig remapped srg jars - createSrgJars(getProject().getLogger()); - } - - if (!minecraftClientPatchedSrgJar.exists() || !minecraftServerPatchedSrgJar.exists()) { - this.dirty = true; - patchJars(getProject().getLogger()); - } + testCleanAllCaches(); + beginTransform(); } - public void finishProvide() throws Exception { - if (dirty || !minecraftMergedPatchedSrgJar.exists()) { - mergeJars(getProject().getLogger()); - } - - if (atDirty || !minecraftMergedPatchedSrgAtJar.exists()) { - this.dirty = true; - accessTransformForge(getProject().getLogger()); - } - - if (forgeMergedJar != null && !forgeMergedJar.exists()) { - this.dirty = true; - } - - if (dirty) { - remapPatchedJar(getProject().getLogger()); + protected abstract void beginTransform() throws Exception; - if (getExtension().isForgeAndOfficial()) { - fillClientExtraJar(); - } - } - - this.filesDirty = dirty; - this.dirty = false; - - if (getExtension().isForgeAndOfficial()) { - addDependency(minecraftClientExtra, Constants.Configurations.FORGE_EXTRA); + public void testCleanAllCaches() throws IOException { + if (isRefreshDeps() || Stream.of(getGlobalCaches()).anyMatch(((Predicate) File::exists).negate()) || !isPatchedJarUpToDate(minecraftMergedPatchedAtJar)) { + cleanAllCache(); + } else if (atDirty || Stream.of(getProjectCache()).anyMatch(((Predicate) File::exists).negate())) { + cleanProjectCache(); } } - private void fillClientExtraJar() throws IOException { + public abstract void endTransform() throws Exception; + + protected void fillClientExtraJar() throws IOException { Files.deleteIfExists(minecraftClientExtra.toPath()); FileSystemUtil.getJarFileSystem(minecraftClientExtra, true).close(); copyNonClassFiles(getExtension().getMinecraftProvider().minecraftClientJar, minecraftClientExtra); } - private TinyRemapper buildRemapper(Path input) throws IOException { + private void writeAtHash() throws IOException { + try (FileOutputStream out = new FileOutputStream(projectAtHash)) { + out.write(getProjectAtsHash()); + } + } + + protected TinyRemapper buildRemapper(Path input, String from, String to) throws IOException { Path[] libraries = TinyRemapperHelper.getMinecraftDependencies(getProject()); MemoryMappingTree mappingsWithSrg = getExtension().getMappingsProvider().getMappingsWithSrg(); TinyRemapper remapper = TinyRemapper.newRemapper() .logger(getProject().getLogger()::lifecycle) .logUnknownInvokeDynamic(false) - .withMappings(TinyRemapperHelper.create(mappingsWithSrg, "srg", "official", true)) - .withMappings(InnerClassRemapper.of(InnerClassRemapper.readClassNames(input), mappingsWithSrg, "srg", "official")) + .withMappings(TinyRemapperHelper.create(mappingsWithSrg, from, to, true)) + .withMappings(InnerClassRemapper.of(InnerClassRemapper.readClassNames(input), mappingsWithSrg, from, to)) .renameInvalidLocals(true) .rebuildSourceFilenames(true) .fixPackageAccess(true) @@ -330,37 +278,7 @@ private TinyRemapper buildRemapper(Path input) throws IOException { return remapper; } - private void writeAtHash() throws IOException { - try (FileOutputStream out = new FileOutputStream(projectAtHash)) { - out.write(getProjectAtsHash()); - } - } - - private void createSrgJars(Logger logger) throws Exception { - MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); - produceSrgJar(getExtension().isForgeAndOfficial(), minecraftProvider.minecraftClientJar.toPath(), minecraftProvider.getMinecraftServerJar().toPath()); - } - - private void produceSrgJar(boolean official, Path clientJar, Path serverJar) throws IOException { - Path tmpSrg = getToSrgMappings(); - Set mcLibs = getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES).resolve(); - - ThreadingUtils.run(() -> { - Files.copy(SpecialSourceExecutor.produceSrgJar(getExtension().getMcpConfigProvider().getRemapAction(), getProject(), "client", mcLibs, clientJar, tmpSrg), minecraftClientSrgJar.toPath()); - }, () -> { - Files.copy(SpecialSourceExecutor.produceSrgJar(getExtension().getMcpConfigProvider().getRemapAction(), getProject(), "server", mcLibs, serverJar, tmpSrg), minecraftServerSrgJar.toPath()); - }); - } - - private Path getToSrgMappings() throws IOException { - if (getExtension().getSrgProvider().isTsrgV2()) { - return getExtension().getSrgProvider().getMergedMojangRaw(); - } else { - return getExtension().getMcpConfigProvider().getMappings(); - } - } - - private void fixParameterAnnotation(File jarFile) throws Exception { + protected void fixParameterAnnotation(File jarFile) throws Exception { getProject().getLogger().info(":fixing parameter annotations for " + jarFile.getAbsolutePath()); Stopwatch stopwatch = Stopwatch.createStarted(); @@ -394,7 +312,7 @@ private void fixParameterAnnotation(File jarFile) throws Exception { getProject().getLogger().info(":fixing parameter annotations for " + jarFile.getAbsolutePath() + " in " + stopwatch); } - private void deleteParameterNames(File jarFile) throws Exception { + protected void deleteParameterNames(File jarFile) throws Exception { getProject().getLogger().info(":deleting parameter names for " + jarFile.getAbsolutePath()); Stopwatch stopwatch = Stopwatch.createStarted(); @@ -448,15 +366,15 @@ public void visitLocalVariable(String name, String descriptor, String signature, getProject().getLogger().info(":deleting parameter names for " + jarFile.getAbsolutePath() + " in " + stopwatch); } - private File getForgeJar() { + protected File getForgeJar() { return getExtension().getForgeUniversalProvider().getForge(); } - private File getForgeUserdevJar() { + protected File getForgeUserdevJar() { return getExtension().getForgeUserdevProvider().getUserdevJar(); } - private boolean isPatchedJarUpToDate(File jar) throws IOException { + protected boolean isPatchedJarUpToDate(File jar) throws IOException { if (!jar.exists()) return false; byte[] manifestBytes = ZipUtils.unpackNullable(jar.toPath(), "META-INF/MANIFEST.MF"); @@ -477,7 +395,7 @@ private boolean isPatchedJarUpToDate(File jar) throws IOException { } } - private void accessTransformForge(Logger logger) throws Exception { + protected void accessTransformForge(Logger logger) throws Exception { MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); List toDelete = new ArrayList<>(); String atDependency = Constants.Dependencies.ACCESS_TRANSFORMERS + (minecraftProvider.isNewerThan21w39a() ? Constants.Dependencies.Versions.ACCESS_TRANSFORMERS_NEW : Constants.Dependencies.Versions.ACCESS_TRANSFORMERS); @@ -497,7 +415,7 @@ private void accessTransformForge(Logger logger) throws Exception { args.add(target.getAbsolutePath()); for (File jar : ImmutableList.of(getForgeJar(), getForgeUserdevJar(), minecraftMergedPatchedSrgJar)) { - byte[] atBytes = ZipUtils.unpackNullable(jar.toPath(), Constants.Forge.ACCESS_TRANSFORMER_PATH); + byte[] atBytes = ZipUtils.unpackNullable(jar.toPath(), accessTransformerPath); if (atBytes != null) { File tmpFile = File.createTempFile("at-conf", ".cfg"); @@ -521,8 +439,7 @@ private void accessTransformForge(Logger logger) throws Exception { spec.setClasspath(classpath); // if running with INFO or DEBUG logging - if (getProject().getGradle().getStartParameter().getShowStacktrace() != ShowStacktrace.INTERNAL_EXCEPTIONS - || getProject().getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0) { + if (getProject().getGradle().getStartParameter().getShowStacktrace() != ShowStacktrace.INTERNAL_EXCEPTIONS || getProject().getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0) { spec.setStandardOutput(System.out); spec.setErrorOutput(System.err); } else { @@ -538,32 +455,10 @@ private void accessTransformForge(Logger logger) throws Exception { logger.lifecycle(":access transformed minecraft in " + stopwatch.stop()); } - public enum Environment { - CLIENT(provider -> provider.minecraftClientSrgJar, - provider -> provider.minecraftClientPatchedSrgJar - ), - SERVER(provider -> provider.minecraftServerSrgJar, - provider -> provider.minecraftServerPatchedSrgJar - ); - - final Function srgJar; - final Function patchedSrgJar; - - Environment(Function srgJar, - Function patchedSrgJar) { - this.srgJar = srgJar; - this.patchedSrgJar = patchedSrgJar; - } - - public String side() { - return name().toLowerCase(Locale.ROOT); - } - } - - private void remapPatchedJar(Logger logger) throws Exception { + protected void remapPatchedJar(Logger logger) throws Exception { getProject().getLogger().lifecycle(":remapping minecraft (TinyRemapper, srg -> official)"); Path mcInput = minecraftMergedPatchedSrgAtJar.toPath(); - Path mcOutput = minecraftMergedPatchedJar.toPath(); + Path mcOutput = minecraftMergedPatchedAtJar.toPath(); Path forgeJar = getForgeJar().toPath(); Path forgeUserdevJar = getForgeUserdevJar().toPath(); Path forgeOutput = null; @@ -575,11 +470,10 @@ private void remapPatchedJar(Logger logger) throws Exception { Files.deleteIfExists(forgeOutput); } - TinyRemapper remapper = buildRemapper(mcInput); + TinyRemapper remapper = buildRemapper(mcInput, "srg", "official"); - try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(mcOutput).build(); - Closeable outputConsumerForge = !splitJars ? () -> { - } : new OutputConsumerPath.Builder(forgeOutput).build()) { + try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(mcOutput).build(); Closeable outputConsumerForge = !splitJars ? () -> { + } : new OutputConsumerPath.Builder(forgeOutput).build()) { outputConsumer.addNonClassFiles(mcInput); InputTag mcTag = remapper.createInputTag(); @@ -594,90 +488,32 @@ private void remapPatchedJar(Logger logger) throws Exception { remapper.finish(); } - copyNonClassFiles(forgeJar.toFile(), splitJars ? forgeMergedJar : minecraftMergedPatchedJar); - copyUserdevFiles(forgeUserdevJar.toFile(), splitJars ? forgeMergedJar : minecraftMergedPatchedJar); + copyNonClassFiles(forgeJar.toFile(), splitJars ? forgeMergedJar : minecraftMergedPatchedAtJar); + copyUserdevFiles(forgeUserdevJar.toFile(), splitJars ? forgeMergedJar : minecraftMergedPatchedAtJar); applyLoomPatchVersion(mcOutput); } - private void patchJars(Logger logger) throws IOException { - Stopwatch stopwatch = Stopwatch.createStarted(); - logger.lifecycle(":patching jars"); - - PatchProvider patchProvider = getExtension().getPatchProvider(); - patchJars(minecraftClientSrgJar, minecraftClientPatchedSrgJar, patchProvider.clientPatches); - patchJars(minecraftServerSrgJar, minecraftServerPatchedSrgJar, patchProvider.serverPatches); - - ThreadingUtils.run(Environment.values(), environment -> { - copyMissingClasses(environment.srgJar.apply(this), environment.patchedSrgJar.apply(this)); - deleteParameterNames(environment.patchedSrgJar.apply(this)); - - if (getExtension().isForgeAndNotOfficial()) { - fixParameterAnnotation(environment.patchedSrgJar.apply(this)); - } - }); - - logger.lifecycle(":patched jars in " + stopwatch.stop()); - } - - private void patchJars(File clean, File output, Path patches) throws IOException { - PrintStream previous = System.out; - - try { - System.setOut(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM)); - } catch (SecurityException ignored) { - // Failed to replace logger filter, just ignore - } - - ConsoleTool.main(new String[] { - "--clean", clean.getAbsolutePath(), - "--output", output.getAbsolutePath(), - "--apply", patches.toAbsolutePath().toString() - }); - - try { - System.setOut(previous); - } catch (SecurityException ignored) { - // Failed to replace logger filter, just ignore - } - } - - private void mergeJars(Logger logger) throws IOException { - // FIXME: Hack here: There are no server-only classes so we can just copy the client JAR. - // This will change if upstream Loom adds the possibility for separate projects/source sets per environment. - Files.copy(minecraftClientPatchedSrgJar.toPath(), minecraftMergedPatchedSrgJar.toPath()); + protected abstract void patchJars(File clean, File output, Path patches, String side) throws IOException; - logger.lifecycle(":copying resources"); - - // Copy resources - if (getExtension().isForgeAndNotOfficial()) { - // Copy resources - MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); - copyNonClassFiles(minecraftProvider.minecraftClientJar, minecraftMergedPatchedSrgJar); - copyNonClassFiles(minecraftProvider.getMinecraftServerJar(), minecraftMergedPatchedSrgJar); - } - } + protected abstract void mergeJars(Logger logger) throws Exception; private void walkFileSystems(File source, File target, Predicate filter, Function> toWalk, FsPathConsumer action) - throws IOException { - try (FileSystemUtil.Delegate sourceFs = FileSystemUtil.getJarFileSystem(source, false); - FileSystemUtil.Delegate targetFs = FileSystemUtil.getJarFileSystem(target, false)) { + throws IOException { + try (FileSystemUtil.Delegate sourceFs = FileSystemUtil.getJarFileSystem(source, false); FileSystemUtil.Delegate targetFs = FileSystemUtil.getJarFileSystem(target, false)) { for (Path sourceDir : toWalk.apply(sourceFs.get())) { Path dir = sourceDir.toAbsolutePath(); if (!Files.exists(dir)) continue; - Files.walk(dir) - .filter(Files::isRegularFile) - .filter(filter) - .forEach(it -> { - boolean root = dir.getParent() == null; - - try { - Path relativeSource = root ? it : dir.relativize(it); - Path targetPath = targetFs.get().getPath(relativeSource.toString()); - action.accept(sourceFs.get(), targetFs.get(), it, targetPath); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - }); + Files.walk(dir).filter(Files::isRegularFile).filter(filter).forEach(it -> { + boolean root = dir.getParent() == null; + + try { + Path relativeSource = root ? it : dir.relativize(it); + Path targetPath = targetFs.get().getPath(relativeSource.toString()); + action.accept(sourceFs.get(), targetFs.get(), it, targetPath); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); } } } @@ -690,7 +526,7 @@ private void copyAll(File source, File target) throws IOException { walkFileSystems(source, target, it -> true, this::copyReplacing); } - private void copyMissingClasses(File source, File target) throws IOException { + protected void copyMissingClasses(File source, File target) throws IOException { walkFileSystems(source, target, it -> it.toString().endsWith(".class"), (sourceFs, targetFs, sourcePath, targetPath) -> { if (Files.exists(targetPath)) return; Path parent = targetPath.getParent(); @@ -703,7 +539,7 @@ private void copyMissingClasses(File source, File target) throws IOException { }); } - private void copyNonClassFiles(File source, File target) throws IOException { + protected void copyNonClassFiles(File source, File target) throws IOException { Predicate filter = getExtension().isForgeAndOfficial() ? file -> { String s = file.toString(); return !s.endsWith(".class"); @@ -725,7 +561,7 @@ private void copyReplacing(FileSystem sourceFs, FileSystem targetFs, Path source Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING); } - private void copyUserdevFiles(File source, File target) throws IOException { + protected void copyUserdevFiles(File source, File target) throws IOException { // Removes the Forge name mapping service definition so that our own is used. // If there are multiple name mapping services with the same "understanding" pair // (source -> target namespace pair), modlauncher throws a fit and will crash. @@ -764,7 +600,7 @@ public void applyLoomPatchVersion(Path target) throws IOException { } public File getMergedJar() { - return minecraftMergedPatchedJar; + return minecraftMergedPatchedAtJar; } public File getForgeMergedJar() { diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java index d49ae8b49..3029a7826 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java @@ -24,6 +24,7 @@ package net.fabricmc.loom.configuration.providers.forge; +import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; import java.net.URI; @@ -53,10 +54,26 @@ public PatchProvider(Project project) { public void provide(DependencyInfo dependency, Consumer postPopulationScheduler) throws Exception { init(dependency.getDependency().getVersion()); + if (getExtension().getForgeProvider().isFG2()) { + File forge = getExtension().getForgeUniversalProvider().getForge(); + + if (Files.notExists(clientPatches) || isRefreshDeps()) { + getProject().getLogger().info(":extracting forge patches"); + + try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + forge.toURI()), ImmutableMap.of("create", false))) { + Files.copy(fs.getPath("binpatches.pack.lzma"), clientPatches, StandardCopyOption.REPLACE_EXISTING); + Files.copy(fs.getPath("binpatches.pack.lzma"), serverPatches, StandardCopyOption.REPLACE_EXISTING); + } + } + + return; + } + if (Files.notExists(clientPatches) || Files.notExists(serverPatches) || isRefreshDeps()) { getProject().getLogger().info(":extracting forge patches"); - Path installerJar = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve Forge installer")).toPath(); + Path installerJar = dependency.resolveFile().orElseThrow(() -> new RuntimeException( + "Could not resolve Forge installer")).toPath(); try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + installerJar.toUri()), ImmutableMap.of("create", false))) { Files.copy(fs.getPath("data", "client.lzma"), clientPatches, StandardCopyOption.REPLACE_EXISTING); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java index 32a12dc62..9257add9e 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java @@ -36,6 +36,7 @@ import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; +import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; @@ -69,6 +70,7 @@ public class SrgProvider extends DependencyProvider { private Path srg; private Boolean isTsrgV2; + private boolean isLegacy; private Path mergedMojangRaw; private Path mergedMojang; private Path mergedMojangTrimmed; @@ -87,12 +89,18 @@ public void provide(DependencyInfo dependency, Consumer postPopulation Path srgZip = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve srg")).toPath(); try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + srgZip.toUri()), ImmutableMap.of("create", false))) { - Files.copy(fs.getPath("config", "joined.tsrg"), srg, StandardCopyOption.REPLACE_EXISTING); + try { + Files.copy(fs.getPath("config", "joined.tsrg"), srg, StandardCopyOption.REPLACE_EXISTING); + } catch (NoSuchFileException e) { + Files.copy(fs.getPath("joined.srg"), srg, StandardCopyOption.REPLACE_EXISTING); + } } } try (BufferedReader reader = Files.newBufferedReader(srg)) { - isTsrgV2 = reader.readLine().startsWith("tsrg2"); + String line = reader.readLine(); + isTsrgV2 = line.startsWith("tsrg2"); + isLegacy = line.startsWith("PK:") || line.startsWith("CL:"); } if (isTsrgV2) { @@ -217,6 +225,10 @@ public boolean isTsrgV2() { return isTsrgV2; } + public boolean isLegacy() { + return isLegacy; + } + public static Path getMojmapTsrg(Project project, LoomGradleExtension extension) throws IOException { if (mojmapTsrg != null) return mojmapTsrg; diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/FG2TaskApplyBinPatches.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/FG2TaskApplyBinPatches.java new file mode 100644 index 000000000..cef631791 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/FG2TaskApplyBinPatches.java @@ -0,0 +1,211 @@ +/* + * A Gradle plugin for the creation of Minecraft mods and MinecraftForge plugins. + * Copyright (C) 2013 Minecraft Forge + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +package net.fabricmc.loom.configuration.providers.forge.fg2; + +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; +import java.util.jar.JarOutputStream; +import java.util.regex.Pattern; +import java.util.zip.Adler32; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +import com.google.common.base.Joiner; +import com.google.common.collect.Maps; +import com.google.common.io.ByteArrayDataInput; +import com.google.common.io.ByteStreams; +import com.nothome.delta.GDiffPatcher; +import lzma.sdk.lzma.Decoder; +import lzma.streams.LzmaInputStream; +import org.gradle.api.Project; + +import net.fabricmc.loom.LoomGradleExtension; + +public class FG2TaskApplyBinPatches { + private final HashMap patches = Maps.newHashMap(); + private final GDiffPatcher patcher = new GDiffPatcher(); + + private Project project; + + public FG2TaskApplyBinPatches(Project project) { + this.project = project; + } + + public void doTask(File input, File patches, File output, String side) throws IOException { + setup(patches, side); + output.delete(); + + final HashSet entries = new HashSet<>(); + + try (ZipFile in = new ZipFile(input); + ZipInputStream classesIn = new ZipInputStream(new FileInputStream(input)); + ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(output)))) { + // DO PATCHES + log("Patching Class:"); + + for (ZipEntry e : Collections.list(in.entries())) { + if (e.getName().contains("META-INF")) { + continue; + } + + if (e.isDirectory()) { + out.putNextEntry(e); + } else { + ZipEntry n = new ZipEntry(e.getName()); + n.setTime(e.getTime()); + out.putNextEntry(n); + + byte[] data = ByteStreams.toByteArray(in.getInputStream(e)); + ClassPatch patch = this.patches.get(e.getName().replace('\\', '/')); + + if (patch != null) { + log("\t%s (%s) (input size %d)", patch.targetClassName, patch.sourceClassName, data.length); + int inputChecksum = adlerHash(data); + + if (patch.inputChecksum != inputChecksum) { + throw new RuntimeException(String.format("There is a binary discrepancy between the expected input class %s (%s) and the actual class. Checksum on disk is %x, in patch %x. Things are probably about to go very wrong. Did you put something into the jar file?", patch.targetClassName, patch.sourceClassName, inputChecksum, patch.inputChecksum)); + } + + synchronized (patcher) { + data = patcher.patch(data, patch.patch); + } + } + + out.write(data); + } + + // add the names to the hashset + entries.add(e.getName()); + } + + // COPY DATA + ZipEntry entry; + + while ((entry = classesIn.getNextEntry()) != null) { + if (entries.contains(entry.getName())) { + continue; + } + + out.putNextEntry(entry); + out.write(ByteStreams.toByteArray(classesIn)); + entries.add(entry.getName()); + } + } + } + + private static int adlerHash(byte[] input) { + Adler32 hasher = new Adler32(); + hasher.update(input); + return (int) hasher.getValue(); + } + + public void setup(File patches, String side) { + Pattern matcher = Pattern.compile(String.format("binpatch/%s/.*.binpatch", side)); + + JarInputStream jis; + + try { + LzmaInputStream binpatchesDecompressed = new LzmaInputStream(new FileInputStream(patches), new Decoder()); + ByteArrayOutputStream jarBytes = new ByteArrayOutputStream(); + JarOutputStream jos = new JarOutputStream(jarBytes); + Pack200Provider provider = LoomGradleExtension.get(project).getForge().getPack200Provider().get(); + + if (provider == null) { + throw new IllegalStateException("No provider for Pack200 has been found. Did you declare a provider?"); + } + + provider.unpack(binpatchesDecompressed, jos); + jis = new JarInputStream(new ByteArrayInputStream(jarBytes.toByteArray())); + } catch (Exception e) { + throw new RuntimeException(e); + } + + log("Reading Patches:"); + + do { + try { + JarEntry entry = jis.getNextJarEntry(); + + if (entry == null) { + break; + } + + if (matcher.matcher(entry.getName()).matches()) { + ClassPatch cp = readPatch(entry, jis); + this.patches.put(cp.sourceClassName.replace('.', '/') + ".class", cp); + } else { + log("skipping entry: %s", entry.getName()); + jis.closeEntry(); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } while (true); + log("Read %d binary patches", this.patches.size()); + log("Patch list :\n\t%s", Joiner.on("\n\t").join(this.patches.entrySet())); + } + + private ClassPatch readPatch(JarEntry patchEntry, JarInputStream jis) throws IOException { + log("\t%s", patchEntry.getName()); + ByteArrayDataInput input = ByteStreams.newDataInput(ByteStreams.toByteArray(jis)); + + String name = input.readUTF(); + String sourceClassName = input.readUTF(); + String targetClassName = input.readUTF(); + boolean exists = input.readBoolean(); + int inputChecksum = 0; + + if (exists) { + inputChecksum = input.readInt(); + } + + int patchLength = input.readInt(); + byte[] patchBytes = new byte[patchLength]; + input.readFully(patchBytes); + + return new ClassPatch(name, sourceClassName, targetClassName, exists, inputChecksum, patchBytes); + } + + private void log(String format, Object... args) { + project.getLogger().info(String.format(format, args)); + } + + public record ClassPatch(String name, String sourceClassName, String targetClassName, boolean existsAtTarget, int inputChecksum, + byte[] patch) { + + @Override + public String toString() { + return String.format("%s : %s => %s (%b) size %d", name, sourceClassName, targetClassName, existsAtTarget, patch.length); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftPatchedProviderFG2.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftPatchedProviderFG2.java new file mode 100644 index 000000000..5ba25dfb1 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftPatchedProviderFG2.java @@ -0,0 +1,272 @@ +/* + * 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.configuration.providers.forge.fg2; + +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Locale; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import com.google.common.base.Stopwatch; +import dev.architectury.tinyremapper.InputTag; +import dev.architectury.tinyremapper.OutputConsumerPath; +import dev.architectury.tinyremapper.TinyRemapper; +import org.apache.commons.io.output.NullOutputStream; +import org.gradle.api.Project; +import org.gradle.api.logging.Logger; + +import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl; +import net.fabricmc.loom.configuration.providers.forge.MinecraftPatchedProvider; +import net.fabricmc.loom.configuration.providers.forge.PatchProvider; +import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.ThreadingUtils; +import net.fabricmc.loom.util.srg.SpecialSourceExecutor; + +public class MinecraftPatchedProviderFG2 extends MinecraftPatchedProvider { + // step 0: strip minecraft jars + private File minecraftClientJar; + private File minecraftServerJar; + + // Step 1: Binary Patch (global) + private File minecraftClientPatchedJar; + private File minecraftServerPatchedJar; + + // Step 2: Merge (global) + private File minecraftMergedPatchedJar; + + // Step 3: Srg Transform (global) + // field in super + + // step 4: Access Transform (global or project) + // field in super + + // Step 5: Remap Patched AT & Forge to Official (global or project) + // fields in super + + public MinecraftPatchedProviderFG2(Project project) { + // use AT from forge universal (btw userdev at's are named `forge_at.cfg` if this should be changed) + super(project, "forge_at.cfg"); + } + + @Override + public void initFiles() throws IOException { + super.initFiles(); + File globalCache = getExtension().getForgeProvider().getGlobalCache(); + File projectDir = usesProjectCache() ? getExtension().getForgeProvider().getProjectCache() : globalCache; + minecraftClientJar = new File(globalCache, "client-stripped.jar"); + minecraftServerJar = new File(globalCache, "server-stripped.jar"); + minecraftClientPatchedJar = new File(globalCache, "client-patched.jar"); + minecraftServerPatchedJar = new File(globalCache, "server-patched.jar"); + minecraftMergedPatchedJar = new File(globalCache, "merged-patched.jar"); + } + + @Override + protected File[] getGlobalCaches() { + File[] files = { + minecraftClientJar, + minecraftServerJar, + minecraftClientPatchedJar, + minecraftServerPatchedJar, + minecraftMergedPatchedJar, + minecraftMergedPatchedSrgJar + }; + + if (forgeMergedJar != null) { + files = Arrays.copyOf(files, files.length + 1); + files[files.length - 1] = forgeMergedJar; + } + + return files; + } + + private boolean dirty; + + @Override + protected void beginTransform() throws Exception { + if (atDirty) { + getProject().getLogger().lifecycle(":found dirty access transformers"); + } + + this.dirty = false; + + // Step 0: strip the client/server jars + if (!minecraftClientJar.exists() || !minecraftServerJar.exists()) { + this.dirty = true; + stripJars(getProject().getLogger()); + } + + // Step 1: Binary Patch (global) + if (!minecraftClientPatchedJar.exists() || !minecraftServerPatchedJar.exists()) { + this.dirty = true; + patchJars(getProject().getLogger()); + } + } + + @Override + public void endTransform() throws Exception { + // Step 2: Merge (global) + if (dirty || !minecraftMergedPatchedJar.exists()) { + mergeJars(getProject().getLogger()); + } + + // Step 3: Srg Transform (global) + if (dirty || !minecraftMergedPatchedSrgJar.exists()) { + remapPatchedJarToSrg(getProject().getLogger()); + } + + // Step 4: Access Transform (global or project) + if (atDirty || !minecraftMergedPatchedSrgAtJar.exists()) { + this.dirty = true; + accessTransformForge(getProject().getLogger()); + } + + if (forgeMergedJar != null && !forgeMergedJar.exists()) { + this.dirty = true; + } + + // Step 5: Remap Patched AT & Forge to Official (global or project) + if (dirty) { + remapPatchedJar(getProject().getLogger()); + } + + this.filesDirty = dirty; + this.dirty = false; + } + + public enum Environment { + CLIENT(provider -> provider.minecraftClientJar, + provider -> provider.minecraftClientPatchedJar + ), + SERVER(provider -> provider.minecraftServerJar, + provider -> provider.minecraftServerPatchedJar + ); + + final Function srgJar; + final Function patchedSrgJar; + + Environment(Function srgJar, + Function patchedSrgJar) { + this.srgJar = srgJar; + this.patchedSrgJar = patchedSrgJar; + } + + public String side() { + return name().toLowerCase(Locale.ROOT); + } + } + + private void stripJars(Logger logger) throws IOException { + logger.lifecycle(":stripping jars"); + Set filter = Files.readAllLines(getExtension().getMcpConfigProvider().getMappings(), StandardCharsets.UTF_8).stream() + .filter(s -> s.startsWith("CL:")) + .map(s -> s.split(" ")[1] + ".class") + .collect(Collectors.toSet()); + MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); + SpecialSourceExecutor.stripJar(getProject(), minecraftProvider.minecraftClientJar.toPath(), minecraftClientJar.toPath(), filter); + SpecialSourceExecutor.stripJar(getProject(), minecraftProvider.getMinecraftServerJar().toPath(), minecraftServerJar.toPath(), filter); + } + + private void patchJars(Logger logger) throws IOException { + Stopwatch stopwatch = Stopwatch.createStarted(); + logger.lifecycle(":patching jars"); + + PatchProvider patchProvider = getExtension().getPatchProvider(); + patchJars(minecraftClientJar, minecraftClientPatchedJar, patchProvider.clientPatches, "client"); + patchJars(minecraftServerJar, minecraftServerPatchedJar, patchProvider.serverPatches, "server"); + + ThreadingUtils.run(Environment.values(), environment -> { + copyMissingClasses(environment.srgJar.apply(this), environment.patchedSrgJar.apply(this)); + deleteParameterNames(environment.patchedSrgJar.apply(this)); + + fixParameterAnnotation(environment.patchedSrgJar.apply(this)); + }); + + logger.lifecycle(":patched jars in " + stopwatch.stop()); + } + + @Override + protected void patchJars(File clean, File output, Path patches, String side) throws IOException { + PrintStream previous = System.out; + + try { + System.setOut(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM)); + } catch (SecurityException ignored) { + // Failed to replace logger filter, just ignore + } + + new FG2TaskApplyBinPatches(getProject()).doTask(clean.getAbsoluteFile(), patches.toFile().getAbsoluteFile(), output.getAbsoluteFile(), side); + + try { + System.setOut(previous); + } catch (SecurityException ignored) { + // Failed to replace logger filter, just ignore + } + } + + @Override + protected void mergeJars(Logger logger) throws Exception { + // FIXME: Hack here: There are no server-only classes so we can just copy the client JAR. + // This will change if upstream Loom adds the possibility for separate projects/source sets per environment. + + logger.lifecycle(":merging jars"); + Set mcLibs = getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES).resolve(); + Files.copy(minecraftClientPatchedJar.toPath(), minecraftMergedPatchedJar.toPath()); + + logger.lifecycle(":copying resources"); + + // Copy resources + MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); + copyNonClassFiles(minecraftProvider.minecraftClientJar, minecraftMergedPatchedJar); + copyNonClassFiles(minecraftProvider.getMinecraftServerJar(), minecraftMergedPatchedJar); + } + + protected void remapPatchedJarToSrg(Logger logger) throws Exception { + getProject().getLogger().lifecycle(":remapping minecraft (TinyRemapper, official -> srg)"); + Path mcInput = minecraftMergedPatchedJar.toPath(); + Path mcOutput = minecraftMergedPatchedSrgJar.toPath(); + + TinyRemapper remapper = buildRemapper(mcInput, "official", "srg"); + + try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(mcOutput).build()) { + InputTag mcTag = remapper.createInputTag(); + remapper.readInputsAsync(mcTag, mcInput).join(); + remapper.apply(outputConsumer, mcTag); + } finally { + remapper.finish(); + } + + logger.lifecycle(":copying resources"); + MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); + copyNonClassFiles(minecraftProvider.minecraftClientJar, mcOutput.toFile()); + copyNonClassFiles(minecraftProvider.getMinecraftServerJar(), mcOutput.toFile()); + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/Pack200Provider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/Pack200Provider.java new file mode 100644 index 000000000..8912c42cf --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/Pack200Provider.java @@ -0,0 +1,8 @@ +package net.fabricmc.loom.configuration.providers.forge.fg2; + +import java.io.InputStream; +import java.util.jar.JarOutputStream; + +public interface Pack200Provider { + void unpack(InputStream inputStream, JarOutputStream outputStream); +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg3/MinecraftPatchedProviderFG3.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg3/MinecraftPatchedProviderFG3.java new file mode 100644 index 000000000..d8caba924 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg3/MinecraftPatchedProviderFG3.java @@ -0,0 +1,262 @@ +/* + * 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.configuration.providers.forge.fg3; + +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Locale; +import java.util.Set; +import java.util.function.Function; + +import com.google.common.base.Stopwatch; +import net.minecraftforge.binarypatcher.ConsoleTool; +import org.apache.commons.io.output.NullOutputStream; +import org.gradle.api.Project; +import org.gradle.api.logging.Logger; + +import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl; +import net.fabricmc.loom.configuration.providers.forge.MinecraftPatchedProvider; +import net.fabricmc.loom.configuration.providers.forge.PatchProvider; +import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.ThreadingUtils; +import net.fabricmc.loom.util.srg.SpecialSourceExecutor; + +public class MinecraftPatchedProviderFG3 extends MinecraftPatchedProvider { + // Step 1: Remap Minecraft to SRG (global) + private File minecraftClientSrgJar; + private File minecraftServerSrgJar; + + // Step 2: Binary Patch (global) + private File minecraftClientPatchedSrgJar; + private File minecraftServerPatchedSrgJar; + + // Step 3: Merge (global) + // field in super + + // Step 4: Access Transform (global or project) + // field in super + + // Step 5: Remap Patched AT & Forge to Official (global or project) + // fields in super + + private Path[] mergedMojangTsrg2Files; + + public MinecraftPatchedProviderFG3(Project project) { + super(project, Constants.Forge.ACCESS_TRANSFORMER_PATH); + } + + @Override + public void initFiles() throws IOException { + super.initFiles(); + File globalCache = getExtension().getForgeProvider().getGlobalCache(); + minecraftClientSrgJar = new File(globalCache, "minecraft-client-srg.jar"); + minecraftServerSrgJar = new File(globalCache, "minecraft-server-srg.jar"); + minecraftClientPatchedSrgJar = new File(globalCache, "client-srg-patched.jar"); + minecraftServerPatchedSrgJar = new File(globalCache, "server-srg-patched.jar"); + } + + @Override + protected File[] getGlobalCaches() { + File[] files = { + minecraftClientSrgJar, + minecraftServerSrgJar, + minecraftClientPatchedSrgJar, + minecraftServerPatchedSrgJar, + minecraftMergedPatchedSrgJar, + minecraftClientExtra, + }; + + if (forgeMergedJar != null) { + files = Arrays.copyOf(files, files.length + 1); + files[files.length - 1] = forgeMergedJar; + } + + return files; + } + + private boolean dirty; + + @Override + public void beginTransform() throws Exception { + if (atDirty) { + getProject().getLogger().lifecycle(":found dirty access transformers"); + } + + this.dirty = false; + + if (!minecraftClientSrgJar.exists() || !minecraftServerSrgJar.exists()) { + this.dirty = true; + // Remap official jars to MCPConfig remapped srg jars + createSrgJars(getProject().getLogger()); + } + + if (!minecraftClientPatchedSrgJar.exists() || !minecraftServerPatchedSrgJar.exists()) { + this.dirty = true; + patchJars(getProject().getLogger()); + } + } + + @Override + public void endTransform() throws Exception { + if (dirty || !minecraftMergedPatchedSrgJar.exists()) { + mergeJars(getProject().getLogger()); + } + + if (atDirty || !minecraftMergedPatchedSrgAtJar.exists()) { + this.dirty = true; + accessTransformForge(getProject().getLogger()); + } + + if (forgeMergedJar != null && !forgeMergedJar.exists()) { + this.dirty = true; + } + + if (dirty) { + remapPatchedJar(getProject().getLogger()); + + if (getExtension().isForgeAndOfficial()) { + fillClientExtraJar(); + } + } + + this.filesDirty = dirty; + this.dirty = false; + + if (getExtension().isForgeAndOfficial()) { + addDependency(minecraftClientExtra, Constants.Configurations.FORGE_EXTRA); + } + } + + private void createSrgJars(Logger logger) throws Exception { + MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); + produceSrgJar(getExtension().isForgeAndOfficial(), minecraftProvider.minecraftClientJar.toPath(), minecraftProvider.getMinecraftServerJar().toPath()); + } + + private void produceSrgJar(boolean official, Path clientJar, Path serverJar) throws IOException { + Path tmpSrg = getToSrgMappings(); + Set mcLibs = getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES).resolve(); + + ThreadingUtils.run(() -> { + Files.copy(SpecialSourceExecutor.produceSrgJar(getExtension().getMcpConfigProvider().getRemapAction(), getProject(), "client", mcLibs, clientJar, tmpSrg), minecraftClientSrgJar.toPath()); + }, () -> { + Files.copy(SpecialSourceExecutor.produceSrgJar(getExtension().getMcpConfigProvider().getRemapAction(), getProject(), "server", mcLibs, serverJar, tmpSrg), minecraftServerSrgJar.toPath()); + }); + } + + private Path getToSrgMappings() throws IOException { + if (getExtension().getSrgProvider().isTsrgV2()) { + return getExtension().getSrgProvider().getMergedMojangRaw(); + } else { + return getExtension().getMcpConfigProvider().getMappings(); + } + } + + public enum Environment { + CLIENT(provider -> provider.minecraftClientSrgJar, + provider -> provider.minecraftClientPatchedSrgJar + ), + SERVER(provider -> provider.minecraftServerSrgJar, + provider -> provider.minecraftServerPatchedSrgJar + ); + + final Function srgJar; + public final Function patchedSrgJar; + + Environment(Function srgJar, + Function patchedSrgJar) { + this.srgJar = srgJar; + this.patchedSrgJar = patchedSrgJar; + } + + public String side() { + return name().toLowerCase(Locale.ROOT); + } + } + + private void patchJars(Logger logger) throws IOException { + Stopwatch stopwatch = Stopwatch.createStarted(); + logger.lifecycle(":patching jars"); + + PatchProvider patchProvider = getExtension().getPatchProvider(); + patchJars(minecraftClientSrgJar, minecraftClientPatchedSrgJar, patchProvider.clientPatches, "client"); + patchJars(minecraftServerSrgJar, minecraftServerPatchedSrgJar, patchProvider.serverPatches, "server"); + + ThreadingUtils.run(Environment.values(), environment -> { + copyMissingClasses(environment.srgJar.apply(this), environment.patchedSrgJar.apply(this)); + deleteParameterNames(environment.patchedSrgJar.apply(this)); + + if (getExtension().isForgeAndNotOfficial()) { + fixParameterAnnotation(environment.patchedSrgJar.apply(this)); + } + }); + + logger.lifecycle(":patched jars in " + stopwatch.stop()); + } + + @Override + protected void patchJars(File clean, File output, Path patches, String side) throws IOException { + PrintStream previous = System.out; + + try { + System.setOut(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM)); + } catch (SecurityException ignored) { + // Failed to replace logger filter, just ignore + } + + ConsoleTool.main(new String[] { + "--clean", clean.getAbsolutePath(), + "--output", output.getAbsolutePath(), + "--apply", patches.toAbsolutePath().toString() + }); + + try { + System.setOut(previous); + } catch (SecurityException ignored) { + // Failed to replace logger filter, just ignore + } + } + + @Override + protected void mergeJars(Logger logger) throws Exception { + // FIXME: Hack here: There are no server-only classes so we can just copy the client JAR. + // This will change if upstream Loom adds the possibility for separate projects/source sets per environment. + + Files.copy(minecraftClientPatchedSrgJar.toPath(), minecraftMergedPatchedSrgJar.toPath()); + + logger.lifecycle(":copying resources"); + + // Copy resources + if (getExtension().isForgeAndNotOfficial()) { + // Copy resources + MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); + copyNonClassFiles(minecraftProvider.minecraftClientJar, minecraftMergedPatchedSrgJar); + copyNonClassFiles(minecraftProvider.getMinecraftServerJar(), minecraftMergedPatchedSrgJar); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java index 23bf71c83..2aebab2a2 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java @@ -37,6 +37,7 @@ import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -63,8 +64,11 @@ import net.fabricmc.loom.configuration.accesswidener.TransitiveAccessWidenerJarProcessor; import net.fabricmc.loom.configuration.processors.JarProcessorManager; import net.fabricmc.loom.configuration.processors.MinecraftProcessedProvider; +import net.fabricmc.loom.configuration.providers.MinecraftProvider; import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl; import net.fabricmc.loom.configuration.providers.forge.MinecraftPatchedProvider; +import net.fabricmc.loom.configuration.providers.forge.fg2.MinecraftPatchedProviderFG2; +import net.fabricmc.loom.configuration.providers.forge.fg3.MinecraftPatchedProviderFG3; import net.fabricmc.loom.configuration.providers.forge.SrgProvider; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvider; import net.fabricmc.loom.util.Constants; @@ -74,6 +78,7 @@ import net.fabricmc.loom.util.srg.MCPReader; import net.fabricmc.loom.util.srg.SrgMerger; import net.fabricmc.loom.util.srg.SrgNamedWriter; +import net.fabricmc.mappingio.MappedElementKind; import net.fabricmc.mappingio.MappingReader; import net.fabricmc.mappingio.adapter.MappingNsCompleter; import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch; @@ -86,6 +91,11 @@ import net.fabricmc.stitch.commands.CommandProposeFieldNames; import net.fabricmc.stitch.commands.tinyv2.TinyFile; import net.fabricmc.stitch.commands.tinyv2.TinyV2Writer; +import net.fabricmc.stitch.representation.JarClassEntry; +import net.fabricmc.stitch.representation.JarFieldEntry; +import net.fabricmc.stitch.representation.JarMethodEntry; +import net.fabricmc.stitch.representation.JarReader; +import net.fabricmc.stitch.representation.JarRootEntry; public class MappingsProviderImpl extends DependencyProvider implements MappingsProvider { public MinecraftMappedProvider mappedProvider; @@ -147,8 +157,13 @@ public void provide(DependencyInfo dependency, Consumer postPopulation } if (getExtension().isForge()) { - patchedProvider = new MinecraftPatchedProvider(getProject()); - patchedProvider.provide(dependency, postPopulationScheduler); + if (getExtension().getForgeProvider().isFG2()) { + patchedProvider = new MinecraftPatchedProviderFG2(getProject()); + patchedProvider.provide(dependency, postPopulationScheduler); + } else { + patchedProvider = new MinecraftPatchedProviderFG3(getProject()); + patchedProvider.provide(dependency, postPopulationScheduler); + } } mappingTree = readMappings(tinyMappings); @@ -157,7 +172,7 @@ public void provide(DependencyInfo dependency, Consumer postPopulation if (getExtension().shouldGenerateSrgTiny()) { if (Files.notExists(tinyMappingsWithSrg) || isRefreshDeps()) { // Merge tiny mappings with srg - SrgMerger.mergeSrg(getProject().getLogger(), getExtension().getMappingsProvider()::getMojmapSrgFileIfPossible, getRawSrgFile(), tinyMappings, tinyMappingsWithSrg, true); + SrgMerger.mergeSrg(getProject().getLogger(), getExtension().getMappingsProvider()::getMojmapSrgFileIfPossible, getRawSrgFile(), tinyMappings, tinyMappingsWithSrg, true, getExtension().getSrgProvider().isLegacy()); } mappingTreeWithSrg = readMappings(tinyMappingsWithSrg); @@ -219,7 +234,7 @@ public void provide(DependencyInfo dependency, Consumer postPopulation processorManager.setupProcessors(); if (extension.isForge()) { - patchedProvider.finishProvide(); + patchedProvider.endTransform(); } if (processorManager.active() || (extension.isForge() && patchedProvider.usesProjectCache())) { @@ -343,7 +358,7 @@ private void readAndMergeMCP(Path mcpJar, Consumer postPopulationSched } Path srgPath = getRawSrgFile(); - TinyFile file = new MCPReader(intermediaryTinyPath, srgPath).read(mcpJar); + TinyFile file = new MCPReader(intermediaryTinyPath, srgPath, getExtension().getSrgProvider().isLegacy()).read(mcpJar); TinyV2Writer.write(file, tinyMappings); } @@ -573,6 +588,22 @@ public Path getIntermediaryTiny() throws IOException { if (intermediaryTiny == null) { intermediaryTiny = getMinecraftProvider().file("intermediary-v2.tiny").toPath(); + if (getExtension().getStubIntermediaries().get()) { + intermediaryTiny = getMinecraftProvider().file("stub-intermediary-v2.tiny").toPath(); + + if (isRefreshDeps() && !hasRefreshed) { + Files.deleteIfExists(intermediaryTiny); + } + + if (Files.exists(intermediaryTiny)) { + return intermediaryTiny; + } + + hasRefreshed = true; + generateIntermediary(intermediaryTiny); + return intermediaryTiny; + } + if (!Files.exists(intermediaryTiny) || (isRefreshDeps() && !hasRefreshed)) { hasRefreshed = true; @@ -588,6 +619,55 @@ public Path getIntermediaryTiny() throws IOException { return intermediaryTiny; } + private void generateIntermediary(Path output) throws IOException { + MinecraftProviderImpl provider = getExtension().getMinecraftProvider(); + JarRootEntry entry = new JarRootEntry(provider.getMergedJar()); + MemoryMappingTree tree = new MemoryMappingTree(); + + MappingNsCompleter visitor = new MappingNsCompleter(tree, Collections.singletonMap(MappingsNamespace.INTERMEDIARY.toString(), MappingsNamespace.OFFICIAL.toString()), true); + + if (visitor.visitHeader()) { + visitor.visitNamespaces(MappingsNamespace.OFFICIAL.toString(), Collections.emptyList()); + } + + if (visitor.visitContent()) { + try { + JarReader reader = new JarReader(entry); + reader.apply(); + } catch (IOException e) { + e.printStackTrace(); + } + + for (JarClassEntry classEntry : entry.getAllClasses()) { + if (visitor.visitClass(classEntry.getFullyQualifiedName())) { + if (!visitor.visitElementContent(MappedElementKind.CLASS)) return; + + for (JarFieldEntry field : classEntry.getFields()) { + if (visitor.visitField(field.getName(), field.getDescriptor())) { + if (!visitor.visitElementContent(MappedElementKind.FIELD)) return; + } + } + + for (JarMethodEntry method : classEntry.getMethods()) { + if (method.getName().startsWith("<")) continue; + + if (visitor.visitMethod(method.getName(), method.getDescriptor())) { + if (!visitor.visitElementContent(MappedElementKind.METHOD)) return; + } + } + } + } + } + + visitor.visitEnd(); + + try (Tiny2Writer writer = new Tiny2Writer(Files.newBufferedWriter(output, StandardOpenOption.CREATE), false)) { + tree.accept(writer); + } catch (IOException e) { + e.printStackTrace(); + } + } + @Override public Path mappingsWorkingDir() { return mappingsWorkingDir; diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java index ec0039958..8c069f9bd 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java @@ -26,6 +26,8 @@ import java.io.File; import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; @@ -339,6 +341,16 @@ public void visit(int version, int access, String name, String signature, String if (forge != null) { OutputRemappingHandler.remap(remapper, forge.assets, outputForge, null, forgeTag); + + if (getExtension().getForgeProvider().isFG2()) { + //FG2 - remove binpatches for the dev environment to work + try (FileSystem fs = FileSystems.newFileSystem(outputForge, Map.of("create", "false"))) { + Files.deleteIfExists(fs.getPath("binpatches.pack.lzma")); + + //TODO: FIXME, hack. remove forge trying to transform class names for fg2 dev launch + Files.deleteIfExists(fs.getPath("net/minecraftforge/fml/common/asm/transformers/DeobfuscationTransformer.class")); + } + } } getProject().getLogger().lifecycle(":remapped minecraft (TinyRemapper, " + fromM + " -> " + toM + ") in " + stopwatch); diff --git a/src/main/java/net/fabricmc/loom/extension/ForgeExtensionImpl.java b/src/main/java/net/fabricmc/loom/extension/ForgeExtensionImpl.java index 25da6717a..5a4956d95 100644 --- a/src/main/java/net/fabricmc/loom/extension/ForgeExtensionImpl.java +++ b/src/main/java/net/fabricmc/loom/extension/ForgeExtensionImpl.java @@ -42,6 +42,7 @@ import net.fabricmc.loom.api.ForgeExtensionAPI; import net.fabricmc.loom.api.ForgeLocalMod; import net.fabricmc.loom.configuration.ide.RunConfigSettings; +import net.fabricmc.loom.configuration.providers.forge.fg2.Pack200Provider; public class ForgeExtensionImpl implements ForgeExtensionAPI { private final LoomGradleExtension extension; @@ -52,6 +53,7 @@ public class ForgeExtensionImpl implements ForgeExtensionAPI { private final Property useCustomMixin; private final List dataGenMods = new ArrayList<>(); // not a property because it has custom adding logic private final NamedDomainObjectContainer localMods; + private final Property pack200Provider; @Inject public ForgeExtensionImpl(Project project, LoomGradleExtension extension) { @@ -63,6 +65,7 @@ public ForgeExtensionImpl(Project project, LoomGradleExtension extension) { useCustomMixin = project.getObjects().property(Boolean.class).convention(true); localMods = project.container(ForgeLocalMod.class, baseName -> new ForgeLocalMod(project, baseName, new ArrayList<>())); + pack200Provider = project.getObjects().property(Pack200Provider.class); // Create default mod from main source set localMods(mod -> mod.create("main").add("main")); @@ -133,4 +136,9 @@ public void localMods(Action> action) public NamedDomainObjectContainer getLocalMods() { return localMods; } + + @Override + public Property getPack200Provider() { + return pack200Provider; + } } diff --git a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java index d3f6d6712..ce5b558f8 100644 --- a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java +++ b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java @@ -94,6 +94,7 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA private final List tasksBeforeRun = Collections.synchronizedList(new ArrayList<>()); public final List> settingsPostEdit = new ArrayList<>(); private NamedDomainObjectContainer launchConfigs; + private final Property stubIntermediaries; protected LoomGradleExtensionApiImpl(Project project, LoomFiles directories) { this.runConfigs = project.container(RunConfigSettings.class, @@ -140,6 +141,8 @@ protected LoomGradleExtensionApiImpl(Project project, LoomFiles directories) { baseName -> new LaunchProviderSettings(project, baseName)); this.archDecompilers = project.getObjects().listProperty(ArchitecturyLoomDecompiler.class) .empty(); + this.stubIntermediaries = project.getObjects().property(Boolean.class) + .convention(false); } @Override @@ -295,6 +298,11 @@ public ListProperty getArchGameDecompilers() { return archDecompilers; } + @Override + public Property getStubIntermediaries() { + return stubIntermediaries; + } + // This is here to ensure that LoomGradleExtensionApiImpl compiles without any unimplemented methods private final class EnsureCompile extends LoomGradleExtensionApiImpl { private EnsureCompile() { diff --git a/src/main/java/net/fabricmc/loom/extension/MinecraftGradleExtension.java b/src/main/java/net/fabricmc/loom/extension/MinecraftGradleExtension.java index bd33fd622..fb377e51b 100644 --- a/src/main/java/net/fabricmc/loom/extension/MinecraftGradleExtension.java +++ b/src/main/java/net/fabricmc/loom/extension/MinecraftGradleExtension.java @@ -243,4 +243,10 @@ public void forge(Action action) { reportDeprecation(); parent.forge(action); } + + @Override + public Property getStubIntermediaries() { + reportDeprecation(); + return parent.getStubIntermediaries(); + } } diff --git a/src/main/java/net/fabricmc/loom/util/srg/MCPReader.java b/src/main/java/net/fabricmc/loom/util/srg/MCPReader.java index 96382bb5d..5d2449be8 100644 --- a/src/main/java/net/fabricmc/loom/util/srg/MCPReader.java +++ b/src/main/java/net/fabricmc/loom/util/srg/MCPReader.java @@ -37,13 +37,17 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.function.BiConsumer; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.opencsv.CSVReader; import com.opencsv.exceptions.CsvValidationException; +import dev.architectury.refmapremapper.utils.DescriptorRemapper; import org.apache.commons.io.IOUtils; import org.cadixdev.lorenz.MappingSet; +import org.cadixdev.lorenz.io.srg.SrgReader; import org.cadixdev.lorenz.io.srg.tsrg.TSrgReader; import org.cadixdev.lorenz.model.ClassMapping; import org.cadixdev.lorenz.model.FieldMapping; @@ -66,17 +70,20 @@ public class MCPReader { private final Path intermediaryTinyPath; private final Path srgTsrgPath; - public MCPReader(Path intermediaryTinyPath, Path srgTsrgPath) { + private final boolean isLegacySrg; + + public MCPReader(Path intermediaryTinyPath, Path srgTsrgPath, boolean isLegacySrg) { this.intermediaryTinyPath = intermediaryTinyPath; this.srgTsrgPath = srgTsrgPath; + this.isLegacySrg = isLegacySrg; } public TinyFile read(Path mcpJar) throws IOException { - Map srgTokens = readSrg(); + Map, String> srgTokens = readSrg(); TinyFile intermediaryTiny = TinyV2Reader.read(intermediaryTinyPath); - Map intermediaryToMCPMap = createIntermediaryToMCPMap(intermediaryTiny, srgTokens); - Map intermediaryToDocsMap = new HashMap<>(); - Map> intermediaryToParamsMap = new HashMap<>(); + Map, String> intermediaryToMCPMap = createIntermediaryToMCPMap(intermediaryTiny, srgTokens); + Map, String[]> intermediaryToDocsMap = new HashMap<>(); + Map, Map> intermediaryToParamsMap = new HashMap<>(); try { injectMcp(mcpJar, intermediaryToMCPMap, intermediaryToDocsMap, intermediaryToParamsMap); @@ -88,43 +95,54 @@ public TinyFile read(Path mcpJar) throws IOException { return intermediaryTiny; } - private Map createIntermediaryToMCPMap(TinyFile tiny, Map officialToMCP) { - Map map = new HashMap<>(); + private Map, String> createIntermediaryToMCPMap(TinyFile tiny, Map, String> officialToMCP) { + Map, String> map = new HashMap<>(); + BiConsumer, MemberToken> adder = (intermediary, obf) -> { + String mcp = officialToMCP.get(obf); + + if (mcp != null && !intermediary.name.equals(mcp)) { + map.put(intermediary, mcp); + } + }; for (TinyClass tinyClass : tiny.getClassEntries()) { String classObf = tinyClass.getMapping().get(0); String classIntermediary = tinyClass.getMapping().get(1); - MemberToken classTokenObf = MemberToken.ofClass(classObf); + MemberToken classTokenIntermediary = MemberToken.ofClass(classIntermediary); + MemberToken classTokenObf = MemberToken.ofClass(classObf); - if (officialToMCP.containsKey(classTokenObf)) { - map.put(classIntermediary, officialToMCP.get(classTokenObf)); - } + adder.accept(classTokenIntermediary, classTokenObf); for (TinyField tinyField : tinyClass.getFields()) { String fieldObf = tinyField.getMapping().get(0); String fieldIntermediary = tinyField.getMapping().get(1); - MemberToken fieldTokenObf = MemberToken.ofField(classTokenObf, fieldObf); + MemberToken fieldTokenObf = MemberToken.ofField(classTokenObf, fieldObf); - if (officialToMCP.containsKey(fieldTokenObf)) { - map.put(fieldIntermediary, officialToMCP.get(fieldTokenObf)); - } + adder.accept(MemberToken.ofField(classTokenIntermediary, fieldIntermediary), 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()); + String methodDescIntermediary = remapDescriptor(tinyMethod.getMethodDescriptorInFirstNamespace(), tiny); + MemberToken methodTokenObf = MemberToken.ofMethod(classTokenObf, methodObf, tinyMethod.getMethodDescriptorInFirstNamespace()); - if (officialToMCP.containsKey(methodTokenObf)) { - map.put(methodIntermediary, officialToMCP.get(methodTokenObf)); - } + adder.accept(MemberToken.ofMethod(classTokenIntermediary, methodIntermediary, methodDescIntermediary), methodTokenObf); } } return map; } - private void mergeTokensIntoIntermediary(TinyFile tiny, Map intermediaryToMCPMap, Map intermediaryToDocsMap, Map> intermediaryToParamsMap) { + private String remapDescriptor(String descriptor, TinyFile file) { + return DescriptorRemapper.remapDescriptor(descriptor, s -> { + TinyClass tinyClass = file.mapClassesByFirstNamespace().get(s); + return tinyClass == null ? s : tinyClass.getMapping().get(1); + }); + } + + private void mergeTokensIntoIntermediary(TinyFile tiny, Map, String> intermediaryToMCPMap, Map, String[]> intermediaryToDocsMap, + Map, Map> intermediaryToParamsMap) { stripTinyWithParametersAndLocal(tiny); // We will be adding the "named" namespace with MCP @@ -132,12 +150,14 @@ private void mergeTokensIntoIntermediary(TinyFile tiny, Map inte for (TinyClass tinyClass : tiny.getClassEntries()) { String classIntermediary = tinyClass.getMapping().get(1); - tinyClass.getMapping().add(intermediaryToMCPMap.getOrDefault(classIntermediary, classIntermediary)); + MemberToken classMemberToken = MemberToken.ofClass(classIntermediary); + tinyClass.getMapping().add(intermediaryToMCPMap.getOrDefault(classMemberToken, classIntermediary)); for (TinyField tinyField : tinyClass.getFields()) { String fieldIntermediary = tinyField.getMapping().get(1); - String[] docs = intermediaryToDocsMap.get(fieldIntermediary); - tinyField.getMapping().add(intermediaryToMCPMap.getOrDefault(fieldIntermediary, fieldIntermediary)); + MemberToken fieldMemberToken = MemberToken.ofField(classMemberToken, fieldIntermediary); + String[] docs = intermediaryToDocsMap.get(fieldMemberToken); + tinyField.getMapping().add(intermediaryToMCPMap.getOrDefault(fieldMemberToken, fieldIntermediary)); if (docs != null) { tinyField.getComments().clear(); @@ -147,15 +167,17 @@ private void mergeTokensIntoIntermediary(TinyFile tiny, Map inte for (TinyMethod tinyMethod : tinyClass.getMethods()) { String methodIntermediary = tinyMethod.getMapping().get(1); - String[] docs = intermediaryToDocsMap.get(methodIntermediary); - tinyMethod.getMapping().add(intermediaryToMCPMap.getOrDefault(methodIntermediary, methodIntermediary)); + String methodDescIntermediary = remapDescriptor(tinyMethod.getMethodDescriptorInFirstNamespace(), tiny); + MemberToken methodMemberToken = MemberToken.ofMethod(classMemberToken, methodIntermediary, methodDescIntermediary); + String[] docs = intermediaryToDocsMap.get(methodMemberToken); + tinyMethod.getMapping().add(intermediaryToMCPMap.getOrDefault(methodMemberToken, methodIntermediary)); if (docs != null) { tinyMethod.getComments().clear(); tinyMethod.getComments().addAll(Arrays.asList(docs)); } - Map params = intermediaryToParamsMap.get(methodIntermediary); + Map params = intermediaryToParamsMap.get(methodMemberToken); if (params != null) { for (Map.Entry entry : params.entrySet()) { @@ -182,8 +204,8 @@ private void stripTinyWithParametersAndLocal(TinyFile tiny) { } } - private Map readSrg() throws IOException { - Map tokens = new HashMap<>(); + private Map, String> readSrg() throws IOException { + Map, String> tokens = new HashMap<>(); try (BufferedReader reader = Files.newBufferedReader(srgTsrgPath, StandardCharsets.UTF_8)) { String content = IOUtils.toString(reader); @@ -191,7 +213,13 @@ private Map readSrg() throws IOException { if (content.startsWith("tsrg2")) { readTsrg2(tokens, content); } else { - MappingSet mappingSet = new TSrgReader(new StringReader(content)).read(); + MappingSet mappingSet; + + if (isLegacySrg) { + mappingSet = new SrgReader(new StringReader(content)).read(); + } else { + mappingSet = new TSrgReader(new StringReader(content)).read(); + } for (TopLevelClassMapping classMapping : mappingSet.getTopLevelClassMappings()) { appendClass(tokens, classMapping); @@ -202,14 +230,14 @@ private Map readSrg() throws IOException { return tokens; } - private void readTsrg2(Map tokens, String content) throws IOException { + private void readTsrg2(Map, 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)); + MemberToken ofClass = MemberToken.ofClass(classDef.getName(obfIndex)); tokens.put(ofClass, classDef.getName(srgIndex)); for (MappingTree.FieldMapping fieldDef : classDef.getFields()) { @@ -223,17 +251,19 @@ private void readTsrg2(Map tokens, String content) throws I } } - private void injectMcp(Path mcpJar, Map intermediaryToSrgMap, Map intermediaryToDocsMap, Map> intermediaryToParamsMap) + private void injectMcp(Path mcpJar, Map, String> intermediaryToSrgMap, Map, String[]> intermediaryToDocsMap, + Map, Map> intermediaryToParamsMap) throws IOException, CsvValidationException { - Map> srgToIntermediary = inverseMap(intermediaryToSrgMap); - Map> simpleSrgToIntermediary = new HashMap<>(); + Map>> srgToIntermediary = inverseMap(intermediaryToSrgMap); + Map>> simpleSrgToIntermediary = new HashMap<>(); Pattern methodPattern = Pattern.compile("(func_\\d*)_.*"); - for (Map.Entry> entry : srgToIntermediary.entrySet()) { + for (Map.Entry>> entry : srgToIntermediary.entrySet()) { Matcher matcher = methodPattern.matcher(entry.getKey()); if (matcher.matches()) { - simpleSrgToIntermediary.put(matcher.group(1), entry.getValue()); + simpleSrgToIntermediary.put(matcher.group(1), + (List>) (List>) entry.getValue()); } } @@ -248,11 +278,11 @@ private void injectMcp(Path mcpJar, Map intermediaryToSrgMap, Ma String[] line; while ((line = reader.readNext()) != null) { - List intermediaryField = srgToIntermediary.get(line[0]); + List> intermediaryField = (List>) (List>) srgToIntermediary.get(line[0]); String[] docs = line[3].split("\n"); if (intermediaryField != null) { - for (String s : intermediaryField) { + for (MemberToken s : intermediaryField) { intermediaryToSrgMap.put(s, line[1]); if (!line[3].trim().isEmpty() && docs.length > 0) { @@ -268,11 +298,11 @@ private void injectMcp(Path mcpJar, Map intermediaryToSrgMap, Ma String[] line; while ((line = reader.readNext()) != null) { - List intermediaryMethod = srgToIntermediary.get(line[0]); + List> intermediaryMethod = (List>) (List>) srgToIntermediary.get(line[0]); String[] docs = line[3].split("\n"); if (intermediaryMethod != null) { - for (String s : intermediaryMethod) { + for (MemberToken s : intermediaryMethod) { intermediaryToSrgMap.put(s, line[1]); if (!line[3].trim().isEmpty() && docs.length > 0) { @@ -295,10 +325,10 @@ private void injectMcp(Path mcpJar, Map intermediaryToSrgMap, Ma String named = line[1]; String srgMethodStartWith = "func_" + param.group(1); int lvIndex = Integer.parseInt(param.group(2)); - List intermediaryMethod = simpleSrgToIntermediary.get(srgMethodStartWith); + List> intermediaryMethod = simpleSrgToIntermediary.get(srgMethodStartWith); if (intermediaryMethod != null) { - for (String s : intermediaryMethod) { + for (MemberToken s : intermediaryMethod) { intermediaryToParamsMap.computeIfAbsent(s, s1 -> new HashMap<>()).put(lvIndex, named); } } @@ -309,18 +339,18 @@ private void injectMcp(Path mcpJar, Map intermediaryToSrgMap, Ma } } - private Map> inverseMap(Map intermediaryToMCPMap) { - Map> map = new HashMap<>(); + private Map> inverseMap(Map intermediaryToMCPMap) { + Map> map = new HashMap<>(); - for (Map.Entry token : intermediaryToMCPMap.entrySet()) { + for (Map.Entry token : intermediaryToMCPMap.entrySet()) { map.computeIfAbsent(token.getValue(), s -> new ArrayList<>()).add(token.getKey()); } return map; } - private void appendClass(Map tokens, ClassMapping classMapping) { - MemberToken ofClass = MemberToken.ofClass(classMapping.getFullObfuscatedName()); + private void appendClass(Map, String> tokens, ClassMapping classMapping) { + MemberToken ofClass = MemberToken.ofClass(classMapping.getFullObfuscatedName()); tokens.put(ofClass, classMapping.getFullDeobfuscatedName()); for (FieldMapping fieldMapping : classMapping.getFieldMappings()) { @@ -336,28 +366,102 @@ private void appendClass(Map tokens, ClassMapping cla } } - 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); + private interface TokenType { + enum Class implements TokenType { } - static MemberToken ofField(MemberToken owner, String name) { - return new MemberToken(TokenType.FIELD, owner, name, null); + enum Method implements TokenType { } - static MemberToken ofMethod(MemberToken owner, String name, String descriptor) { - return new MemberToken(TokenType.METHOD, owner, name, descriptor); + enum Field implements TokenType { } } - private enum TokenType { - CLASS, - METHOD, - FIELD + private static class MemberToken { + @Nullable + private MemberToken owner; + private String name; + @Nullable private String descriptor; + + public MemberToken(@Nullable MemberToken owner, String name, @Nullable String descriptor) { + this.owner = owner; + this.name = name; + this.descriptor = descriptor; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof MemberToken that)) return false; + return Objects.equals(owner, that.owner) && Objects.equals(name, that.name) && Objects.equals(descriptor, that.descriptor); + } + + @Override + public int hashCode() { + return Objects.hash(owner, name, descriptor); + } + + static class ClassToken extends MemberToken { + ClassToken(String name) { + super(null, name, null); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof ClassToken)) return false; + return super.equals(o); + } + + @Override + public int hashCode() { + return (1 + super.hashCode()) * 31 + 1; + } + } + + static class FieldToken extends MemberToken { + FieldToken(@Nullable MemberToken owner, String name) { + super(owner, name, null); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof FieldToken)) return false; + return super.equals(o); + } + + @Override + public int hashCode() { + return (1 + super.hashCode()) * 31 + 2; + } + } + + static class MethodToken extends MemberToken { + MethodToken(@Nullable MemberToken owner, String name, @Nullable String descriptor) { + super(owner, name, descriptor); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof MethodToken)) return false; + return super.equals(o); + } + + @Override + public int hashCode() { + return (1 + super.hashCode()) * 31 + 3; + } + } + + static MemberToken ofClass(String name) { + return new MemberToken.ClassToken(name); + } + + static MemberToken ofField(MemberToken owner, String name) { + return new MemberToken.FieldToken(owner, name); + } + + static MemberToken ofMethod(MemberToken owner, String name, String descriptor) { + return new MemberToken.MethodToken(owner, name, descriptor); + } } } diff --git a/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java b/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java index 59392e944..3cc097beb 100644 --- a/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java +++ b/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java @@ -56,20 +56,13 @@ private static String trimLeadingSlash(String string) { return string; } - public static Path produceSrgJar(RemapAction remapAction, Project project, String side, Set mcLibs, Path officialJar, Path mappings) - throws Exception { - Set 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); - + public static void stripJar(Project project, Path inJar, Path outJar, Set filter) throws IOException { Stopwatch stopwatch = Stopwatch.createStarted(); - try (FileSystemUtil.Delegate output = FileSystemUtil.getJarFileSystem(stripped, true)) { - try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(officialJar, false)) { + int count = 0; + + try (FileSystemUtil.Delegate output = FileSystemUtil.getJarFileSystem(outJar, true)) { + try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(inJar, false)) { ThreadingUtils.TaskCompleter completer = ThreadingUtils.taskCompleter(); for (Path path : (Iterable) Files.walk(fs.get().getPath("/"))::iterator) { @@ -91,17 +84,32 @@ public static Path produceSrgJar(RemapAction remapAction, Project project, Strin completer.add(() -> { Files.copy(path, to, StandardCopyOption.COPY_ATTRIBUTES); }); + count++; } completer.complete(); } } finally { - project.getLogger().info("Copied class files in " + stopwatch.stop()); + project.getLogger().info("Copied " + count + " class files in " + stopwatch.stop()); } + } + + public static Path produceSrgJar(RemapAction remapAction, Project project, String side, Set mcLibs, Path officialJar, Path mappings) + throws Exception { + Set 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); + + stripJar(project, officialJar, stripped, filter); 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(); + Stopwatch stopwatch = Stopwatch.createStarted(); List args = remapAction.getArgs(stripped, output, mappings, project.files(mcLibs)); diff --git a/src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java b/src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java index ede2dbad8..d0f02428c 100644 --- a/src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java +++ b/src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java @@ -52,6 +52,7 @@ import net.fabricmc.mappingio.FlatMappingVisitor; import net.fabricmc.mappingio.MappingReader; import net.fabricmc.mappingio.adapter.RegularAsFlatMappingVisitor; +import net.fabricmc.mappingio.format.SrgReader; import net.fabricmc.mappingio.format.Tiny2Writer; import net.fabricmc.mappingio.format.TsrgReader; import net.fabricmc.mappingio.tree.MappingTree; @@ -74,8 +75,11 @@ public final class SrgMerger { private final boolean lenient; private final Set methodSrgNames = new HashSet<>(); - public SrgMerger(Logger logger, Path srg, @Nullable Supplier mojmap, Path tiny, boolean lenient) throws IOException { + private final boolean isLegacySrg; + + public SrgMerger(Logger logger, Path srg, @Nullable Supplier mojmap, Path tiny, boolean lenient, boolean isLegacySrg) throws IOException { this.logger = logger; + this.isLegacySrg = isLegacySrg; this.srg = readSrg(srg, mojmap); this.src = new MemoryMappingTree(); this.output = new MemoryMappingTree(); @@ -119,10 +123,10 @@ public MemoryMappingTree merge() throws IOException { * @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 mojmap, Path srg, Path tiny, Path out, boolean lenient) + public static void mergeSrg(Logger logger, @Nullable Supplier mojmap, Path srg, Path tiny, Path out, boolean lenient, boolean isLegacySrg) throws IOException, MappingException { Stopwatch stopwatch = Stopwatch.createStarted(); - MemoryMappingTree tree = mergeSrg(logger, mojmap, srg, tiny, lenient); + MemoryMappingTree tree = mergeSrg(logger, mojmap, srg, tiny, lenient, isLegacySrg); try (Tiny2Writer writer = new Tiny2Writer(Files.newBufferedWriter(out), false)) { tree.accept(writer); @@ -145,9 +149,9 @@ public static void mergeSrg(Logger logger, @Nullable Supplier mojmap, Path * @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 mojmap, Path srg, Path tiny, boolean lenient) + public static MemoryMappingTree mergeSrg(Logger logger, @Nullable Supplier mojmap, Path srg, Path tiny, boolean lenient, boolean isLegacySrg) throws IOException, MappingException { - return new SrgMerger(logger, srg, mojmap, tiny, lenient).merge(); + return new SrgMerger(logger, srg, mojmap, tiny, lenient, isLegacySrg).merge(); } private MemoryMappingTree readSrg(Path srg, @Nullable Supplier mojmap) throws IOException { @@ -159,7 +163,13 @@ private MemoryMappingTree readSrg(Path srg, @Nullable Supplier mojmap) thr } MemoryMappingTree tsrg = new MemoryMappingTree(); - TsrgReader.read(new StringReader(content), tsrg); + + if (isLegacySrg) { + SrgReader.read(new StringReader(content), tsrg); + } else { + TsrgReader.read(new StringReader(content), tsrg); + } + return tsrg; } }