From 6f10845562dd1fdb87e8cd842e75c649a127ce3b Mon Sep 17 00:00:00 2001 From: shedaniel Date: Wed, 13 Jan 2021 20:32:37 +0800 Subject: [PATCH] Allow switching to fabric mixin in forge, allowing advanced refmap remapper, making yarn for forge working. --- build.gradle | 3 + ...ForgeLoomMixinRemapperInjectorService.java | 71 ++++++ .../mixin/MixinIntermediaryDevRemapper.java | 220 ++++++++++++++++++ ...ods.modlauncher.api.ITransformationService | 1 + .../fabricmc/loom/LoomGradleExtension.java | 1 + .../loom/providers/ForgeUserdevProvider.java | 25 +- .../loom/providers/LaunchProvider.java | 9 +- .../providers/MinecraftPatchedProvider.java | 11 +- 8 files changed, 325 insertions(+), 16 deletions(-) create mode 100644 src/forgeInject/java/net/fabricmc/loom/inject/mixin/ForgeLoomMixinRemapperInjectorService.java create mode 100644 src/forgeInject/java/net/fabricmc/loom/inject/mixin/MixinIntermediaryDevRemapper.java create mode 100644 src/forgeInject/resources/META-INF/services/cpw.mods.modlauncher.api.ITransformationService diff --git a/build.gradle b/build.gradle index cd63f8b7..dbeffef8 100644 --- a/build.gradle +++ b/build.gradle @@ -87,6 +87,9 @@ dependencies { // Forge injection forgeInjectShadow ('net.fabricmc:tiny-mappings-parser:0.2.2.14') forgeInjectImplementation ('cpw.mods:modlauncher:6.1.3') + forgeInjectImplementation ('org.spongepowered:mixin:0.8.2') + forgeInjectImplementation ('com.google.code.gson:gson:2.8.6') + forgeInjectImplementation ('com.google.guava:guava:21.0') // Testing testImplementation(gradleTestKit()) diff --git a/src/forgeInject/java/net/fabricmc/loom/inject/mixin/ForgeLoomMixinRemapperInjectorService.java b/src/forgeInject/java/net/fabricmc/loom/inject/mixin/ForgeLoomMixinRemapperInjectorService.java new file mode 100644 index 00000000..fe0cc378 --- /dev/null +++ b/src/forgeInject/java/net/fabricmc/loom/inject/mixin/ForgeLoomMixinRemapperInjectorService.java @@ -0,0 +1,71 @@ +package net.fabricmc.loom.inject.mixin; + +import cpw.mods.modlauncher.api.IEnvironment; +import cpw.mods.modlauncher.api.ITransformationService; +import cpw.mods.modlauncher.api.ITransformer; +import cpw.mods.modlauncher.api.IncompatibleEnvironmentException; +import net.fabricmc.mapping.tree.TinyMappingFactory; +import net.fabricmc.mapping.tree.TinyTree; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.spongepowered.asm.mixin.MixinEnvironment; + +import javax.annotation.Nonnull; +import java.io.BufferedReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +public class ForgeLoomMixinRemapperInjectorService implements ITransformationService { + private static final Logger LOGGER = LogManager.getLogger("ForgeLoomRemapperInjector"); + + @Nonnull + @Override + public String name() { + return "ForgeLoomMixinRemapperInjector"; + } + + @Override + public void initialize(IEnvironment environment) { + + } + + @Override + public void beginScanning(IEnvironment environment) { + LOGGER.debug("We will be injecting our remapper."); + try { + MixinEnvironment.getDefaultEnvironment().getRemappers().add(new MixinIntermediaryDevRemapper(Objects.requireNonNull(resolveMappings()), "intermediary", "named")); + LOGGER.debug("We have successfully injected our remapper."); + } catch (Exception e) { + LOGGER.debug("We have failed to inject our remapper.", e); + } + } + + @Override + public void onLoad(IEnvironment env, Set otherServices) throws IncompatibleEnvironmentException { + + } + + @Nonnull + @Override + public List transformers() { + return Collections.emptyList(); + } + + private static TinyTree resolveMappings() { + try { + String srgNamedProperty = System.getProperty("mixin.forgeloom.inject.mappings.srg-named"); + Path path = Paths.get(srgNamedProperty); + try (BufferedReader reader = Files.newBufferedReader(path)) { + return TinyMappingFactory.loadWithDetection(reader); + } + } catch (Throwable throwable) { + throwable.printStackTrace(); + return null; + } + } +} diff --git a/src/forgeInject/java/net/fabricmc/loom/inject/mixin/MixinIntermediaryDevRemapper.java b/src/forgeInject/java/net/fabricmc/loom/inject/mixin/MixinIntermediaryDevRemapper.java new file mode 100644 index 00000000..a09c3036 --- /dev/null +++ b/src/forgeInject/java/net/fabricmc/loom/inject/mixin/MixinIntermediaryDevRemapper.java @@ -0,0 +1,220 @@ +/* + * Copyright 2016 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.loom.inject.mixin; + +import net.fabricmc.mapping.tree.ClassDef; +import net.fabricmc.mapping.tree.Descriptored; +import net.fabricmc.mapping.tree.TinyTree; +import net.fabricmc.mapping.util.MixinRemapper; +import org.spongepowered.asm.mixin.transformer.ClassInfo; + +import java.util.*; + +public class MixinIntermediaryDevRemapper extends MixinRemapper { + private static final String ambiguousName = ""; // dummy value for ambiguous mappings - needs querying with additional owner and/or desc info + + private final Set allPossibleClassNames = new HashSet<>(); + private final Map nameFieldLookup = new HashMap<>(); + private final Map nameMethodLookup = new HashMap<>(); + private final Map nameDescFieldLookup = new HashMap<>(); + private final Map nameDescMethodLookup = new HashMap<>(); + + public MixinIntermediaryDevRemapper(TinyTree mappings, String from, String to) { + super(mappings, from, to); + + for (ClassDef classDef : mappings.getClasses()) { + allPossibleClassNames.add(classDef.getName(from)); + allPossibleClassNames.add(classDef.getName(to)); + + putMemberInLookup(from, to, classDef.getFields(), nameFieldLookup, nameDescFieldLookup); + putMemberInLookup(from, to, classDef.getMethods(), nameMethodLookup, nameDescMethodLookup); + } + } + + private void putMemberInLookup(String from, String to, Collection descriptored, Map nameMap, Map nameDescMap) { + for (T field : descriptored) { + String nameFrom = field.getName(from); + String descFrom = field.getDescriptor(from); + String nameTo = field.getName(to); + + String prev = nameMap.putIfAbsent(nameFrom, nameTo); + + if (prev != null && prev != ambiguousName && !prev.equals(nameTo)) { + nameDescMap.put(nameFrom, ambiguousName); + } + + String key = getNameDescKey(nameFrom, descFrom); + prev = nameDescMap.putIfAbsent(key, nameTo); + + if (prev != null && prev != ambiguousName && !prev.equals(nameTo)) { + nameDescMap.put(key, ambiguousName); + } + } + } + + private void throwAmbiguousLookup(String type, String name, String desc) { + throw new RuntimeException("Ambiguous Mixin: " + type + " lookup " + name + " " + desc + " is not unique"); + } + + private String mapMethodNameInner(String owner, String name, String desc) { + String result = super.mapMethodName(owner, name, desc); + if (result.equals(name)) { + String otherClass = unmap(owner); + return super.mapMethodName(otherClass, name, unmapDesc(desc)); + } else { + return result; + } + } + + private String mapFieldNameInner(String owner, String name, String desc) { + String result = super.mapFieldName(owner, name, desc); + if (result.equals(name)) { + String otherClass = unmap(owner); + return super.mapFieldName(otherClass, name, unmapDesc(desc)); + } else { + return result; + } + } + + @Override + public String mapMethodName(String owner, String name, String desc) { + // handle unambiguous values early + if (owner == null || allPossibleClassNames.contains(owner)) { + String newName; + + if (desc == null) { + newName = nameMethodLookup.get(name); + } else { + newName = nameDescMethodLookup.get(getNameDescKey(name, desc)); + } + + if (newName != null) { + if (newName == ambiguousName) { + if (owner == null) { + throwAmbiguousLookup("method", name, desc); + } + } else { + return newName; + } + } else if (owner == null) { + return name; + } else { + // FIXME: this kind of namespace mixing shouldn't happen.. + // TODO: this should not repeat more than once + String unmapOwner = unmap(owner); + String unmapDesc = unmapDesc(desc); + + if (!unmapOwner.equals(owner) || !unmapDesc.equals(desc)) { + return mapMethodName(unmapOwner, name, unmapDesc); + } else { + // take advantage of the fact allPossibleClassNames + // and nameDescLookup cover all sets; if none are present, + // we don't have a mapping for it. + return name; + } + } + } + + Queue classInfos = new ArrayDeque<>(); + classInfos.add(ClassInfo.forName(owner)); + + while (!classInfos.isEmpty()) { + ClassInfo c = classInfos.remove(); + String ownerO = unmap(c.getName()); + String s; + if (!(s = mapMethodNameInner(ownerO, name, desc)).equals(name)) { + return s; + } + + if (!c.getSuperName().startsWith("java/")) { + ClassInfo cSuper = c.getSuperClass(); + if (cSuper != null) { + classInfos.add(cSuper); + } + } + + for (String itf : c.getInterfaces()) { + if (itf.startsWith("java/")) { + continue; + } + + ClassInfo cItf = ClassInfo.forName(itf); + if (cItf != null) { + classInfos.add(cItf); + } + } + } + + return name; + } + + @Override + public String mapFieldName(String owner, String name, String desc) { + // handle unambiguous values early + if (owner == null || allPossibleClassNames.contains(owner)) { + String newName = nameDescFieldLookup.get(getNameDescKey(name, desc)); + + if (newName != null) { + if (newName == ambiguousName) { + if (owner == null) { + throwAmbiguousLookup("field", name, desc); + } + } else { + return newName; + } + } else if (owner == null) { + return name; + } else { + // FIXME: this kind of namespace mixing shouldn't happen.. + // TODO: this should not repeat more than once + String unmapOwner = unmap(owner); + String unmapDesc = unmapDesc(desc); + + if (!unmapOwner.equals(owner) || !unmapDesc.equals(desc)) { + return mapFieldName(unmapOwner, name, unmapDesc); + } else { + // take advantage of the fact allPossibleClassNames + // and nameDescLookup cover all sets; if none are present, + // we don't have a mapping for it. + return name; + } + } + } + + ClassInfo c = ClassInfo.forName(map(owner)); + + while (c != null) { + String nextOwner = unmap(c.getName()); + String s; + if (!(s = mapFieldNameInner(nextOwner, name, desc)).equals(name)) { + return s; + } + + if (c.getSuperName().startsWith("java/")) { + break; + } + + c = c.getSuperClass(); + } + + return name; + } + + private static String getNameDescKey(String name, String descriptor) { + return name + ";;" + descriptor; + } +} diff --git a/src/forgeInject/resources/META-INF/services/cpw.mods.modlauncher.api.ITransformationService b/src/forgeInject/resources/META-INF/services/cpw.mods.modlauncher.api.ITransformationService new file mode 100644 index 00000000..0fb04144 --- /dev/null +++ b/src/forgeInject/resources/META-INF/services/cpw.mods.modlauncher.api.ITransformationService @@ -0,0 +1 @@ +net.fabricmc.loom.inject.mixin.ForgeLoomMixinRemapperInjectorService \ No newline at end of file diff --git a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java index e9dccbc0..3edffd42 100644 --- a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java +++ b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java @@ -71,6 +71,7 @@ public class LoomGradleExtension { public boolean shareCaches = false; public String mixinConfig = null; // FORGE: Passed to Minecraft public List mixinConfigs = null; // FORGE: Passed to Minecraft + public boolean useFabricMixin = false; // FORGE: Use Fabric Mixin for better refmap resolutions private final ConfigurableFileCollection unmappedMods; diff --git a/src/main/java/net/fabricmc/loom/providers/ForgeUserdevProvider.java b/src/main/java/net/fabricmc/loom/providers/ForgeUserdevProvider.java index 03b03f6d..b3481c18 100644 --- a/src/main/java/net/fabricmc/loom/providers/ForgeUserdevProvider.java +++ b/src/main/java/net/fabricmc/loom/providers/ForgeUserdevProvider.java @@ -24,24 +24,19 @@ package net.fabricmc.loom.providers; -import java.io.File; -import java.io.Reader; -import java.net.URI; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.util.function.Consumer; - import com.google.common.collect.ImmutableMap; import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import org.gradle.api.Project; - import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.DependencyProvider; +import org.gradle.api.Project; + +import java.io.File; +import java.io.Reader; +import java.net.URI; +import java.nio.file.*; +import java.util.function.Consumer; public class ForgeUserdevProvider extends DependencyProvider { private File userdevJar; @@ -79,7 +74,11 @@ public class ForgeUserdevProvider extends DependencyProvider { addDependency(json.get("universal").getAsString(), Constants.Configurations.FORGE_UNIVERSAL); for (JsonElement lib : json.get("libraries").getAsJsonArray()) { - addDependency(lib.getAsString(), Constants.Configurations.FORGE_DEPENDENCIES); + if (!getExtension().useFabricMixin || !lib.getAsString().startsWith("org.spongepowered:mixin:")) { + addDependency(lib.getAsString(), Constants.Configurations.FORGE_DEPENDENCIES); + } else { + addDependency("net.fabricmc:sponge-mixin:0.8+build.18", Constants.Configurations.FORGE_DEPENDENCIES); + } } // TODO: Read launch configs from the JSON too diff --git a/src/main/java/net/fabricmc/loom/providers/LaunchProvider.java b/src/main/java/net/fabricmc/loom/providers/LaunchProvider.java index 855cc507..16ef415b 100644 --- a/src/main/java/net/fabricmc/loom/providers/LaunchProvider.java +++ b/src/main/java/net/fabricmc/loom/providers/LaunchProvider.java @@ -83,8 +83,13 @@ public class LaunchProvider extends DependencyProvider { .argument("server", "--launchTarget") .argument("server", "fmluserdevserver") - .property("mixin.env.remapRefMap", "true") - .property("net.minecraftforge.gradle.GradleStart.srg.srg-mcp", getExtension().getMappingsProvider().srgToNamedSrg.getAbsolutePath()); + .property("mixin.env.remapRefMap", "true"); + + if (getExtension().useFabricMixin) { + launchConfig.property("mixin.forgeloom.inject.mappings.srg-named", getExtension().getMappingsProvider().mixinTinyMappingsWithSrg.getAbsolutePath()); + } else { + launchConfig.property("net.minecraftforge.gradle.GradleStart.srg.srg-mcp", getExtension().getMappingsProvider().srgToNamedSrg.getAbsolutePath()); + } String mixinConfig = getExtension().mixinConfig; List mixinConfigs = getExtension().mixinConfigs; diff --git a/src/main/java/net/fabricmc/loom/providers/MinecraftPatchedProvider.java b/src/main/java/net/fabricmc/loom/providers/MinecraftPatchedProvider.java index 1e6ea739..efcd3781 100644 --- a/src/main/java/net/fabricmc/loom/providers/MinecraftPatchedProvider.java +++ b/src/main/java/net/fabricmc/loom/providers/MinecraftPatchedProvider.java @@ -128,6 +128,10 @@ public class MinecraftPatchedProvider extends DependencyProvider { PatchProvider patchProvider = getExtension().getPatchProvider(); String minecraftVersion = minecraftProvider.getMinecraftVersion(); String jarSuffix = "-patched-forge-" + patchProvider.forgeVersion; + if (getExtension().useFabricMixin) { + jarSuffix += "-fabric-mixin"; + } + minecraftProvider.setJarSuffix(jarSuffix); File globalCache = getExtension().getUserCache(); @@ -301,7 +305,12 @@ public class MinecraftPatchedProvider extends DependencyProvider { for (Environment environment : Environment.values()) { String side = environment.side(); File target = environment.patchedSrgJar.apply(this); - walkFileSystems(injection, target, it -> !it.getFileName().toString().equals("MANIFEST.MF"), this::copyReplacing); + walkFileSystems(injection, target, it -> { + if (it.getFileName().toString().equals("MANIFEST.MF")) { + return false; + } + return getExtension().useFabricMixin || !it.getFileName().toString().endsWith("cpw.mods.modlauncher.api.ITransformationService"); + }, this::copyReplacing); } }