Merge pull request 'TheKodeToad/loading-fixes' (#7) from TheKodeToad/loading-fixes into main

Reviewed-on: https://git-esnesnon.ecorous.org/esnesnon/nonsense-loader/pulls/7
This commit is contained in:
owlsys 2024-05-24 16:03:36 -04:00
commit e5d4a343ca
10 changed files with 338 additions and 233 deletions

View file

@ -0,0 +1,25 @@
package org.ecorous.esnesnon.nonsense.loader.example.mixin;
import net.minecraft.client.gui.components.FocusableTextWidget;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.screens.TitleScreen;
import net.minecraft.network.chat.Component;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(TitleScreen.class)
public abstract class TitleScreenMixin extends Screen {
protected TitleScreenMixin(Component title) {
super(title);
}
@Inject(method = "createNormalMenuOptions", at = @At("TAIL"), remap = false)
private void showExample(int y, int rowHeight, CallbackInfo ci) {
var widget = new FocusableTextWidget(200, Component.literal("<insert frog here!>"), this.font);
widget.setPosition(width / 2 - widget.getWidth(), 20);
addRenderableOnly(widget);
}
}

View file

@ -4,7 +4,9 @@
"package": "org.ecorous.esnesnon.nonsense.loader.example.mixin", "package": "org.ecorous.esnesnon.nonsense.loader.example.mixin",
"compatibilityLevel": "JAVA_21", "compatibilityLevel": "JAVA_21",
"mixins": [], "mixins": [],
"client": [], "client": [
"TitleScreenMixin"
],
"injectors": { "injectors": {
"defaultRequire": 1 "defaultRequire": 1
} }

View file

@ -16,6 +16,6 @@ breaks = [
] ]
[nonsense.extensions] [nonsense.extensions]
pre_launch = "org/ecorous/esnesnon/nonsense/loader/example/ExamplePreLaunchExtension" pre_launch = "org.ecorous.esnesnon.nonsense.loader.example.ExamplePreLaunchExtension"
mixin_config = "example_mod.mixins.json" mixin_config = "example_mod.mixins.json"

View file

@ -12,11 +12,12 @@ import java.util.*;
import com.google.gson.Gson; import com.google.gson.Gson;
import org.ecorous.esnesnon.nonsense.loader.api.Loader; import org.ecorous.esnesnon.nonsense.loader.api.Loader;
import org.ecorous.esnesnon.nonsense.loader.api.env.Env; import org.ecorous.esnesnon.nonsense.loader.api.env.Env;
import org.ecorous.esnesnon.nonsense.loader.impl.launch.MixinClassloader; import org.ecorous.esnesnon.nonsense.loader.impl.launch.MixinClassLoader;
import org.ecorous.esnesnon.nonsense.loader.impl.plugin.NonsensePlugin; import org.ecorous.esnesnon.nonsense.loader.impl.plugin.NonsensePlugin;
import lombok.Getter; import lombok.Getter;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.spongepowered.asm.mixin.MixinEnvironment;
public class LoaderImpl implements Loader { public class LoaderImpl implements Loader {
// TODO decide this // TODO decide this
@ -40,14 +41,14 @@ public class LoaderImpl implements Loader {
private final Path gameDir, configDir, modsDir; private final Path gameDir, configDir, modsDir;
@Getter @Getter
private final MixinClassloader classloader; private final MixinClassLoader classloader;
@Getter @Getter
private final Gson gson = new Gson(); private final Gson gson = new Gson();
private LoaderImpl(String[] args, Env env) { private LoaderImpl(String[] args, Env env) {
instance = this; instance = this;
this.classloader = (MixinClassloader) this.getClass().getClassLoader(); this.classloader = (MixinClassLoader) this.getClass().getClassLoader();
this.args = args; this.args = args;
this.env = env; this.env = env;
@ -64,11 +65,21 @@ public class LoaderImpl implements Loader {
} }
discoverPlugins(); discoverPlugins();
advanceMixinState();
LOGGER.info("Launching..."); LOGGER.info("Launching...");
plugins.forEach(NonsensePlugin::run); plugins.forEach(NonsensePlugin::run);
} }
private void advanceMixinState(){
try {
MethodHandle m = MethodHandles.privateLookupIn(MixinEnvironment.class, MethodHandles.lookup()).findStatic(MixinEnvironment.class, "gotoPhase", MethodType.methodType(void.class, MixinEnvironment.Phase.class));
m.invoke(MixinEnvironment.Phase.INIT);
m.invoke(MixinEnvironment.Phase.DEFAULT);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
@SuppressWarnings("unused") @SuppressWarnings("unused")
public static void run(String[] args, Env env) { public static void run(String[] args, Env env) {
if (instance != null) { if (instance != null) {

View file

@ -1,17 +1,9 @@
package org.ecorous.esnesnon.nonsense.loader.impl.launch; package org.ecorous.esnesnon.nonsense.loader.impl.launch;
import java.io.File;
import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType; import java.lang.invoke.MethodType;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import org.ecorous.esnesnon.nonsense.loader.api.env.Env; import org.ecorous.esnesnon.nonsense.loader.api.env.Env;
@ -23,8 +15,7 @@ import org.spongepowered.asm.service.IPropertyKey;
public class Launcher { public class Launcher {
@Getter @Getter
private final MixinClassloader targetClassLoader; private final MixinClassLoader targetClassLoader;
private final List<Path> classPath;
@Getter @Getter
private static Launcher instance; private static Launcher instance;
@ -45,15 +36,12 @@ public class Launcher {
} }
instance = this; instance = this;
this.env = env; this.env = env;
classPath = Arrays.stream(System.getProperty("java.class.path").split(File.pathSeparator)).filter(s -> !"*".equals(s)).map(Paths::get).toList(); targetClassLoader = new MixinClassLoader();
targetClassLoader = new MixinClassloader(classPath.stream().map(Path::toUri).map((URI uri) -> { targetClassLoader.excludePackage("org.slf4j");
try { targetClassLoader.excludePackage("org.spongepowered");
return uri.toURL(); targetClassLoader.excludePackage("org.apache.logging");
} catch (MalformedURLException e) { targetClassLoader.excludePackage("org.ecorous.esnesnon.nonsense.loader.impl.launch");
// TODO targetClassLoader.excludePackage("org.ecorous.esnesnon.nonsense.loader.api.env");
throw new RuntimeException(e);
}
}).toArray(URL[]::new), this.getClass().getClassLoader());
Thread.currentThread().setContextClassLoader(targetClassLoader); Thread.currentThread().setContextClassLoader(targetClassLoader);

View file

@ -0,0 +1,129 @@
package org.ecorous.esnesnon.nonsense.loader.impl.launch;
import org.ecorous.esnesnon.nonsense.loader.impl.mixin.NonsenseMixinService;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.MixinEnvironment;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
public class MixinClassLoader extends URLClassLoader {
private static final ClassLoader SYSTEM = ClassLoader.getSystemClassLoader();
private final List<String> exclusions = new ArrayList<>();
static {
registerAsParallelCapable();
}
public MixinClassLoader() {
super(new URL[0], null);
excludePackage("java");
excludePackage("com.sun");
excludePackage("sun");
excludePackage("jdk");
}
public boolean isClassLoaded(String name) {
return findLoadedClass(name) != null;
}
public byte[] getClassBytes(String name) throws IOException {
String binName = name.replace('.', '/');
String path = binName.concat(".class");
try (InputStream in = getResourceAsStream(path)) {
if (in == null)
return null;
return NonsenseMixinService.getTransformer().transformClass(MixinEnvironment.getCurrentEnvironment(), name, in.readAllBytes());
}
}
public void excludePackage(String name) {
exclusions.add(name + '.');
}
@Override
public void addURL(URL url) {
super.addURL(url);
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
for (String prefix : exclusions) {
if (name.startsWith(prefix)) {
return SYSTEM.loadClass(name);
}
}
Class<?> loaded = findLoadedClass(name);
if (loaded != null)
return loaded;
Class<?> result = findClass(name);
if (resolve)
resolveClass(result);
return result;
}
}
@Override
public synchronized Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] bytes = getClassBytes(name);
if (bytes == null)
throw new ClassNotFoundException(name);
return defineClass(name, bytes, 0, bytes.length);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
}
@Nullable
@Override
public URL getResource(String name) {
URL parentUrl = super.getResource(name);
if (parentUrl != null)
return parentUrl;
return SYSTEM.getResource(name);
}
@Override
public Enumeration<URL> getResources(String name) throws IOException {
Enumeration<URL> parentResources = super.getResources(name);
Enumeration<URL> systemResources = SYSTEM.getResources(name);
if (parentResources.hasMoreElements() && systemResources.hasMoreElements()) {
List<URL> list = new ArrayList<>();
while (parentResources.hasMoreElements())
list.add(parentResources.nextElement());
while (systemResources.hasMoreElements())
list.add(systemResources.nextElement());
return Collections.enumeration(list);
}
if (parentResources.hasMoreElements())
return parentResources;
if (systemResources.hasMoreElements())
return systemResources;
return Collections.enumeration(Collections.emptyList());
}
}

View file

@ -1,54 +0,0 @@
package org.ecorous.esnesnon.nonsense.loader.impl.launch;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
public class MixinClassloader extends URLClassLoader {
static {
registerAsParallelCapable();
}
public MixinClassloader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
@Override
public void addURL(URL url) {
super.addURL(url);
}
public boolean isClassLoaded(String name) {
return findLoadedClass(name) != null;
}
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] bytes = getClassBytes(name);
if (bytes == null) {
throw new ClassNotFoundException(name);
}
synchronized (getClassLoadingLock(name)) {
return defineClass(name, bytes, 0, bytes.length);
}
} catch (IOException aa) {
throw new ClassNotFoundException(name, aa);
}
}
public byte[] getClassBytes(String name) throws IOException {
String path = name.replace(".", "/").concat(".class");
synchronized (getClassLoadingLock(name)) {
try (InputStream in = getResourceAsStream(path)) {
if (in == null)
return null;
return in.readAllBytes();
}
}
}
}

View file

@ -1,5 +1,6 @@
package org.ecorous.esnesnon.nonsense.loader.impl.mixin; package org.ecorous.esnesnon.nonsense.loader.impl.mixin;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -20,7 +21,7 @@ public class NonsenseMixinLogger extends LoggerAdapterAbstract {
public NonsenseMixinLogger(String name){ public NonsenseMixinLogger(String name){
super(name); super(name);
log = LoggerFactory.getLogger(name); log = LoggerFactory.getLogger("Nonsense Loader/"+name.substring(0, 1).toUpperCase(Locale.ROOT)+name.substring(1));
} }
@Override @Override

View file

@ -1,5 +1,13 @@
package org.ecorous.esnesnon.nonsense.loader.impl.mixin; package org.ecorous.esnesnon.nonsense.loader.impl.mixin;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import lombok.Getter;
import org.ecorous.esnesnon.nonsense.loader.impl.launch.Launcher; import org.ecorous.esnesnon.nonsense.loader.impl.launch.Launcher;
import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassReader;
import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.ClassNode;
@ -13,13 +21,8 @@ import org.spongepowered.asm.service.*;
import org.spongepowered.asm.transformers.MixinClassReader; import org.spongepowered.asm.transformers.MixinClassReader;
import org.spongepowered.asm.util.ReEntranceLock; import org.spongepowered.asm.util.ReEntranceLock;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Collection;
import java.util.List;
public class NonsenseMixinService implements IMixinService, IClassProvider, IClassBytecodeProvider, ITransformerProvider, IClassTracker { public class NonsenseMixinService implements IMixinService, IClassProvider, IClassBytecodeProvider, ITransformerProvider, IClassTracker {
@Getter
static IMixinTransformer transformer; static IMixinTransformer transformer;
private final ReEntranceLock lock = new ReEntranceLock(1); private final ReEntranceLock lock = new ReEntranceLock(1);
private final ContainerHandleVirtual handle = new ContainerHandleVirtual(getName()); private final ContainerHandleVirtual handle = new ContainerHandleVirtual(getName());
@ -41,7 +44,7 @@ public class NonsenseMixinService implements IMixinService, IClassProvider, ICla
@Override @Override
public MixinEnvironment.Phase getInitialPhase() { public MixinEnvironment.Phase getInitialPhase() {
return null; return MixinEnvironment.Phase.PREINIT;
} }
@Override @Override
@ -98,7 +101,7 @@ public class NonsenseMixinService implements IMixinService, IClassProvider, ICla
@Override @Override
public Collection<String> getPlatformAgents() { public Collection<String> getPlatformAgents() {
return List.of(); return Collections.singletonList("org.spongepowered.asm.launch.platform.MixinPlatformAgentDefault");
} }
@Override @Override
@ -123,12 +126,12 @@ public class NonsenseMixinService implements IMixinService, IClassProvider, ICla
@Override @Override
public MixinEnvironment.CompatibilityLevel getMinCompatibilityLevel() { public MixinEnvironment.CompatibilityLevel getMinCompatibilityLevel() {
return null; return MixinEnvironment.CompatibilityLevel.JAVA_8;
} }
@Override @Override
public MixinEnvironment.CompatibilityLevel getMaxCompatibilityLevel() { public MixinEnvironment.CompatibilityLevel getMaxCompatibilityLevel() {
return null; return MixinEnvironment.CompatibilityLevel.JAVA_22;
} }
@Override @Override
@ -145,7 +148,7 @@ public class NonsenseMixinService implements IMixinService, IClassProvider, ICla
@Override @Override
public ClassNode getClassNode(String name, boolean runTransformers) throws IOException { public ClassNode getClassNode(String name, boolean runTransformers) throws IOException {
byte[] bytes; byte[] bytes;
if (runTransformers){ if (runTransformers && transformer != null) {
bytes = transformer.transformClass(MixinEnvironment.getCurrentEnvironment(), name, bytes = transformer.transformClass(MixinEnvironment.getCurrentEnvironment(), name,
Launcher.getInstance().getTargetClassLoader().getClassBytes(name)); Launcher.getInstance().getTargetClassLoader().getClassBytes(name));
} else { } else {

View file

@ -70,7 +70,7 @@ public class Minecraft implements NonsensePlugin {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
}).forEachOrdered(LoaderImpl.getInstance().getClassloader()::addURL); }).forEachOrdered(LoaderImpl.getInstance().getClassloader()::addURL);
LOGGER.info("Found {} mods", mods.size()+classpathMods.size()); LOGGER.info("Found {} mod(s)", mods.size()+classpathMods.size());
classpathMods.parallelStream().map(ModPropertiesReader::readFile).forEachOrdered(modProperties::add); classpathMods.parallelStream().map(ModPropertiesReader::readFile).forEachOrdered(modProperties::add);
mods.parallelStream().map(ModPropertiesReader::read).forEachOrdered(opt -> opt.ifPresent(modProperties::add)); mods.parallelStream().map(ModPropertiesReader::read).forEachOrdered(opt -> opt.ifPresent(modProperties::add));