Require extensions to be namespaced
This commit is contained in:
parent
ac3e0d86df
commit
12f9d823b4
|
@ -10,8 +10,8 @@ credits = [
|
||||||
{ name = "You", roles = ["author", "other_role"] }
|
{ name = "You", roles = ["author", "other_role"] }
|
||||||
]
|
]
|
||||||
|
|
||||||
[frog.extensions]
|
[frog.extensions.frogloader]
|
||||||
prelaunch = "dev.frogmc.frogloader.example.ExamplePreLaunchExtension"
|
prelaunch = "dev.frogmc.frogloader.example.ExamplePreLaunchExtension::onPreLaunch"
|
||||||
mixin = "example_mod.mixins.json"
|
mixin = "example_mod.mixins.json"
|
||||||
accesswidener = "example_mod.accesswidener"
|
accesswidener = "example_mod.accesswidener"
|
||||||
modprovider = "dev.frogmc.frogloader.example.ExampleModProvider"
|
modprovider = "dev.frogmc.frogloader.example.ExampleModProvider"
|
||||||
|
|
|
@ -10,6 +10,6 @@ credits = [
|
||||||
{ name = "You", roles = ["author", "other_role"] }
|
{ name = "You", roles = ["author", "other_role"] }
|
||||||
]
|
]
|
||||||
|
|
||||||
[frog.extensions]
|
[frog.extensions.frogloader]
|
||||||
prelaunch="dev.frogmc.example.ExampleTestMod"
|
prelaunch="dev.frogmc.example.ExampleTestMod"
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
package dev.frogmc.frogloader.api.exception;
|
||||||
|
|
||||||
|
public class ModExtensionResolutionException extends Exception {
|
||||||
|
|
||||||
|
public ModExtensionResolutionException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModExtensionResolutionException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModExtensionResolutionException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
package dev.frogmc.frogloader.api.extensions;
|
package dev.frogmc.frogloader.api.extensions;
|
||||||
|
|
||||||
import dev.frogmc.frogloader.api.mod.ModExtensions;
|
import dev.frogmc.frogloader.api.mod.ModExtensions;
|
||||||
import dev.frogmc.frogloader.impl.mod.BuiltinExtensions;
|
import dev.frogmc.frogloader.impl.Constants;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Pre-Launch Extension.
|
* The Pre-Launch Extension.
|
||||||
|
@ -13,7 +13,7 @@ public interface PreLaunchExtension {
|
||||||
/**
|
/**
|
||||||
* This extension's id. This is the key to use in your frog.mod.toml.
|
* This extension's id. This is the key to use in your frog.mod.toml.
|
||||||
*/
|
*/
|
||||||
String ID = BuiltinExtensions.PRE_LAUNCH;
|
String ID = Constants.EXTENSION_PRE_LAUNCH;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The initializer. This method will be invoked when this extension is run.
|
* The initializer. This method will be invoked when this extension is run.
|
||||||
|
|
|
@ -1,150 +1,74 @@
|
||||||
package dev.frogmc.frogloader.api.mod;
|
package dev.frogmc.frogloader.api.mod;
|
||||||
|
|
||||||
import java.lang.invoke.MethodHandle;
|
|
||||||
import java.lang.invoke.MethodHandleProxies;
|
|
||||||
import java.lang.invoke.MethodHandles;
|
|
||||||
import java.lang.invoke.MethodType;
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.lang.reflect.Modifier;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import dev.frogmc.frogloader.api.exception.ModExtensionResolutionException;
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class stores a mod's extensions.
|
* This class stores a mod's extensions.
|
||||||
* <p>An extension is a simple key-value mapping of a string name to any value.</p>
|
* <p>
|
||||||
* <p>This class further provides utility methods to easily work with extension values of various types,
|
* An extension is a simple key-value mapping of a string name to any value.
|
||||||
* especially for extensions providing some form of class or method reference.</p>
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* This class further provides utility methods to easily work with extension
|
||||||
|
* values of various types, especially for extensions providing some form of
|
||||||
|
* class or method reference.
|
||||||
|
* </p>
|
||||||
*
|
*
|
||||||
* @see ModProperties
|
* @see ModProperties
|
||||||
*/
|
*/
|
||||||
public final class ModExtensions {
|
public interface ModExtensions {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(ModExtensions.class);
|
|
||||||
private final Map<String, Object> extensions;
|
|
||||||
|
|
||||||
private ModExtensions(Map<String, Object> entries) {
|
|
||||||
extensions = entries;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve a new instance of this class.
|
|
||||||
* <p><strong>Internal use only.</strong></p>
|
|
||||||
*
|
|
||||||
* @param entries the entries read from the mod's properties file
|
|
||||||
* @return an instance of this class
|
|
||||||
*/
|
|
||||||
public static ModExtensions of(Map<String, Object> entries) {
|
|
||||||
return new ModExtensions(entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the value of the provided extension name.
|
* Get the value of the provided extension name.
|
||||||
*
|
*
|
||||||
|
* @param modId The mod ID this extension is stored under
|
||||||
* @param key The extension name to query
|
* @param key The extension name to query
|
||||||
* @param <T> The type of the value of this extension
|
* @param <T> The type of the value of this extension
|
||||||
|
*
|
||||||
* @return The value of the extension, or null
|
* @return The value of the extension, or null
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
<T> T get(String modId, String key);
|
||||||
public <T> T get(String key) {
|
|
||||||
return (T) extensions.get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the value of the provided extension name.
|
* Get the value of the provided extension name.
|
||||||
*
|
*
|
||||||
|
* @param modId The mod ID this extension is stored under
|
||||||
* @param key The extension name to query
|
* @param key The extension name to query
|
||||||
* @param defaultValue a default value
|
* @param defaultValue A default value
|
||||||
* @param <T> The type of the value of this extension
|
* @param <T> The type of the value of this extension
|
||||||
|
*
|
||||||
* @return The value of the extension, or the default value if it isn't present
|
* @return The value of the extension, or the default value if it isn't present
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
<T> T getOrDefault(String modId, String key, T defaultValue);
|
||||||
public <T> T getOrDefault(String key, T defaultValue) {
|
|
||||||
return (T) extensions.getOrDefault(key, defaultValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run the given action on this extension if it is present.
|
* Run the given action on this extension if it is present.
|
||||||
*
|
*
|
||||||
* @param key the extension name to query
|
* @param modId The mod ID this extension is stored under
|
||||||
* @param action the action to run on the value of the extension if it is present
|
* @param key The extension name to query
|
||||||
|
* @param action The action to run on the value of the extension if it is present
|
||||||
* @param <T> The type of the value of this extension
|
* @param <T> The type of the value of this extension
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
<T> void runIfPresent(String modId, String key, Consumer<T> action);
|
||||||
public <T> void runIfPresent(String key, Consumer<T> action) {
|
|
||||||
Object value = get(key);
|
|
||||||
if (value == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (value instanceof Collection c) {
|
|
||||||
((Collection<T>) c).forEach(action);
|
|
||||||
} else {
|
|
||||||
action.accept((T) value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run the given action on this extension if it is present.
|
* Run the given action on this extension if it is present.
|
||||||
* <p>This method simplifies handling references to classes or methods.</p>
|
* <p>
|
||||||
* It will first query the string value of the extension and, if it is found, create a new instance of the referenced class
|
* This method simplifies handling references to classes or methods.
|
||||||
* or invoke the referenced method to retrieve an instance of the provided class. Then the provided action is run on this
|
* </p>
|
||||||
* object.
|
* It will first query the string value of the extension and, if it is found,
|
||||||
|
* create a new instance of the referenced class or invoke the referenced method
|
||||||
|
* to retrieve an instance of the provided class. Then the provided action is
|
||||||
|
* run on this object.
|
||||||
*
|
*
|
||||||
|
* @param modId The mod ID this extension is stored under
|
||||||
* @param key The name of the extension
|
* @param key The name of the extension
|
||||||
* @param type The class type of the extension (The class the extension class is extending/implementing)
|
* @param type The class type of the extension (The class the extension class is extending/implementing)
|
||||||
* @param action The action to run on the newly retrieved instance of the provided class
|
* @param action The action to run on the newly retrieved instance of the provided class
|
||||||
* @param <T> The type of the class
|
* @param <T> The type of the class
|
||||||
|
*
|
||||||
|
* @throws ModExtensionResolutionException If the reference could not be resolved
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({"unchecked"})
|
<T> void runIfPresent(String modId, String key, Class<T> type, Consumer<T> action) throws ModExtensionResolutionException;
|
||||||
public <T> void runIfPresent(String key, Class<T> type, Consumer<T> action) {
|
|
||||||
runIfPresent(key, (Consumer<String>) s -> {
|
|
||||||
try {
|
|
||||||
T object;
|
|
||||||
if (s.contains("::")) {
|
|
||||||
String[] parts = s.split("::");
|
|
||||||
Class<?> extension = Class.forName(parts[0]);
|
|
||||||
object = (T) handleReference(extension, type, parts[1]);
|
|
||||||
} else {
|
|
||||||
object = (T) MethodHandles.lookup().findConstructor(Class.forName(s), MethodType.methodType(void.class)).invoke();
|
|
||||||
}
|
|
||||||
if (object != null) {
|
|
||||||
action.accept(object);
|
|
||||||
}
|
|
||||||
} catch (Throwable e) {
|
|
||||||
LOGGER.warn("Failed to instantiate Extension: ", e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private Object handleReference(Class<?> clazz, Class<?> type, String ref) throws Throwable {
|
|
||||||
try {
|
|
||||||
Field found = clazz.getDeclaredField(ref);
|
|
||||||
found.setAccessible(true);
|
|
||||||
return found.get(null);
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
|
|
||||||
Method found = null;
|
|
||||||
for (Method method : clazz.getDeclaredMethods()) {
|
|
||||||
if (method.getName().equals(ref)) {
|
|
||||||
if (found != null) {
|
|
||||||
throw new IllegalArgumentException("Ambiguous method reference: "+ref+" in "+clazz.getName());
|
|
||||||
}
|
|
||||||
found = method;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (found != null) {
|
|
||||||
MethodHandle method = MethodHandles.lookup().unreflect(found);
|
|
||||||
if (!Modifier.isStatic(found.getModifiers())) {
|
|
||||||
method = method.bindTo(MethodHandles.lookup().findConstructor(clazz, MethodType.methodType(void.class)).invoke());
|
|
||||||
}
|
|
||||||
return MethodHandleProxies.asInterfaceInstance(type, method);
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException("Could not find either a static field or a method named '" + ref + "' in class " + clazz.getName() + "!");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,8 +65,9 @@ public interface ModProvider {
|
||||||
/**
|
/**
|
||||||
* This method will be invoked just before the game is launched. It may be used for a pre-launch entrypoint for mods.
|
* This method will be invoked just before the game is launched. It may be used for a pre-launch entrypoint for mods.
|
||||||
* @param mods The mods loaded by this provider
|
* @param mods The mods loaded by this provider
|
||||||
|
* @throws Exception If an exception occurs during pre-launch. It will be handled by the loader.
|
||||||
*/
|
*/
|
||||||
default void preLaunch(Collection<ModProperties> mods) {
|
default void preLaunch(Collection<ModProperties> mods) throws Exception {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
14
src/main/java/dev/frogmc/frogloader/impl/Constants.java
Normal file
14
src/main/java/dev/frogmc/frogloader/impl/Constants.java
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
package dev.frogmc.frogloader.impl;
|
||||||
|
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
|
public class Constants {
|
||||||
|
public final String MOD_ID = "frogloader";
|
||||||
|
public final String EXTENSION_MIXIN_CONFIG = "mixin";
|
||||||
|
public final String EXTENSION_INCLUDED_JARS = "included_jars";
|
||||||
|
public final String EXTENSION_PRE_LAUNCH = "prelaunch";
|
||||||
|
public final String EXTENSION_ACCESSWIDENER = "accesswidener";
|
||||||
|
public final String EXTENSION_LOADING_TYPE = "loading_type";
|
||||||
|
public final String EXTENSION_MOD_PROVIDER = "modprovider";
|
||||||
|
}
|
|
@ -78,7 +78,8 @@ public class FrogLoaderImpl implements FrogLoader {
|
||||||
modIds = collectModIds();
|
modIds = collectModIds();
|
||||||
LOGGER.info(ModUtil.getModList(getMods()));
|
LOGGER.info(ModUtil.getModList(getMods()));
|
||||||
LOGGER.info("Launching...");
|
LOGGER.info("Launching...");
|
||||||
modProviders.forEach(plugin -> plugin.preLaunch(mods.get(plugin.id()).values()));
|
for (ModProvider plugin : modProviders)
|
||||||
|
plugin.preLaunch(mods.get(plugin.id()).values());
|
||||||
gamePlugin.run();
|
gamePlugin.run();
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
LoaderGui.execReport(CrashReportGenerator.writeReport(t, getMods()), false);
|
LoaderGui.execReport(CrashReportGenerator.writeReport(t, getMods()), false);
|
||||||
|
|
|
@ -11,7 +11,6 @@ import dev.frogmc.frogloader.api.plugin.GamePlugin;
|
||||||
import dev.frogmc.frogloader.api.plugin.ModProvider;
|
import dev.frogmc.frogloader.api.plugin.ModProvider;
|
||||||
import dev.frogmc.frogloader.impl.gui.LoaderGui;
|
import dev.frogmc.frogloader.impl.gui.LoaderGui;
|
||||||
import dev.frogmc.frogloader.impl.mixin.AWProcessor;
|
import dev.frogmc.frogloader.impl.mixin.AWProcessor;
|
||||||
import dev.frogmc.frogloader.impl.mod.BuiltinExtensions;
|
|
||||||
import dev.frogmc.frogloader.impl.mod.ModDependencyResolver;
|
import dev.frogmc.frogloader.impl.mod.ModDependencyResolver;
|
||||||
import dev.frogmc.frogloader.impl.util.CrashReportGenerator;
|
import dev.frogmc.frogloader.impl.util.CrashReportGenerator;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -47,10 +46,12 @@ public class PluginLoader {
|
||||||
properties.put(plugin.id(), loadedMods);
|
properties.put(plugin.id(), loadedMods);
|
||||||
|
|
||||||
LOGGER.debug("Loaded {} mod(s) from provider: {}", loadedMods.size(), plugin.id());
|
LOGGER.debug("Loaded {} mod(s) from provider: {}", loadedMods.size(), plugin.id());
|
||||||
loadedMods.forEach(p -> p.extensions().runIfPresent(BuiltinExtensions.MOD_PROVIDER, ModProvider.class, provider -> {
|
for (ModProperties p : loadedMods) {
|
||||||
|
p.extensions().runIfPresent(Constants.MOD_ID, Constants.EXTENSION_MOD_PROVIDER, ModProvider.class, provider -> {
|
||||||
providers.add(provider);
|
providers.add(provider);
|
||||||
size[0]++;
|
size[0]++;
|
||||||
}));
|
});
|
||||||
|
}
|
||||||
modProviders.add(plugin);
|
modProviders.add(plugin);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
LOGGER.error("Error during plugin initialisation: ", e);
|
LOGGER.error("Error during plugin initialisation: ", e);
|
||||||
|
@ -78,7 +79,7 @@ public class PluginLoader {
|
||||||
private static void initializeModMixins(Collection<ModProperties> loadedMods) {
|
private static void initializeModMixins(Collection<ModProperties> loadedMods) {
|
||||||
Map<String, ModProperties> configs = new HashMap<>();
|
Map<String, ModProperties> configs = new HashMap<>();
|
||||||
loadedMods.forEach(props -> {
|
loadedMods.forEach(props -> {
|
||||||
Object o = props.extensions().get(BuiltinExtensions.MIXIN_CONFIG);
|
Object o = props.extensions().get(Constants.MOD_ID, Constants.EXTENSION_MIXIN_CONFIG);
|
||||||
if (o instanceof String name) {
|
if (o instanceof String name) {
|
||||||
configs.put(name, props);
|
configs.put(name, props);
|
||||||
Mixins.addConfiguration(name);
|
Mixins.addConfiguration(name);
|
||||||
|
|
|
@ -14,12 +14,11 @@ import java.util.regex.Pattern;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import dev.frogmc.frogloader.api.mod.ModProperties;
|
import dev.frogmc.frogloader.api.mod.ModProperties;
|
||||||
|
import dev.frogmc.frogloader.impl.Constants;
|
||||||
import dev.frogmc.frogloader.impl.launch.transformer.AccessWidener;
|
import dev.frogmc.frogloader.impl.launch.transformer.AccessWidener;
|
||||||
import dev.frogmc.frogloader.impl.mod.BuiltinExtensions;
|
|
||||||
|
|
||||||
public class AWProcessor {
|
public class AWProcessor {
|
||||||
|
|
||||||
private static final String AW_EXTENSION_NAME = BuiltinExtensions.ACCESSWIDENER;
|
|
||||||
private static final Predicate<String> HEADER = Pattern.compile("accessWidener\\s+v[12]\\s+.*").asMatchPredicate();
|
private static final Predicate<String> HEADER = Pattern.compile("accessWidener\\s+v[12]\\s+.*").asMatchPredicate();
|
||||||
private static final String SEPARATOR = "[\\t ]+";
|
private static final String SEPARATOR = "[\\t ]+";
|
||||||
|
|
||||||
|
@ -30,7 +29,7 @@ public class AWProcessor {
|
||||||
Map<String, Map<String, AccessWidener.Entry>> mutations = new ConcurrentHashMap<>();
|
Map<String, Map<String, AccessWidener.Entry>> mutations = new ConcurrentHashMap<>();
|
||||||
Set<String> classNames = new ConcurrentSkipListSet<>();
|
Set<String> classNames = new ConcurrentSkipListSet<>();
|
||||||
|
|
||||||
mods.stream().map(ModProperties::extensions).map(e -> e.get(AW_EXTENSION_NAME)).flatMap(o -> {
|
mods.stream().map(ModProperties::extensions).map(e -> e.get(Constants.MOD_ID, Constants.EXTENSION_ACCESSWIDENER)).flatMap(o -> {
|
||||||
if (o instanceof Collection<?> c) {
|
if (o instanceof Collection<?> c) {
|
||||||
return c.stream().map(Object::toString);
|
return c.stream().map(Object::toString);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
package dev.frogmc.frogloader.impl.mod;
|
|
||||||
|
|
||||||
import lombok.experimental.UtilityClass;
|
|
||||||
|
|
||||||
@UtilityClass
|
|
||||||
public class BuiltinExtensions {
|
|
||||||
public final String MIXIN_CONFIG = "mixin";
|
|
||||||
public final String INCLUDED_JARS = "included_jars";
|
|
||||||
public final String PRE_LAUNCH = "prelaunch";
|
|
||||||
public final String ACCESSWIDENER = "accesswidener";
|
|
||||||
public final String LOADING_TYPE = "loading_type";
|
|
||||||
public final String MOD_PROVIDER = "modprovider";
|
|
||||||
}
|
|
|
@ -25,7 +25,7 @@ public class JavaModProperties {
|
||||||
"",
|
"",
|
||||||
Map.of(System.getProperty("java.vm.vendor"), Collections.singleton("Vendor")),
|
Map.of(System.getProperty("java.vm.vendor"), Collections.singleton("Vendor")),
|
||||||
new ModDependencies(Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), Collections.emptySet()),
|
new ModDependencies(Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), Collections.emptySet()),
|
||||||
ModExtensions.of(Collections.emptyMap()), Collections.emptySet());
|
new ModExtensionsImpl(Collections.emptyMap()), Collections.emptySet());
|
||||||
}
|
}
|
||||||
return INSTANCE;
|
return INSTANCE;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import com.google.common.collect.MultimapBuilder;
|
||||||
import dev.frogmc.frogloader.api.mod.ModDependencies;
|
import dev.frogmc.frogloader.api.mod.ModDependencies;
|
||||||
import dev.frogmc.frogloader.api.mod.ModProperties;
|
import dev.frogmc.frogloader.api.mod.ModProperties;
|
||||||
import dev.frogmc.frogloader.api.mod.SemVer;
|
import dev.frogmc.frogloader.api.mod.SemVer;
|
||||||
|
import dev.frogmc.frogloader.impl.Constants;
|
||||||
import dev.frogmc.frogloader.impl.SemVerParseException;
|
import dev.frogmc.frogloader.impl.SemVerParseException;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
@ -121,7 +122,7 @@ public class ModDependencyResolver {
|
||||||
DependencyEntry value = entry.getValue();
|
DependencyEntry value = entry.getValue();
|
||||||
if (!(presentMods.containsKey(s) && value.range.versionMatches(presentOrProvided.get(s)))) { // The dependency isn't fulfilled by any present mods
|
if (!(presentMods.containsKey(s) && value.range.versionMatches(presentOrProvided.get(s)))) { // The dependency isn't fulfilled by any present mods
|
||||||
if (!(provides.containsKey(s) && provides.get(s).stream().map(ProvidedMod::version).allMatch(value.range::versionMatches))) { // The dependency also isn't fulfilled by any provided mods
|
if (!(provides.containsKey(s) && provides.get(s).stream().map(ProvidedMod::version).allMatch(value.range::versionMatches))) { // The dependency also isn't fulfilled by any provided mods
|
||||||
if (value.origin.extensions().getOrDefault(BuiltinExtensions.LOADING_TYPE, "required").equals("required")) {
|
if (value.origin.extensions().getOrDefault(Constants.MOD_ID, Constants.EXTENSION_LOADING_TYPE, "required").equals("required")) {
|
||||||
unfulfilled.add(new UnfulfilledDependencyException.Entry(value.origin, s, value.range, presentOrProvided.get(s), value.link(), value.name()));
|
unfulfilled.add(new UnfulfilledDependencyException.Entry(value.origin, s, value.range, presentOrProvided.get(s), value.link(), value.name()));
|
||||||
} else {
|
} else {
|
||||||
LOGGER.debug("Skipping optional mod: {} ({})", value.origin().id(), value.origin().name());
|
LOGGER.debug("Skipping optional mod: {} ({})", value.origin().id(), value.origin().name());
|
||||||
|
|
|
@ -0,0 +1,129 @@
|
||||||
|
package dev.frogmc.frogloader.impl.mod;
|
||||||
|
|
||||||
|
import java.lang.invoke.MethodHandle;
|
||||||
|
import java.lang.invoke.MethodHandleProxies;
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.lang.invoke.MethodType;
|
||||||
|
import java.lang.invoke.WrongMethodTypeException;
|
||||||
|
import java.lang.reflect.AccessibleObject;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Member;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import dev.frogmc.frogloader.api.exception.ModExtensionResolutionException;
|
||||||
|
import dev.frogmc.frogloader.api.mod.ModExtensions;
|
||||||
|
|
||||||
|
public class ModExtensionsImpl implements ModExtensions {
|
||||||
|
|
||||||
|
private final Map<Key, Object> map;
|
||||||
|
|
||||||
|
public ModExtensionsImpl(Map<Key, Object> map) {
|
||||||
|
this.map = map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> T get(String modId, String key) {
|
||||||
|
return (T) map.get(new Key(modId, key));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> T getOrDefault(String modId, String key, T defaultValue) {
|
||||||
|
return (T) map.getOrDefault(new Key(modId, key), defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> void runIfPresent(String modId, String key, Consumer<T> action) {
|
||||||
|
Object value = get(modId, key);
|
||||||
|
|
||||||
|
if (value == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (value instanceof Collection)
|
||||||
|
((Collection<T>) value).forEach(action);
|
||||||
|
else
|
||||||
|
action.accept((T) value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> void runIfPresent(String modId, String key, Class<T> type, Consumer<T> action)
|
||||||
|
throws ModExtensionResolutionException {
|
||||||
|
String s = get(modId, key);
|
||||||
|
|
||||||
|
if (s == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
T object;
|
||||||
|
if (s.contains("::")) {
|
||||||
|
String[] parts = s.split("::", 2);
|
||||||
|
try {
|
||||||
|
Class<?> extension = Class.forName(parts[0]);
|
||||||
|
object = (T) handleReference(extension, type, parts[1]);
|
||||||
|
} catch (ModExtensionResolutionException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw new ModExtensionResolutionException(e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
Class<?> extension = Class.forName(s);
|
||||||
|
|
||||||
|
if (!type.isAssignableFrom(extension))
|
||||||
|
throw new ModExtensionResolutionException(extension + " does not inherit from " + type);
|
||||||
|
|
||||||
|
object = (T) MethodHandles.lookup().findConstructor(extension, MethodType.methodType(void.class))
|
||||||
|
.invoke();
|
||||||
|
} catch (ModExtensionResolutionException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw new ModExtensionResolutionException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (object != null) {
|
||||||
|
action.accept(object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object handleReference(Class<?> owner, Class<?> type, String name)
|
||||||
|
throws ModExtensionResolutionException, IllegalAccessException {
|
||||||
|
try {
|
||||||
|
Field found = owner.getDeclaredField(name);
|
||||||
|
found.setAccessible(true);
|
||||||
|
return found.get(null);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Method found = null;
|
||||||
|
for (Method method : owner.getDeclaredMethods()) {
|
||||||
|
if (method.getName().equals(name)) {
|
||||||
|
if (found != null) {
|
||||||
|
throw new ModExtensionResolutionException("Ambiguous method reference: " + name + " in " + owner.getName());
|
||||||
|
}
|
||||||
|
found = method;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found != null) {
|
||||||
|
found.setAccessible(true);
|
||||||
|
|
||||||
|
MethodHandle method = MethodHandles.lookup().unreflect(found);
|
||||||
|
return MethodHandleProxies.asInterfaceInstance(type, method);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException("Could not find either a static field or a method named '" + name + "' in class " + owner.getName() + "!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Key(String modId, String key) {
|
||||||
|
}
|
||||||
|
}
|
|
@ -153,16 +153,23 @@ public class ModPropertiesReader {
|
||||||
var suggests = parseDependencies.apply("suggests");
|
var suggests = parseDependencies.apply("suggests");
|
||||||
var provides = parseDependencies.apply("provides");
|
var provides = parseDependencies.apply("provides");
|
||||||
|
|
||||||
|
UnmodifiableConfig extensionsConfig = config.get("frog.extensions");
|
||||||
|
Map<ModExtensionsImpl.Key, Object> extensions = new HashMap<>();
|
||||||
|
if (extensionsConfig != null) {
|
||||||
|
for (var modEntry : extensionsConfig.entrySet()) {
|
||||||
|
UnmodifiableConfig modExtensionsConig = modEntry.getValue();
|
||||||
|
|
||||||
|
for (var entry : modExtensionsConig.entrySet())
|
||||||
|
extensions.put(new ModExtensionsImpl.Key(modEntry.getKey(), entry.getKey()), entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!badProperties.isEmpty())
|
if (!badProperties.isEmpty())
|
||||||
throw new InvalidModPropertiesException(id, sources, badProperties);
|
throw new InvalidModPropertiesException(id, sources, badProperties);
|
||||||
|
|
||||||
UnmodifiableConfig extensionsConfig = config.get("frog.extensions");
|
|
||||||
Map<String, Object> extensions = new HashMap<>();
|
|
||||||
if (extensionsConfig != null)
|
|
||||||
extensionsConfig.entrySet().forEach(entry -> extensions.put(entry.getKey(), entry.getValue()));
|
|
||||||
|
|
||||||
return new ModPropertiesImpl(id, name, icon, semVer, license, Collections.unmodifiableMap(credits),
|
return new ModPropertiesImpl(id, name, icon, semVer, license, Collections.unmodifiableMap(credits),
|
||||||
new ModDependencies(depends, breaks, suggests, provides), ModExtensions.of(extensions), sources);
|
new ModDependencies(depends, breaks, suggests, provides), new ModExtensionsImpl(extensions), sources);
|
||||||
});
|
});
|
||||||
|
|
||||||
private static final Map<String, Parser> versions = Arrays.stream(values()).collect(Collectors.toMap(v -> v.version, v -> v.parser));
|
private static final Map<String, Parser> versions = Arrays.stream(values()).collect(Collectors.toMap(v -> v.version, v -> v.parser));
|
||||||
|
|
|
@ -6,6 +6,7 @@ import java.util.stream.Collectors;
|
||||||
|
|
||||||
import com.electronwill.nightconfig.core.UnmodifiableConfig;
|
import com.electronwill.nightconfig.core.UnmodifiableConfig;
|
||||||
import dev.frogmc.frogloader.api.mod.ModProperties;
|
import dev.frogmc.frogloader.api.mod.ModProperties;
|
||||||
|
import dev.frogmc.frogloader.impl.Constants;
|
||||||
|
|
||||||
public class ModUtil {
|
public class ModUtil {
|
||||||
|
|
||||||
|
@ -66,7 +67,7 @@ public class ModUtil {
|
||||||
private static Map<ModProperties, Collection<String>> getParentMods(Collection<ModProperties> mods) {
|
private static Map<ModProperties, Collection<String>> getParentMods(Collection<ModProperties> mods) {
|
||||||
Map<ModProperties, Collection<String>> children = new HashMap<>();
|
Map<ModProperties, Collection<String>> children = new HashMap<>();
|
||||||
for (ModProperties mod : mods) {
|
for (ModProperties mod : mods) {
|
||||||
List<UnmodifiableConfig> entries = mod.extensions().get(BuiltinExtensions.INCLUDED_JARS);
|
List<UnmodifiableConfig> entries = mod.extensions().get(Constants.MOD_ID, Constants.EXTENSION_INCLUDED_JARS);
|
||||||
if (entries != null) {
|
if (entries != null) {
|
||||||
for (var jar : entries) {
|
for (var jar : entries) {
|
||||||
String id = jar.get("id");
|
String id = jar.get("id");
|
||||||
|
|
|
@ -21,6 +21,7 @@ import dev.frogmc.frogloader.api.mod.ModExtensions;
|
||||||
import dev.frogmc.frogloader.api.mod.ModProperties;
|
import dev.frogmc.frogloader.api.mod.ModProperties;
|
||||||
import dev.frogmc.frogloader.api.plugin.GamePlugin;
|
import dev.frogmc.frogloader.api.plugin.GamePlugin;
|
||||||
import dev.frogmc.frogloader.impl.FrogLoaderImpl;
|
import dev.frogmc.frogloader.impl.FrogLoaderImpl;
|
||||||
|
import dev.frogmc.frogloader.impl.mod.ModExtensionsImpl;
|
||||||
import dev.frogmc.frogloader.impl.mod.ModPropertiesImpl;
|
import dev.frogmc.frogloader.impl.mod.ModPropertiesImpl;
|
||||||
import dev.frogmc.frogloader.impl.util.SystemProperties;
|
import dev.frogmc.frogloader.impl.util.SystemProperties;
|
||||||
import dev.frogmc.thyroxine.HttpHelper;
|
import dev.frogmc.thyroxine.HttpHelper;
|
||||||
|
@ -204,6 +205,6 @@ public class MinecraftGamePlugin implements GamePlugin {
|
||||||
MinecraftSemVerImpl.get(version), "MC-EULA",
|
MinecraftSemVerImpl.get(version), "MC-EULA",
|
||||||
Map.of("Mojang AB", Collections.singleton("Author")),
|
Map.of("Mojang AB", Collections.singleton("Author")),
|
||||||
new ModDependencies(Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), Collections.emptySet()),
|
new ModDependencies(Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), Collections.emptySet()),
|
||||||
ModExtensions.of(Collections.emptyMap()), Collections.emptySet());
|
new ModExtensionsImpl(Collections.emptyMap()), Collections.emptySet());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,10 +10,11 @@ import java.util.function.Consumer;
|
||||||
|
|
||||||
import com.electronwill.nightconfig.core.UnmodifiableConfig;
|
import com.electronwill.nightconfig.core.UnmodifiableConfig;
|
||||||
import dev.frogmc.frogloader.api.FrogLoader;
|
import dev.frogmc.frogloader.api.FrogLoader;
|
||||||
|
import dev.frogmc.frogloader.api.exception.ModExtensionResolutionException;
|
||||||
import dev.frogmc.frogloader.api.extensions.PreLaunchExtension;
|
import dev.frogmc.frogloader.api.extensions.PreLaunchExtension;
|
||||||
import dev.frogmc.frogloader.api.mod.ModProperties;
|
import dev.frogmc.frogloader.api.mod.ModProperties;
|
||||||
import dev.frogmc.frogloader.api.plugin.ModProvider;
|
import dev.frogmc.frogloader.api.plugin.ModProvider;
|
||||||
import dev.frogmc.frogloader.impl.mod.BuiltinExtensions;
|
import dev.frogmc.frogloader.impl.Constants;
|
||||||
import dev.frogmc.frogloader.impl.mod.ModPropertiesReader;
|
import dev.frogmc.frogloader.impl.mod.ModPropertiesReader;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -115,7 +116,7 @@ public class FrogModProvider implements ModProvider {
|
||||||
if (opt.isPresent()) {
|
if (opt.isPresent()) {
|
||||||
ModProperties p = opt.get();
|
ModProperties p = opt.get();
|
||||||
modPaths.put(mod, p);
|
modPaths.put(mod, p);
|
||||||
List<UnmodifiableConfig> entries = p.extensions().getOrDefault(BuiltinExtensions.INCLUDED_JARS, Collections.emptyList());
|
List<UnmodifiableConfig> entries = p.extensions().getOrDefault(Constants.MOD_ID, Constants.EXTENSION_INCLUDED_JARS, Collections.emptyList());
|
||||||
if (entries.isEmpty()) {
|
if (entries.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -135,7 +136,8 @@ public class FrogModProvider implements ModProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void preLaunch(Collection<ModProperties> mods) {
|
public void preLaunch(Collection<ModProperties> mods) throws ModExtensionResolutionException {
|
||||||
mods.forEach(mod -> mod.extensions().runIfPresent(PreLaunchExtension.ID, PreLaunchExtension.class, PreLaunchExtension::onPreLaunch));
|
for (ModProperties mod : mods)
|
||||||
|
mod.extensions().runIfPresent(Constants.MOD_ID, Constants.EXTENSION_PRE_LAUNCH, PreLaunchExtension.class, PreLaunchExtension::onPreLaunch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue