Require extensions to be namespaced

This commit is contained in:
TheKodeToad 2024-09-01 14:09:22 +01:00
parent ac3e0d86df
commit 12f9d823b4
18 changed files with 241 additions and 156 deletions

View file

@ -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"

View file

@ -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"

View file

@ -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);
}
}

View file

@ -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.

View file

@ -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 key The extension name to query * @param modId The mod ID this extension is stored under
* @param defaultValue a default value * @param key The extension name to query
* @param <T> The type of the value of this extension * @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 * @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 <T> The type of the value of this extension * @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"}) <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 key The name of the extension * @param modId The mod ID this extension is stored under
* @param type The class type of the extension (The class the extension class is extending/implementing) * @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 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() + "!");
}
} }

View file

@ -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 {
} }
/** /**

View 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";
}

View file

@ -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);

View file

@ -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) {
providers.add(provider); p.extensions().runIfPresent(Constants.MOD_ID, Constants.EXTENSION_MOD_PROVIDER, ModProvider.class, provider -> {
size[0]++; providers.add(provider);
})); 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);

View file

@ -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);
} }

View file

@ -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";
}

View file

@ -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;
} }

View file

@ -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());

View file

@ -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) {
}
}

View file

@ -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));

View file

@ -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");

View file

@ -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());
} }
} }

View file

@ -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);
} }
} }