diff --git a/build.gradle b/build.gradle index 0b50d6861..2e4695ded 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ buildscript { gradlePluginPortal() } dependencies { - classpath 'top.outlands.gradle:ForgeGradle:6.0.55' + classpath 'top.outlands.gradle:ForgeGradle:6.0.57' classpath('top.outlands:artifactural:3.0.4') { transitive = false } diff --git a/gradle.properties b/gradle.properties index 56b1d42e4..621f4a1f1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,6 +20,7 @@ asm_version = 9.10.1 asm_deprecated = 7.1 netty_version = 4.2.15.Final lwjgl_version = 3.4.1 +cleanmix_version = 0.4.6 # Nsight Graphics # Check https://docs.nvidia.com/nsight-graphics/UserGuide/launch-application-overview.html#cli-arguments-details for details diff --git a/patches/minecraft/net/minecraft/crash/CrashReport.java.patch b/patches/minecraft/net/minecraft/crash/CrashReport.java.patch index f7e815d42..f5984f234 100644 --- a/patches/minecraft/net/minecraft/crash/CrashReport.java.patch +++ b/patches/minecraft/net/minecraft/crash/CrashReport.java.patch @@ -13,7 +13,7 @@ } - return s; -+ return s + net.minecraftforge.common.ForgeHooks.gatherMixinInfo(throwable); ++ return s + com.cleanroommc.cleanmix.CleanMixHooks.addMixinMetadataToCrashReport(throwable); } public String getCompleteReport() diff --git a/projects/cleanroom/build.gradle b/projects/cleanroom/build.gradle index c47b913de..a08540154 100644 --- a/projects/cleanroom/build.gradle +++ b/projects/cleanroom/build.gradle @@ -381,7 +381,7 @@ dependencies { installer 'io.github.classgraph:classgraph:4.8.184' // Mixin - installer 'com.cleanroommc:sponge-mixin:0.20.12+mixin.0.8.7' + installer "com.cleanroommc:cleanmix:$props.cleanmix_version" installer annotationProcessor('io.github.llamalad7:mixinextras-common:0.5.4') testImplementation platform('org.junit:junit-bom:6.1.0') @@ -390,6 +390,13 @@ dependencies { testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } +processResources { + inputs.property 'cleanmix_version', props.cleanmix_version + filesMatching('cleanmix.info') { + expand cleanmix_version: props.cleanmix_version + } +} + test { maxParallelForks = 1 useJUnitPlatform() diff --git a/src/main/java/com/cleanroommc/cleanmix/CleanMixHooks.java b/src/main/java/com/cleanroommc/cleanmix/CleanMixHooks.java new file mode 100644 index 000000000..2ec9ea323 --- /dev/null +++ b/src/main/java/com/cleanroommc/cleanmix/CleanMixHooks.java @@ -0,0 +1,167 @@ +package com.cleanroommc.cleanmix; + +import com.cleanroommc.discovery.CleanroomModDiscoverer; +import net.minecraftforge.fml.common.discovery.ASMDataTable; +import net.minecraftforge.fml.relauncher.CoreModManager; +import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin; +import org.spongepowered.asm.logging.ILogger; +import org.spongepowered.asm.mixin.Mixins; +import org.spongepowered.asm.mixin.extensibility.IMixinInfo; +import org.spongepowered.asm.mixin.transformer.ClassInfo; +import org.spongepowered.asm.mixin.transformer.Config; +import org.spongepowered.asm.mixin.transformer.Proxy; +import org.spongepowered.asm.service.MixinService; +import zone.rong.mixinbooter.*; + +import java.util.*; + +public class CleanMixHooks { + + public static String addMixinMetadataToCrashReport(Throwable throwable) { + Map classes = new LinkedHashMap<>(); + while (throwable != null) { + if (throwable instanceof NoClassDefFoundError) { + ClassInfo classInfo = ClassInfo.fromCache(throwable.getMessage()); + if (classInfo != null) { + classes.put(throwable.getMessage(), classInfo); + } + } + StackTraceElement[] stacktrace = throwable.getStackTrace(); + for (StackTraceElement stackTraceElement : stacktrace) { + String className = stackTraceElement.getClassName().replace('.', '/'); + if (classes.containsKey(className)) { + ClassInfo classInfo = ClassInfo.fromCache(className); + while (classInfo != null) { + classes.put(className, classInfo); + className = classInfo.getSuperName(); + if (className == null || className.isEmpty() || "java/lang/Object".equals(className)) { + break; + } + classInfo = classInfo.getSuperClass(); + } + } + } + throwable = throwable.getCause(); + } + if (classes.isEmpty()) { + return "\nNo Mixin Metadata is found in the Stacktrace.\n"; + } else { + StringBuilder mixinMetadataBuilder = new StringBuilder("\nMixins in Stacktrace:"); + boolean addedMetadata = false; + for (Map.Entry entry : classes.entrySet()) { + addedMetadata |= findAndAddMixinMetadata(mixinMetadataBuilder, entry.getKey(), entry.getValue()); + } + if (addedMetadata) { + return mixinMetadataBuilder.toString(); + } else { + return "\nNo Mixin Metadata is found in the Stacktrace.\n"; + } + } + } + + public static void loadMixinBooterEarlyMixins(List loadPlugins) { + ILogger logger = MixinService.getService().getLogger("CleanMix"); + Set presentMods = CleanroomModDiscoverer.instance().presentMods(); + Set queuedLoaders = new LinkedHashSet<>(); + Context context = new Context(null, presentMods); + for (CoreModManager.FMLPluginWrapper plugin : loadPlugins) { + IFMLLoadingPlugin thePlugin = plugin.coreModInstance; + if (thePlugin instanceof IMixinConfigHijacker interceptor) { + logger.info("Loading config hijacker {}.", interceptor.getClass().getName()); + for (String hijacked : interceptor.getHijackedMixinConfigs(context)) { + Config.blacklist(hijacked); + logger.info("{} will hijack the mixin config {}", interceptor.getClass().getName(), hijacked); + } + } + if (thePlugin instanceof IEarlyMixinLoader loader) { + queuedLoaders.add(loader); + } + } + for (IEarlyMixinLoader queuedLoader : queuedLoaders) { + logger.info("Loading early loader {} for its mixins.", queuedLoader.getClass().getName()); + try { + for (String mixinConfig : queuedLoader.getMixinConfigs()) { + context = new Context(mixinConfig, presentMods); + if (queuedLoader.shouldMixinConfigQueue(context)) { + logger.info("Adding [{}] mixin configuration.", mixinConfig); + Mixins.addConfiguration(mixinConfig); + queuedLoader.onMixinConfigQueued(context); + } + } + } catch (Throwable t) { + logger.error("Failed to execute early loader [{}].", queuedLoader.getClass().getName(), t); + } + } + } + + public static void loadMixinBooterLateMixins(ASMDataTable data) { + ILogger logger = MixinService.getService().getLogger("CleanMix"); + + // Gather ILateMixinLoaders + Set interfaceData = data.getAll(ILateMixinLoader.class.getName().replace('.', '/')); + Set lateLoaders = new HashSet<>(); + + // Instantiate all @MixinLoader annotated classes + Set annotatedData = data.getAll(MixinLoader.class.getName()); + for (ASMDataTable.ASMData annotated : annotatedData) { + try { + Class clazz = Class.forName(annotated.getClassName()); + logger.info("Loading annotated late loader [{}] for its mixins.", clazz.getName()); + Object instance = clazz.getConstructor().newInstance(); + if (instance instanceof ILateMixinLoader) { + lateLoaders.add((ILateMixinLoader) instance); + } + } catch (Throwable t) { + throw new RuntimeException("Unexpected error.", t); + } + } + + // Instantiate all ILateMixinLoader implemented classes + for (ASMDataTable.ASMData itf : interfaceData) { + try { + Class clazz = Class.forName(itf.getClassName().replace('/', '.')); + logger.info("Loading late loader [{}] for its mixins.", clazz.getName()); + lateLoaders.add((ILateMixinLoader) clazz.getConstructor().newInstance()); + } catch (Throwable t) { + throw new RuntimeException("Unexpected error.", t); + } + } + + for (ILateMixinLoader lateLoader : lateLoaders) { + try { + for (String mixinConfig : lateLoader.getMixinConfigs()) { + Context context = new Context(mixinConfig, CleanroomModDiscoverer.instance().presentMods()); + if (lateLoader.shouldMixinConfigQueue(context)) { + logger.info("Adding [{}] mixin configuration.", mixinConfig); + Mixins.addConfiguration(mixinConfig); + lateLoader.onMixinConfigQueued(context); + } + } + } catch (Throwable t) { + logger.error("Failed to execute late loader [{}].", lateLoader.getClass().getName(), t); + } + } + Proxy.refreshMixins(); + } + + private static boolean findAndAddMixinMetadata(StringBuilder mixinMetadataBuilder, String className, ClassInfo classInfo) { + Set mixinInfos = classInfo.getMixins(); + if (!mixinInfos.isEmpty()) { + mixinMetadataBuilder.append("\n\t"); + mixinMetadataBuilder.append(className); + mixinMetadataBuilder.append(':'); + for (IMixinInfo mixinInfo : mixinInfos) { + mixinMetadataBuilder.append("\n\t\t"); + mixinMetadataBuilder.append(mixinInfo.getClassName()); + mixinMetadataBuilder.append(" ("); + mixinMetadataBuilder.append(mixinInfo.getConfig()); + mixinMetadataBuilder.append(") ["); + mixinMetadataBuilder.append(mixinInfo.getConfig().getCleanSourceId()); + mixinMetadataBuilder.append("]"); + } + return true; + } + return false; + } + +} diff --git a/src/main/java/com/cleanroommc/cleanmix/CleanMixModContainer.java b/src/main/java/com/cleanroommc/cleanmix/CleanMixModContainer.java new file mode 100644 index 000000000..1b9c2b408 --- /dev/null +++ b/src/main/java/com/cleanroommc/cleanmix/CleanMixModContainer.java @@ -0,0 +1,62 @@ +package com.cleanroommc.cleanmix; + +import com.google.common.eventbus.EventBus; +import net.minecraftforge.fml.common.DummyModContainer; +import net.minecraftforge.fml.common.LoadController; +import net.minecraftforge.fml.common.MetadataCollection; +import net.minecraftforge.fml.common.ModMetadata; +import net.minecraftforge.fml.common.asm.FMLSanityChecker; +import org.spongepowered.asm.mixin.Mixin; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; + +public class CleanMixModContainer extends DummyModContainer { + + public static File location() { + try { + String resource = Mixin.class.getName().replace('.', '/') + ".class"; + URL url = Mixin.class.getClassLoader().getResource(resource); + String path = url.getPath(); + int index = path.indexOf('!'); + if (index != -1) { + path = path.substring(0, index); + } + return new File(new URI(path)).getAbsoluteFile(); + } catch (Exception ignore) { } + return FMLSanityChecker.fmlLocation; + } + + public CleanMixModContainer() { + super(loadMetadata()); + } + + @Override + public boolean registerBus(EventBus bus, LoadController controller) { + bus.register(this); + return true; + } + + private static ModMetadata loadMetadata() { + Map fallbackData = new HashMap<>(); + fallbackData.put("name", "CleanMix"); + fallbackData.put("version", "0.2.11"); + + InputStream inputStream = CleanMixModContainer.class.getClassLoader().getResourceAsStream("cleanmix.info"); + try { + return MetadataCollection.from(inputStream, "cleanmix.info").getMetadataForId("cleanmix", fallbackData); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException ignored) { } + } + } + } + +} diff --git a/src/main/java/com/cleanroommc/cleanmix/service/CleanMixBootstrap.java b/src/main/java/com/cleanroommc/cleanmix/service/CleanMixBootstrap.java new file mode 100644 index 000000000..39383ef87 --- /dev/null +++ b/src/main/java/com/cleanroommc/cleanmix/service/CleanMixBootstrap.java @@ -0,0 +1,22 @@ +package com.cleanroommc.cleanmix.service; + +import org.spongepowered.asm.service.IMixinServiceBootstrap; + +public class CleanMixBootstrap implements IMixinServiceBootstrap { + + private static final String OWN_SERVICE = "com.cleanroommc.cleanmix.service.CleanMixService"; + + @Override + public String getName() { + return "CleanMix"; + } + + @Override + public String getServiceClassName() { + return OWN_SERVICE; + } + + @Override + public void bootstrap() { } + +} diff --git a/src/main/java/com/cleanroommc/cleanmix/service/CleanMixService.java b/src/main/java/com/cleanroommc/cleanmix/service/CleanMixService.java new file mode 100644 index 000000000..f6de54291 --- /dev/null +++ b/src/main/java/com/cleanroommc/cleanmix/service/CleanMixService.java @@ -0,0 +1,97 @@ +package com.cleanroommc.cleanmix.service; + +import com.cleanroommc.cleanmix.CleanMixModContainer; +import com.cleanroommc.common.CleanroomEnvironment; +import com.cleanroommc.discovery.CleanroomModDiscoverer; +import com.google.common.base.Strings; +import net.minecraft.launchwrapper.LaunchClassLoader; +import net.minecraftforge.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper; +import net.minecraftforge.fml.common.launcher.FMLTweaker; +import net.minecraftforge.fml.relauncher.CoreModManager; +import org.spongepowered.asm.launch.platform.container.ContainerHandleURI; +import org.spongepowered.asm.launch.platform.container.IContainerHandle; +import org.spongepowered.asm.mixin.MixinEnvironment; +import org.spongepowered.asm.mixin.Mixins; +import org.spongepowered.asm.mixin.extensibility.IRemapper; + +import org.spongepowered.asm.obfuscation.mapping.mcp.Srg2McpRemapper; +import org.spongepowered.asm.obfuscation.mapping.remap.CleanroomRemapper; +import org.spongepowered.asm.service.mojang.AbstractMixinServiceLaunchWrapper; +import org.spongepowered.asm.service.mojang.MixinAuditFile; + +import java.io.File; +import java.net.URI; +import java.util.Collection; +import java.util.Collections; + +public class CleanMixService extends AbstractMixinServiceLaunchWrapper { + + private boolean initialized; + + @Override + public String getName() { + return "CleanMix"; + } + + @Override + public String getSideName() { + return CleanroomEnvironment.side().name(); + } + + @Override + public MixinEnvironment.CompatibilityLevel getMaxCompatibilityLevel() { + return MixinEnvironment.CompatibilityLevel.JAVA_25; + } + + @Override + public void init() { + if (this.initialized) { + return; + } + this.initialized = true; + super.init(); + IRemapper remapper = isDevelopment() ? + new Srg2McpRemapper(MixinEnvironment.getDefaultEnvironment()) : + new CleanroomRemapper<>(FMLDeobfuscatingRemapper.INSTANCE); + MixinEnvironment.getDefaultEnvironment().getRemappers().add(remapper); + String devConfigs = System.getProperty("crl.dev.mixin"); + if (!Strings.isNullOrEmpty(devConfigs)) { + for (String singleMixinConfig : devConfigs.split(",")) { + Mixins.addConfiguration(singleMixinConfig.trim()); + } + } + } + + @Override + public IContainerHandle getPrimaryContainer() { + return new ContainerHandleURI(CleanMixModContainer.location().toURI()); + } + + /** + * @see CoreModManager#handleLaunch(File, LaunchClassLoader, FMLTweaker) + * @see CleanroomModDiscoverer#discoverMixinMods() + */ + @Override + public Collection getMixinContainers() { + return Collections.emptyList(); + } + + @Override + protected MixinAuditFile createAuditLog() { + return new MixinAuditFile("cleanmix.log", "cleanmix.auditTrail"); + } + + @Override + protected String resolveSourceId(URI source) { + if ("file".equals(source.getScheme())) { + try { + String modId = CleanroomModDiscoverer.instance().modFromSource(new File(source)); + if (modId != null) { + return modId; + } + } catch (IllegalArgumentException ignored) { } + } + return null; + } + +} diff --git a/src/main/java/com/cleanroommc/common/CleanroomEnvironment.java b/src/main/java/com/cleanroommc/common/CleanroomEnvironment.java new file mode 100644 index 000000000..bde60e513 --- /dev/null +++ b/src/main/java/com/cleanroommc/common/CleanroomEnvironment.java @@ -0,0 +1,39 @@ +package com.cleanroommc.common; + +import net.minecraftforge.fml.relauncher.Side; + +// TODO: temp +public final class CleanroomEnvironment { + + private static Side side; + private static final boolean deobfuscatedEnvironment; + + // TODO: check obf class name? + static { + boolean deobfEnvironment = System.getProperty("net.minecraftforge.gradle.GradleStart.srg.srg-mcp") != null; + deobfEnvironment |= System.getProperty("crl.dev.extrapath") != null; + + deobfuscatedEnvironment = deobfEnvironment; + } + + public static void setSide(Side side) { + if (CleanroomEnvironment.side != null) { + return; + } + CleanroomEnvironment.side = side; + } + + public static boolean isDev() { + return deobfuscatedEnvironment; + } + + public static Side side() { + if (side == null) { + throw new IllegalStateException("Side not yet set."); + } + return side; + } + + private CleanroomEnvironment() { } + +} diff --git a/src/main/java/com/cleanroommc/common/MixinContainer.java b/src/main/java/com/cleanroommc/common/MixinContainer.java deleted file mode 100644 index 662bed153..000000000 --- a/src/main/java/com/cleanroommc/common/MixinContainer.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.cleanroommc.common; - -import com.google.common.eventbus.EventBus; -import net.minecraftforge.common.ForgeEarlyConfig; -import net.minecraftforge.fml.common.DummyModContainer; -import net.minecraftforge.fml.common.LoadController; -import net.minecraftforge.fml.common.ModMetadata; -import net.minecraftforge.fml.common.versioning.ArtifactVersion; -import net.minecraftforge.fml.common.versioning.DefaultArtifactVersion; - -public final class MixinContainer extends DummyModContainer{ - public MixinContainer() { - super(new ModMetadata()); - ModMetadata meta = this.getMetadata(); - meta.modId = "mixinbooter"; - meta.name = "MixinBooter"; - meta.description = "A Mixin library and loader."; - meta.version = ForgeEarlyConfig.CUSTOM_BUILT_IN_MOD_VERSION ? ForgeEarlyConfig.MIXIN_BOOTER_VERSION : "10.7"; - meta.authorList.add("Rongmario"); - } - - @Override - public boolean registerBus(EventBus bus, LoadController controller) { - return true; - } - - @Override - public ArtifactVersion getProcessedVersion() { - return new DefaultArtifactVersion("mixinbooter", true); - } -} diff --git a/src/main/java/com/cleanroommc/discovery/CleanroomModDiscoverer.java b/src/main/java/com/cleanroommc/discovery/CleanroomModDiscoverer.java new file mode 100644 index 000000000..eda09e5b9 --- /dev/null +++ b/src/main/java/com/cleanroommc/discovery/CleanroomModDiscoverer.java @@ -0,0 +1,645 @@ +package com.cleanroommc.discovery; + +import com.cleanroommc.cleanmix.CleanMixModContainer; +import com.cleanroommc.common.CleanroomContainer; +import com.cleanroommc.common.CleanroomEnvironment; +import com.cleanroommc.common.ConfigAnytimeContainer; +import com.cleanroommc.kirino.KirinoCommonCore; +import com.cleanroommc.util.CleanroomLog; +import com.google.common.base.Strings; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.LinkedHashMultimap; +import com.google.common.collect.SetMultimap; +import com.google.common.primitives.Ints; +import com.google.gson.*; +import net.minecraft.launchwrapper.Launch; +import net.minecraft.launchwrapper.LaunchClassLoader; +import net.minecraftforge.classloading.FMLForgePlugin; +import net.minecraftforge.common.ForgeEarlyConfig; +import net.minecraftforge.common.ForgeVersion; +import net.minecraftforge.fml.common.*; +import net.minecraftforge.fml.common.asm.FMLSanityChecker; +import net.minecraftforge.fml.common.asm.transformers.ModAccessTransformer; +import net.minecraftforge.fml.common.discovery.ModDiscoverer; +import net.minecraftforge.fml.common.launcher.FMLTweaker; +import net.minecraftforge.fml.common.discovery.ASMDataTable; +import net.minecraftforge.fml.common.discovery.ContainerType; +import net.minecraftforge.fml.common.discovery.ModCandidate; +import net.minecraftforge.fml.common.discovery.asm.ASMModParser; +import net.minecraftforge.fml.common.discovery.asm.ModAnnotation; +import net.minecraftforge.fml.relauncher.CoreModManager; +import net.minecraftforge.fml.relauncher.libraries.LibraryManager; +import org.apache.commons.io.IOUtils; +import org.spongepowered.asm.launch.MixinBootstrap; +import org.spongepowered.asm.launch.platform.container.ContainerHandleURI; +import org.spongepowered.asm.util.Constants.ManifestAttributes; +import zone.rong.mixinbooter.MixinBooterModContainer; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.Manifest; +import java.util.stream.Stream; +import java.util.zip.ZipEntry; + +public final class CleanroomModDiscoverer extends ModDiscoverer { + + private static final CleanroomModDiscoverer INSTANCE = new CleanroomModDiscoverer(); + + private static final String TWEAK_CLASS = ManifestAttributes.TWEAKER; + private static final String MIXIN_CONFIGS = ManifestAttributes.MIXINCONFIGS; + private static final String MIXIN_CONNECTOR = ManifestAttributes.MIXINCONNECTOR; + private static final String MIXIN_TWEAKER = "org.spongepowered.asm.launch.MixinTweaker"; + private static final String FORCE_LOAD_AS_MOD = "ForceLoadAsMod"; + private static final String COREMOD_CONTAINS_FML_MOD = "FMLCorePluginContainsFMLMod"; + private static final String FML_CORE_PLUGIN = "FMLCorePlugin"; + + public static CleanroomModDiscoverer instance() { + return INSTANCE; + } + + private final Gson gson = new GsonBuilder().setLenient().create(); + private final SetMultimap modIdToFiles = HashMultimap.create(); + private final SetMultimap fileToModIds = LinkedHashMultimap.create(); + private final Map discoveredFiles = new LinkedHashMap<>(); + + private final ASMDataTable asmDataTable = new ASMDataTable(); + private List nonModLibs = List.of(); + + private boolean hasForgeMods; + + private CleanroomModDiscoverer() { + this.discover(); + } + + public boolean hasForgeMods() { + return hasForgeMods; + } + + public boolean isModPresent(String modId) { + return modIdToFiles.containsKey(modId); + } + + public Set presentMods() { + return Collections.unmodifiableSet(modIdToFiles.keySet()); + } + + public Set modSources(String modId) { + return Collections.unmodifiableSet(modIdToFiles.get(modId)); + } + + public String modFromSource(File source) { + Set ids = modsFromSource(source); + return ids.isEmpty() ? null : ids.iterator().next(); + } + + public Set modsFromSource(File source) { + return Collections.unmodifiableSet(fileToModIds.get(source.getAbsoluteFile())); + } + + @Override + public ASMDataTable getASMTable() { + return asmDataTable; + } + + @Override + public List getNonModLibs() { + return nonModLibs; + } + + public void discoverMixinMods() { + for (DiscoveredMod mod : discoveredFiles.values()) { + if (!mod.hasMixinManifestAttributes()) { + continue; + } + if (mod.coremod() == null) { + try { + Launch.classLoader.addURL(mod.file().toURI().toURL()); + } catch (MalformedURLException e) { + CleanroomLog.get().error("Failed to manually load {} as a mixin mod {}.", mod.file().getName(), e); + } + } + CleanroomLog.get().debug("Submitting mixin container for {}", mod.file().getName()); + MixinBootstrap.getPlatform().addContainer(new ContainerHandleURI(mod.file().toURI())); + } + } + + public void discoverCoreMods(File minecraftDirectory, LaunchClassLoader classLoader, FMLTweaker tweaker) { + File modsDir = setupCoreModDir(minecraftDirectory); + findDerpMods(modsDir); + + CleanroomLog.get().debug("Discovering coremods"); + Set mixinConfigs = new LinkedHashSet<>(); + File containedDepsDir = new File(new File(Launch.minecraftHome, "mods"), ForgeVersion.mcVersion); + for (DiscoveredMod discoveredMod : discoveredFiles.values()) { + File coreMod = discoveredMod.file(); + if (coreMod.isDirectory()) { + CleanroomLog.get().debug("Ignoring folder {} in coremod searching", coreMod); + continue; + } + discoverCoreMod(classLoader, tweaker, discoveredMod, containedDepsDir, mixinConfigs); + } + } + + public IdentifiedMods identifyMods(ModClassLoader modClassLoader, List injectedContainers, List builtInMods) { + List modCandidates = new ArrayList<>(); + List nonModLibs = new ArrayList<>(); + + List mods = new ArrayList<>(builtInMods); + CleanroomLog.get().debug("Building injected Mod Containers {}", injectedContainers); + for (String containerClass : injectedContainers) { + ModContainer modContainer; + try { + modContainer = (ModContainer) Class.forName(containerClass, true, modClassLoader).getConstructor().newInstance(); + } catch (Exception e) { + CleanroomLog.get().error("A problem occurred instantiating the injected mod container {}", containerClass, e); + throw new LoaderException(e); + } + mods.add(new InjectedModContainer(modContainer, modContainer.getSource())); + } + + CleanroomLog.get().debug("Attempting to load mods contained in the minecraft jar file and associated classes"); + addClasspathCandidates(modClassLoader, modCandidates); + CleanroomLog.get().debug("Minecraft jar mods loaded successfully"); + addLibraryCandidates(modCandidates); + + mods.addAll(exploreModCandidates(modCandidates, nonModLibs)); + this.nonModLibs = List.copyOf(nonModLibs); + return new IdentifiedMods(mods, nonModLibs); + } + + public void addBuiltInModContainers(List mods, ModContainer minecraft, ModContainer mcp) { + // Minecraft + mods.add(minecraft); + // Minecraft Coder Pack + mods.add(new InjectedModContainer(mcp, new File("minecraft.jar"))); + // Cleanroom + mods.add(new InjectedModContainer(new CleanroomContainer(), FMLSanityChecker.fmlLocation)); + mods.add(new InjectedModContainer(new CleanMixModContainer(), CleanMixModContainer.location())); + // Included Mods + mods.add(new InjectedModContainer(new MixinBooterModContainer(), FMLSanityChecker.fmlLocation)); + mods.add(new InjectedModContainer(new ConfigAnytimeContainer(), FMLSanityChecker.fmlLocation)); + // Kirino + KirinoCommonCore.identifyMods(mods); + } + + public void rescueDroppedCoremods() { + for (DiscoveredMod discovered : discoveredFiles.values()) { + if (!discovered.mixinTweakerForceMod()) { + continue; + } + String coremod = discovered.coremod(); + if (coremod == null || CoreModManager.isCoreModLoaded(coremod)) { + continue; + } + if (CoreModManager.loadCoreModFromDiscoveredJar(Launch.classLoader, coremod, discovered.file())) { + CleanroomLog.get().warn("{} declares both a MixinTweaker TweakClass and FMLCorePlugin. Forge skips the coremod in this case and {} was loaded by Cleanroom.", discovered.file().getName(), coremod); + } else { + CleanroomLog.get().error("Failed to manually load coremod {} from {}.", coremod, discovered.file().getName()); + } + if (discovered.coreModContainsMod()) { + CoreModManager.getReparseableCoremods().add(discovered.file().getName()); + CleanroomLog.get().warn("Added {} back to reparseable collection, ready to be identified as a mod.", discovered.file().getName()); + } + } + } + + private void discover() { + CleanroomLog.get().info("Discovering mods"); + long startTime = System.nanoTime(); + + List candidates = new ArrayList<>(); + for (File candidate : LibraryManager.getCandidates()) { + File absolute = candidate.getAbsoluteFile(); + if (!candidates.contains(absolute)) { + candidates.add(absolute); + } + } + for (File candidate : candidates) { + scan(candidate); + } + for (URL url : Launch.classLoader.getURLs()) { + try { + File file = new File(url.toURI()).getAbsoluteFile(); + if (isArchive(file) && !discoveredFiles.containsKey(file)) { + scan(file); + } + } catch (URISyntaxException ignored) { } + } + + long elapsedMillis = (System.nanoTime() - startTime) / 1_000_000L; + CleanroomLog.get().info("Finished discovering mods. Found {} discovered mod ids in {} ms.", modIdToFiles.keySet().size(), elapsedMillis); + CleanroomLog.get().debug("Discovered mod ids: {}", String.join(", ", modIdToFiles.keySet())); + } + + private void scan(File file) { + File absolute = file.getAbsoluteFile(); + DiscoveredMod cached = discoveredFiles.get(absolute); + if (cached != null) { + return; + } +// if (!isArchive(absolute)) { +// DiscoveredMod info = new DiscoveredMod(absolute, Map.of(), List.of(), ManifestAttributes.FORGEMODTYPE, false, false, false, null, null); +// discoveredFiles.put(absolute, info); +// return; +// } + + Attributes attributes = null; + List modIds = List.of(); + String modType = ManifestAttributes.FORGEMODTYPE; + String coremod = null, tweaker = null; + boolean hasMixinManifestAttributes = false, mixinTweakerForceMod = false, coreModContainsMod = false; + + try (JarFile jarFile = new JarFile(absolute)) { + attributes = readManifestAttributes(absolute, jarFile); + if (attributes != null) { + modType = attributes.getValue(ManifestAttributes.MODTYPE); + if (Strings.isNullOrEmpty(modType)) { + modType = ManifestAttributes.FORGEMODTYPE; + } + coremod = attributes.getValue(FML_CORE_PLUGIN); + tweaker = attributes.getValue(TWEAK_CLASS); + hasMixinManifestAttributes = attributes.getValue(MIXIN_CONFIGS) != null || attributes.getValue(MIXIN_CONNECTOR) != null; + mixinTweakerForceMod = "true".equalsIgnoreCase(attributes.getValue(FORCE_LOAD_AS_MOD)) && MIXIN_TWEAKER.equals(tweaker); + coreModContainsMod = attributes.getValue(COREMOD_CONTAINS_FML_MOD) != null; + if ("optifine.OptiFineForgeTweaker".equals(tweaker)) { + modIds = List.of("optifine"); + } + } + if (modIds.isEmpty()) { + ZipEntry entry = jarFile.getEntry("mcmod.info"); + modIds = entry != null ? parseMcmodInfo(gson, jarFile.getInputStream(entry)) : List.of(); + if (modIds.isEmpty()) { + modIds = scanModAnnotations(jarFile); + } + } + for (String modId : modIds) { + recordMod(modId, absolute); + } + } catch (IOException e) { + CleanroomLog.get().error("Failed to read mod metadata from {}", absolute.getName(), e); + } + + DiscoveredMod info = new DiscoveredMod( + absolute, + attributes, + modIds, + modType, + hasMixinManifestAttributes, + mixinTweakerForceMod, + coreModContainsMod, + coremod, + tweaker); + discoveredFiles.put(absolute, info); + + if (!hasForgeMods && ManifestAttributes.FORGEMODTYPE.equals(info.modType())) { + hasForgeMods = true; + } + } + + private void discoverCoreMod(LaunchClassLoader classLoader, FMLTweaker tweaker, DiscoveredMod discoveredMod, File containedDepsDir, Set mixinConfigs) { + File file = discoveredMod.file(); + CleanroomLog.get().debug("Examining for coremod candidacy {}", file.getName()); + JarFile jar = null; + Attributes attributes; + String fmlCorePlugin; + boolean containNonMods; + try { + attributes = discoveredMod.attributes(); + if (attributes == null) { + return; + } + + String modSide = attributes.getValue(LibraryManager.MODSIDE); + if (modSide != null && !"BOTH".equals(modSide) && !modSide.equals(CleanroomEnvironment.side().name())) { + CleanroomLog.get().debug("Skipping {}, as we're on {} side and the mod is on {} side", file.getName(), CleanroomEnvironment.side(), modSide); + CoreModManager.getIgnoredMods().add(file.getName()); + return; + } + + String accessTransformers = attributes.getValue(ModAccessTransformer.FMLAT); + if (accessTransformers != null && !accessTransformers.isEmpty()) { + jar = new JarFile(file); + ModAccessTransformer.addJar(jar, accessTransformers); + } + + containNonMods = Boolean.parseBoolean(attributes.getValue("NonModDeps")); + if (discoveredMod.tweaker() != null) { + if (containNonMods) { + addContainedDepsToClasspath(classLoader, attributes, containedDepsDir); + } + CleanroomLog.get().info("Cascading tweaker {} from {}", discoveredMod.tweaker(), file.getName()); + Integer sortOrder = Ints.tryParse(Strings.nullToEmpty(attributes.getValue("TweakOrder"))); + CoreModManager.injectDiscoveredCascadingTweaker(file, discoveredMod.tweaker(), classLoader, tweaker, sortOrder == null ? 0 : sortOrder); + if (discoveredMod.mixinTweakerForceMod() || discoveredMod.coreModContainsMod()) { + CoreModManager.getReparseableCoremods().add(file.getName()); + } + CoreModManager.getIgnoredMods().add(file.getName()); + return; + } + + String modType = discoveredMod.modType(); + if (!ManifestAttributes.FORGEMODTYPE.equals(modType) && !ManifestAttributes.CLEANROOMMODTYPE.equals(modType)) { + CleanroomLog.get().warn("Adding {} to the list of things to skip. It is not an FML or Cleanroom mod, it is of type: {}", file.getName(), modType); + CoreModManager.getIgnoredMods().add(file.getName()); + return; + } + + fmlCorePlugin = discoveredMod.coremod(); + if (fmlCorePlugin == null) { + return; + } + if (isCorePluginBlacklisted(fmlCorePlugin)) { + CoreModManager.getIgnoredMods().add(file.getName()); + CleanroomLog.get().warn("Loading plugin {} for mod {} is in the forge_early.cfg blacklist and won't be loaded.", fmlCorePlugin, file.getName()); + return; + } + } catch (IOException ioe) { + CleanroomLog.get().error("Unable to read the jar file {} - ignoring", file.getName(), ioe); + return; + } finally { + try { + if (jar != null) { + jar.close(); + } + } catch (IOException ignored) { } + } + + try { + if (containNonMods) { + addContainedDepsToClasspath(classLoader, attributes, containedDepsDir); + } + + classLoader.addURL(file.toURI().toURL()); + + if (!discoveredMod.coreModContainsMod() && !discoveredMod.mixinTweakerForceMod()) { + CleanroomLog.get().trace("Adding {} to the list of known coremods, it will not be examined again", file.getName()); + CoreModManager.getIgnoredMods().add(file.getName()); + } else { + CleanroomLog.get().debug("Found FMLCorePluginContainsFMLMod marker in {}.", file.getName()); + CoreModManager.getReparseableCoremods().add(file.getName()); + } + } catch (MalformedURLException e) { + CleanroomLog.get().error("Unable to convert file {} into a URL, weird", file.getName(), e); + return; + } + + CoreModManager.loadCoreModFromDiscoveredJar(classLoader, fmlCorePlugin, file); + } + + private static void addContainedDepsToClasspath(LaunchClassLoader classLoader, Attributes attributes, File containedDepsDir) throws MalformedURLException { + String containedDeps = attributes.getValue(LibraryManager.MODCONTAINSDEPS); + if (containedDeps == null || containedDeps.isEmpty()) { + return; + } + for (String file : containedDeps.split(" ")) { + classLoader.addURL(new File(containedDepsDir, file).getAbsoluteFile().toURI().toURL()); + } + } + + private static boolean isCorePluginBlacklisted(String fmlCorePlugin) { + for (String plugin : ForgeEarlyConfig.LOADING_PLUGIN_BLACKLIST) { + if (plugin.equals(fmlCorePlugin)) { + return true; + } + } + return false; + } + + private static File setupCoreModDir(File minecraftDirectory) { + File coreModDir = new File(minecraftDirectory, "mods"); + try { + coreModDir = coreModDir.getCanonicalFile(); + } catch (IOException e) { + throw new RuntimeException(String.format("Unable to canonicalize the coremod dir at %s", minecraftDirectory.getName()), e); + } + if (!coreModDir.exists()) { + coreModDir.mkdir(); + } else if (!coreModDir.isDirectory()) { + throw new RuntimeException(String.format("Found a coremod file in %s that's not a directory", minecraftDirectory.getName())); + } + return coreModDir; + } + + private void findDerpMods(File directory) { + Path dirPath = directory.toPath(); + List derpMods = List.of(), extractedPaths = List.of(); + try (Stream stream = Files.walk(dirPath, 1)) { + derpMods = stream.filter(path -> path.getFileName().toString().endsWith(".jar.zip")).toList(); + } catch (IOException e) { + CleanroomLog.get().error("Unable to scan {} for derp mods", directory.getName(), e); + } + try (Stream stream = Files.walk(dirPath, 2)) { + extractedPaths = stream + .filter(path -> "META-INF".equals(path.getFileName().toString())) + .filter(Files::isDirectory) + .toList(); + } catch (IOException e) { + CleanroomLog.get().error("Unable to scan {} for derp directories", directory.getName(), e); + } + + if (!derpMods.isEmpty()) { + CleanroomLog.get().error("Cleanroom detected files with .jar.zip as the extension. These are usually failed downloads or jars renamed as zips. Download them again as .jar files before continuing."); + for (Path derpMod : derpMods) { + CleanroomLog.get().error("Problem file: {}", derpMod.getFileName()); + } + throw new RuntimeException("Cleanroom detected files with .jar.zip as the extension! Check the logs for more information!"); + } + if (!extractedPaths.isEmpty()) { + CleanroomLog.get().error("Cleanroom detected META-INF directories. These look like extracted mod jars and cannot be loaded correctly."); + CleanroomLog.get().error("Remove these extracted directories and add the jar directly into the mods directory."); + for (Path extractedPath : extractedPaths) { + CleanroomLog.get().error("Directory {} contains META-INF entries.", extractedPath.getFileName()); + } + throw new RuntimeException("Cleanroom detected extracted mod jars! Check the logs for more information!"); + } + } + + + private List exploreModCandidates(List modCandidates, List nonModLibs) { + List modList = new ArrayList<>(); + for (ModCandidate candidate : modCandidates) { + try { + List mods = candidate.explore(asmDataTable); + if (mods.isEmpty() && !candidate.isClasspath()) { + nonModLibs.add(candidate.getModContainer()); + } else { + modList.addAll(mods); + } + } catch (LoaderException le) { + CleanroomLog.get().warn("Identified a problem with the mod candidate {}, ignoring this source", candidate.getModContainer(), le); + } + } + return modList; + } + + private void addCandidate(List modCandidates, ModCandidate candidate) { + for (ModCandidate existing : modCandidates) { + if (existing.getModContainer().equals(candidate.getModContainer())) { + CleanroomLog.get().trace(" Skipping already in list {}", candidate.getModContainer()); + return; + } + } + modCandidates.add(candidate); + } + + private void addClasspathCandidates(ModClassLoader modClassLoader, List modCandidates) { + Set knownLibraries = new HashSet<>(); + knownLibraries.addAll(modClassLoader.getDefaultLibraries()); + knownLibraries.addAll(CoreModManager.getIgnoredMods()); + knownLibraries.addAll(CoreModManager.getReparseableCoremods()); + + File[] minecraftSources = modClassLoader.getParentSources(); + List devPaths = new ArrayList<>(); + for (File source : minecraftSources) { + if (source.isFile()) { + if (knownLibraries.contains(source.getName()) || modClassLoader.isDefaultLibrary(source)) { + CleanroomLog.get().trace("Skipping known library file {}", source.getAbsolutePath()); + } else { + CleanroomLog.get().debug("Found a minecraft related file at {}, examining for mod candidates", source.getAbsolutePath()); + addCandidate(modCandidates, new ModCandidate(source, source, ContainerType.JAR, false, true)); + } + } else if (source.isDirectory()) { + CleanroomLog.get().debug("Found a minecraft related directory at {}, collecting as developing mod", source.getAbsolutePath()); + devPaths.add(source); + } + } + + Map> pathsBySourceSet = new HashMap<>(); + for (File path : devPaths) { + if (path.equals(FMLForgePlugin.forgeLocation)) { + continue; + } + pathsBySourceSet.computeIfAbsent(path.getName(), key -> new ArrayList<>()).add(path); + } + for (List paths : pathsBySourceSet.values()) { + List classDirs = new ArrayList<>(); + File resourcesDir = null; + for (File path : paths) { + String type = path.getParentFile().getName(); + if ("resources".equals(type)) { + resourcesDir = path; + } else { + classDirs.add(path); + } + } + if (resourcesDir == null) { + continue; + } + for (File classDir : classDirs) { + CleanroomLog.get().debug("Adding path {} and {} as developing mod", classDir, resourcesDir); + addCandidate(modCandidates, new ModCandidate(classDir, resourcesDir)); + } + } + } + + private void addLibraryCandidates(List modCandidates) { + for (DiscoveredMod discoveredMod : discoveredFiles.values()) { + File mod = discoveredMod.file(); + if (CoreModManager.getIgnoredMods().contains(mod.getName()) && + !CoreModManager.getReparseableCoremods().contains(mod.getName())) { + CleanroomLog.get().trace("Skipping already parsed coremod or tweaker {}", mod.getName()); + } else if (mod.isDirectory()) { + CleanroomLog.get().trace("Skipping directory {}", mod.getName()); + } else { + CleanroomLog.get().debug("Found a candidate zip or jar file {}", mod.getName()); + addCandidate(modCandidates, new ModCandidate(mod, mod, ContainerType.JAR)); + } + } + } + + private static boolean isArchive(File file) { + if (!file.isFile()) { + return false; + } + String name = file.getName(); + return name.endsWith(".jar") || name.endsWith(".zip"); + } + + private Attributes readManifestAttributes(File file, JarFile jarFile) { + File externalManifest = new File(file.getAbsolutePath() + ".meta"); + if (!LibraryManager.DISABLE_EXTERNAL_MANIFEST && externalManifest.isFile()) { + try (FileInputStream input = new FileInputStream(externalManifest)) { + return new Manifest(input).getMainAttributes(); + } catch (IOException e) { + CleanroomLog.get().error("Error reading {} as an external manifest.", externalManifest.getAbsolutePath(), e); + } + } + try { + Manifest manifest = jarFile.getManifest(); + return manifest == null ? null : manifest.getMainAttributes(); + } catch (IOException e) { + CleanroomLog.get().error("Error reading manifest from {}.", file.getAbsolutePath(), e); + } + return null; + } + + private void recordMod(String modId, File source) { + File abs = source.getAbsoluteFile(); + modIdToFiles.put(modId, abs); + fileToModIds.put(abs, modId); + } + + private List parseMcmodInfo(Gson gson, InputStream stream) { + try { + List ids = new ArrayList<>(); + JsonElement root = gson.fromJson(new InputStreamReader(stream, StandardCharsets.UTF_8), JsonElement.class); + if (root instanceof JsonArray rootArray) { + for (JsonElement element : rootArray) { + if (element instanceof JsonObject mod && mod.has("modid")) { + ids.add(mod.get("modid").getAsString()); + } + } + } else if (root instanceof JsonObject rootObject && rootObject.get("modList") instanceof JsonArray modList) { + for (JsonElement element : modList) { + if (element instanceof JsonObject modId && modId.get("modid") instanceof JsonObject modid) { + ids.add(modid.getAsString()); + } + } + } + return ids; + } catch (Throwable t) { + CleanroomLog.get().error("Failed to parse mcmod.info", t); + } finally { + IOUtils.closeQuietly(stream); + } + return List.of(); + } + + private List scanModAnnotations(JarFile jar) { + Set modIds = new LinkedHashSet<>(); + var entries = jar.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + if (entry.isDirectory() || !entry.getName().endsWith(".class")) { + continue; + } + try (InputStream in = jar.getInputStream(entry)) { + ASMModParser parser = new ASMModParser(in); + parser.validate(); + for (ModAnnotation annotation : parser.getAnnotations()) { + if (ModContainerFactory.modTypes.containsKey(annotation.getASMType())) { + Object modId = annotation.getValues().get("modid"); + if (modId instanceof String stringModId && !stringModId.isEmpty()) { + modIds.add(stringModId); + } + } + } + } catch (Exception ignored) { } + } + return List.copyOf(modIds); + } + +} diff --git a/src/main/java/com/cleanroommc/discovery/DiscoveredMod.java b/src/main/java/com/cleanroommc/discovery/DiscoveredMod.java new file mode 100644 index 000000000..65a1074c8 --- /dev/null +++ b/src/main/java/com/cleanroommc/discovery/DiscoveredMod.java @@ -0,0 +1,23 @@ +package com.cleanroommc.discovery; + +import java.io.File; +import java.util.List; +import java.util.jar.Attributes; + +public record DiscoveredMod( + File file, + Attributes attributes, + List modIds, + String modType, + boolean hasMixinManifestAttributes, + boolean mixinTweakerForceMod, + boolean coreModContainsMod, + String coremod, + String tweaker) { + + public DiscoveredMod { + file = file.getAbsoluteFile(); + modIds = List.copyOf(modIds); + } + +} diff --git a/src/main/java/com/cleanroommc/discovery/IdentifiedMods.java b/src/main/java/com/cleanroommc/discovery/IdentifiedMods.java new file mode 100644 index 000000000..68dff6d08 --- /dev/null +++ b/src/main/java/com/cleanroommc/discovery/IdentifiedMods.java @@ -0,0 +1,16 @@ +package com.cleanroommc.discovery; + +import net.minecraftforge.fml.common.ModContainer; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public record IdentifiedMods(List mods, List nonModLibs) { + + public IdentifiedMods { + mods = new ArrayList<>(mods); // Can't be unmodifiable + nonModLibs = List.copyOf(nonModLibs); + } + +} diff --git a/src/main/java/com/cleanroommc/util/CleanroomLog.java b/src/main/java/com/cleanroommc/util/CleanroomLog.java new file mode 100644 index 000000000..006de0285 --- /dev/null +++ b/src/main/java/com/cleanroommc/util/CleanroomLog.java @@ -0,0 +1,17 @@ +package com.cleanroommc.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// TODO: temp +public final class CleanroomLog { + + private static final Logger LOGGER = LoggerFactory.getLogger("Cleanroom"); + + public static Logger get() { + return LOGGER; + } + + private CleanroomLog() { } + +} diff --git a/src/main/java/net/minecraftforge/common/ForgeEarlyConfig.java b/src/main/java/net/minecraftforge/common/ForgeEarlyConfig.java index dca97a1d6..f4c4d4d09 100644 --- a/src/main/java/net/minecraftforge/common/ForgeEarlyConfig.java +++ b/src/main/java/net/minecraftforge/common/ForgeEarlyConfig.java @@ -1,6 +1,7 @@ package net.minecraftforge.common; import net.minecraftforge.common.config.Config; +import zone.rong.mixinbooter.Tags; @Config(modid = ForgeVersion.MOD_ID, name = ForgeVersion.MOD_ID + "_early") public class ForgeEarlyConfig { @@ -75,7 +76,7 @@ public class ForgeEarlyConfig { public static boolean CUSTOM_BUILT_IN_MOD_VERSION = false; public static String CONFIG_ANY_TIME_VERSION = "3.0"; - public static String MIXIN_BOOTER_VERSION = "10.7"; + public static String MIXIN_BOOTER_VERSION = Tags.VERSION; @Config.Comment(""" Mods in this list have one or more of the problems list below: diff --git a/src/main/java/net/minecraftforge/common/ForgeHooks.java b/src/main/java/net/minecraftforge/common/ForgeHooks.java index f2c023c2a..e02524896 100644 --- a/src/main/java/net/minecraftforge/common/ForgeHooks.java +++ b/src/main/java/net/minecraftforge/common/ForgeHooks.java @@ -166,9 +166,6 @@ import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; -import org.spongepowered.asm.mixin.extensibility.IMixinInfo; -import org.spongepowered.asm.mixin.transformer.ClassInfo; -import org.spongepowered.asm.mixin.transformer.MixinInfo; public class ForgeHooks { @@ -1534,47 +1531,4 @@ public static int getSerializerId(DataSerializer serializer, IntIdentityHashB return id; } - public static String gatherMixinInfo(Throwable throwable){ - StackTraceElement[] stacktrace = throwable.getStackTrace(); - if (stacktrace.length > 0) { - try { - StringBuilder mixinMetadataBuilder = null; - ObjectOpenHashSet classes = new ObjectOpenHashSet<>(); - for (StackTraceElement stackTraceElement : stacktrace) { - classes.add(stackTraceElement.getClassName()); - } - for (String className : classes) { - ClassInfo classInfo = ClassInfo.fromCache(className); - if (classInfo != null) { - java.util.Set mixinInfos = classInfo.getMixins(); - if (!mixinInfos.isEmpty()) { - if (mixinMetadataBuilder == null) { - mixinMetadataBuilder = new StringBuilder("\n(MixinBooter) Mixins in Stacktrace:"); - } - mixinMetadataBuilder.append("\n\t"); - mixinMetadataBuilder.append(className); - mixinMetadataBuilder.append(":"); - for (IMixinInfo mixinInfo : mixinInfos) { - mixinMetadataBuilder.append("\n\t\t"); - mixinMetadataBuilder.append(mixinInfo.getClassName()); - mixinMetadataBuilder.append(" ("); - mixinMetadataBuilder.append(mixinInfo.getConfig().getName()); - mixinMetadataBuilder.append(")"); - } - } - } - } - - if (mixinMetadataBuilder == null) { - return "No Mixin Metadata is found in the Stacktrace.\n"; - } else { - return mixinMetadataBuilder.toString(); - } - } catch (Throwable t) { - return "Failed to find Mixin Metadata in Stacktrace:\n" + t; - } - } - - return ""; - } } diff --git a/src/main/java/net/minecraftforge/fml/client/FMLClientHandler.java b/src/main/java/net/minecraftforge/fml/client/FMLClientHandler.java index 6964a907b..d48c3fb75 100644 --- a/src/main/java/net/minecraftforge/fml/client/FMLClientHandler.java +++ b/src/main/java/net/minecraftforge/fml/client/FMLClientHandler.java @@ -36,6 +36,7 @@ import java.util.function.Predicate; import com.cleanroommc.common.PatchModPresentChecker; +import com.cleanroommc.discovery.CleanroomModDiscoverer; import com.cleanroommc.kirino.KirinoClientCore; import com.cleanroommc.kirino.KirinoCommonCore; import net.minecraft.client.Minecraft; @@ -115,7 +116,6 @@ import net.minecraftforge.fml.common.network.FMLNetworkEvent; import net.minecraftforge.fml.common.network.internal.FMLNetworkHandler; import net.minecraftforge.fml.common.toposort.ModSortingException; -import net.minecraftforge.fml.relauncher.CoreModManager; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.registries.GameData; @@ -221,8 +221,7 @@ public void beginMinecraftLoading(Minecraft minecraft, List resou SplashProgress.start(); client = minecraft; - if (PatchModPresentChecker.isNotPresent() - && CoreModManager.hasNonCrlMods()) + if (PatchModPresentChecker.isNotPresent() && CleanroomModDiscoverer.instance().hasForgeMods()) { String warning = PatchModPresentChecker.getWarningMessage(); String prompt = "Press any key to continue, ESC to exit."; diff --git a/src/main/java/net/minecraftforge/fml/common/FMLContextQuery.java b/src/main/java/net/minecraftforge/fml/common/FMLContextQuery.java deleted file mode 100644 index 30754cd8b..000000000 --- a/src/main/java/net/minecraftforge/fml/common/FMLContextQuery.java +++ /dev/null @@ -1,105 +0,0 @@ -package net.minecraftforge.fml.common; - -import com.google.common.base.Strings; -import net.minecraft.launchwrapper.Launch; -import net.minecraftforge.fml.common.discovery.ASMDataTable; -import net.minecraftforge.fml.common.discovery.ModCandidate; -import net.minecraftforge.fml.relauncher.MixinBooterPlugin; -import org.spongepowered.asm.mixin.extensibility.IMixinConfig; -import org.spongepowered.asm.mixin.extensibility.MixinContextQuery; - -import java.io.File; -import java.lang.reflect.Field; -import java.util.Collection; -import java.util.Set; - -public final class FMLContextQuery extends MixinContextQuery { - - private static final FMLContextQuery INSTANCE = new FMLContextQuery(); - - public static void init() { } - - private final ASMDataTable asmDataTable; - - private FMLContextQuery() { - super(); - ASMDataTable asmDataTable = null; - try { - Field modApiManager$dataTable = ModAPIManager.class.getDeclaredField("dataTable"); - modApiManager$dataTable.setAccessible(true); - asmDataTable = (ASMDataTable) modApiManager$dataTable.get(ModAPIManager.INSTANCE); - } catch (ReflectiveOperationException e) { - FMLLog.log.fatal("Not able to reflect ModAPIManager#dataTable", e); - } - this.asmDataTable = asmDataTable; - } - - private static String getResourceName(IMixinConfig config) { - String resource = Launch.classLoader.getResource(config.getName()).getPath(); - if (resource.contains("!/")) { - String filePath = resource.split("!/")[0]; - String[] parts = filePath.split("/"); - if (parts.length != 0) { - return parts[parts.length - 1]; - } - } - return null; - } - - @Override - public String getOwner(IMixinConfig config) { - if (this.asmDataTable == null) { - return getSanitizedModIdFromResource(config); - } else { - return getCandidates(config).stream() - .map(ModCandidate::getContainedMods) - .flatMap(Collection::stream) - .map(ModContainer::getModId) - .filter(modId -> !Strings.isNullOrEmpty(modId)) - .findFirst() - .orElseGet(() -> getSanitizedModIdFromResource(config)); - } - } - - @Override - public String getLocation(IMixinConfig config) { - if (this.asmDataTable == null) { - return getSanitizedModIdFromResource(config); - } else { - return getCandidates(config).stream() - .map(ModCandidate::getClassPathRoot) - .map(File::getName) - .findFirst() - .orElse(null); - } - } - - private Set getCandidates(IMixinConfig config) { - String pkg = config.getMixinPackage(); - pkg = pkg.charAt(pkg.length() - 1) == '.' ? pkg.substring(0, pkg.length() - 1) : pkg; - return this.asmDataTable.getCandidatesFor(pkg); - } - - private String getSanitizedModIdFromResource(IMixinConfig config) { - String baseModId = getResourceName(config); - if (baseModId == null) { - return null; - } - if (baseModId.endsWith(".jar") || baseModId.endsWith(".zip")) { - baseModId = baseModId.substring(0, baseModId.length() - 4); - } - // Wipe Minecraft Versioning - baseModId = baseModId.replaceAll("1\\.12(\\.2)?", ""); - StringBuilder sanitizedModId = new StringBuilder(); - for (int i = 0; i < baseModId.length(); i++) { - char character = baseModId.charAt(i); - if ((character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z') || (i > 0 && character >= '0' && character <= '9')) { - sanitizedModId.append(character); - } else { - sanitizedModId.append('_'); - } - } - return sanitizedModId.toString(); - } - -} \ No newline at end of file diff --git a/src/main/java/net/minecraftforge/fml/common/FMLModContainer.java b/src/main/java/net/minecraftforge/fml/common/FMLModContainer.java index dccb788d9..c36633a65 100644 --- a/src/main/java/net/minecraftforge/fml/common/FMLModContainer.java +++ b/src/main/java/net/minecraftforge/fml/common/FMLModContainer.java @@ -544,15 +544,6 @@ private void parseSimpleFieldAnnotation(SetMultimap annotations public void constructMod(FMLConstructionEvent event) { ModClassLoader modClassLoader = event.getModClassLoader(); - try - { - modClassLoader.addFile(source); - } - catch (MalformedURLException e) - { - FormattedMessage message = new FormattedMessage("{} Failed to add file to classloader: {}", getModId(), source); - throw new LoaderException(message.getFormattedMessage(), e); - } modClassLoader.clearNegativeCacheFor(candidate.getClassList()); //Only place I could think to add this... diff --git a/src/main/java/net/minecraftforge/fml/common/LoadController.java b/src/main/java/net/minecraftforge/fml/common/LoadController.java index e1bfeeff2..4bcba6f88 100644 --- a/src/main/java/net/minecraftforge/fml/common/LoadController.java +++ b/src/main/java/net/minecraftforge/fml/common/LoadController.java @@ -19,6 +19,7 @@ package net.minecraftforge.fml.common; +import com.cleanroommc.cleanmix.CleanMixHooks; import com.google.common.base.Strings; import com.google.common.base.Throwables; import com.google.common.collect.*; @@ -34,29 +35,20 @@ import net.minecraftforge.fml.common.event.*; import net.minecraftforge.fml.common.eventhandler.FMLThrowingEventBus; import net.minecraftforge.fml.common.versioning.ArtifactVersion; -import net.minecraftforge.fml.relauncher.MixinBooterPlugin; import net.minecraftforge.fml.relauncher.libraries.LibraryManager; import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.message.FormattedMessage; -import org.spongepowered.asm.mixin.MixinEnvironment; import org.spongepowered.asm.mixin.Mixins; -import org.spongepowered.asm.mixin.ModUtil; -import org.spongepowered.asm.mixin.transformer.Config; -import org.spongepowered.asm.mixin.transformer.Proxy; -import org.spongepowered.asm.service.MixinService; -import org.spongepowered.asm.service.mojang.MixinServiceLaunchWrapper; import org.spongepowered.asm.util.Constants; -import zone.rong.mixinbooter.Context; -import zone.rong.mixinbooter.ILateMixinLoader; import javax.annotation.Nullable; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.net.MalformedURLException; import java.util.*; -import java.util.function.Supplier; import java.util.jar.Attributes; import java.util.jar.JarFile; import java.util.stream.Collectors; @@ -137,98 +129,6 @@ public void distributeStateMessage(LoaderState state, Object... eventData) { if (state.hasEvent()) { - if (state == LoaderState.CONSTRUCTING) { // This state is where Forge adds mod files to ModClassLoader - - ModClassLoader modClassLoader = (ModClassLoader) eventData[0]; - ASMDataTable asmDataTable = (ASMDataTable) eventData[1]; - - try { - - // Add mods into the delegated ModClassLoader - for (ModContainer container : this.loader.getActiveModList()) { - modClassLoader.addFile(container.getSource()); - } - // Handle Mixin Configs and non-mod libraries - File mods_ver = new File(new File(Launch.minecraftHome, "mods"), ForgeVersion.mcVersion); - for (ModContainer container : this.loader.getActiveModList()) { - try (JarFile jarFile = new JarFile(container.getSource())) { - Attributes mfAttributes = jarFile.getManifest() == null ? null : jarFile.getManifest().getMainAttributes(); - if (mfAttributes != null) { - String configs = mfAttributes.getValue(Constants.ManifestAttributes.MIXINCONFIGS); - if (!Strings.isNullOrEmpty(configs)) { - Mixins.addConfigurations(configs.split(",")); - } - boolean containNonMods = Boolean.parseBoolean(mfAttributes.getValue("NonModDeps")); - if (containNonMods) { - for (String file: mfAttributes.getValue(LibraryManager.MODCONTAINSDEPS).split(" ")) { - modClassLoader.addFile(new File(mods_ver, file)); - } - } - } - } catch (IOException ignored) {} - } - - FMLContextQuery.init(); // Initialize FMLContextQuery and add it to the global list - - - // Load late mixins - FMLLog.log.info("Instantiating all ILateMixinLoader implemented classes..."); - for (ASMDataTable.ASMData asmData : asmDataTable.getAll(ILateMixinLoader.class)) { - try { - modClassLoader.addFile(asmData.getCandidate().getModContainer()); // Add to path before `newInstance` - Class clazz = Class.forName(asmData.getClassName().replace('/', '.')); - FMLLog.log.info("Instantiating {} for its mixins.", clazz); - @SuppressWarnings("deprecation") - ILateMixinLoader loader = (ILateMixinLoader) clazz.getConstructor().newInstance(); - for (String mixinConfig : loader.getMixinConfigs()) { - @SuppressWarnings("deprecation") - Context context = new Context(mixinConfig); - if (loader.shouldMixinConfigQueue(context)) { - try { - FMLLog.log.info("Adding {} mixin configuration.", mixinConfig); - Mixins.addConfiguration(mixinConfig); - loader.onMixinConfigQueued(context); - } catch (Throwable t) { - FMLLog.log.error("Error adding mixin configuration for {}", mixinConfig, t); - } - } - } - } catch (ClassNotFoundException | ClassCastException | InstantiationException | IllegalAccessException e) { - FMLLog.log.error("Unable to load the ILateMixinLoader", e); - } - } - - // mark config owners : for earlys, lates, and mfAttributes. - for (Config config : Mixins.getConfigs()) { - if (!config.getConfig().hasDecoration(ModUtil.OWNER_DECORATOR)) { - String pkg = config.getConfig().getMixinPackage(); - pkg = pkg.charAt(pkg.length() - 1) == '.' ? pkg.substring(0, pkg.length() - 1) : pkg; - List owners = getPackageOwners(pkg); - if (owners.isEmpty()) { - config.getConfig().decorate(ModUtil.OWNER_DECORATOR, (Supplier) () -> ModUtil.UNKNOWN_OWNER); - } else { - final String owner = owners.get(0).getModId(); // better assign ? - config.getConfig().decorate(ModUtil.OWNER_DECORATOR, (Supplier) () -> owner); - } - } - } - - for (ModContainer container : this.loader.getActiveModList()) { - modClassLoader.addFile(container.getSource()); - } - } catch (Throwable t) { - FMLLog.log.error("Error loading Mods", t); - } - if (MixinService.getService() instanceof MixinServiceLaunchWrapper) { - ((MixinServiceLaunchWrapper) MixinService.getService()).setDelegatedTransformers(null); - } - MixinEnvironment current = MixinEnvironment.getCurrentEnvironment(); - - Proxy.transformer.processor.selectConfigs(current); - Proxy.transformer.processor.prepareConfigs(current, Proxy.transformer.processor.extensions); - - } - MixinEnvironment.gotoPhase(MixinEnvironment.Phase.MOD); masterChannel.post(state.getEvent(eventData)); } } diff --git a/src/main/java/net/minecraftforge/fml/common/Loader.java b/src/main/java/net/minecraftforge/fml/common/Loader.java index ab8bf26ee..ef4482d0f 100644 --- a/src/main/java/net/minecraftforge/fml/common/Loader.java +++ b/src/main/java/net/minecraftforge/fml/common/Loader.java @@ -37,11 +37,10 @@ import java.util.Properties; import java.util.Set; +import com.cleanroommc.cleanmix.CleanMixHooks; import com.cleanroommc.client.LoadingTracker; -import com.cleanroommc.common.CleanroomContainer; -import com.cleanroommc.common.MixinContainer; -import com.cleanroommc.common.ConfigAnytimeContainer; -import com.cleanroommc.kirino.KirinoCommonCore; +import com.cleanroommc.discovery.CleanroomModDiscoverer; +import com.cleanroommc.discovery.IdentifiedMods; import net.minecraft.util.ResourceLocation; import net.minecraftforge.common.ForgeVersion; import net.minecraftforge.common.capabilities.CapabilityManager; @@ -50,11 +49,7 @@ import net.minecraftforge.fml.common.LoaderState.ModState; import net.minecraftforge.fml.common.ModContainer.Disableable; import net.minecraftforge.fml.common.ProgressManager.ProgressBar; -import net.minecraftforge.fml.common.asm.FMLSanityChecker; import net.minecraftforge.fml.common.discovery.ASMDataTable; -import net.minecraftforge.fml.common.discovery.ContainerType; -import net.minecraftforge.fml.common.discovery.ModCandidate; -import net.minecraftforge.fml.common.discovery.ModDiscoverer; import net.minecraftforge.fml.common.event.FMLInterModComms; import net.minecraftforge.fml.common.event.FMLLoadEvent; import net.minecraftforge.fml.common.event.FMLModIdMappingEvent; @@ -66,9 +61,7 @@ import net.minecraftforge.fml.common.versioning.ArtifactVersion; import net.minecraftforge.fml.common.versioning.DependencyParser; import net.minecraftforge.fml.common.versioning.VersionParser; -import net.minecraftforge.fml.relauncher.CoreModManager; import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.libraries.LibraryManager; import net.minecraftforge.registries.GameData; import net.minecraftforge.registries.ObjectHolderRegistry; @@ -100,6 +93,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import org.apache.logging.log4j.message.FormattedMessage; import javax.annotation.Nullable; @@ -181,7 +175,7 @@ public class Loader private static List injectedContainers; private ImmutableMap fmlBrandingProperties; private File forcedModFile; - private ModDiscoverer discoverer; + private CleanroomModDiscoverer discoverer; private ProgressBar progressBar; public static Loader instance() @@ -367,70 +361,6 @@ else if (wrongMinecraftExceptions.size()==1 && missingModsExceptions.isEmpty()) * Finally, if they are successfully loaded as classes, they are then added * to the available mod list. */ - private ModDiscoverer identifyMods(List additionalContainers) - { - injectedContainers.addAll(additionalContainers); - FMLLog.log.debug("Building injected Mod Containers {}", injectedContainers); - mods.add(minecraft); - // Add in the MCP mod container - mods.add(new InjectedModContainer(mcp,new File("minecraft.jar"))); - mods.add(new InjectedModContainer(new CleanroomContainer(), FMLSanityChecker.fmlLocation)); - mods.add(new InjectedModContainer(new MixinContainer(), FMLSanityChecker.fmlLocation)); - mods.add(new InjectedModContainer(new ConfigAnytimeContainer(), FMLSanityChecker.fmlLocation)); - KirinoCommonCore.identifyMods(mods); - - for (String cont : injectedContainers) - { - ModContainer mc; - try - { - mc = (ModContainer) Class.forName(cont,true, modClassLoader).getConstructor().newInstance(); - } - catch (Exception e) - { - FMLLog.log.error("A problem occurred instantiating the injected mod container {}", cont, e); - throw new LoaderException(e); - } - mods.add(new InjectedModContainer(mc,mc.getSource())); - } - ModDiscoverer discoverer = new ModDiscoverer(); - - //if (!FMLForgePlugin.RUNTIME_DEOBF) //Only descover mods in the classpath if we're in the dev env. - { //TODO: Move this to GradleStart? And add a specific mod canidate for Forge itself. - FMLLog.log.debug("Attempting to load mods contained in the minecraft jar file and associated classes"); - discoverer.findClasspathMods(modClassLoader); - FMLLog.log.debug("Minecraft jar mods loaded successfully"); - } - - List candidates = LibraryManager.getCandidates(); - //Do we want to sort the full list after resolving artifacts? - //TODO: Add dependency gathering? - - for (File mod : candidates) - { - // skip loaded coremods - if (CoreModManager.getIgnoredMods().contains(mod.getName())) - { - FMLLog.log.trace("Skipping already parsed coremod or tweaker {}", mod.getName()); - } - else if(mod.isDirectory()) - { - FMLLog.log.trace("Skipping directory {}", mod.getName()); - } - else - { - FMLLog.log.debug("Found a candidate zip or jar file {}", mod.getName()); - discoverer.addCandidate(new ModCandidate(mod, mod, ContainerType.JAR)); - } - } - - mods.addAll(discoverer.identifyMods()); - identifyDuplicates(mods); - namedMods = Maps.uniqueIndex(mods, ModContainer::getModId); - FMLLog.log.info("Forge Mod Loader has identified {} mod{} to load", mods.size(), mods.size() != 1 ? "s" : ""); - return discoverer; - } - private static int compareModId(ModContainer o1, ModContainer o2){ return o1.getModId().compareTo(o2.getModId()); } @@ -546,8 +476,8 @@ public void setupTestHarness(ModContainer... containers) } /** - * Called from the hook to start mod loading. We trigger the - * {@link #identifyMods(List)} and Constructing, Preinitalization, and Initalization phases here. Finally, + * Called from the hook to start mod loading. We trigger discovery and + * Constructing, Preinitalization, and Initalization phases here. Finally, * the mod list is frozen completely and is consider immutable from then on. * @param injectedModContainers containers to inject */ @@ -561,15 +491,23 @@ public void loadMods(List injectedModContainers) namedMods = Maps.newHashMap(); modController = new LoadController(this); modController.transition(LoaderState.LOADING, false); - discoverer = identifyMods(injectedModContainers); - ModAPIManager.INSTANCE.manageAPI(modClassLoader, discoverer); + injectedContainers.addAll(injectedModContainers); + discoverer = CleanroomModDiscoverer.instance(); + discoverer.addBuiltInModContainers(mods, minecraft, mcp); + IdentifiedMods identifiedMods = discoverer.identifyMods(modClassLoader, injectedContainers, mods); + mods = identifiedMods.mods(); + identifyDuplicates(mods); + namedMods = Maps.uniqueIndex(mods, ModContainer::getModId); + FMLLog.log.info("Forge Mod Loader has identified {} mod{} to load", mods.size(), mods.size() != 1 ? "s" : ""); + ASMDataTable asmDataTable = discoverer.getASMTable(); + ModAPIManager.INSTANCE.manageAPI(modClassLoader, asmDataTable); disableRequestedMods(); modController.distributeStateMessage(FMLLoadEvent.class); sortModList(); ModAPIManager.INSTANCE.cleanupAPIContainers(modController.getActiveModList()); ModAPIManager.INSTANCE.cleanupAPIContainers(mods); mods = ImmutableList.copyOf(mods); - for (File nonMod : discoverer.getNonModLibs()) + for (File nonMod : identifiedMods.nonModLibs()) { if (nonMod.isFile()) { @@ -584,11 +522,24 @@ public void loadMods(List injectedModContainers) } } } + for (ModContainer container : this.getActiveModList()) + { + try + { + modClassLoader.addFile(container.getSource()); + } + catch (MalformedURLException e) + { + FormattedMessage message = new FormattedMessage("{} Failed to add file to classloader: {}", container.getModId(), container.getSource()); + throw new LoaderException(message.getFormattedMessage(), e); + } + } - ConfigManager.loadData(discoverer.getASMTable()); + ConfigManager.loadData(asmDataTable); + CleanMixHooks.loadMixinBooterLateMixins(asmDataTable); modController.transition(LoaderState.CONSTRUCTING, false); - modController.distributeStateMessage(LoaderState.CONSTRUCTING, modClassLoader, discoverer.getASMTable(), reverseDependencies); + modController.distributeStateMessage(LoaderState.CONSTRUCTING, modClassLoader, asmDataTable, reverseDependencies); FMLLog.log.debug("Mod signature data"); FMLLog.log.debug(" \tValid Signatures:"); diff --git a/src/main/java/net/minecraftforge/fml/common/ModAPIManager.java b/src/main/java/net/minecraftforge/fml/common/ModAPIManager.java index de54b024a..0a53648af 100644 --- a/src/main/java/net/minecraftforge/fml/common/ModAPIManager.java +++ b/src/main/java/net/minecraftforge/fml/common/ModAPIManager.java @@ -29,8 +29,8 @@ import net.minecraftforge.fml.common.asm.transformers.ModAPITransformer; import net.minecraftforge.fml.common.discovery.ASMDataTable; import net.minecraftforge.fml.common.discovery.ModCandidate; -import net.minecraftforge.fml.common.discovery.ModDiscoverer; import net.minecraftforge.fml.common.discovery.ASMDataTable.ASMData; +import net.minecraftforge.fml.common.discovery.ModDiscoverer; import net.minecraftforge.fml.common.versioning.ArtifactVersion; import net.minecraftforge.fml.common.versioning.DefaultArtifactVersion; import net.minecraftforge.fml.common.versioning.VersionParser; @@ -225,8 +225,13 @@ public void registerDataTableAndParseAPI(ASMDataTable dataTable) public void manageAPI(ModClassLoader modClassLoader, ModDiscoverer discoverer) { - registerDataTableAndParseAPI(discoverer.getASMTable()); - transformer = modClassLoader.addModAPITransformer(dataTable); + this.manageAPI(modClassLoader, discoverer.getASMTable()); + } + + public void manageAPI(ModClassLoader modClassLoader, ASMDataTable dataTable) + { + registerDataTableAndParseAPI(dataTable); + transformer = modClassLoader.addModAPITransformer(this.dataTable); } public void injectAPIModContainers(List mods, Map nameLookup) diff --git a/src/main/java/net/minecraftforge/fml/common/asm/FMLSanityChecker.java b/src/main/java/net/minecraftforge/fml/common/asm/FMLSanityChecker.java index 347e458dd..36601d815 100644 --- a/src/main/java/net/minecraftforge/fml/common/asm/FMLSanityChecker.java +++ b/src/main/java/net/minecraftforge/fml/common/asm/FMLSanityChecker.java @@ -45,8 +45,6 @@ import net.minecraftforge.fml.relauncher.Side; import com.google.common.io.ByteStreams; -import org.spongepowered.asm.bridge.RemapperAdapterFML; -import org.spongepowered.asm.launch.platform.MixinPlatformAgentFMLLegacy; public class FMLSanityChecker implements IFMLCallHook { @@ -190,7 +188,7 @@ public void injectData(Map data) fmlLocation = (File)data.get("coremodLocation"); ClassPatchManager.INSTANCE.setup(FMLLaunchHandler.side()); FMLDeobfuscatingRemapper.INSTANCE.setup(mcDir, cl, (String) data.get("deobfuscationFileName"), liveEnv); - MixinPlatformAgentFMLLegacy.injectRemapper(RemapperAdapterFML.create(FMLDeobfuscatingRemapper.INSTANCE, FMLDeobfuscatingRemapper.INSTANCE::unmap)); + } } diff --git a/src/main/java/net/minecraftforge/fml/common/asm/transformers/deobf/FMLDeobfuscatingRemapper.java b/src/main/java/net/minecraftforge/fml/common/asm/transformers/deobf/FMLDeobfuscatingRemapper.java index a38e870f0..7a5661c6c 100644 --- a/src/main/java/net/minecraftforge/fml/common/asm/transformers/deobf/FMLDeobfuscatingRemapper.java +++ b/src/main/java/net/minecraftforge/fml/common/asm/transformers/deobf/FMLDeobfuscatingRemapper.java @@ -50,10 +50,11 @@ import com.google.common.collect.Sets; import com.google.common.io.CharSource; import com.google.common.io.Files; +import org.spongepowered.asm.obfuscation.mapping.remap.Unmapper; import javax.annotation.Nullable; -public class FMLDeobfuscatingRemapper extends Remapper { +public class FMLDeobfuscatingRemapper extends Remapper implements Unmapper { public static final FMLDeobfuscatingRemapper INSTANCE = new FMLDeobfuscatingRemapper(); private BiMap classNameBiMap; diff --git a/src/main/java/net/minecraftforge/fml/common/discovery/ModCandidate.java b/src/main/java/net/minecraftforge/fml/common/discovery/ModCandidate.java index ac6055514..eb471cd20 100644 --- a/src/main/java/net/minecraftforge/fml/common/discovery/ModCandidate.java +++ b/src/main/java/net/minecraftforge/fml/common/discovery/ModCandidate.java @@ -47,7 +47,7 @@ public ModCandidate(File classPathRoot, File modContainer, ContainerType sourceT this(classPathRoot, modContainer, sourceType, false, false); } - protected ModCandidate(File classPathRoot, File resourcePathRoot) + public ModCandidate(File classPathRoot, File resourcePathRoot) { this.classPathRoot = classPathRoot; this.resourcePathRoot = resourcePathRoot; diff --git a/src/main/java/net/minecraftforge/fml/common/launcher/FMLDeobfTweaker.java b/src/main/java/net/minecraftforge/fml/common/launcher/FMLDeobfTweaker.java index dfb4f572e..5bdb74651 100644 --- a/src/main/java/net/minecraftforge/fml/common/launcher/FMLDeobfTweaker.java +++ b/src/main/java/net/minecraftforge/fml/common/launcher/FMLDeobfTweaker.java @@ -29,7 +29,6 @@ import net.minecraftforge.fml.common.asm.transformers.DeobfuscationTransformer; import net.minecraftforge.fml.relauncher.CoreModManager; import net.minecraftforge.fml.relauncher.FMLInjectionData; -import org.spongepowered.asm.mixin.MixinEnvironment; import top.outlands.foundation.TransformerDelegate; public class FMLDeobfTweaker implements ITweaker { @@ -53,7 +52,6 @@ public void injectIntoClassLoader(LaunchClassLoader classLoader) classLoader.registerTransformer("net.minecraftforge.fml.common.asm.transformers.ItemBlockTransformer"); classLoader.registerTransformer("net.minecraftforge.fml.common.asm.transformers.ItemBlockSpecialTransformer"); classLoader.registerTransformer("net.minecraftforge.fml.common.asm.transformers.PotionEffectTransformer"); - MixinEnvironment.gotoPhase(MixinEnvironment.Phase.INIT); try { FMLLog.log.debug("Validating minecraft"); diff --git a/src/main/java/net/minecraftforge/fml/common/launcher/FMLServerTweaker.java b/src/main/java/net/minecraftforge/fml/common/launcher/FMLServerTweaker.java index e6ee55822..ad4af714c 100644 --- a/src/main/java/net/minecraftforge/fml/common/launcher/FMLServerTweaker.java +++ b/src/main/java/net/minecraftforge/fml/common/launcher/FMLServerTweaker.java @@ -22,7 +22,8 @@ import java.io.File; import java.util.List; -import net.minecraft.launchwrapper.Launch; +import com.cleanroommc.common.CleanroomEnvironment; +import net.minecraftforge.fml.relauncher.Side; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.core.LoggerContext; @@ -33,7 +34,6 @@ public class FMLServerTweaker extends FMLTweaker { public FMLServerTweaker() { - Launch.blackboard.put("fml.side", "server"); } @Override @@ -60,4 +60,10 @@ public void injectIntoClassLoader(LaunchClassLoader classLoader) FMLLaunchHandler.configureForServerLaunch(classLoader, this); FMLLaunchHandler.appendCoreMods(); } + + @Override + public void setSide() + { + CleanroomEnvironment.setSide(Side.SERVER); + } } diff --git a/src/main/java/net/minecraftforge/fml/common/launcher/FMLTweaker.java b/src/main/java/net/minecraftforge/fml/common/launcher/FMLTweaker.java index a03813278..faf27b031 100644 --- a/src/main/java/net/minecraftforge/fml/common/launcher/FMLTweaker.java +++ b/src/main/java/net/minecraftforge/fml/common/launcher/FMLTweaker.java @@ -19,25 +19,22 @@ package net.minecraftforge.fml.common.launcher; +import com.cleanroommc.common.CleanroomEnvironment; import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import com.llamalad7.mixinextras.MixinExtrasBootstrap; import net.minecraft.launchwrapper.ITweaker; import net.minecraft.launchwrapper.Launch; import net.minecraft.launchwrapper.LaunchClassLoader; import net.minecraftforge.fml.relauncher.FMLLaunchHandler; +import net.minecraftforge.fml.relauncher.Side; import org.apache.logging.log4j.LogManager; -import org.spongepowered.asm.launch.GlobalProperties; -import org.spongepowered.asm.launch.MixinBootstrap; - import java.io.File; import java.io.IOException; import java.net.JarURLConnection; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -50,18 +47,13 @@ public class FMLTweaker implements ITweaker { public FMLTweaker() { - Launch.blackboard.put("fml.side", "client"); + this.setSide(); if (System.getProperty("java.net.preferIPv4Stack") == null) { System.setProperty("java.net.preferIPv4Stack", "true"); } - LogManager.getLogger("FML.TWEAK").info("Initializing Mixins..."); - MixinBootstrap.init(); - LogManager.getLogger("FML.TWEAK").info("Initializing MixinExtras..."); - MixinExtrasBootstrap.init(); - - GlobalProperties.put(GlobalProperties.Keys.CLEANROOM_DISABLE_MIXIN_CONFIGS, new HashSet<>()); } + @SuppressWarnings("unchecked") @Override public void acceptOptions(List args, File gameDir, File assetsDir, String profile) @@ -199,4 +191,9 @@ public void injectCascadingTweak(String tweakClassName) List tweakClasses = (List) Launch.blackboard.get("TweakClasses"); tweakClasses.add(tweakClassName); } + + public void setSide() + { + CleanroomEnvironment.setSide(Side.CLIENT); + } } diff --git a/src/main/java/net/minecraftforge/fml/relauncher/CoreModManager.java b/src/main/java/net/minecraftforge/fml/relauncher/CoreModManager.java index bb10ad96c..04b9a3a47 100644 --- a/src/main/java/net/minecraftforge/fml/relauncher/CoreModManager.java +++ b/src/main/java/net/minecraftforge/fml/relauncher/CoreModManager.java @@ -19,47 +19,35 @@ package net.minecraftforge.fml.relauncher; -import com.cleanroommc.common.PatchModPresentChecker; +import com.cleanroommc.cleanmix.CleanMixHooks; import com.google.common.base.Strings; import com.google.common.collect.*; import com.google.common.primitives.Ints; +import com.cleanroommc.common.CleanroomEnvironment; +import com.cleanroommc.discovery.CleanroomModDiscoverer; import net.minecraft.launchwrapper.ITweaker; import net.minecraft.launchwrapper.Launch; import net.minecraft.launchwrapper.LaunchClassLoader; -import net.minecraftforge.common.ForgeEarlyConfig; -import net.minecraftforge.common.ForgeVersion; import net.minecraftforge.fml.common.CertificateHelper; import net.minecraftforge.fml.common.FMLLog; import net.minecraftforge.fml.common.asm.ASMTransformerWrapper; -import net.minecraftforge.fml.common.asm.transformers.ModAccessTransformer; import net.minecraftforge.fml.common.launcher.FMLInjectionAndSortingTweaker; import net.minecraftforge.fml.common.launcher.FMLTweaker; import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin.*; -import net.minecraftforge.fml.relauncher.libraries.LibraryManager; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.core.LoggerContext; -import org.spongepowered.asm.service.mojang.MixinServiceLaunchWrapper; -import org.spongepowered.asm.util.Constants; import java.io.*; import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; import java.security.cert.Certificate; import java.util.*; import java.util.function.ToIntFunction; -import java.util.jar.Attributes; import java.util.jar.JarFile; -import java.util.jar.Manifest; public class CoreModManager { - private static final Attributes.Name COREMODCONTAINSFMLMOD = new Attributes.Name("FMLCorePluginContainsFMLMod"); - private static final Attributes.Name FORCELOADASMOD = new Attributes.Name("ForceLoadAsMod"); - private static final Attributes.Name MODTYPE = new Attributes.Name("ModType"); - private static boolean hasNonCrlMods; private static final Set loadedPlugins = new HashSet<>(); - private static String[] rootPlugins = { "net.minecraftforge.fml.relauncher.FMLCorePlugin", "net.minecraftforge.classloading.FMLForgePlugin", "net.minecraftforge.fml.relauncher.MixinBooterPlugin" }; + private static String[] rootPlugins = { "net.minecraftforge.fml.relauncher.FMLCorePlugin", "net.minecraftforge.classloading.FMLForgePlugin" }; private static List ignoredModFiles = Lists.newArrayList(); private static Map> transformers = Maps.newHashMap(); private static List loadPlugins; @@ -79,7 +67,7 @@ public class CoreModManager { } } - private static class FMLPluginWrapper implements ITweaker { + public static final class FMLPluginWrapper implements ITweaker { public final String name; public final IFMLLoadingPlugin coreModInstance; public final List predepends; @@ -186,25 +174,10 @@ public String[] getLaunchArguments() public static void handleLaunch(File mcDir, LaunchClassLoader classLoader, FMLTweaker tweaker) { - try { CoreModManager.mcDir = mcDir; CoreModManager.tweaker = tweaker; - //TODO: Detect this better? Support install time deobfusication? Decouple deobf from dev? Add dev flag in GradleStart? - try - { - // Are we in a 'decompiled' environment? - byte[] bs = classLoader.getClassBytes("net.minecraft.world.World"); - if (bs != null) - { - FMLLog.log.info("Managed to load a deobfuscated Minecraft name- we are in a deobfuscated environment. Skipping runtime deobfuscation"); - deobfuscatedEnvironment = true; - } - } - catch (IOException e1) - { - // NOOP - } + deobfuscatedEnvironment = CleanroomEnvironment.isDev(); // TODO: check if (!deobfuscatedEnvironment) { @@ -260,8 +233,7 @@ public static void handleLaunch(File mcDir, LaunchClassLoader classLoader, FMLTw } } } - // Now that we have the root plugins loaded - lets see what else might - // be around + // Now that we have the root plugins loaded - lets see what else might be around String commandLineCoremods = System.getProperty("fml.coreMods.load", ""); for (String coreModClassName : commandLineCoremods.split(",")) { @@ -272,302 +244,48 @@ public static void handleLaunch(File mcDir, LaunchClassLoader classLoader, FMLTw FMLLog.log.info("Found a command line coremod : {}", coreModClassName); loadCoreMod(classLoader, coreModClassName, null); } - discoverCoreMods(mcDir, classLoader); - } finally { - PatchModPresentChecker.performCheck(); - } + // Coremods + CleanroomModDiscoverer.instance().discoverCoreMods(mcDir, classLoader, tweaker); + // Mixin Mods + CleanroomModDiscoverer.instance().discoverMixinMods(); } - private static void findDerpMods(LaunchClassLoader classLoader, File modDir, File modDirVer) - { - File[] derplist = listFiles(path -> path.getName().endsWith(".jar.zip"), modDir, modDirVer); - if (derplist != null && derplist.length > 0) - { - FMLLog.log.fatal("FML has detected several badly downloaded jar files, which have been named as zip files. You probably need to download them again, or they may not work properly"); - for (File f : derplist) - FMLLog.log.fatal("Problem file : {}", f.getName()); - } - - FileFilter derpdirfilter = pathname -> pathname.isDirectory() && new File(pathname,"META-INF").isDirectory(); - File[] derpdirlist = listFiles(derpdirfilter, modDir, modDirVer); - if (derpdirlist != null && derpdirlist.length > 0) - { - FMLLog.log.fatal("There appear to be jars extracted into the mods directory. This is VERY BAD and will almost NEVER WORK WELL"); - FMLLog.log.fatal("You should place original jars only in the mods directory. NEVER extract them to the mods directory."); - FMLLog.log.fatal("The directories below appear to be extracted jar files. Fix this before you continue."); - - for (File f : derpdirlist) - { - FMLLog.log.fatal("Directory {} contains {}", f.getName(), Arrays.asList(new File(f,"META-INF").list())); - } - - RuntimeException re = new RuntimeException("Extracted mod jars found, loading will NOT continue"); - // We're generating a crash report for the launcher to show to the user here - // Does this actually work with the obfed names? - try - { - Class crashreportclass = classLoader.loadClass("b"); - Object crashreport = crashreportclass.getMethod("a", Throwable.class, String.class).invoke(null, re, "FML has discovered extracted jar files in the mods directory.\nThis breaks mod loading functionality completely.\nRemove the directories and replace with the jar files originally provided."); - File crashreportfile = new File(new File(modDir.getParentFile(),"crash-reports"),String.format("fml-crash-%1$tY-%1$tm-%1$td_%1$tH.%1$tM.%1$tS.txt",Calendar.getInstance())); - crashreportclass.getMethod("a",File.class).invoke(crashreport, crashreportfile); - FMLLog.log.fatal("#@!@# FML has crashed the game deliberately. Crash report saved to: #@!@# {}", crashreportfile.getAbsolutePath()); - } catch (Exception e) - { - FMLLog.log.fatal("#@!@# FML has crashed while generating a crash report, please report this. #@!@#", e); - // NOOP - hopefully - } - throw re; - } + // Some mods call this reflectively + private static void handleCascadingTweak(File coreMod, JarFile jar, String cascadedTweaker, LaunchClassLoader classLoader, Integer sortingOrder) throws MalformedURLException { + injectDiscoveredCascadingTweaker(coreMod, cascadedTweaker, classLoader, tweaker, sortingOrder); } - private static File[] listFiles(FileFilter filter, File ... dirs) + public static List getIgnoredMods() { - File[] ret = null; - for (File dir : dirs) - { - if (!dir.isDirectory() || !dir.exists()) - continue; - if (ret == null) - ret = dir.listFiles(filter); - else - ret = ObjectArrays.concat(ret, dir.listFiles(filter), File.class); - } - return ret == null ? new File[0] : ret; + return ignoredModFiles; } - private static void discoverCoreMods(File mcDir, LaunchClassLoader classLoader) + public static Map> getTransformers() { + return transformers; + } - File modsDir = setupCoreModDir(mcDir); - File modsDirVer = new File(modsDir, FMLInjectionData.mccversion); - - findDerpMods(classLoader, modsDir, modsDirVer); - - //By the time we get here, all bundeled jars should be extracted to the proper repos. - //As well as the mods folders being cleaned up {any files that have maven info being moved to maven folder} - - FMLLog.log.debug("Discovering coremods"); - //Do we want to sort the full list after resolving artifacts? - //TODO: Add dependency gathering? - List candidates = LibraryManager.getCandidates(); - Set mixin_configs = new HashSet<>(); - File mods_ver = new File(new File(Launch.minecraftHome, "mods"), ForgeVersion.mcVersion); - for (File coreMod : candidates) - { - if (coreMod.isDirectory()) - { - FMLLog.log.debug("Ignoring folder {} in coremod searching", coreMod); - continue; - } - FMLLog.log.debug("Examining for coremod candidacy {}", coreMod.getName()); - JarFile jar = null; - Attributes mfAttributes; - String fmlCorePlugin; - String configs; - String cascadedTweaker; - boolean containNonMods, ignoreMods = false; - try - { - File manifest = new File(coreMod.getAbsolutePath() + ".meta"); - - if (LibraryManager.DISABLE_EXTERNAL_MANIFEST || !manifest.exists()) - { - jar = new JarFile(coreMod); - mfAttributes = jar.getManifest() == null ? null : jar.getManifest().getMainAttributes(); - } - else - { - FileInputStream fis = new FileInputStream(manifest); - mfAttributes = new Manifest(fis).getMainAttributes(); - fis.close(); - } - - if (mfAttributes == null) // Not a coremod and no access transformer list - continue; - - String modSide = mfAttributes.getValue(LibraryManager.MODSIDE); - if (modSide != null && !"BOTH".equals(modSide) && !FMLLaunchHandler.side().name().equals(modSide)) - { - FMLLog.log.debug("Mod {} has ModSide meta-inf value {}, and we're {} It will be ignored", coreMod.getName(), modSide, FMLLaunchHandler.side.name()); - ignoredModFiles.add(coreMod.getName()); - continue; - } - - String ats = mfAttributes.getValue(ModAccessTransformer.FMLAT); - if (ats != null && !ats.isEmpty()) - { - if (jar == null) //We could of loaded the external manifest earlier, if so the jar isn't loaded. - jar = new JarFile(coreMod); - ModAccessTransformer.addJar(jar, ats); - } - configs = mfAttributes.getValue(Constants.ManifestAttributes.MIXINCONFIGS); - cascadedTweaker = mfAttributes.getValue("TweakClass"); - containNonMods = Boolean.parseBoolean(mfAttributes.getValue("NonModDeps")); - if (cascadedTweaker != null) - { - if (containNonMods) - { - for (String file: mfAttributes.getValue(LibraryManager.MODCONTAINSDEPS).split(" ")) - { - classLoader.addURL(new File(mods_ver, file).getAbsoluteFile().toURI().toURL()); - } - } - FMLLog.log.info("Loading tweaker {} from {}", cascadedTweaker, coreMod.getName()); - Integer sortOrder = Ints.tryParse(Strings.nullToEmpty(mfAttributes.getValue("TweakOrder"))); - sortOrder = (sortOrder == null ? Integer.valueOf(0) : sortOrder); - handleCascadingTweak(coreMod, jar, cascadedTweaker, classLoader, sortOrder); - if (!Strings.isNullOrEmpty(configs)) - for (String singleMixinConfig : configs.split(",")) - mixin_configs.add(singleMixinConfig.trim()); - ignoredModFiles.add(coreMod.getName()); - if (!MixinServiceLaunchWrapper.MIXIN_TWEAKER_CLASS.equals(cascadedTweaker)) - { - continue; - } - } - List modTypes = mfAttributes.containsKey(MODTYPE) ? Arrays.asList(mfAttributes.getValue(MODTYPE).split(",")) : ImmutableList.of("FML"); - - if (modTypes.contains("FML")) hasNonCrlMods = true; - - if (!modTypes.contains("FML") && !modTypes.contains("CRL")) - { - FMLLog.log.debug("Adding {} to the list of things to skip. It is not an FML mod, it has types {}", coreMod.getName(), modTypes); - ignoredModFiles.add(coreMod.getName()); - continue; - } - fmlCorePlugin = mfAttributes.getValue("FMLCorePlugin"); - for (String plugin : ForgeEarlyConfig.LOADING_PLUGIN_BLACKLIST) - { - if (plugin.equals(fmlCorePlugin)) - { - ignoreMods = true; - break; - } - } - if (ignoreMods) - { - ignoredModFiles.add(coreMod.getName()); - FMLLog.log.warn("The mod with loading plugin {} is in blacklist and won't be loaded. Check forge_early.cfg for more info.", fmlCorePlugin); - continue; - } - if (fmlCorePlugin == null) - { - // Not a coremod - FMLLog.log.debug("Not found coremod data in {}", coreMod.getName()); - if (MixinServiceLaunchWrapper.MIXIN_TWEAKER_CLASS.equals(cascadedTweaker) && (mfAttributes.containsKey(COREMODCONTAINSFMLMOD) || mfAttributes.containsKey(FORCELOADASMOD))) - { - FMLLog.log.info("Found FMLCorePluginContainsFMLMod marker in mixin container {}.", coreMod.getName()); - candidateModFiles.add(coreMod.getName()); - ignoredModFiles.remove(coreMod.getName()); - } - continue; - } - } - catch (IOException ioe) - { - FMLLog.log.error("Unable to read the jar file {} - ignoring", coreMod.getName(), ioe); - continue; - } - finally - { - closeQuietly(jar); - } - // Support things that are mod jars, but not FML mod jars - try - { - if (containNonMods) - { - for (String file: mfAttributes.getValue(LibraryManager.MODCONTAINSDEPS).split(" ")) - { - classLoader.addURL(new File(mods_ver, file).getAbsoluteFile().toURI().toURL()); - } - } - classLoader.addURL(coreMod.toURI().toURL()); - if (!Strings.isNullOrEmpty(configs)) - mixin_configs.addAll(List.of(configs.split(","))); - if (!(mfAttributes.containsKey(COREMODCONTAINSFMLMOD) || mfAttributes.containsKey(FORCELOADASMOD))) - { - FMLLog.log.trace("Adding {} to the list of known coremods, it will not be examined again", coreMod.getName()); - ignoredModFiles.add(coreMod.getName()); - } - else - { - FMLLog.log.info("Found FMLCorePluginContainsFMLMod marker in {}.", - coreMod.getName()); - candidateModFiles.add(coreMod.getName()); - ignoredModFiles.remove(coreMod.getName()); - } - } - catch (MalformedURLException e) - { - FMLLog.log.error("Unable to convert file into a URL. weird", e); - continue; - } - loadCoreMod(classLoader, fmlCorePlugin, coreMod); - } - String devConfigs = System.getProperty("crl.dev.mixin"); - if (!Strings.isNullOrEmpty(devConfigs)) - { - for (String singleMixinConfig : devConfigs.split(",")) - mixin_configs.add(singleMixinConfig.trim()); - } - Launch.blackboard.put(Constants.ManifestAttributes.MIXINCONFIGS, mixin_configs); + public static List getReparseableCoremods() + { + return candidateModFiles; } - private static void handleCascadingTweak(File coreMod, JarFile jar, String cascadedTweaker, LaunchClassLoader classLoader, Integer sortingOrder) throws MalformedURLException { + public static void injectDiscoveredCascadingTweaker(File coreMod, String cascadedTweaker, LaunchClassLoader classLoader, FMLTweaker tweaker, int sortingOrder) throws MalformedURLException { try { classLoader.addURL(coreMod.toURI().toURL()); - CoreModManager.tweaker.injectCascadingTweak(cascadedTweaker); - tweakSorting.put(cascadedTweaker,sortingOrder); + tweaker.injectCascadingTweak(cascadedTweaker); + tweakSorting.put(cascadedTweaker, sortingOrder); } catch (Exception e) { - FMLLog.log.info("There was a problem trying to load the mod dir tweaker {}", coreMod.getAbsolutePath(), e); - //FMLLog.log.info("Thread loader: " + classLoader.getClass() + ",\nLoader of loader: " + classLoader.getClass().getClassLoader().getClass() + ",\nCoremod loader: " + coreMod.toURI().toURL().getClass()); + FMLLog.log.info("There was a problem trying to load tweaker {} from {}", cascadedTweaker.getClass().getName(), coreMod.getAbsolutePath(), e); } } - /** - * @param mcDir - * the minecraft home directory - * @return the coremod directory - */ - private static File setupCoreModDir(File mcDir) + public static boolean isCoreModLoaded(String coreModClass) { - File coreModDir = new File(mcDir, "mods"); - try - { - coreModDir = coreModDir.getCanonicalFile(); - } - catch (IOException e) - { - throw new RuntimeException(String.format("Unable to canonicalize the coremod dir at %s", mcDir.getName()), e); - } - if (!coreModDir.exists()) - { - coreModDir.mkdir(); - } - else if (coreModDir.exists() && !coreModDir.isDirectory()) - { - throw new RuntimeException(String.format("Found a coremod file in %s that's not a directory", mcDir.getName())); - } - return coreModDir; - } - - public static List getIgnoredMods() - { - return ignoredModFiles; - } - - public static Map> getTransformers() - { - return transformers; - } - - public static List getReparseableCoremods() - { - return candidateModFiles; + return loadedPlugins.contains(coreModClass); } private static FMLPluginWrapper loadCoreMod(LaunchClassLoader classLoader, String coreModClass, File location) @@ -659,7 +377,6 @@ else if (deobfuscatedEnvironment && location == null) // This is probably a mod loadPlugins.add(wrap); loadedPlugins.add(coreModClass); FMLLog.log.debug("Enqueued coremod {}", coreModName); - MixinBooterPlugin.queneEarlyMixinLoader(plugin); return wrap; } catch (ClassNotFoundException cnfe) @@ -684,6 +401,11 @@ else if (deobfuscatedEnvironment && location == null) // This is probably a mod return null; } + public static boolean loadCoreModFromDiscoveredJar(LaunchClassLoader classLoader, String coreModClass, File location) + { + return loadCoreMod(classLoader, coreModClass, location) != null; + } + public static void injectTransformers(LaunchClassLoader classLoader) { @@ -698,6 +420,11 @@ public static void injectCoreModTweaks(FMLInjectionAndSortingTweaker fmlInjectio List tweakers = (List) Launch.blackboard.get("Tweaks"); // Add the sorting tweaker first- it'll appear twice in the list tweakers.addFirst(fmlInjectionAndSortingTweaker); + // Load rescued (ForceLoadAsMod) coremods + CleanroomModDiscoverer.instance().rescueDroppedCoremods(); + // Early Mixin Loaders Instantiation + CleanMixHooks.loadMixinBooterEarlyMixins(loadPlugins); + // Add loadPlugins (including rescued coremods) tweakers.addAll(loadPlugins); } @@ -735,15 +462,4 @@ public static void onCrash(StringBuilder builder) } } - private static void closeQuietly(Closeable closeable) { - try { - if (closeable != null) - closeable.close(); - } catch (final IOException _){} - } - - public static boolean hasNonCrlMods() { - return hasNonCrlMods; - } - } diff --git a/src/main/java/net/minecraftforge/fml/relauncher/FMLLaunchHandler.java b/src/main/java/net/minecraftforge/fml/relauncher/FMLLaunchHandler.java index f0268a974..5eccf7530 100644 --- a/src/main/java/net/minecraftforge/fml/relauncher/FMLLaunchHandler.java +++ b/src/main/java/net/minecraftforge/fml/relauncher/FMLLaunchHandler.java @@ -23,8 +23,10 @@ import java.util.Arrays; import java.util.stream.Collectors; +import com.cleanroommc.common.CleanroomEnvironment; import com.cleanroommc.common.CleanroomVersion; -import net.minecraft.launchwrapper.Launch; +import com.cleanroommc.util.CleanroomLog; +import com.llamalad7.mixinextras.MixinExtrasBootstrap; import net.minecraft.launchwrapper.LaunchClassLoader; import net.minecraftforge.fml.common.FMLLog; import net.minecraftforge.fml.common.TracingPrintStream; @@ -32,11 +34,12 @@ import net.minecraftforge.fml.relauncher.libraries.LibraryManager; import org.apache.logging.log4j.LogManager; +import org.spongepowered.asm.launch.MixinBootstrap; public class FMLLaunchHandler { private static FMLLaunchHandler INSTANCE; - static Side side; + static Side side = CleanroomEnvironment.side(); private LaunchClassLoader classLoader; private FMLTweaker tweaker; private File minecraftHome; @@ -66,6 +69,13 @@ private FMLLaunchHandler(LaunchClassLoader launchLoader, FMLTweaker tweaker) this.classLoader = launchLoader; this.tweaker = tweaker; this.minecraftHome = tweaker.getGameDir(); + this.classLoader.addTransformerExclusion("org.spongepowered.asm.launch."); + this.classLoader.addTransformerExclusion("org.spongepowered.asm.service."); + this.classLoader.addTransformerExclusion("org.spongepowered.asm.mixin."); + this.classLoader.addTransformerExclusion("org.spongepowered.asm.logging."); + this.classLoader.addTransformerExclusion("org.spongepowered.asm.util."); + this.classLoader.addTransformerExclusion("org.spongepowered.asm.lib."); + this.classLoader.addTransformerExclusion("org.objectweb.asm."); this.classLoader.addTransformerExclusion("com.cleanroommc.loader."); this.classLoader.addTransformerExclusion("net.minecraftforge.fml.relauncher."); this.classLoader.addTransformerExclusion("net.minecraftforge.classloading."); @@ -80,13 +90,11 @@ private FMLLaunchHandler(LaunchClassLoader launchLoader, FMLTweaker tweaker) private void setupClient() { - side = Side.CLIENT; setupHome(); } private void setupServer() { - side = Side.SERVER; setupHome(); } @@ -111,6 +119,10 @@ private void setupHome() try { LibraryManager.setup(minecraftHome); + CleanroomLog.get().info("Initializing CleanMix..."); + MixinBootstrap.init(); + CleanroomLog.get().info("Initializing MixinExtras..."); + MixinExtrasBootstrap.init(); CoreModManager.handleLaunch(minecraftHome, classLoader, tweaker); } catch (Throwable t) @@ -144,6 +156,6 @@ public static void appendCoreMods() public static boolean isDeobfuscatedEnvironment() { - return CoreModManager.deobfuscatedEnvironment; + return CleanroomEnvironment.isDev(); } } diff --git a/src/main/java/net/minecraftforge/fml/relauncher/MixinBooterPlugin.java b/src/main/java/net/minecraftforge/fml/relauncher/MixinBooterPlugin.java deleted file mode 100644 index c28b3b915..000000000 --- a/src/main/java/net/minecraftforge/fml/relauncher/MixinBooterPlugin.java +++ /dev/null @@ -1,124 +0,0 @@ -package net.minecraftforge.fml.relauncher; - -import net.minecraft.launchwrapper.Launch; -import net.minecraftforge.common.ForgeVersion; -import net.minecraftforge.fml.common.FMLLog; - -import com.google.gson.*; - -import org.spongepowered.asm.mixin.Mixins; -import org.spongepowered.asm.launch.GlobalProperties; -import zone.rong.mixinbooter.Context; -import zone.rong.mixinbooter.IEarlyMixinLoader; -import zone.rong.mixinbooter.IMixinConfigHijacker; - -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.HashMap; -import java.util.Set; -import java.util.Map; -import java.util.Enumeration; -import java.io.InputStreamReader; -import java.net.URL; - - -@SuppressWarnings("deprecation") -@IFMLLoadingPlugin.Name("MixinBooter") -@IFMLLoadingPlugin.MCVersion(ForgeVersion.mcVersion) -@IFMLLoadingPlugin.SortingIndex(1) -public final class MixinBooterPlugin implements IFMLLoadingPlugin { - - static Set earlyMixinLoaders = new HashSet<>(); - - @Override - public void injectData(Map data) { - loadEarlyLoaders(earlyMixinLoaders); - earlyMixinLoaders = null; - } - - - static void queneEarlyMixinLoader(IFMLLoadingPlugin plugin) { - if (plugin instanceof IEarlyMixinLoader earlyMixinLoader) earlyMixinLoaders.add(earlyMixinLoader); - if (plugin instanceof IMixinConfigHijacker hijacker) { - Collection disabledConfigs = GlobalProperties.get(GlobalProperties.Keys.CLEANROOM_DISABLE_MIXIN_CONFIGS); - Context context = new Context(null, Collections.emptySet()); - FMLLog.log.info("Loading config hijacker {}.", hijacker.getClass().getName()); - for (String hijacked : hijacker.getHijackedMixinConfigs(context)) { - disabledConfigs.add(hijacked); - FMLLog.log.info("{} will hijack the mixin config {}", hijacker.getClass().getName(), hijacked); - } - } - } - - private static void loadEarlyLoaders(Collection queuedLoaders) { - Set modlist = speculatedModList(); - for (IEarlyMixinLoader queuedLoader : queuedLoaders) { - FMLLog.log.info("Loading early loader {} for its mixins.", queuedLoader.getClass().getName()); - for (String mixinConfig : queuedLoader.getMixinConfigs()) { - Context context = new Context(mixinConfig, modlist); - if (queuedLoader.shouldMixinConfigQueue(context)) { - FMLLog.log.info("Adding {} mixin configuration.", mixinConfig); - Mixins.addConfiguration(mixinConfig); - queuedLoader.onMixinConfigQueued(context); - } - } - } - } - - public static Set speculatedModList() { - HashSet presentMods = new HashSet<>(); - - // buildIn mods : - presentMods.add("minecraft"); - presentMods.add("fml"); - presentMods.add("forge"); - presentMods.add("mixinbooter"); - presentMods.add("configanytime"); - - // mcmod.info - try { - Enumeration resources = Launch.classLoader.getResources("mcmod.info"); - while (resources.hasMoreElements()) { - presentMods.addAll(parseMcmodInfo(resources.nextElement())); - } - } catch (Exception e) { - throw new RuntimeException("Failed to gather present mods from mcmod.info (s)", e); - } - - // optifine : - if (Launch.classLoader.isClassExist("optifine.OptiFineTweaker")) { - presentMods.add("optifine"); - } - - return presentMods; - - } - - private static Set parseMcmodInfo(URL url) { - try { - HashSet ids = new HashSet<>(); - JsonElement root = JsonParser.parseReader(new InputStreamReader(url.openStream())); - if (root.isJsonArray()) { - for (JsonElement element : root.getAsJsonArray()) { - if (element.isJsonObject()) { - ids.add(element.getAsJsonObject().get("modid").getAsString()); - } - } - } else { - for (JsonElement element : root.getAsJsonObject().get("modList").getAsJsonArray()) { - if (element.isJsonObject()) { - ids.add(element.getAsJsonObject().get("modid").getAsString()); - } - } - } - return ids; - } catch (Throwable t) { - FMLLog.log.error("Failed to parse mcmod.info for " + url, t); - } - return Collections.emptySet(); - } - - - -} diff --git a/src/main/java/net/minecraftforge/fml/relauncher/libraries/LibraryManager.java b/src/main/java/net/minecraftforge/fml/relauncher/libraries/LibraryManager.java index b985269c4..8ec0e42fd 100644 --- a/src/main/java/net/minecraftforge/fml/relauncher/libraries/LibraryManager.java +++ b/src/main/java/net/minecraftforge/fml/relauncher/libraries/LibraryManager.java @@ -111,8 +111,9 @@ public static void setup(File minecraftHome) } } } - - + + candidates = null; + } private static File findLibraryFolder(File minecraftHome) diff --git a/src/main/java/net/minecraftforge/fml/server/FMLServerHandler.java b/src/main/java/net/minecraftforge/fml/server/FMLServerHandler.java index 7fb9c8898..cb5ec5b81 100644 --- a/src/main/java/net/minecraftforge/fml/server/FMLServerHandler.java +++ b/src/main/java/net/minecraftforge/fml/server/FMLServerHandler.java @@ -27,6 +27,7 @@ import java.util.zip.ZipFile; import com.cleanroommc.common.PatchModPresentChecker; +import com.cleanroommc.discovery.CleanroomModDiscoverer; import com.cleanroommc.kirino.KirinoCommonCore; import com.cleanroommc.kirino.KirinoServerCore; import net.minecraft.network.INetHandler; @@ -47,7 +48,6 @@ import net.minecraftforge.fml.common.StartupQuery; import net.minecraftforge.fml.common.eventhandler.EventBus; import net.minecraftforge.fml.common.network.FMLNetworkEvent; -import net.minecraftforge.fml.relauncher.CoreModManager; import net.minecraftforge.fml.relauncher.FMLLaunchHandler; import net.minecraftforge.fml.relauncher.Side; @@ -101,7 +101,7 @@ public void beginServerLoading(MinecraftServer minecraftServer) server = minecraftServer; KirinoCommonCore.configEvent(); - if (PatchModPresentChecker.isNotPresent() && CoreModManager.hasNonCrlMods()) + if (PatchModPresentChecker.isNotPresent() && CleanroomModDiscoverer.instance().hasForgeMods()) { String text = PatchModPresentChecker.getWarningMessage() + "\n\n" + "Run the command /fml confirm to proceed, or /fml cancel to abort."; diff --git a/src/main/java/zone/rong/mixinbooter/Context.java b/src/main/java/zone/rong/mixinbooter/Context.java index d1a9fa716..d5887cd41 100644 --- a/src/main/java/zone/rong/mixinbooter/Context.java +++ b/src/main/java/zone/rong/mixinbooter/Context.java @@ -1,59 +1,46 @@ package zone.rong.mixinbooter; -import net.minecraft.launchwrapper.Launch; -import net.minecraftforge.fml.common.Loader; -import net.minecraftforge.fml.relauncher.FMLLaunchHandler; +import zone.rong.mixinbooter.service.ModDiscoverer; +import zone.rong.mixinbooter.util.Environment; import java.util.Collection; /** * This class contains loading context for callers * - * * @since 10.0 - * @deprecated We forked Mixin. - *
New approach: - * Check Fugue.
- * Summary:
- * If you are coremod, just call {@link org.spongepowered.asm.mixin.Mixins#addConfigurations(String...)} in loadingPluging or Tweaker, and handle shouldApply in IMixinConfigPlugin
- * If you aren't coremod:
- * Group mixins by phase, add target env in config, use @env(MOD) for mod mixins.
- * Add {"MixinConfigs": "modid.mod.mixin.json,modid.default.mixin.json"} to your jar manifest.
- * Handle shouldApply in IMixinConfigPlugin. You can call {@link net.minecraftforge.fml.common.Loader#isModLoaded(String)} for {@link org.spongepowered.asm.mixin.MixinEnvironment.Phase#MOD} mixin.
- * Recommend to group target mod name by package name. You can also get config instance from {@link org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin#injectConfig(org.spongepowered.asm.mixin.transformer.Config)}. + * @deprecated since 11.0, use {@link Environment#inDev()} and {@link ModDiscoverer#getPresentMods()} instead. */ @Deprecated public final class Context { public enum ModLoader { + FORGE, CLEANROOM; + } private final String mixinConfig; private final Collection presentMods; - public Context(String mixinConfigIn, Collection presentModsIn) { - this.mixinConfig = mixinConfigIn; - this.presentMods = presentModsIn; - } - - public Context(String mixinConfigIn) { - this(mixinConfigIn, null); + public Context(String mixinConfig, Collection presentMods) { + this.mixinConfig = mixinConfig; + this.presentMods = presentMods; } /** * @return the current mod loader */ - public ModLoader modLoader() { - return ModLoader.CLEANROOM; - } + public ModLoader modLoader() { + return ModLoader.CLEANROOM; + } /** * @return if the current environment is in-dev */ public boolean inDev() { - return FMLLaunchHandler.isDeobfuscatedEnvironment(); + return Environment.inDev(); } /** @@ -64,18 +51,11 @@ public String mixinConfig() { } /** - *

For early contexts, the list of mods are gathered from culling the classloader - * for any jars that has the mcmod.info file. The mod IDs are obtained from the mcmod.info file. - * This means mostly, if not only coremods are queryable here, - * make sure to test a normal mod's existence in your mixin plugin or in the mixin itself.

- * - *

For late contexts, it comes from {@link net.minecraftforge.fml.common.Loader#getActiveModList} - * akin to {@link net.minecraftforge.fml.common.Loader#isModLoaded(String)}

* @param modId to check against the list of present mods in the context * @return whether the mod is present */ public boolean isModPresent(String modId) { - return presentMods == null ? Loader.isModLoaded(modId) : presentMods.contains(modId); + return presentMods.contains(modId); } -} \ No newline at end of file +} diff --git a/src/main/java/zone/rong/mixinbooter/IEarlyMixinLoader.java b/src/main/java/zone/rong/mixinbooter/IEarlyMixinLoader.java index 34f49259b..cd4da5071 100644 --- a/src/main/java/zone/rong/mixinbooter/IEarlyMixinLoader.java +++ b/src/main/java/zone/rong/mixinbooter/IEarlyMixinLoader.java @@ -3,16 +3,18 @@ import java.util.List; /** - * @deprecated We forked Mixin. - *
New approach: - * Check Fugue.
- * Summary:
- * If you are coremod, just call {@link org.spongepowered.asm.mixin.Mixins#addConfigurations(String...)} in loadingPluging or Tweaker, and handle shouldApply in IMixinConfigPlugin
- * If you aren't coremod:
- * Group mixins by phase, add target env in config, use @env(MOD) for mod mixins.
- * Add {"MixinConfigs": "modid.mod.mixin.json,modid.default.mixin.json"} to your jar manifest.
- * Handle shouldApply in IMixinConfigPlugin. You can call {@link net.minecraftforge.fml.common.Loader#isModLoaded(String)} for {@link org.spongepowered.asm.mixin.MixinEnvironment.Phase#MOD} mixin.
- * Recommend to group target mod name by package name. You can also get config instance from {@link org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin#injectConfig(org.spongepowered.asm.mixin.transformer.Config)}. + * Early mixins are defined as mixins that affects vanilla or forge classes. + * Or technically, classes that can be queried via the current state of {@link net.minecraft.launchwrapper.LaunchClassLoader} + * + * If you want to add mixins that affect mods, use {@link ILateMixinLoader} + * + * Implement this in your {@link net.minecraftforge.fml.relauncher.IFMLLoadingPlugin}. + * Return all early mixin configs you want MixinBooter to queue and send to Mixin library. + * + * @deprecated as of 11.0, the line of "early" and "late" mixin loading no longer is present, use + * {@code MixinConfigs} manifest entry to list your configs or {@code MixinConnector} to denote + * a class implementing {@link org.spongepowered.asm.mixin.connect.IMixinConnector} and call + * {@link org.spongepowered.asm.mixin.Mixins#addConfiguration(String)} (or related methods) there */ @Deprecated public interface IEarlyMixinLoader { @@ -57,4 +59,5 @@ default void onMixinConfigQueued(Context context) { * @param mixinConfig mixin config name, queried via {@link IEarlyMixinLoader#getMixinConfigs()}. */ default void onMixinConfigQueued(String mixinConfig) { } + } diff --git a/src/main/java/zone/rong/mixinbooter/ILateMixinLoader.java b/src/main/java/zone/rong/mixinbooter/ILateMixinLoader.java index f774b2499..ab132fc87 100644 --- a/src/main/java/zone/rong/mixinbooter/ILateMixinLoader.java +++ b/src/main/java/zone/rong/mixinbooter/ILateMixinLoader.java @@ -3,16 +3,20 @@ import java.util.List; /** - * @deprecated We forked Mixin. - *
New approach: - * Check Fugue.
- * Summary:
- * If you are coremod, just call {@link org.spongepowered.asm.mixin.Mixins#addConfigurations(String...)} in loadingPlugin or Tweaker, and handle shouldApply in IMixinConfigPlugin
- * If you aren't coremod:
- * Group mixins by phase, add target env in config, use @env(MOD) for mod mixins.
- * Add {"MixinConfigs": "modid.mod.mixin.json,modid.default.mixin.json"} to your jar manifest.
- * Handle shouldApply in IMixinConfigPlugin. You can call {@link net.minecraftforge.fml.common.Loader#isModLoaded(String)} for {@link org.spongepowered.asm.mixin.MixinEnvironment.Phase#MOD} mixin.
- * Recommend to group target mod name by package name. You can also get config instance from {@link org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin#injectConfig(org.spongepowered.asm.mixin.transformer.Config)}. + * Late mixins are defined as mixins that affects mod classes. + * Or technically, classes that can be queried via the current state of + * {@link net.minecraft.launchwrapper.LaunchClassLoader} and {@link net.minecraftforge.fml.common.ModClassLoader} + * + * Majority if not all vanilla and forge classes would have been loaded here. + * If you want to add mixins that affect vanilla or forge, use and consult {@link IEarlyMixinLoader} + * + * Implement this in any arbitrary class. Said class will be constructed when mixins are ready to be queued. + * Return all late mixin configs you want MixinBooter to queue and send to Mixin library. + * + * @deprecated as of 11.0, the line of "early" and "late" mixin loading no longer is present, use + * {@code MixinConfigs} manifest entry to list your configs or {@code MixinConnector} to denote + * a class implementing {@link org.spongepowered.asm.mixin.connect.IMixinConnector} and call + * {@link org.spongepowered.asm.mixin.Mixins#addConfiguration(String)} (or related methods) there */ @Deprecated public interface ILateMixinLoader { @@ -36,7 +40,7 @@ default boolean shouldMixinConfigQueue(Context context) { /** * Runs when a mixin config is successfully queued and sent to Mixin library. * - * @param mixinConfig mixin config name, queried via {@link IEarlyMixinLoader#getMixinConfigs()}. + * @param mixinConfig mixin config name, queried via {@link ILateMixinLoader#getMixinConfigs()}. * @return true if the mixinConfig should be queued, false if it should not. */ default boolean shouldMixinConfigQueue(String mixinConfig) { @@ -54,7 +58,7 @@ default void onMixinConfigQueued(Context context) { /** * Runs when a mixin config is successfully queued and sent to Mixin library. - * @param mixinConfig mixin config name, queried via {@link IEarlyMixinLoader#getMixinConfigs()}. + * @param mixinConfig mixin config name, queried via {@link ILateMixinLoader#getMixinConfigs()}. */ default void onMixinConfigQueued(String mixinConfig) { } diff --git a/src/main/java/zone/rong/mixinbooter/IMixinConfigHijacker.java b/src/main/java/zone/rong/mixinbooter/IMixinConfigHijacker.java index 2b9293323..9b3a7041d 100644 --- a/src/main/java/zone/rong/mixinbooter/IMixinConfigHijacker.java +++ b/src/main/java/zone/rong/mixinbooter/IMixinConfigHijacker.java @@ -8,20 +8,8 @@ * Requested by: @Desoroxxx * * @since 9.0 - * @deprecated We forked Mixin. - *
New approach: - * Check Fugue.
- * Summary:
- * If you are coremod, just call {@link org.spongepowered.asm.mixin.Mixins#addConfigurations(String...)} in loadingPlugin or Tweaker, and handle shouldApply in IMixinConfigPlugin
- * If you aren't coremod:
- * Group mixins by phase, add target env in config, use @env(MOD) for mod mixins.
- * Add {"MixinConfigs": "modid.mod.mixin.json,modid.default.mixin.json"} to your jar manifest.
- * Handle shouldApply in IMixinConfigPlugin. You can call {@link net.minecraftforge.fml.common.Loader#isModLoaded(String)} for {@link org.spongepowered.asm.mixin.MixinEnvironment.Phase#MOD} mixin.
- * Recommend to group target mod name by package name. You can also get config instance from {@link org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin#injectConfig(org.spongepowered.asm.mixin.transformer.Config)}. - * - * If you what to block a mixin config, {@code GlobalProperties.get(GlobalProperties.Keys.CLEANROOM_DISABLE_MIXIN_CONFIGS)#add(String)} + * @deprecated since 11.0, use {@link org.spongepowered.asm.mixin.transformer.Config#blacklist(String)} */ - @Deprecated public interface IMixinConfigHijacker { @@ -42,4 +30,4 @@ default Set getHijackedMixinConfigs(Context context) { return getHijackedMixinConfigs(); } -} \ No newline at end of file +} diff --git a/src/main/java/zone/rong/mixinbooter/MixinBooterModContainer.java b/src/main/java/zone/rong/mixinbooter/MixinBooterModContainer.java new file mode 100644 index 000000000..eb87cde26 --- /dev/null +++ b/src/main/java/zone/rong/mixinbooter/MixinBooterModContainer.java @@ -0,0 +1,48 @@ +package zone.rong.mixinbooter; + +import com.google.common.eventbus.EventBus; +import net.minecraftforge.common.ForgeEarlyConfig; +import net.minecraftforge.fml.common.DummyModContainer; +import net.minecraftforge.fml.common.LoadController; +import net.minecraftforge.fml.common.MetadataCollection; +import net.minecraftforge.fml.common.ModMetadata; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +public class MixinBooterModContainer extends DummyModContainer { + + public MixinBooterModContainer() { + super(loadMetadata()); + ModMetadata meta = getMetadata(); + if (ForgeEarlyConfig.CUSTOM_BUILT_IN_MOD_VERSION) { + meta.version = ForgeEarlyConfig.MIXIN_BOOTER_VERSION; + } + } + + @Override + public boolean registerBus(EventBus bus, LoadController controller) { + bus.register(this); + return true; + } + + private static ModMetadata loadMetadata() { + Map fallbackData = new HashMap<>(); + fallbackData.put("name", Tags.MOD_NAME); + fallbackData.put("version", ForgeEarlyConfig.CUSTOM_BUILT_IN_MOD_VERSION ? ForgeEarlyConfig.MIXIN_BOOTER_VERSION : Tags.VERSION); + + InputStream inputStream = MixinBooterModContainer.class.getClassLoader().getResourceAsStream("mixinbooter.info"); + try { + return MetadataCollection.from(inputStream, "mixinbooter.info").getMetadataForId(Tags.MOD_ID, fallbackData); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException ignored) { } + } + } + } + +} diff --git a/src/main/java/zone/rong/mixinbooter/MixinBooterPlugin.java b/src/main/java/zone/rong/mixinbooter/MixinBooterPlugin.java new file mode 100644 index 000000000..43f18bb91 --- /dev/null +++ b/src/main/java/zone/rong/mixinbooter/MixinBooterPlugin.java @@ -0,0 +1,36 @@ +package zone.rong.mixinbooter; + +import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin; +import org.spongepowered.asm.launch.MixinBootstrap; +import java.util.*; + +@IFMLLoadingPlugin.Name(Tags.MOD_NAME) +@IFMLLoadingPlugin.SortingIndex(Integer.MIN_VALUE + 1) +public final class MixinBooterPlugin implements IFMLLoadingPlugin { + + @Override + public String[] getASMTransformerClass() { + return new String[0]; + } + + @Override + public String getModContainerClass() { + return "zone.rong.mixinbooter.MixinBooterModContainer"; + } + + @Override + public String getSetupClass() { + return null; + } + + @Override + public String getAccessTransformerClass() { + return null; + } + + @Override + public void injectData(Map data) { + // TODO: place this somewhere else + MixinBootstrap.getPlatform().inject(); + } +} diff --git a/src/main/java/zone/rong/mixinbooter/MixinLoader.java b/src/main/java/zone/rong/mixinbooter/MixinLoader.java new file mode 100644 index 000000000..db41f268a --- /dev/null +++ b/src/main/java/zone/rong/mixinbooter/MixinLoader.java @@ -0,0 +1,22 @@ +package zone.rong.mixinbooter; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Purely a marker annotation. Use it in classes and said classes will be instantiated. + * Make sure there is an empty-arg constructor as LoaderMixin will call newInstance on it. + * Feel free to do any Mixin related things in the constructor. But, most importantly, add (mod mixin) configs there. + * + * @since 4.2 this class was deprecated. + * @since 9.2 this class was reinstated because of Forge not gathering interfaces in 1.8.x. + * When used and paired with {@link ILateMixinLoader}, it will act the same way as it would in a 1.9+ setting. + * Consult the usage of {@link ILateMixinLoader} for mod mixins, and for vanilla/forge mixins, consult the usage of {@link IEarlyMixinLoader} + * @deprecated since 11.0, use manifest entries MixinConfigs and MixinConnector instead, as original Mixin intended + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.CLASS) +@Deprecated +public @interface MixinLoader { } diff --git a/src/main/java/zone/rong/mixinbooter/Tags.java b/src/main/java/zone/rong/mixinbooter/Tags.java new file mode 100644 index 000000000..86c3b93df --- /dev/null +++ b/src/main/java/zone/rong/mixinbooter/Tags.java @@ -0,0 +1,11 @@ +package zone.rong.mixinbooter; + +public final class Tags { + + public static final String MOD_ID = "mixinbooter"; + public static final String MOD_NAME = "MixinBooter"; + public static final String VERSION = "11.2"; + + private Tags() { } + +} diff --git a/src/main/java/zone/rong/mixinbooter/service/ModDiscoverer.java b/src/main/java/zone/rong/mixinbooter/service/ModDiscoverer.java new file mode 100644 index 000000000..aae85f735 --- /dev/null +++ b/src/main/java/zone/rong/mixinbooter/service/ModDiscoverer.java @@ -0,0 +1,40 @@ +package zone.rong.mixinbooter.service; + +import com.cleanroommc.discovery.CleanroomModDiscoverer; + +import java.io.File; +import java.util.Set; + +public final class ModDiscoverer { + + private ModDiscoverer() { } + + public static boolean isModPresent(String modId) { + return CleanroomModDiscoverer.instance().isModPresent(modId); + } + + public static Set getPresentMods() { + return CleanroomModDiscoverer.instance().presentMods(); + } + + public static Set getModSources(String modId) { + return CleanroomModDiscoverer.instance().modSources(modId); + } + + public static String getModFromSource(File source) { + return CleanroomModDiscoverer.instance().modFromSource(source); + } + + public static Set getModsFromSource(File source) { + return CleanroomModDiscoverer.instance().modsFromSource(source); + } + + public static void applyForceLoadAsMod() { + // NO-OP + } + + public static void rescueDroppedCoremods() { + // NO-OP + } + +} diff --git a/src/main/java/zone/rong/mixinbooter/util/Environment.java b/src/main/java/zone/rong/mixinbooter/util/Environment.java new file mode 100644 index 000000000..65272e959 --- /dev/null +++ b/src/main/java/zone/rong/mixinbooter/util/Environment.java @@ -0,0 +1,19 @@ +package zone.rong.mixinbooter.util; + +import com.cleanroommc.common.CleanroomEnvironment; + +public class Environment { + + public static String minecraftVersion() { + return "1.12.2"; + } + + public static boolean inDev() { + return CleanroomEnvironment.isDev(); + } + + public static String side() { + return CleanroomEnvironment.side().name(); + } + +} diff --git a/src/main/resources/META-INF/services/org.spongepowered.asm.service.IMixinService b/src/main/resources/META-INF/services/org.spongepowered.asm.service.IMixinService new file mode 100644 index 000000000..388892a7c --- /dev/null +++ b/src/main/resources/META-INF/services/org.spongepowered.asm.service.IMixinService @@ -0,0 +1 @@ +com.cleanroommc.cleanmix.service.CleanMixService \ No newline at end of file diff --git a/src/main/resources/META-INF/services/org.spongepowered.asm.service.IMixinServiceBootstrap b/src/main/resources/META-INF/services/org.spongepowered.asm.service.IMixinServiceBootstrap new file mode 100644 index 000000000..5d9f236f4 --- /dev/null +++ b/src/main/resources/META-INF/services/org.spongepowered.asm.service.IMixinServiceBootstrap @@ -0,0 +1 @@ +com.cleanroommc.cleanmix.service.CleanMixBootstrap \ No newline at end of file diff --git a/src/main/resources/assets/mixinbooter/icon.png b/src/main/resources/assets/mixinbooter/icon.png new file mode 100644 index 000000000..014608ef4 Binary files /dev/null and b/src/main/resources/assets/mixinbooter/icon.png differ diff --git a/src/main/resources/cleanmix.info b/src/main/resources/cleanmix.info new file mode 100644 index 000000000..92c4795fc --- /dev/null +++ b/src/main/resources/cleanmix.info @@ -0,0 +1,11 @@ +[{ + "modid": "cleanmix", + "name": "CleanMix", + "version": "${cleanmix_version}", + "mcversion": "1.12.2", + "description": "Fork of Fabric Mixin that aims to target 1.12.2 environments.", + "authorList": [ "Rongmario" ], + "credits": "Thanks to Fabric for providing an upstream mixin fork.", + "url": "https://github.com/CleanroomMC/CleanMix", + "logoFile": "/assets/cleanmix/icon.png" +}] diff --git a/src/main/resources/mixinbooter.info b/src/main/resources/mixinbooter.info new file mode 100644 index 000000000..8dfeed526 --- /dev/null +++ b/src/main/resources/mixinbooter.info @@ -0,0 +1,11 @@ +[{ + "modid": "mixinbooter", + "name": "MixinBooter", + "version": "11.2", + "mcversion": "1.12.2", + "description": "De facto standard on Minecraft 1.8 - 1.12.2 that provides Mixins & standard API for mods to write mixins comfortably", + "authorList": [ "Rongmario" ], + "credits": "Thanks to Fabric for providing an upstream mixin fork.", + "url": "https://github.com/CleanroomMC/MixinBooter", + "logoFile": "/assets/mixinbooter/icon.png" +}]