Skip to content

Commit

Permalink
OneConfigSourceFile property in jar manifest
Browse files Browse the repository at this point in the history
If a mod creates a custom tweaker but still wants the Mixin tweaker to be injected, they can add this attribute to their jar manifest to signal to OneConfig that it should inject the Mixin tweaker regardless of the tweaker class.
Also rewrite to not use MHUtils because kotlin wont be in classpath when this runs
  • Loading branch information
Wyvest committed Jan 3, 2025
1 parent 2ed4f67 commit d9fc803
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 79 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name=OneConfig
mod_id=oneconfig
version_major=1
version_minor=0
version_patch=0-alpha.49
version_patch=0-alpha.50

polyfrost.defaults.loom=3

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.polyfrost.oneconfig.api.platform.v1.Platform;
import org.polyfrost.oneconfig.utils.v1.MHUtils;
import org.spongepowered.asm.launch.MixinBootstrap;
import org.spongepowered.asm.mixin.Mixins;

import java.io.File;
import java.lang.invoke.MethodHandle;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URL;
import java.nio.file.Files;
Expand All @@ -49,20 +49,22 @@
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.jar.Attributes;
import java.util.jar.JarFile;

/**
* Mixin-related loading code adapted from EssentialGG's EssentialLoader under GPL-3.0
* <a href="https://github.com/EssentialGG/EssentialLoader/blob/master/LICENSE">here</a>
* <p>
* The most important part of this code is `OneConfigSourceFile` in the manifest. If a mod creates a custom tweaker
* but still wants the Mixin tweaker to be injected, they can add this attribute to their jar manifest to signal
* to OneConfig that it should inject the Mixin tweaker regardless of the tweaker class.
* </p>
*/
@SuppressWarnings("unused")
public class OneConfigTweaker implements ITweaker {
private static final Logger LOGGER = LogManager.getLogger("OneConfig/Tweaker");
private static final String MIXIN_TWEAKER = "org.spongepowered.asm.launch.MixinTweaker";
private static MethodHandle loadCoreMod;
private static Consumer<URI> addContainer;

public OneConfigTweaker() {
final List<SourceFile> sourceFiles = getSourceFiles();
Expand All @@ -86,7 +88,7 @@ public OneConfigTweaker() {
}

@SuppressWarnings("unchecked")
private static void setupSourceFile(SourceFile sourceFile) throws Throwable {
private void setupSourceFile(SourceFile sourceFile) throws Exception {
String path = sourceFile.path.toString();
// Forge will by default ignore a mod file if it contains a tweaker
// So we need to remove ourselves from that exclusion list
Expand All @@ -101,50 +103,75 @@ private static void setupSourceFile(SourceFile sourceFile) throws Throwable {
// Mixin takes care of this as well, so we mustn't if it will.
String coreMod = sourceFile.coreMod;
if (coreMod != null && !sourceFile.mixin) {
ITweaker tweaker = loadCoreMod(Launch.classLoader, coreMod, sourceFile.path.toFile());
Method loadCoreMod = CoreModManager.class.getDeclaredMethod("loadCoreMod", LaunchClassLoader.class, String.class, File.class);
loadCoreMod.setAccessible(true);
ITweaker tweaker = (ITweaker) loadCoreMod.invoke(null, Launch.classLoader, coreMod, sourceFile.path.toFile());
((List<ITweaker>) Launch.blackboard.get("Tweaks")).add(tweaker);
}

// Mixin will only look at jar files which declare the MixinTweaker as their tweaker class, so we need
// to manually add our source files for inspection.
if (sourceFile.mixin) addContainer(sourceFile.path.toUri());
}
// If they declared our tweaker but also want to use mixin, then we'll inject the mixin tweaker
// for them.
if (sourceFile.mixin) {
// Mixin will only look at jar files which declare the MixinTweaker as their tweaker class, so we need
// to manually add our source files for inspection.
try {
injectMixinTweaker();

private static ITweaker loadCoreMod(LaunchClassLoader loader, String cls, File src) throws Throwable {
MethodHandle mh = loadCoreMod == null ? loadCoreMod = createLoadCoreMod() : loadCoreMod;
return (ITweaker) mh.invoke(loader, src, src);
Class<?> MixinBootstrap = Class.forName("org.spongepowered.asm.launch.MixinBootstrap");
Class<?> MixinPlatformManager = Class.forName("org.spongepowered.asm.launch.platform.MixinPlatformManager");
Object platformManager = MixinBootstrap.getDeclaredMethod("getPlatform").invoke(null);
Method addContainer;
Object arg;
try {
// Mixin 0.7
addContainer = MixinPlatformManager.getDeclaredMethod("addContainer", URI.class);
arg = sourceFile.path.toUri();
} catch (NoSuchMethodException ignored) {
// Mixin 0.8
Class<?> IContainerHandle = Class.forName("org.spongepowered.asm.launch.platform.container.IContainerHandle");
Class<?> ContainerHandleURI = Class.forName("org.spongepowered.asm.launch.platform.container.ContainerHandleURI");
addContainer = MixinPlatformManager.getDeclaredMethod("addContainer", IContainerHandle);
arg = ContainerHandleURI.getDeclaredConstructor(URI.class).newInstance(sourceFile.path.toUri());
}
addContainer.invoke(platformManager, arg);
} catch (Exception e) {
LOGGER.error("failed to inject mixin tweaker for {}", path, e);
}
}
}

private static MethodHandle createLoadCoreMod() {
return MHUtils.getStaticMethodHandle(CoreModManager.class, "loadCoreMod", ITweaker.class, LaunchClassLoader.class, String.class, File.class).getOrThrow();
}
private void injectMixinTweaker() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
@SuppressWarnings("unchecked")
List<String> tweakClasses = (List<String>) Launch.blackboard.get("TweakClasses");

private static void addContainer(URI uri) {
Consumer<URI> c = addContainer == null ? addContainer = createAddContainer() : addContainer;
c.accept(uri);
}
// If the MixinTweaker is already queued (because of another mod), then there's nothing we need to to
if (tweakClasses.contains(MIXIN_TWEAKER)) {
// Except we do need to initialize the MixinTweaker immediately so we can add containers
// for our mods.
// This is idempotent, so we can call it without adding to the tweaks list (and we must not add to
// it because the queued tweaker will already get added and there is nothing we can do about that).
initMixinTweaker();
return;
}

@SuppressWarnings("unchecked")
private static Consumer<URI> createAddContainer() {
try {
Class<?> MixinBootstrap = Class.forName("org.spongepowered.asm.launch.MixinBootstrap");
Class<?> MixinPlatformManager = Class.forName("org.spongepowered.asm.launch.platform.MixinPlatformManager");
Object platformManager = MHUtils.invokeStatic(MixinBootstrap, "getPlatform");
try {
// Mixin 0.7
return MHUtils.getConsumerHandle(platformManager, "addContainer", URI.class).getOrThrow();
} catch (Throwable ignored) {
// Mixin 0.8
Class<?> IContainerHandle = Class.forName("org.spongepowered.asm.launch.platform.container.IContainerHandle");
Class<?> ContainerHandleURI = Class.forName("org.spongepowered.asm.launch.platform.container.ContainerHandleURI");
// as we can't refer to IContainerHandle, we have to use unchecked casts here.
Consumer<Object> addWrappedContainer = (Consumer<Object>) MHUtils.getConsumerHandle(MixinPlatformManager, "addContainer", IContainerHandle).getOrThrow();
MethodHandle wrapper = MHUtils.getConstructorHandle(ContainerHandleURI, URI.class).getOrThrow();
return it -> addWrappedContainer.accept(MHUtils.invokeCatching(wrapper, it).getOrThrow());
}
} catch (Throwable t) {
throw new RuntimeException("Failed to crack addContainer method. mixin will not work!", t);
// If it is already booted, we're also good to go
if (Launch.blackboard.get("mixin.initialised") != null) {
return;
}

System.out.println("Injecting MixinTweaker from OneConfigTweaker");

// Otherwise, we need to take things into our own hands because the normal way to chainload a tweaker
// (by adding it to the TweakClasses list during injectIntoClassLoader) is too late for Mixin.
// Instead we instantiate the MixinTweaker on our own and add it to the current Tweaks list immediately.
@SuppressWarnings("unchecked")
List<ITweaker> tweaks = (List<ITweaker>) Launch.blackboard.get("Tweaks");
tweaks.add(initMixinTweaker());
}

private ITweaker initMixinTweaker() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Launch.classLoader.addClassLoaderExclusion(MIXIN_TWEAKER.substring(0, MIXIN_TWEAKER.lastIndexOf('.')));
return (ITweaker) Class.forName(MIXIN_TWEAKER, true, Launch.classLoader).newInstance();
}

private static List<SourceFile> getSourceFiles() {
Expand All @@ -162,15 +189,17 @@ private static List<SourceFile> getSourceFiles() {
String tweakClass = null;
String coreMod = null;
boolean mixin = false;
boolean manualSourceFile = false;
try (JarFile jar = new JarFile(file.toFile())) {
if (jar.getManifest() != null) {
Attributes attributes = jar.getManifest().getMainAttributes();
tweakClass = attributes.getValue("TweakClass");
coreMod = attributes.getValue("FMLCorePlugin");
mixin = attributes.getValue("MixinConfigs") != null;
manualSourceFile = attributes.getValue("OneConfigSourceFile") != null; // This is for when a mod uses a custom tweaker but still wants the Mixin tweaker to be injected
}
}
if (Objects.equals(tweakClass, "cc.polyfrost.oneconfigwrapper.OneConfigWrapper") || Objects.equals(tweakClass, "cc.polyfrost.oneconfig.loader.stage0.LaunchWrapperTweaker")) {
if (Objects.equals(tweakClass, "org.polyfrost.oneconfig.loader.stage0.LaunchWrapperTweaker") || manualSourceFile) {
sourceFiles.add(new SourceFile(file, coreMod, mixin));
}
} catch (Exception e) {
Expand All @@ -180,50 +209,18 @@ private static List<SourceFile> getSourceFiles() {
return sourceFiles;
}

private static void injectMixinTweaker() throws ClassNotFoundException {
@SuppressWarnings("unchecked")
List<String> tweakClasses = (List<String>) Launch.blackboard.get("TweakClasses");

// If the MixinTweaker is already queued (because of another mod), then there's nothing we need to do
if (tweakClasses.contains(MIXIN_TWEAKER)) {
// Except we do need to initialize the MixinTweaker immediately so we can add containers
// for our mods.
// This is idempotent, so we can call it without adding to the tweaks list (and we must not add to
// it because the queued tweaker will already get added and there is nothing we can do about that).
initMixinTweaker();
return;
}

// If it is already booted, we're also good to go
if (Launch.blackboard.get("mixin.initialised") != null) {
return;
}

LOGGER.info("Injecting MixinTweaker from OneConfigTweaker");

// Otherwise, we need to take things into our own hands because the normal way to chainload a tweaker
// (by adding it to the TweakClasses list during injectIntoClassLoader) is too late for Mixin.
// Instead we instantiate the MixinTweaker on our own and add it to the current Tweaks list immediately.
@SuppressWarnings("unchecked")
List<ITweaker> tweaks = (List<ITweaker>) Launch.blackboard.get("Tweaks");
tweaks.add(initMixinTweaker());
}

private static ITweaker initMixinTweaker() throws ClassNotFoundException {
Launch.classLoader.addClassLoaderExclusion(MIXIN_TWEAKER.substring(0, MIXIN_TWEAKER.lastIndexOf('.')));
return (ITweaker) MHUtils.instantiate(Class.forName(MIXIN_TWEAKER, true, Launch.classLoader), false).getOrThrow();
}

/**
* Taken from LWJGLTwoPointFive under The Unlicense
* <a href="https://github.com/DJtheRedstoner/LWJGLTwoPointFive/blob/master/LICENSE/">https://github.com/DJtheRedstoner/LWJGLTwoPointFive/blob/master/LICENSE/</a>
*/
@SuppressWarnings("unchecked")
private static void removeLWJGLException() {
try {
Set<String> exceptions = (Set<String>) MHUtils.getField(Launch.classLoader, "classLoaderExceptions").getOrThrow();
Field f_exceptions = LaunchClassLoader.class.getDeclaredField("classLoaderExceptions");
f_exceptions.setAccessible(true);
Set<String> exceptions = (Set<String>) f_exceptions.get(Launch.classLoader);
exceptions.remove("org.lwjgl.");
} catch (Throwable e) {
} catch (Exception e) {
throw new RuntimeException(e);
}
}
Expand Down

0 comments on commit d9fc803

Please sign in to comment.