Require extensions to be namespaced #14
|
@ -10,7 +10,7 @@ credits = [
|
|||
{ name = "You", roles = ["author", "other_role"] }
|
||||
]
|
||||
|
||||
[frog.extensions]
|
||||
[frog.extensions.frogloader]
|
||||
prelaunch = "dev.frogmc.frogloader.example.ExamplePreLaunchExtension"
|
||||
mixin = "example_mod.mixins.json"
|
||||
accesswidener = "example_mod.accesswidener"
|
||||
|
|
|
@ -10,6 +10,6 @@ credits = [
|
|||
{ name = "You", roles = ["author", "other_role"] }
|
||||
]
|
||||
|
||||
[frog.extensions]
|
||||
[frog.extensions.frogloader]
|
||||
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;
|
||||
|
||||
import dev.frogmc.frogloader.api.mod.ModExtensions;
|
||||
import dev.frogmc.frogloader.impl.mod.BuiltinExtensions;
|
||||
import dev.frogmc.frogloader.impl.Constants;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
String ID = BuiltinExtensions.PRE_LAUNCH;
|
||||
String ID = Constants.EXTENSION_PRE_LAUNCH;
|
||||
|
||||
/**
|
||||
* The initializer. This method will be invoked when this extension is run.
|
||||
|
|
|
@ -1,150 +1,74 @@
|
|||
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 org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import dev.frogmc.frogloader.api.exception.ModExtensionResolutionException;
|
||||
|
||||
/**
|
||||
* 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>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>
|
||||
* <p>
|
||||
* An extension is a simple key-value mapping of a string name to any value.
|
||||
* </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
|
||||
*/
|
||||
public final class 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);
|
||||
}
|
||||
public interface ModExtensions {
|
||||
|
||||
/**
|
||||
* 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 <T> The type of the value of this extension
|
||||
*
|
||||
* @return The value of the extension, or null
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T get(String key) {
|
||||
return (T) extensions.get(key);
|
||||
}
|
||||
<T> T get(String modId, String key);
|
||||
|
||||
/**
|
||||
* Get the value of the provided extension name.
|
||||
*
|
||||
* @param key The extension name to query
|
||||
* @param defaultValue a default value
|
||||
* @param <T> The type of the value of this extension
|
||||
* @param modId The mod ID this extension is stored under
|
||||
* @param key The extension name to query
|
||||
* @param defaultValue A default value
|
||||
* @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
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getOrDefault(String key, T defaultValue) {
|
||||
return (T) extensions.getOrDefault(key, defaultValue);
|
||||
}
|
||||
<T> T getOrDefault(String modId, String key, T defaultValue);
|
||||
|
||||
/**
|
||||
* Run the given action on this 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 modId The mod ID this extension is stored under
|
||||
* @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
|
||||
*/
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
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);
|
||||
}
|
||||
}
|
||||
<T> void runIfPresent(String modId, String key, Consumer<T> action);
|
||||
|
||||
/**
|
||||
* Run the given action on this extension if it is present.
|
||||
* <p>This method simplifies handling references to classes or methods.</p>
|
||||
* 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.
|
||||
* <p>
|
||||
* This method simplifies handling references to classes or methods.
|
||||
* </p>
|
||||
* 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 key The name of the extension
|
||||
* @param type The class type of the extension (The class the extension class is extending/implementing)
|
||||
* @param modId The mod ID this extension is stored under
|
||||
* @param key The name of the extension
|
||||
* @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 <T> The type of the class
|
||||
* @param <T> The type of the class
|
||||
*
|
||||
* @throws ModExtensionResolutionException If the reference could not be resolved
|
||||
*/
|
||||
@SuppressWarnings({"unchecked"})
|
||||
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() + "!");
|
||||
}
|
||||
<T> void runIfPresent(String modId, String key, Class<T> type, Consumer<T> action) throws ModExtensionResolutionException;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
* @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();
|
||||
LOGGER.info(ModUtil.getModList(getMods()));
|
||||
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();
|
||||
} catch (Throwable t) {
|
||||
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.impl.gui.LoaderGui;
|
||||
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.util.CrashReportGenerator;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -47,10 +46,12 @@ public class PluginLoader {
|
|||
properties.put(plugin.id(), loadedMods);
|
||||
|
||||
LOGGER.debug("Loaded {} mod(s) from provider: {}", loadedMods.size(), plugin.id());
|
||||
loadedMods.forEach(p -> p.extensions().runIfPresent(BuiltinExtensions.MOD_PROVIDER, ModProvider.class, provider -> {
|
||||
providers.add(provider);
|
||||
size[0]++;
|
||||
}));
|
||||
for (ModProperties p : loadedMods) {
|
||||
p.extensions().runIfPresent(Constants.MOD_ID, Constants.EXTENSION_MOD_PROVIDER, ModProvider.class, provider -> {
|
||||
providers.add(provider);
|
||||
size[0]++;
|
||||
owlsys marked this conversation as resolved
|
||||
});
|
||||
}
|
||||
modProviders.add(plugin);
|
||||
} catch (Throwable e) {
|
||||
LOGGER.error("Error during plugin initialisation: ", e);
|
||||
|
@ -78,7 +79,7 @@ public class PluginLoader {
|
|||
private static void initializeModMixins(Collection<ModProperties> loadedMods) {
|
||||
Map<String, ModProperties> configs = new HashMap<>();
|
||||
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) {
|
||||
configs.put(name, props);
|
||||
Mixins.addConfiguration(name);
|
||||
|
|
|
@ -14,12 +14,11 @@ import java.util.regex.Pattern;
|
|||
import java.util.stream.Stream;
|
||||
|
||||
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.mod.BuiltinExtensions;
|
||||
|
||||
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 String SEPARATOR = "[\\t ]+";
|
||||
|
||||
|
@ -30,7 +29,7 @@ public class AWProcessor {
|
|||
Map<String, Map<String, AccessWidener.Entry>> mutations = new ConcurrentHashMap<>();
|
||||
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) {
|
||||
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")),
|
||||
new ModDependencies(Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), Collections.emptySet()),
|
||||
ModExtensions.of(Collections.emptyMap()), Collections.emptySet());
|
||||
new ModExtensionsImpl(Collections.emptyMap()), Collections.emptySet());
|
||||
}
|
||||
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.ModProperties;
|
||||
import dev.frogmc.frogloader.api.mod.SemVer;
|
||||
import dev.frogmc.frogloader.impl.Constants;
|
||||
import dev.frogmc.frogloader.impl.SemVerParseException;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
@ -121,7 +122,7 @@ public class ModDependencyResolver {
|
|||
DependencyEntry value = entry.getValue();
|
||||
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 (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()));
|
||||
} else {
|
||||
LOGGER.debug("Skipping optional mod: {} ({})", value.origin().id(), value.origin().name());
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
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);
|
||||
|
||||
if (Modifier.isStatic(found.getModifiers())) {
|
||||
found.setAccessible(true);
|
||||
return found.get(null);
|
||||
}
|
||||
} catch (NoSuchFieldException ignored) {
|
||||
}
|
||||
|
||||
Method found = null;
|
||||
for (Method method : owner.getDeclaredMethods()) {
|
||||
if (!Modifier.isStatic(method.getModifiers()))
|
||||
continue;
|
||||
|
||||
if (!method.getName().equals(name))
|
||||
continue;
|
||||
|
||||
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 ModExtensionResolutionException("Could not find a static field or 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 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())
|
||||
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),
|
||||
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));
|
||||
|
|
|
@ -6,6 +6,7 @@ import java.util.stream.Collectors;
|
|||
|
||||
import com.electronwill.nightconfig.core.UnmodifiableConfig;
|
||||
import dev.frogmc.frogloader.api.mod.ModProperties;
|
||||
import dev.frogmc.frogloader.impl.Constants;
|
||||
|
||||
public class ModUtil {
|
||||
|
||||
|
@ -66,7 +67,7 @@ public class ModUtil {
|
|||
private static Map<ModProperties, Collection<String>> getParentMods(Collection<ModProperties> mods) {
|
||||
Map<ModProperties, Collection<String>> children = new HashMap<>();
|
||||
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) {
|
||||
for (var jar : entries) {
|
||||
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.plugin.GamePlugin;
|
||||
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.util.SystemProperties;
|
||||
import dev.frogmc.thyroxine.HttpHelper;
|
||||
|
@ -203,6 +204,6 @@ public class MinecraftGamePlugin implements GamePlugin {
|
|||
MinecraftSemVerImpl.get(version), "MC-EULA",
|
||||
Map.of("Mojang AB", Collections.singleton("Author")),
|
||||
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 dev.frogmc.frogloader.api.FrogLoader;
|
||||
import dev.frogmc.frogloader.api.exception.ModExtensionResolutionException;
|
||||
import dev.frogmc.frogloader.api.extensions.PreLaunchExtension;
|
||||
import dev.frogmc.frogloader.api.mod.ModProperties;
|
||||
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 org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -115,7 +116,7 @@ public class FrogModProvider implements ModProvider {
|
|||
if (opt.isPresent()) {
|
||||
ModProperties p = opt.get();
|
||||
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()) {
|
||||
return;
|
||||
}
|
||||
|
@ -135,7 +136,8 @@ public class FrogModProvider implements ModProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void preLaunch(Collection<ModProperties> mods) {
|
||||
mods.forEach(mod -> mod.extensions().runIfPresent(PreLaunchExtension.ID, PreLaunchExtension.class, PreLaunchExtension::onPreLaunch));
|
||||
public void preLaunch(Collection<ModProperties> mods) throws ModExtensionResolutionException {
|
||||
owlsys marked this conversation as resolved
owlsys
commented
the exception should probably be handled inside the loop to just skip the faulty mod instead of breaking and propagating the error upwards the exception should probably be handled inside the loop to just skip the faulty mod instead of breaking and propagating the error upwards
kode
commented
If a mod wants to handle an error without crashing, shouldn't it have a try catch block in its prelaunch handler? If a mod wants to handle an error without crashing, shouldn't it have a try catch block in its prelaunch handler?
owlsys
commented
it may still be okay like this considering mod devs should definitely know when their stuff is malformed or throwing errors it may still be okay like this considering mod devs should definitely know when their stuff is malformed or throwing errors
|
||||
for (ModProperties mod : mods)
|
||||
mod.extensions().runIfPresent(Constants.MOD_ID, Constants.EXTENSION_PRE_LAUNCH, PreLaunchExtension.class, PreLaunchExtension::onPreLaunch);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue
this single-element array could be replaced with a normal int since it doesn't need to be used anymore (since the lambda was replaced with a loop)
I think it is still needed as it is a callback to runIfPresent
I tried to look for other usages already but it's fairly possible I missed something in the diff view.