Compare commits
6 commits
287214acba
...
e5d4a343ca
Author | SHA1 | Date | |
---|---|---|---|
owlsys | e5d4a343ca | ||
moehreag | ba409f9905 | ||
moehreag | 798fc26fe4 | ||
moehreag | df66596b5e | ||
moehreag | c42681f445 | ||
TheKodeToad | 4ec31a0082 |
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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));
|
||||||
|
|
Loading…
Reference in a new issue