Upload magma

This commit is contained in:
Diamond Creeper 2024-03-04 21:16:02 +13:00
commit dfa9ee0b24
5008 changed files with 653442 additions and 0 deletions

8
fmlloader/src/index.html Normal file
View file

@ -0,0 +1,8 @@
<html>
<head><title>Index of /mirror/src/Magma-1-20-x/fmlloader/src/</title></head>
<body>
<h1>Index of /mirror/src/Magma-1-20-x/fmlloader/src/</h1><hr><pre><a href="../">../</a>
<a href="main/">main/</a> 07-Oct-2023 14:12 -
</pre><hr><script defer src="https://static.cloudflareinsights.com/beacon.min.js/v84a3a4012de94ce1a686ba8c167c359c1696973893317" integrity="sha512-euoFGowhlaLqXsPWQ48qSkBSCFs3DPRyiwVu3FjR96cMPx+Fr+gpWRhIafcHwqwCqWS42RZhIudOvEI+Ckf6MA==" nonce="91d16a6d665394db2f7cfdd321dfa634" data-cf-beacon='{"rayId":"85f0152aed2250c2","version":"2024.2.4","r":1,"token":"583109dda43e47a593fd006526a81120","b":1}' crossorigin="anonymous"></script>
</body>
</html>

View file

@ -0,0 +1,9 @@
<html>
<head><title>Index of /mirror/src/Magma-1-20-x/fmlloader/src/main/</title></head>
<body>
<h1>Index of /mirror/src/Magma-1-20-x/fmlloader/src/main/</h1><hr><pre><a href="../">../</a>
<a href="java/">java/</a> 07-Oct-2023 14:12 -
<a href="resources/">resources/</a> 07-Oct-2023 14:12 -
</pre><hr><script defer src="https://static.cloudflareinsights.com/beacon.min.js/v84a3a4012de94ce1a686ba8c167c359c1696973893317" integrity="sha512-euoFGowhlaLqXsPWQ48qSkBSCFs3DPRyiwVu3FjR96cMPx+Fr+gpWRhIafcHwqwCqWS42RZhIudOvEI+Ckf6MA==" nonce="7ec831f476513194ed381c547260c6f6" data-cf-beacon='{"rayId":"85f01566efc850c2","version":"2024.2.4","r":1,"token":"583109dda43e47a593fd006526a81120","b":1}' crossorigin="anonymous"></script>
</body>
</html>

View file

@ -0,0 +1,9 @@
<html>
<head><title>Index of /mirror/src/Magma-1-20-x/fmlloader/src/main/java/</title></head>
<body>
<h1>Index of /mirror/src/Magma-1-20-x/fmlloader/src/main/java/</h1><hr><pre><a href="../">../</a>
<a href="net/">net/</a> 07-Oct-2023 14:12 -
<a href="org/">org/</a> 07-Oct-2023 14:12 -
</pre><hr><script defer src="https://static.cloudflareinsights.com/beacon.min.js/v84a3a4012de94ce1a686ba8c167c359c1696973893317" integrity="sha512-euoFGowhlaLqXsPWQ48qSkBSCFs3DPRyiwVu3FjR96cMPx+Fr+gpWRhIafcHwqwCqWS42RZhIudOvEI+Ckf6MA==" nonce="9186e5d6825585a0362134686becda89" data-cf-beacon='{"rayId":"85f0159f8a8550c2","version":"2024.2.4","r":1,"token":"583109dda43e47a593fd006526a81120","b":1}' crossorigin="anonymous"></script>
</body>
</html>

View file

@ -0,0 +1,8 @@
<html>
<head><title>Index of /mirror/src/Magma-1-20-x/fmlloader/src/main/java/net/</title></head>
<body>
<h1>Index of /mirror/src/Magma-1-20-x/fmlloader/src/main/java/net/</h1><hr><pre><a href="../">../</a>
<a href="minecraftforge/">minecraftforge/</a> 07-Oct-2023 14:12 -
</pre><hr><script defer src="https://static.cloudflareinsights.com/beacon.min.js/v84a3a4012de94ce1a686ba8c167c359c1696973893317" integrity="sha512-euoFGowhlaLqXsPWQ48qSkBSCFs3DPRyiwVu3FjR96cMPx+Fr+gpWRhIafcHwqwCqWS42RZhIudOvEI+Ckf6MA==" nonce="f3d63fad07738d6b406698182a53728a" data-cf-beacon='{"rayId":"85f01614198550c2","version":"2024.2.4","r":1,"token":"583109dda43e47a593fd006526a81120","b":1}' crossorigin="anonymous"></script>
</body>
</html>

View file

@ -0,0 +1,120 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.common.asm;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.EnumSet;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.signature.SignatureReader;
import org.objectweb.asm.signature.SignatureVisitor;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
/**
* Implements getType() in CapabilityToken subclasses.
*
* Using the class's signature to determine the generic type of TypeToken, and then implements getType() using that value.
* <pre>
* Example:
*
* <code>new CapabilityToken&lt;String>(){}</code>
* Has the signature <code>"CapabilityToken&lt;Ljava/lang/String;>"</code>
*
* Implements the method:
* <code>public String getType() {
* return "java/lang/String";
* }</code>
* </pre>
*/
public class CapabilityTokenSubclass implements ILaunchPluginService {
private final String FUNC_NAME = "getType";
private final String FUNC_DESC = "()Ljava/lang/String;";
private final String CAP_INJECT = "net/minecraftforge/common/capabilities/CapabilityToken"; //Don't directly reference this to prevent class loading.
@Override
public String name() {
return "capability_token_subclass";
}
private static final EnumSet<Phase> YAY = EnumSet.of(Phase.AFTER);
private static final EnumSet<Phase> NAY = EnumSet.noneOf(Phase.class);
@Override
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty)
{
return isEmpty ? NAY : YAY;
}
@Override
public int processClassWithFlags(final Phase phase, final ClassNode classNode, final Type classType, final String reason)
{
if (CAP_INJECT.equals(classNode.name))
{
for (MethodNode mtd : classNode.methods)
{
if (FUNC_NAME.equals(mtd.name) && FUNC_DESC.equals(mtd.desc))
{
mtd.access &= ~Opcodes.ACC_FINAL; // We have it final in code so people don't override it, cuz that'd be stupid, and make our transformer more complicated.
}
}
return ComputeFlags.SIMPLE_REWRITE;
}
else if (CAP_INJECT.equals(classNode.superName))
{
Holder cls = new Holder();
SignatureReader reader = new SignatureReader(classNode.signature); // Having a node version of this would probably be useful.
reader.accept(new SignatureVisitor(Opcodes.ASM9)
{
Deque<String> stack = new ArrayDeque<>();
@Override
public void visitClassType(final String name)
{
stack.push(name);
}
@Override
public void visitInnerClassType(final String name)
{
stack.push(stack.pop() + '$' + name);
}
@Override
public void visitEnd()
{
var val = stack.pop();
if (!stack.isEmpty() && CAP_INJECT.equals(stack.peek()))
cls.value = val;
}
});
if (cls.value == null)
throw new IllegalStateException("Could not find signature for CapabilityToken on " + classNode.name + " from " + classNode.signature);
var mtd = classNode.visitMethod(Opcodes.ACC_PUBLIC, FUNC_NAME, FUNC_DESC, null, new String[0]);
mtd.visitLdcInsn(cls.value);
mtd.visitInsn(Opcodes.ARETURN);
mtd.visitEnd();
return ComputeFlags.COMPUTE_MAXS;
}
else
{
return ComputeFlags.NO_REWRITE;
}
}
private static class Holder {
String value;
}
}

View file

@ -0,0 +1,110 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.common.asm;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
/**
* Removes the final modifier from fields with the @ObjectHolder annotation, prevents the JITer from in lining them so our runtime replacements can work.
* Will also de-finalize all fields in on class level annotations.
*/
public class ObjectHolderDefinalize implements ILaunchPluginService {
// Hardcoded map of vanilla classes that should have object holders for each field of the given registry type.
// IMPORTANT: Updates to this collection must be reflected in ObjectHolderRegistry. Duplicated cuz classloaders, yay!
// Classnames are validated in ObjectHolderRegistry.
private static final Map<String, VanillaObjectHolderData> VANILLA_OBJECT_HOLDERS = Stream.of(
new VanillaObjectHolderData("net.minecraft.world.level.block.Blocks", "block", "net.minecraft.world.level.block.Block"),
new VanillaObjectHolderData("net.minecraft.world.item.Items", "item", "net.minecraft.world.item.Item"),
new VanillaObjectHolderData("net.minecraft.world.item.enchantment.Enchantments", "enchantment", "net.minecraft.world.item.enchantment.Enchantment"),
new VanillaObjectHolderData("net.minecraft.world.effect.MobEffects", "mob_effect", "net.minecraft.world.effect.MobEffect"),
new VanillaObjectHolderData("net.minecraft.core.particles.ParticleTypes", "particle_type", "net.minecraft.core.particles.ParticleType"),
new VanillaObjectHolderData("net.minecraft.sounds.SoundEvents", "sound_event", "net.minecraft.sounds.SoundEvent")
).collect(Collectors.toMap(VanillaObjectHolderData::holderClass, Function.identity()));
private final String OBJECT_HOLDER = "Lnet/minecraftforge/registries/ObjectHolder;"; //Don't directly reference this to prevent class loading.
@Override
public String name() {
return "object_holder_definalize";
}
private static final EnumSet<Phase> YAY = EnumSet.of(Phase.AFTER);
private static final EnumSet<Phase> NAY = EnumSet.noneOf(Phase.class);
@Override
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty)
{
return isEmpty ? NAY : YAY;
}
private boolean hasHolder(List<AnnotationNode> lst)
{
return lst != null && lst.stream().anyMatch(n -> n.desc.equals(OBJECT_HOLDER));
}
private String getValue(List<AnnotationNode> lst)
{
AnnotationNode ann = lst.stream().filter(n -> n.desc.equals(OBJECT_HOLDER)).findFirst().get();
if (ann.values != null)
{
for (int x = 0; x < ann.values.size() - 1; x += 2) {
if (ann.values.get(x).equals("value")) {
return (String)ann.values.get(x + 1);
}
}
}
return null;
}
@Override
public int processClassWithFlags(final Phase phase, final ClassNode classNode, final Type classType, final String reason)
{
final AtomicBoolean changes = new AtomicBoolean();
//Must be public static finals, and non-array objects
final int flags = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL;
//Fix Annotated Fields before injecting from class level
classNode.fields.stream().filter(f -> ((f.access & flags) == flags) && f.desc.startsWith("L") && hasHolder(f.visibleAnnotations)).forEach(f ->
{
int prev = f.access;
f.access &= ~Opcodes.ACC_FINAL; //Strip final
f.access |= Opcodes.ACC_SYNTHETIC; //Add Synthetic so we can check in runtime. ? Good idea?
changes.compareAndSet(false, prev != f.access);
});
if (VANILLA_OBJECT_HOLDERS.containsKey(classType.getClassName())) //Class level, de-finalize all fields and add @ObjectHolder to them!
{
classNode.fields.stream().filter(f -> ((f.access & flags) == flags) && f.desc.startsWith("L")).forEach(f ->
{
int prev = f.access;
f.access &= ~Opcodes.ACC_FINAL;
f.access |= Opcodes.ACC_SYNTHETIC;
/*if (!hasHolder(f.visibleAnnotations)) //Add field level annotation, doesn't do anything until after we figure out how ASMDataTable is gatherered
{
if (value == null)
f.visitAnnotation(OBJECT_HOLDER, true);
else
f.visitAnnotation(OBJECT_HOLDER, true).visit("value", value + ":" + f.name.toLowerCase());
}*/
changes.compareAndSet(false, prev != f.access);
});
}
return changes.get() ? ComputeFlags.SIMPLE_REWRITE : ComputeFlags.NO_REWRITE;
}
private record VanillaObjectHolderData(String holderClass, String registryName, String registryType) {}
}

View file

@ -0,0 +1,240 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.common.asm;
import java.util.EnumSet;
import java.util.List;
import java.util.stream.Collectors;
import com.mojang.logging.LogUtils;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.InstructionAdapter;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.MethodNode;
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
import org.slf4j.Logger;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
/**
* Modifies specified enums to allow runtime extension by making the $VALUES field non-final and
* injecting constructor calls which are not valid in normal java code.
*/
public class RuntimeEnumExtender implements ILaunchPluginService {
private static final Logger LOGGER = LogUtils.getLogger();
private final Type STRING = Type.getType(String.class);
private final Type ENUM = Type.getType(Enum.class);
private final Type MARKER_IFACE = Type.getType("Lnet/minecraftforge/common/IExtensibleEnum;");
private final Type ARRAY_UTILS = Type.getType("Lorg/apache/commons/lang3/ArrayUtils;"); //Don't directly reference this to prevent class loading.
private final String ADD_DESC = Type.getMethodDescriptor(Type.getType(Object[].class), Type.getType(Object[].class), Type.getType(Object.class));
private final Type UNSAFE_HACKS = Type.getType("Lnet/minecraftforge/fml/unsafe/UnsafeHacks;"); //Again, not direct reference to prevent class loading.
private final String CLEAN_DESC = Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Class.class));
private final String NAME_DESC = Type.getMethodDescriptor(STRING);
private final String EQUALS_DESC = Type.getMethodDescriptor(Type.BOOLEAN_TYPE, STRING);
@Override
public String name() {
return "runtime_enum_extender";
}
private static final EnumSet<Phase> YAY = EnumSet.of(Phase.AFTER);
private static final EnumSet<Phase> NAY = EnumSet.noneOf(Phase.class);
@Override
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty)
{
return isEmpty ? NAY : YAY;
}
@Override
public int processClassWithFlags(final Phase phase, final ClassNode classNode, final Type classType, final String reason)
{
if ((classNode.access & Opcodes.ACC_ENUM) == 0)
return ComputeFlags.NO_REWRITE;
Type array = Type.getType("[" + classType.getDescriptor());
final int flags = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC;
FieldNode values = classNode.fields.stream().filter(f -> f.desc.contentEquals(array.getDescriptor()) && ((f.access & flags) == flags)).findFirst().orElse(null);
if (!classNode.interfaces.contains(MARKER_IFACE.getInternalName())) {
return ComputeFlags.NO_REWRITE;
}
//Static methods named "create" with first argument as a string
List<MethodNode> candidates = classNode.methods.stream()
.filter(m -> ((m.access & Opcodes.ACC_STATIC) != 0) && m.name.equals("create"))
.collect(Collectors.toList());
if (candidates.isEmpty()) {
throw new IllegalStateException("IExtensibleEnum has no candidate factory methods: " + classType.getClassName());
}
candidates.forEach(mtd ->
{
Type[] args = Type.getArgumentTypes(mtd.desc);
if (args.length == 0 || !args[0].equals(STRING)) {
if (LOGGER.isErrorEnabled(LogUtils.FATAL_MARKER))
{
StringBuilder sb = new StringBuilder();
sb.append("Enum has create method without String as first parameter:\n");
sb.append(" Enum: ").append(classType.getDescriptor()).append("\n");
sb.append(" Target: ").append(mtd.name).append(mtd.desc).append("\n");
LOGGER.error(LogUtils.FATAL_MARKER, sb.toString());
}
throw new IllegalStateException("Enum has create method without String as first parameter: " + mtd.name + mtd.desc);
}
Type ret = Type.getReturnType(mtd.desc);
if (!ret.equals(classType)) {
if (LOGGER.isErrorEnabled(LogUtils.FATAL_MARKER))
{
StringBuilder sb = new StringBuilder();
sb.append("Enum has create method with incorrect return type:\n");
sb.append(" Enum: ").append(classType.getDescriptor()).append("\n");
sb.append(" Target: ").append(mtd.name).append(mtd.desc).append("\n");
sb.append(" Found: ").append(ret.getClassName()).append(", Expected: ").append(classType.getClassName());
LOGGER.error(LogUtils.FATAL_MARKER, sb.toString());
}
throw new IllegalStateException("Enum has create method with incorrect return type: " + mtd.name + mtd.desc);
}
Type[] ctrArgs = new Type[args.length + 1];
ctrArgs[0] = STRING;
ctrArgs[1] = Type.INT_TYPE;
for (int x = 1; x < args.length; x++)
ctrArgs[1 + x] = args[x];
String desc = Type.getMethodDescriptor(Type.VOID_TYPE, ctrArgs);
MethodNode ctr = classNode.methods.stream().filter(m -> m.name.equals("<init>") && m.desc.equals(desc)).findFirst().orElse(null);
if (ctr == null)
{
if (LOGGER.isErrorEnabled(LogUtils.FATAL_MARKER))
{
StringBuilder sb = new StringBuilder();
sb.append("Enum has create method with no matching constructor:\n");
sb.append(" Enum: ").append(classType.getDescriptor()).append("\n");
sb.append(" Candidate: ").append(mtd.desc).append("\n");
sb.append(" Target: ").append(desc).append("\n");
classNode.methods.stream().filter(m -> m.name.equals("<init>")).forEach(m -> sb.append(" : ").append(m.desc).append("\n"));
LOGGER.error(LogUtils.FATAL_MARKER, sb.toString());
}
throw new IllegalStateException("Enum has create method with no matching constructor: " + desc);
}
if (values == null)
{
if (LOGGER.isErrorEnabled(LogUtils.FATAL_MARKER))
{
StringBuilder sb = new StringBuilder();
sb.append("Enum has create method but we could not find $VALUES. Found:\n");
classNode.fields.stream().filter(f -> (f.access & Opcodes.ACC_STATIC) != 0).
forEach(m -> sb.append(" ").append(m.name).append(" ").append(m.desc).append("\n"));
LOGGER.error(LogUtils.FATAL_MARKER, sb.toString());
}
throw new IllegalStateException("Enum has create method but we could not find $VALUES");
}
values.access &= values.access & ~Opcodes.ACC_FINAL; //Strip the final so JITer doesn't inline things.
mtd.access |= Opcodes.ACC_SYNCHRONIZED;
mtd.instructions.clear();
mtd.localVariables.clear();
if (mtd.tryCatchBlocks != null)
{
mtd.tryCatchBlocks.clear();
}
if (mtd.visibleLocalVariableAnnotations != null)
{
mtd.visibleLocalVariableAnnotations.clear();
}
if (mtd.invisibleLocalVariableAnnotations != null)
{
mtd.invisibleLocalVariableAnnotations.clear();
}
InstructionAdapter ins = new InstructionAdapter(mtd);
int vars = 0;
for (Type arg : args)
vars += arg.getSize();
{
vars += 1; //int x
Label for_start = new Label();
Label for_condition = new Label();
Label for_inc = new Label();
ins.iconst(0);
ins.store(vars, Type.INT_TYPE);
ins.goTo(for_condition);
//if (!VALUES[x].name().equalsIgnoreCase(name)) goto for_inc
ins.mark(for_start);
ins.getstatic(classType.getInternalName(), values.name, values.desc);
ins.load(vars, Type.INT_TYPE);
ins.aload(array);
ins.invokevirtual(ENUM.getInternalName(), "name", NAME_DESC, false);
ins.load(0, STRING);
ins.invokevirtual(STRING.getInternalName(), "equalsIgnoreCase", EQUALS_DESC, false);
ins.ifeq(for_inc);
//return VALUES[x];
ins.getstatic(classType.getInternalName(), values.name, values.desc);
ins.load(vars, Type.INT_TYPE);
ins.aload(array);
ins.areturn(classType);
//x++
ins.mark(for_inc);
ins.iinc(vars, 1);
//if (x < VALUES.length) goto for_start
ins.mark(for_condition);
ins.load(vars, Type.INT_TYPE);
ins.getstatic(classType.getInternalName(), values.name, values.desc);
ins.arraylength();
ins.ificmplt(for_start);
}
{
vars += 1; //enum ret;
//ret = new ThisType(name, VALUES.length, args..)
ins.anew(classType);
ins.dup();
ins.load(0, STRING);
ins.getstatic(classType.getInternalName(), values.name, values.desc);
ins.arraylength();
int idx = 1;
for (int x = 1; x < args.length; x++)
{
ins.load(idx, args[x]);
idx += args[x].getSize();
}
ins.invokespecial(classType.getInternalName(), "<init>", desc, false);
ins.store(vars, classType);
// VALUES = ArrayUtils.add(VALUES, ret)
ins.getstatic(classType.getInternalName(), values.name, values.desc);
ins.load(vars, classType);
ins.invokestatic(ARRAY_UTILS.getInternalName(), "add", ADD_DESC, false);
ins.checkcast(array);
ins.putstatic(classType.getInternalName(), values.name, values.desc);
//EnumHelper.cleanEnumCache(ThisType.class)
ins.visitLdcInsn(classType);
ins.invokestatic(UNSAFE_HACKS.getInternalName(), "cleanEnumCache", CLEAN_DESC, false);
//init ret
ins.load(vars, classType);
ins.invokeinterface(MARKER_IFACE.getInternalName(), "init", "()V");
//return ret
ins.load(vars, classType);
ins.areturn(classType);
}
});
return ComputeFlags.COMPUTE_FRAMES;
}
}

View file

@ -0,0 +1,10 @@
<html>
<head><title>Index of /mirror/src/Magma-1-20-x/fmlloader/src/main/java/net/minecraftforge/fml/common/asm/</title></head>
<body>
<h1>Index of /mirror/src/Magma-1-20-x/fmlloader/src/main/java/net/minecraftforge/fml/common/asm/</h1><hr><pre><a href="../">../</a>
<a href="CapabilityTokenSubclass.java">CapabilityTokenSubclass.java</a> 07-Oct-2023 14:12 3973
<a href="ObjectHolderDefinalize.java">ObjectHolderDefinalize.java</a> 07-Oct-2023 14:12 5332
<a href="RuntimeEnumExtender.java">RuntimeEnumExtender.java</a> 07-Oct-2023 14:12 11K
</pre><hr><script defer src="https://static.cloudflareinsights.com/beacon.min.js/v84a3a4012de94ce1a686ba8c167c359c1696973893317" integrity="sha512-euoFGowhlaLqXsPWQ48qSkBSCFs3DPRyiwVu3FjR96cMPx+Fr+gpWRhIafcHwqwCqWS42RZhIudOvEI+Ckf6MA==" nonce="4f8ae20b3a7870db0094f1271a754c58" data-cf-beacon='{"rayId":"85f039a67b6e1c4c","version":"2024.2.4","r":1,"token":"583109dda43e47a593fd006526a81120","b":1}' crossorigin="anonymous"></script>
</body>
</html>

View file

@ -0,0 +1,8 @@
<html>
<head><title>Index of /mirror/src/Magma-1-20-x/fmlloader/src/main/java/net/minecraftforge/fml/common/</title></head>
<body>
<h1>Index of /mirror/src/Magma-1-20-x/fmlloader/src/main/java/net/minecraftforge/fml/common/</h1><hr><pre><a href="../">../</a>
<a href="asm/">asm/</a> 07-Oct-2023 14:12 -
</pre><hr><script defer src="https://static.cloudflareinsights.com/beacon.min.js/v84a3a4012de94ce1a686ba8c167c359c1696973893317" integrity="sha512-euoFGowhlaLqXsPWQ48qSkBSCFs3DPRyiwVu3FjR96cMPx+Fr+gpWRhIafcHwqwCqWS42RZhIudOvEI+Ckf6MA==" nonce="95f25cfad9859ef3bb75df7406f45487" data-cf-beacon='{"rayId":"85f026fa9ba01c5c","version":"2024.2.4","r":1,"token":"583109dda43e47a593fd006526a81120","b":1}' crossorigin="anonymous"></script>
</body>
</html>

View file

@ -0,0 +1,10 @@
<html>
<head><title>Index of /mirror/src/Magma-1-20-x/fmlloader/src/main/java/net/minecraftforge/fml/</title></head>
<body>
<h1>Index of /mirror/src/Magma-1-20-x/fmlloader/src/main/java/net/minecraftforge/fml/</h1><hr><pre><a href="../">../</a>
<a href="common/">common/</a> 07-Oct-2023 14:12 -
<a href="loading/">loading/</a> 07-Oct-2023 14:12 -
<a href="server/">server/</a> 07-Oct-2023 14:12 -
</pre><hr><script defer src="https://static.cloudflareinsights.com/beacon.min.js/v84a3a4012de94ce1a686ba8c167c359c1696973893317" integrity="sha512-euoFGowhlaLqXsPWQ48qSkBSCFs3DPRyiwVu3FjR96cMPx+Fr+gpWRhIafcHwqwCqWS42RZhIudOvEI+Ckf6MA==" nonce="16a19db08a1ea808d53e91f221156487" data-cf-beacon='{"rayId":"85f019856cc750c2","version":"2024.2.4","r":1,"token":"583109dda43e47a593fd006526a81120","b":1}' crossorigin="anonymous"></script>
</body>
</html>

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class BackgroundWaiter {
private static ExecutorService runner = Executors.newSingleThreadExecutor();
public static void runAndTick(Runnable r, Runnable tick) {
ImmediateWindowHandler.updateProgress("Loading bootstrap resources");
final Future<?> work = runner.submit(r);
do {
tick.run();
try {
Thread.sleep(50);
} catch (InterruptedException ignored) {
}
} while (!work.isDone());
try {
runner.shutdown();
work.get();
} catch (ExecutionException ee) {
throw new RuntimeException(ee.getCause());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}

View file

@ -0,0 +1,38 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import com.mojang.logging.LogUtils;
import org.slf4j.Logger;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
public class ClasspathLocatorUtils {
private static final Logger LOGGER = LogUtils.getLogger();
public static Path findJarPathFor(final String resourceName, final String jarName, final URL resource) {
try {
Path path;
final URI uri = resource.toURI();
if (uri.getScheme().equals("jar") && uri.getRawSchemeSpecificPart().contains("!/")) {
int lastExcl = uri.getRawSchemeSpecificPart().lastIndexOf("!/");
path = Paths.get(new URI(uri.getRawSchemeSpecificPart().substring(0, lastExcl)));
} else {
path = Paths.get(new URI("file://"+uri.getRawSchemeSpecificPart().substring(0, uri.getRawSchemeSpecificPart().length()-resourceName.length())));
}
//LOGGER.debug(CORE, "Found JAR {} at path {}", jarName, path.toString());
return path;
} catch (NullPointerException | URISyntaxException e) {
LOGGER.error(LogMarkers.SCAN, "Failed to find JAR for class {} - {}", resourceName, jarName);
throw new RuntimeException("Unable to locate "+resourceName+" - "+jarName, e);
}
}
}

View file

@ -0,0 +1,61 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import cpw.mods.modlauncher.api.NamedPath;
import cpw.mods.modlauncher.serviceapi.ITransformerDiscoveryService;
import org.apache.logging.log4j.LogManager;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
public class ClasspathTransformerDiscoverer implements ITransformerDiscoveryService {
private final List<Path> legacyClasspath = Arrays.stream(System.getProperty("legacyClassPath", "").split(File.pathSeparator)).map(Path::of).toList();
@Override
public List<NamedPath> candidates(Path gameDirectory) {
return Collections.emptyList();
}
@Override
public List<NamedPath> candidates(final Path gameDirectory, final String launchTarget) {
if (launchTarget != null && launchTarget.contains("dev")) {
this.scan(gameDirectory);
}
return List.copyOf(found);
}
private final static List<NamedPath> found = new ArrayList<>();
public static List<Path> allExcluded() {
return found.stream().map(np->np.paths()[0]).toList();
}
private void scan(final Path gameDirectory) {
try {
locateTransformers("META-INF/services/cpw.mods.modlauncher.api.ITransformationService");
locateTransformers("META-INF/services/net.minecraftforge.forgespi.locating.IModLocator");
} catch (IOException e) {
LogManager.getLogger().error("Error during discovery of transform services from the classpath", e);
}
}
private void locateTransformers(String resource) throws IOException {
final Enumeration<URL> resources = ClassLoader.getSystemClassLoader().getResources(resource);
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
Path path = ClasspathLocatorUtils.findJarPathFor(resource, url.toString(), url);
if (legacyClasspath.stream().anyMatch(path::equals) || !Files.exists(path) || Files.isDirectory(path))
continue;
found.add(new NamedPath(path.toUri().toString(), path));
}
}
}

View file

@ -0,0 +1,56 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import net.minecraftforge.forgespi.language.IModInfo;
import java.util.List;
/**
* Thrown during early loading phase, and collected by the LoadingModList for handoff to the client
* or server.
*/
public class EarlyLoadingException extends RuntimeException {
public static class ExceptionData {
private final IModInfo modInfo;
private final String i18message;
private final Object[] args;
public ExceptionData(final String message, Object... args) {
this(message, null, args);
}
public ExceptionData(final String message, final IModInfo modInfo, Object... args) {
this.i18message = message;
this.modInfo = modInfo;
this.args = args;
}
public String getI18message() {
return i18message;
}
public Object[] getArgs() {
return args;
}
public IModInfo getModInfo() {
return modInfo;
}
}
private final List<ExceptionData> errorMessages;
public List<ExceptionData> getAllData() {
return errorMessages;
}
public EarlyLoadingException(final String message, final Throwable originalException, List<ExceptionData> errorMessages) {
super(message, originalException);
this.errorMessages = errorMessages;
}
}

View file

@ -0,0 +1,156 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import com.electronwill.nightconfig.core.CommentedConfig;
import com.electronwill.nightconfig.core.ConfigSpec;
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import com.electronwill.nightconfig.core.file.FileNotFoundAction;
import com.electronwill.nightconfig.core.io.ParsingException;
import com.electronwill.nightconfig.core.io.WritingMode;
import com.mojang.logging.LogUtils;
import org.slf4j.Logger;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
public class FMLConfig
{
public enum ConfigValue {
EARLY_WINDOW_CONTROL("earlyWindowControl", Boolean.TRUE, "Should we control the window. Disabling this disables new GL features and can be bad for mods that rely on them."),
MAX_THREADS("maxThreads", -1, "Max threads for early initialization parallelism, -1 is based on processor count", FMLConfig::maxThreads),
VERSION_CHECK("versionCheck", Boolean.TRUE, "Enable forge global version checking"),
DEFAULT_CONFIG_PATH("defaultConfigPath", "defaultconfigs", "Default config path for servers"),
DISABLE_OPTIMIZED_DFU("disableOptimizedDFU", Boolean.TRUE, "Disables Optimized DFU client-side - already disabled on servers"),
EARLY_WINDOW_PROVIDER("earlyWindowProvider", "fmlearlywindow", "Early window provider"),
EARLY_WINDOW_WIDTH("earlyWindowWidth", 854, "Early window width"),
EARLY_WINDOW_HEIGHT("earlyWindowHeight", 480, "Early window height"),
EARLY_WINDOW_FBSCALE("earlyWindowFBScale", 1, "Early window framebuffer scale"),
EARLY_WINDOW_MAXIMIZED("earlyWindowMaximized", Boolean.FALSE, "Early window starts maximized"),
EARLY_WINDOW_SKIP_GL_VERSIONS("earlyWindowSkipGLVersions", List.of(), "Skip specific GL versions, may help with buggy graphics card drivers"),
EARLY_WINDOW_SQUIR("earlyWindowSquir", Boolean.FALSE, "Squir?")
;
private final String entry;
private final Object defaultValue;
private final String comment;
private final Class<?> valueType;
private final Function<Object, Object> entryFunction;
ConfigValue(final String entry, final Object defaultValue, final String comment) {
this(entry, defaultValue, comment, Function.identity());
}
ConfigValue(final String entry, final Object defaultValue, final String comment, Function<Object, Object> entryFunction) {
this.entry = entry;
this.defaultValue = defaultValue;
this.comment = comment;
this.valueType = defaultValue.getClass();
this.entryFunction = entryFunction;
}
void buildConfigEntry(ConfigSpec spec, CommentedConfig commentedConfig) {
if (this.defaultValue instanceof List<?> list) {
spec.defineList(this.entry, list, e -> e instanceof String);
} else {
spec.define(this.entry, this.defaultValue);
}
commentedConfig.add(this.entry, this.defaultValue);
commentedConfig.setComment(this.entry, this.comment);
}
@SuppressWarnings("unchecked")
private <T> T getConfigValue(CommentedFileConfig config) {
return (T) this.entryFunction.apply(config.get(this.entry));
}
public <T> void updateValue(final CommentedFileConfig configData, final T value) {
configData.set(this.entry, value);
}
}
private static Object maxThreads(final Object value) {
int val = (Integer)value;
if (val <= 0) return Runtime.getRuntime().availableProcessors();
else return val;
}
private static final Logger LOGGER = LogUtils.getLogger();
private static final FMLConfig INSTANCE = new FMLConfig();
private static final ConfigSpec configSpec = new ConfigSpec();
private static final CommentedConfig configComments = CommentedConfig.inMemory();
static {
for (ConfigValue cv: ConfigValue.values()) {
cv.buildConfigEntry(configSpec, configComments);
}
}
private CommentedFileConfig configData;
private void loadFrom(final Path configFile)
{
configData = CommentedFileConfig.builder(configFile).sync()
.onFileNotFound(FileNotFoundAction.copyData(Objects.requireNonNull(getClass().getResourceAsStream("/META-INF/defaultfmlconfig.toml"))))
.writingMode(WritingMode.REPLACE)
.build();
try
{
configData.load();
}
catch (ParsingException e)
{
throw new RuntimeException("Failed to load FML config from " + configFile, e);
}
if (!configSpec.isCorrect(configData)) {
LOGGER.warn(CORE, "Configuration file {} is not correct. Correcting", configFile);
configSpec.correct(configData, (action, path, incorrectValue, correctedValue) ->
LOGGER.info(CORE, "Incorrect key {} was corrected from {} to {}", path, incorrectValue, correctedValue));
}
configData.putAllComments(configComments);
configData.save();
}
public static void load()
{
final Path configFile = FMLPaths.FMLCONFIG.get();
INSTANCE.loadFrom(configFile);
if (LOGGER.isTraceEnabled(CORE))
{
LOGGER.trace(CORE, "Loaded FML config from {}", FMLPaths.FMLCONFIG.get());
for (ConfigValue cv: ConfigValue.values()) {
LOGGER.trace(CORE, "FMLConfig {} is {}", cv.entry, cv.getConfigValue(INSTANCE.configData));
}
}
FMLPaths.getOrCreateGameRelativePath(Paths.get(FMLConfig.getConfigValue(ConfigValue.DEFAULT_CONFIG_PATH)));
}
public static String getConfigValue(ConfigValue v) {
return v.getConfigValue(INSTANCE.configData);
}
public static boolean getBoolConfigValue(ConfigValue v) {
return v.getConfigValue(INSTANCE.configData);
}
public static int getIntConfigValue(ConfigValue v) {
return v.getConfigValue(INSTANCE.configData);
}
public static <A> List<A> getListConfigValue(ConfigValue v) {
return v.getConfigValue(INSTANCE.configData);
}
public static <T> void updateConfig(ConfigValue v, T value) {
v.updateValue(INSTANCE.configData, value);
INSTANCE.configData.save();
}
public static String defaultConfigPath() {
return getConfigValue(ConfigValue.DEFAULT_CONFIG_PATH);
}
}

View file

@ -0,0 +1,30 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import cpw.mods.modlauncher.api.IEnvironment;
import cpw.mods.modlauncher.api.TypesafeMap;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.forgespi.Environment;
import java.util.function.Supplier;
public class FMLEnvironment
{
public static final Dist dist = FMLLoader.getDist();
public static final String naming = FMLLoader.getNaming();
public static final boolean production = FMLLoader.isProduction() || System.getProperties().containsKey("production");
public static final boolean secureJarsEnabled = FMLLoader.isSecureJarEnabled();
static void setupInteropEnvironment(IEnvironment environment) {
environment.computePropertyIfAbsent(IEnvironment.Keys.NAMING.get(), v->naming);
environment.computePropertyIfAbsent(Environment.Keys.DIST.get(), v->dist);
}
public static class Keys {
public static final Supplier<TypesafeMap.Key<ClassLoader>> LOCATORCLASSLOADER = IEnvironment.buildKey("LOCATORCLASSLOADER",ClassLoader.class);
}
}

View file

@ -0,0 +1,257 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.Launcher;
import cpw.mods.modlauncher.api.*;
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
import cpw.mods.modlauncher.util.ServiceLoaderUtils;
import net.minecraftforge.fml.loading.moddiscovery.BackgroundScanHandler;
import net.minecraftforge.fml.loading.moddiscovery.ModDiscoverer;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import net.minecraftforge.fml.loading.moddiscovery.ModValidator;
import net.minecraftforge.accesstransformer.service.AccessTransformerService;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
import net.minecraftforge.fml.loading.targets.CommonLaunchHandler;
import net.minecraftforge.forgespi.Environment;
import net.minecraftforge.forgespi.coremod.ICoreModProvider;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
import static net.minecraftforge.fml.loading.LogMarkers.SCAN;
public class FMLLoader
{
private static final Logger LOGGER = LogUtils.getLogger();
private static AccessTransformerService accessTransformer;
private static ModDiscoverer modDiscoverer;
private static ICoreModProvider coreModProvider;
private static ILaunchPluginService eventBus;
private static LanguageLoadingProvider languageLoadingProvider;
private static Dist dist;
private static String naming;
private static LoadingModList loadingModList;
private static RuntimeDistCleaner runtimeDistCleaner;
private static Path gamePath;
private static VersionInfo versionInfo;
private static String launchHandlerName;
private static CommonLaunchHandler commonLaunchHandler;
public static Runnable progressWindowTick;
private static ModValidator modValidator;
public static BackgroundScanHandler backgroundScanHandler;
private static boolean production;
private static IModuleLayerManager moduleLayerManager;
static void onInitialLoad(IEnvironment environment, Set<String> otherServices) throws IncompatibleEnvironmentException
{
final String version = LauncherVersion.getVersion();
LOGGER.debug(CORE,"FML {} loading", version);
final Package modLauncherPackage = ITransformationService.class.getPackage();
LOGGER.debug(CORE,"FML found ModLauncher version : {}", modLauncherPackage.getImplementationVersion());
if (!modLauncherPackage.isCompatibleWith("4.0")) {
LOGGER.error(CORE, "Found incompatible ModLauncher specification : {}, version {} from {}", modLauncherPackage.getSpecificationVersion(), modLauncherPackage.getImplementationVersion(), modLauncherPackage.getImplementationVendor());
throw new IncompatibleEnvironmentException("Incompatible modlauncher found "+modLauncherPackage.getSpecificationVersion());
}
accessTransformer = (AccessTransformerService) environment.findLaunchPlugin("accesstransformer").orElseThrow(()-> {
LOGGER.error(CORE, "Access Transformer library is missing, we need this to run");
return new IncompatibleEnvironmentException("Missing AccessTransformer, cannot run");
});
final Package atPackage = accessTransformer.getClass().getPackage();
LOGGER.debug(CORE,"FML found AccessTransformer version : {}", atPackage.getImplementationVersion());
if (!atPackage.isCompatibleWith("1.0")) {
LOGGER.error(CORE, "Found incompatible AccessTransformer specification : {}, version {} from {}", atPackage.getSpecificationVersion(), atPackage.getImplementationVersion(), atPackage.getImplementationVendor());
throw new IncompatibleEnvironmentException("Incompatible accesstransformer found "+atPackage.getSpecificationVersion());
}
eventBus = environment.findLaunchPlugin("eventbus").orElseThrow(()-> {
LOGGER.error(CORE, "Event Bus library is missing, we need this to run");
return new IncompatibleEnvironmentException("Missing EventBus, cannot run");
});
final Package eventBusPackage = eventBus.getClass().getPackage();
LOGGER.debug(CORE,"FML found EventBus version : {}", eventBusPackage.getImplementationVersion());
if (!eventBusPackage.isCompatibleWith("1.0")) {
LOGGER.error(CORE, "Found incompatible EventBus specification : {}, version {} from {}", eventBusPackage.getSpecificationVersion(), eventBusPackage.getImplementationVersion(), eventBusPackage.getImplementationVendor());
throw new IncompatibleEnvironmentException("Incompatible eventbus found "+eventBusPackage.getSpecificationVersion());
}
runtimeDistCleaner = (RuntimeDistCleaner)environment.findLaunchPlugin("runtimedistcleaner").orElseThrow(()-> {
LOGGER.error(CORE, "Dist Cleaner is missing, we need this to run");
return new IncompatibleEnvironmentException("Missing DistCleaner, cannot run!");
});
LOGGER.debug(CORE, "Found Runtime Dist Cleaner");
var coreModProviders = ServiceLoaderUtils.streamWithErrorHandling(ServiceLoader.load(FMLLoader.class.getModule().getLayer(), ICoreModProvider.class), sce -> LOGGER.error(CORE, "Failed to load a coremod library, expect problems", sce)).toList();
if (coreModProviders.isEmpty()) {
LOGGER.error(CORE, "Found no coremod provider. Cannot run");
throw new IncompatibleEnvironmentException("No coremod library found");
} else if (coreModProviders.size() > 1) {
LOGGER.error(CORE, "Found multiple coremod providers : {}. Cannot run", coreModProviders.stream().map(p -> p.getClass().getName()).collect(Collectors.toList()));
throw new IncompatibleEnvironmentException("Multiple coremod libraries found");
}
coreModProvider = coreModProviders.get(0);
final Package coremodPackage = coreModProvider.getClass().getPackage();
LOGGER.debug(CORE,"FML found CoreMod version : {}", coremodPackage.getImplementationVersion());
LOGGER.debug(CORE, "Found ForgeSPI package implementation version {}", Environment.class.getPackage().getImplementationVersion());
LOGGER.debug(CORE, "Found ForgeSPI package specification {}", Environment.class.getPackage().getSpecificationVersion());
if (Integer.parseInt(Environment.class.getPackage().getSpecificationVersion()) < 2) {
LOGGER.error(CORE, "Found an out of date ForgeSPI implementation: {}, loading cannot continue", Environment.class.getPackage().getSpecificationVersion());
throw new IncompatibleEnvironmentException("ForgeSPI is out of date, we cannot continue");
}
try {
Class.forName("com.electronwill.nightconfig.core.Config", false, environment.getClass().getClassLoader());
Class.forName("com.electronwill.nightconfig.toml.TomlFormat", false, environment.getClass().getClassLoader());
} catch (ClassNotFoundException e) {
LOGGER.error(CORE, "Failed to load NightConfig");
throw new IncompatibleEnvironmentException("Missing NightConfig");
}
}
static void setupLaunchHandler(final IEnvironment environment, final Map<String, Object> arguments)
{
final String launchTarget = environment.getProperty(IEnvironment.Keys.LAUNCHTARGET.get()).orElse("MISSING");
arguments.put("launchTarget", launchTarget);
final Optional<ILaunchHandlerService> launchHandler = environment.findLaunchHandler(launchTarget);
LOGGER.debug(CORE, "Using {} as launch service", launchTarget);
if (launchHandler.isEmpty()) {
LOGGER.error(CORE, "Missing LaunchHandler {}, cannot continue", launchTarget);
throw new RuntimeException("Missing launch handler: " + launchTarget);
}
if (!(launchHandler.get() instanceof CommonLaunchHandler)) {
LOGGER.error(CORE, "Incompatible Launch handler found - type {}, cannot continue", launchHandler.get().getClass().getName());
throw new RuntimeException("Incompatible launch handler found");
}
commonLaunchHandler = (CommonLaunchHandler)launchHandler.get();
launchHandlerName = launchHandler.get().name();
gamePath = environment.getProperty(IEnvironment.Keys.GAMEDIR.get()).orElse(Paths.get(".").toAbsolutePath());
naming = commonLaunchHandler.getNaming();
dist = commonLaunchHandler.getDist();
production = commonLaunchHandler.isProduction();
versionInfo = new VersionInfo(arguments);
accessTransformer.getExtension().accept(Pair.of(naming, "srg"));
LOGGER.debug(CORE,"Received command line version data : {}", versionInfo);
runtimeDistCleaner.getExtension().accept(dist);
}
public static List<ITransformationService.Resource> beginModScan(final Map<String,?> arguments)
{
LOGGER.debug(SCAN,"Scanning for Mod Locators");
modDiscoverer = new ModDiscoverer(arguments);
modValidator = modDiscoverer.discoverMods();
var pluginResources = modValidator.getPluginResources();
return List.of(pluginResources);
}
public static List<ITransformationService.Resource> completeScan(IModuleLayerManager layerManager) {
moduleLayerManager = layerManager;
languageLoadingProvider = new LanguageLoadingProvider();
backgroundScanHandler = modValidator.stage2Validation();
loadingModList = backgroundScanHandler.getLoadingModList();
return List.of(modValidator.getModResources());
}
public static ICoreModProvider getCoreModProvider() {
return coreModProvider;
}
public static LanguageLoadingProvider getLanguageLoadingProvider()
{
return languageLoadingProvider;
}
static ModDiscoverer getModDiscoverer() {
return modDiscoverer;
}
public static CommonLaunchHandler getLaunchHandler() {
return commonLaunchHandler;
}
public static void addAccessTransformer(Path atPath, ModFile modName)
{
LOGGER.debug(SCAN, "Adding Access Transformer in {}", modName.getFilePath());
accessTransformer.offerResource(atPath, modName.getFileName());
}
public static Dist getDist()
{
return dist;
}
public static void beforeStart(ModuleLayer gameLayer)
{
ImmediateWindowHandler.acceptGameLayer(gameLayer);
ImmediateWindowHandler.updateProgress("Launching minecraft");
progressWindowTick.run();
}
public static LoadingModList getLoadingModList()
{
return loadingModList;
}
public static Path getGamePath()
{
return gamePath;
}
public static String getNaming() {
return naming;
}
public static Optional<BiFunction<INameMappingService.Domain, String, String>> getNameFunction(final String naming) {
return Launcher.INSTANCE.environment().findNameMapping(naming);
}
public static String getLauncherInfo() {
return Launcher.INSTANCE.environment().getProperty(IEnvironment.Keys.MLIMPL_VERSION.get()).orElse("MISSING");
}
public static List<Map<String, String>> modLauncherModList() {
return Launcher.INSTANCE.environment().getProperty(IEnvironment.Keys.MODLIST.get()).orElseGet(Collections::emptyList);
}
public static String launcherHandlerName() {
return launchHandlerName;
}
public static boolean isProduction() {
return production;
}
public static boolean isSecureJarEnabled() {
return true;
}
public static ModuleLayer getGameLayer() {
return moduleLayerManager.getLayer(IModuleLayerManager.Layer.GAME).orElseThrow();
}
public static VersionInfo versionInfo() {
return versionInfo;
}
}

View file

@ -0,0 +1,98 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.api.IEnvironment;
import org.slf4j.Logger;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
public enum FMLPaths
{
GAMEDIR(),
MODSDIR("mods"),
CONFIGDIR("config"),
FMLCONFIG(false, CONFIGDIR, "fml.toml");
private static final Logger LOGGER = LogUtils.getLogger();
private final Path relativePath;
private final boolean isDirectory;
private Path absolutePath;
FMLPaths() {
this("");
}
FMLPaths(String... path) {
relativePath = computePath(path);
this.isDirectory = true;
}
FMLPaths(boolean isDir, FMLPaths parent, String... path) {
this.relativePath = parent.relativePath.resolve(computePath(path));
this.isDirectory = isDir;
}
private Path computePath(String... path)
{
return Paths.get(path[0], Arrays.copyOfRange(path, 1, path.length));
}
public static void setup(IEnvironment env) {
final Path rootPath = env.getProperty(IEnvironment.Keys.GAMEDIR.get()).orElseThrow(() -> new RuntimeException("No game path found"));
loadAbsolutePaths(rootPath);
}
public static void loadAbsolutePaths(Path rootPath)
{
for (FMLPaths path : FMLPaths.values())
{
path.absolutePath = rootPath.resolve(path.relativePath).toAbsolutePath().normalize();
if (path.isDirectory && !Files.isDirectory(path.absolutePath))
{
try {
Files.createDirectories(path.absolutePath);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (LOGGER.isDebugEnabled(CORE))
{
LOGGER.debug(CORE, "Path {} is {}", path, path.absolutePath);
}
}
}
public static Path getOrCreateGameRelativePath(Path path) {
Path gameFolderPath = FMLPaths.GAMEDIR.get().resolve(path);
if (!Files.isDirectory(gameFolderPath)) {
try {
Files.createDirectories(gameFolderPath);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return gameFolderPath;
}
public Path relative() {
return relativePath;
}
public Path get() {
return absolutePath;
}
}

View file

@ -0,0 +1,136 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.api.*;
import joptsimple.ArgumentAcceptingOptionSpec;
import joptsimple.OptionSpecBuilder;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import net.minecraftforge.forgespi.Environment;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
public class FMLServiceProvider implements ITransformationService
{
private static final Logger LOGGER = LogUtils.getLogger();
private ArgumentAcceptingOptionSpec<String> modsOption;
private ArgumentAcceptingOptionSpec<String> modListsOption;
private ArgumentAcceptingOptionSpec<String> mavenRootsOption;
private ArgumentAcceptingOptionSpec<String> forgeOption;
private ArgumentAcceptingOptionSpec<String> mcOption;
private ArgumentAcceptingOptionSpec<String> forgeGroupOption;
private ArgumentAcceptingOptionSpec<String> mcpOption;
private ArgumentAcceptingOptionSpec<String> mappingsOption;
private List<String> modsArgumentList;
private List<String> modListsArgumentList;
private List<String> mavenRootsArgumentList;
private String targetForgeVersion;
private String targetMcVersion;
private String targetMcpVersion;
private String targetMcpMappings;
private String targetForgeGroup;
private Map<String, Object> arguments;
public FMLServiceProvider()
{
final String markerselection = System.getProperty("forge.logging.markers", "");
Arrays.stream(markerselection.split(",")).forEach(marker -> System.setProperty("forge.logging.marker." + marker.toLowerCase(Locale.ROOT), "ACCEPT"));
}
@Override
public String name()
{
return "fml";
}
@Override
public void initialize(IEnvironment environment) {
LOGGER.debug(CORE, "Setting up basic FML game directories");
FMLPaths.setup(environment);
LOGGER.debug(CORE, "Loading configuration");
FMLConfig.load();
LOGGER.debug(CORE, "Preparing ModFile");
environment.computePropertyIfAbsent(Environment.Keys.MODFILEFACTORY.get(), k->ModFile::new);
arguments = new HashMap<>();
arguments.put("modLists", modListsArgumentList);
arguments.put("mods", modsArgumentList);
arguments.put("mavenRoots", mavenRootsArgumentList);
arguments.put("forgeVersion", targetForgeVersion);
arguments.put("forgeGroup", targetForgeGroup);
arguments.put("mcVersion", targetMcVersion);
arguments.put("mcpVersion", targetMcpVersion);
arguments.put("mcpMappings", targetMcpMappings);
LOGGER.debug(CORE, "Preparing launch handler");
FMLLoader.setupLaunchHandler(environment, arguments);
FMLEnvironment.setupInteropEnvironment(environment);
Environment.build(environment);
}
@Override
public List<Resource> beginScanning(final IEnvironment environment) {
LOGGER.debug(CORE,"Initiating mod scan");
return FMLLoader.beginModScan(arguments);
}
@Override
public List<Resource> completeScan(final IModuleLayerManager layerManager) {
return FMLLoader.completeScan(layerManager);
}
@Override
public void onLoad(IEnvironment environment, Set<String> otherServices) throws IncompatibleEnvironmentException
{
// LOGGER.debug("Injecting tracing printstreams for STDOUT/STDERR.");
// System.setOut(new TracingPrintStream(LogManager.getLogger("STDOUT"), System.out));
// System.setErr(new TracingPrintStream(LogManager.getLogger("STDERR"), System.err));
FMLLoader.onInitialLoad(environment, otherServices);
}
@Override
public void arguments(BiFunction<String, String, OptionSpecBuilder> argumentBuilder)
{
forgeOption = argumentBuilder.apply("forgeVersion", "Forge Version number").withRequiredArg().ofType(String.class).required();
forgeGroupOption = argumentBuilder.apply("forgeGroup", "Forge Group (for testing)").withRequiredArg().ofType(String.class).defaultsTo("net.minecraftforge");
mcOption = argumentBuilder.apply("mcVersion", "Minecraft Version number").withRequiredArg().ofType(String.class).required();
mcpOption = argumentBuilder.apply("mcpVersion", "MCP Version number").withRequiredArg().ofType(String.class).required();
mappingsOption = argumentBuilder.apply("mcpMappings", "MCP Mappings Channel and Version").withRequiredArg().ofType(String.class);
modsOption = argumentBuilder.apply("mods", "List of mods to add").withRequiredArg().ofType(String.class).withValuesSeparatedBy(",");
modListsOption = argumentBuilder.apply("modLists", "JSON modlists").withRequiredArg().ofType(String.class).withValuesSeparatedBy(",");
mavenRootsOption = argumentBuilder.apply("mavenRoots", "Maven root directories").withRequiredArg().ofType(String.class).withValuesSeparatedBy(",");
}
@Override
public void argumentValues(OptionResult option)
{
modsArgumentList = option.values(modsOption);
modListsArgumentList = option.values(modListsOption);
mavenRootsArgumentList = option.values(mavenRootsOption);
targetForgeVersion = option.value(forgeOption);
targetForgeGroup = option.value(forgeGroupOption);
targetMcVersion = option.value(mcOption);
targetMcpVersion = option.value(mcpOption);
targetMcpMappings = option.value(mappingsOption);
}
@Override
public @NotNull List<ITransformer> transformers()
{
LOGGER.debug(CORE, "Loading coremod transformers");
return new ArrayList<>(FMLLoader.getCoreModProvider().getCoreModTransformers());
}
}

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import java.io.File;
import java.nio.file.Path;
public class FileUtils
{
public static String fileExtension(final Path path) {
String fileName = path.getFileName().toString();
int idx = fileName.lastIndexOf('.');
if (idx > -1) {
return fileName.substring(idx+1);
} else {
return "";
}
}
public static boolean matchFileName(String path, String... matches) {
// Extract file name from path
String name = path.substring(Math.min(path.lastIndexOf(File.separatorChar) + 1, path.length()));
// Check if it contains any of the desired keywords
for (String match : matches) {
if (name.contains(match)) {
return true;
}
}
return false;
}
}

View file

@ -0,0 +1,159 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.function.*;
import java.util.stream.Collectors;
public class ImmediateWindowHandler {
private static final Logger LOGGER = LogManager.getLogger();
private static ImmediateWindowProvider provider;
private static ProgressMeter earlyProgress;
public static void load(final String launchTarget, final String[] arguments) {
if (!List.of("forgeclient", "forgeclientuserdev", "forgeclientdev").contains(launchTarget)) {
provider = new DummyProvider();
LOGGER.info("ImmediateWindowProvider not loading because launch target is {}", launchTarget);
} else if (!FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_CONTROL)) {
provider = new DummyProvider();
LOGGER.info("ImmediateWindowProvider not loading because splash screen is disabled");
} else {
final var providername = FMLConfig.getConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_PROVIDER);
LOGGER.info("Loading ImmediateWindowProvider {}", providername);
final var maybeProvider = ServiceLoader.load(ImmediateWindowProvider.class)
.stream()
.map(ServiceLoader.Provider::get)
.filter(p -> Objects.equals(p.name(), providername))
.findFirst();
provider = maybeProvider.or(() -> {
LOGGER.info("Failed to find ImmediateWindowProvider {}, disabling", providername);
return Optional.of(new DummyProvider());
}).orElseThrow();
}
// Only update config if the provider isn't the dummy provider
if (!Objects.equals(provider.name(), "dummyprovider"))
FMLConfig.updateConfig(FMLConfig.ConfigValue.EARLY_WINDOW_PROVIDER, provider.name());
FMLLoader.progressWindowTick = provider.initialize(arguments);
earlyProgress = StartupNotificationManager.addProgressBar("EARLY", 0);
earlyProgress.label("Bootstrapping Minecraft");
}
public static long setupMinecraftWindow(final IntSupplier width, final IntSupplier height, final Supplier<String> title, final LongSupplier monitor) {
return provider.setupMinecraftWindow(width, height, title, monitor);
}
public static boolean positionWindow(Optional<Object> monitor,IntConsumer widthSetter, IntConsumer heightSetter, IntConsumer xSetter, IntConsumer ySetter) {
return provider.positionWindow(monitor, widthSetter, heightSetter, xSetter, ySetter);
}
public static void updateFBSize(IntConsumer width, IntConsumer height) {
provider.updateFramebufferSize(width, height);
}
public static <T> Supplier<T> loadingOverlay(Supplier<?> mc, Supplier<?> ri, Consumer<Optional<Throwable>> ex, boolean fade) {
earlyProgress.complete();
return provider.loadingOverlay(mc, ri, ex, fade);
}
public static void acceptGameLayer(final ModuleLayer layer) {
provider.updateModuleReads(layer);
}
public static void renderTick() {
provider.periodicTick();
}
public static String getGLVersion() {
return provider.getGLVersion();
}
public static void updateProgress(final String message) {
earlyProgress.label(message);
}
private record DummyProvider() implements ImmediateWindowProvider {
private static Method NV_HANDOFF;
private static Method NV_POSITION;
private static Method NV_OVERLAY;
private static Method NV_VERSION;
@Override
public String name() {
return "dummyprovider";
}
@Override
public Runnable initialize(String[] args) {
return () -> {};
}
@Override
public void updateFramebufferSize(final IntConsumer width, final IntConsumer height) {
}
@Override
public long setupMinecraftWindow(final IntSupplier width, final IntSupplier height, final Supplier<String> title, final LongSupplier monitor) {
try {
var longsupplier = (LongSupplier)NV_HANDOFF.invoke(null, width, height, title, monitor);
return longsupplier.getAsLong();
} catch (Throwable e) {
throw new IllegalStateException("How did you get here?", e);
}
}
public boolean positionWindow(Optional<Object> monitor, IntConsumer widthSetter, IntConsumer heightSetter, IntConsumer xSetter, IntConsumer ySetter) {
try {
return (boolean)NV_POSITION.invoke(null, monitor, widthSetter, heightSetter, xSetter, ySetter);
} catch (Throwable e) {
throw new IllegalStateException("How did you get here?", e);
}
}
@SuppressWarnings("unchecked")
public <T> Supplier<T> loadingOverlay(Supplier<?> mc, Supplier<?> ri, Consumer<Optional<Throwable>> ex, boolean fade) {
try {
return (Supplier<T>) NV_OVERLAY.invoke(null, mc, ri, ex, fade);
} catch (Throwable e) {
throw new IllegalStateException("How did you get here?", e);
}
}
@Override
public String getGLVersion() {
try {
return (String) NV_VERSION.invoke(null);
} catch (Throwable e) {
return "3.2"; // Vanilla sets 3.2 in com.mojang.blaze3d.platform.Window
}
}
@Override
public void updateModuleReads(final ModuleLayer layer) {
var fm = layer.findModule("forge");
if (fm.isPresent()) {
getClass().getModule().addReads(fm.get());
var clz = fm.map(l -> Class.forName(l, "net.minecraftforge.client.loading.NoVizFallback")).orElseThrow();
var methods = Arrays.stream(clz.getMethods()).filter(m -> Modifier.isStatic(m.getModifiers())).collect(Collectors.toMap(Method::getName, Function.identity()));
NV_HANDOFF = methods.get("windowHandoff");
NV_OVERLAY = methods.get("loadingOverlay");
NV_POSITION = methods.get("windowPositioning");
NV_VERSION = methods.get("glVersion");
}
}
@Override
public void periodicTick() {
// NOOP
}
}
}

View file

@ -0,0 +1,117 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
import java.util.function.IntSupplier;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
/**
* This is for allowing the plugging in of alternative early display implementations.
*
* They can be selected through the config value "earlyWindowProvider" which defaults to "fmlearlywindow" implemented by {@link net.minecraftforge.fml.earlydisplay.DisplayWindow}
*
* There are a few key things to keep in mind if following through on implementation. You cannot access the game state as it
* literally DOES NOT EXIST at the time this object is constructed. You have to be very careful about managing the handoff
* to mojang, be sure that if you're trying to tick your window in a background thread (a nice idea!) that you properly
* transition to the main thread before handoff is complete. Do note that in general, you should construct your GL objects
* on the MAIN thread before starting your ticker, to ensure MacOS compatibility.
*
* No doubt many more things can be said here.
*/
public interface ImmediateWindowProvider {
/**
* @return The name of this window provider. Do NOT use fmlearlywindow.
*/
String name();
/**
* This is called very early on to initialize ourselves. Use this to initialize the window and other GL core resources.
*
* One thing we want to ensure is that we try and create the highest GL_PROFILE we can accomplish.
* GLFW_CONTEXT_VERSION_MAJOR,GLFW_CONTEXT_VERSION_MINOR should be as high as possible on the created window,
* and it should have all the typical profile settings.
*
* @param arguments The arguments provided to the Java process. This is the entire command line, so you can process
* stuff from it.
* @return A runnable that will be periodically ticked by FML during startup ON THE MAIN THREAD. This is usually
* a good place to put glfwPollEvents() tests.
*/
Runnable initialize(String[] arguments);
/**
* This will be called during the handoff to minecraft to update minecraft with the size of the framebuffer we have.
* Generally won't be called because Minecraft figures it out for itself.
* @param width Consumer of the framebuffer width
* @param height Consumer of the framebuffer height
*/
void updateFramebufferSize(IntConsumer width, IntConsumer height);
/**
* This is called to setup the minecraft window, as if Mojang had done it themselves in their Window class. This
* handoff is difficult to get right - you have to make sure that any activities you're doing to the window are finished
* prior to returning. You should try and setup the width and height as Mojang expects - the suppliers give you all that
* information. Alternatively, you can force Mojang to update from the current position of the window in {@link #positionWindow(Optional, IntConsumer, IntConsumer, IntConsumer, IntConsumer)}
* instead. This might give a more seamless experience.
*
* @param width This is the width of the window Mojang expects
* @param height This is the height of the Window Mojang expects.
* @param title This is the title for the window.
* @param monitor This is the monitor it should appear on.
* @return The window id
*/
long setupMinecraftWindow(final IntSupplier width, final IntSupplier height, final Supplier<String> title, final LongSupplier monitor);
/**
* This is called after window handoff to allow us to tell Mojang about our window's position. This might give a
* preferrable user experience to users, because we just tell Mojang our truth, rather than accept theirs.
* @param monitor This is the monitor we're rendering on. Note that this is the Mojang monitor object. You might have trouble unwrapping it.
* @param widthSetter This sets the width on the Mojang side
* @param heightSetter This sets the height on the Mojang side
* @param xSetter This sets the x coordinate on the Mojang side
* @param ySetter This sets the y coordinate on the Mojang side
* @return true if you've handled the window positioning - this skips the "forced fullscreen" code until a later stage
*/
boolean positionWindow(Optional<Object> monitor, IntConsumer widthSetter, IntConsumer heightSetter, IntConsumer xSetter, IntConsumer ySetter);
/**
* Return a Supplier of an object extending the LoadingOverlay class from Mojang. This is what will be used once
* the Mojang window code has taken over rendering of the window, to render the later stages of the loading process.
*
* @param mc This supplies the Minecraft object
* @param ri This supplies the ReloadInstance object that tells us when the loading is finished
* @param ex This Consumes the final state of the loading - if it's an error you pass it the Throwable, otherwise you
* pass Optional.empty()
* @param fade This is the fade flag passed to LoadingOverlay. You probably want to ignore it.
* @param <T> This is the type LoadingOverlay to allow type binding on the Mojang side
* @return A supplier of your later LoadingOverlay screen.
*/
<T> Supplier<T> loadingOverlay(Supplier<?> mc, Supplier<?> ri, Consumer<Optional<Throwable>> ex, boolean fade);
/**
* This is called during the module loading process to allow us to find objects inside the GAME layer, such as a
* later loading screen.
* @param layer This is the GAME layer from ModLauncher
*/
void updateModuleReads(ModuleLayer layer);
/**
* This is called periodically during the loading process to "tick" the window. It is typically the same as the Runnable
* from {@link #initialize(String[])}
*/
void periodicTick();
/**
* This is called to construct a {@link net.minecraftforge.forgespi.locating.ForgeFeature} for the GL_VERSION we
* managed to create for the window. Should be a string of the format {MAJOR}.{MINOR}, such as 4.6, 4.5 or such.
*
* @return the GL profile we created
*/
String getGLVersion();
}

View file

@ -0,0 +1,44 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import java.util.Optional;
/**
* Finds Version data from a package, with possible default values
*/
public class JarVersionLookupHandler {
public static Optional<String> getImplementationVersion(final String pkgName) {
// Note that with Java 9, you'll probably want the module's version data, hence pulling this out
final String pkgVersion = Package.getPackage(pkgName).getImplementationVersion();
return Optional.ofNullable(pkgVersion);
}
public static Optional<String> getSpecificationVersion(final String pkgName) {
// Note that with Java 9, you'll probably want the module's version data, hence pulling this out
final String pkgVersion = Package.getPackage(pkgName).getSpecificationVersion();
return Optional.ofNullable(pkgVersion);
}
public static Optional<String> getImplementationVersion(final Class<?> clazz) {
// With java 9 we'll use the module's version if it exists in preference.
final String pkgVersion = clazz.getPackage().getImplementationVersion();
return Optional.ofNullable(pkgVersion);
}
public static Optional<String> getImplementationTitle(final Class<?> clazz) {
// With java 9 we'll use the module's version if it exists in preference.
final String pkgVersion = clazz.getPackage().getImplementationTitle();
return Optional.ofNullable(pkgVersion);
}
public static Optional<String> getSpecificationVersion(final Class<?> clazz) {
// With java 9 we'll use the module's version if it exists in preference.
final String pkgVersion = clazz.getPackage().getSpecificationVersion();
return Optional.ofNullable(pkgVersion);
}
}

View file

@ -0,0 +1,150 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.Launcher;
import cpw.mods.modlauncher.api.IModuleLayerManager;
import cpw.mods.modlauncher.util.ServiceLoaderUtils;
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
import net.minecraftforge.forgespi.language.IModLanguageProvider;
import net.minecraftforge.fml.loading.moddiscovery.ExplodedDirectoryLocator;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.artifact.versioning.VersionRange;
import org.slf4j.Logger;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream;
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
import static net.minecraftforge.fml.loading.LogMarkers.LOADING;
public class LanguageLoadingProvider
{
private static final Logger LOGGER = LogUtils.getLogger();
private final List<IModLanguageProvider> languageProviders = new ArrayList<>();
private final ServiceLoader<IModLanguageProvider> serviceLoader;
private final Map<String, ModLanguageWrapper> languageProviderMap = new HashMap<>();
private List<Path> languagePaths = new ArrayList<>();
public void forEach(final Consumer<IModLanguageProvider> consumer)
{
languageProviders.forEach(consumer);
}
public <T> Stream<T> applyForEach(final Function<IModLanguageProvider, T> function) {
return languageProviders.stream().map(function);
}
private static class ModLanguageWrapper {
private final IModLanguageProvider modLanguageProvider;
private final ArtifactVersion version;
public ModLanguageWrapper(IModLanguageProvider modLanguageProvider, ArtifactVersion version)
{
this.modLanguageProvider = modLanguageProvider;
this.version = version;
}
public ArtifactVersion getVersion()
{
return version;
}
public IModLanguageProvider getModLanguageProvider()
{
return modLanguageProvider;
}
}
LanguageLoadingProvider() {
var sl = Launcher.INSTANCE.environment().findModuleLayerManager().flatMap(lm->lm.getLayer(IModuleLayerManager.Layer.PLUGIN)).orElseThrow();
serviceLoader = ServiceLoader.load(sl, IModLanguageProvider.class);
loadLanguageProviders();
}
private void loadLanguageProviders() {
LOGGER.debug(CORE, "Found {} language providers", ServiceLoaderUtils.streamServiceLoader(()->serviceLoader, sce->LOGGER.error("Problem with language loaders")).count());
serviceLoader.forEach(languageProviders::add);
ImmediateWindowHandler.updateProgress("Loading language providers");
languageProviders.forEach(lp -> {
final Path lpPath;
try {
lpPath = Paths.get(lp.getClass().getProtectionDomain().getCodeSource().getLocation().toURI());
} catch (URISyntaxException e) {
throw new RuntimeException("Huh?", e);
}
Optional<String> implementationVersion = JarVersionLookupHandler.getImplementationVersion(lp.getClass());
String impl = implementationVersion.orElse(Files.isDirectory(lpPath) ? FMLLoader.versionInfo().forgeVersion().split("\\.")[0] : null);
if (impl == null) {
LOGGER.error(CORE, "Found unversioned language provider {}", lp.name());
throw new RuntimeException("Failed to find implementation version for language provider "+ lp.name());
}
LOGGER.debug(CORE, "Found language provider {}, version {}", lp.name(), impl);
ImmediateWindowHandler.updateProgress("Loaded language provider "+lp.name()+ " " + impl);
languageProviderMap.put(lp.name(), new ModLanguageWrapper(lp, new DefaultArtifactVersion(impl)));
});
}
void addForgeLanguage(final Path forgePath) {
if (!languageProviderMap.containsKey("javafml")) {
LOGGER.debug(CORE,"Adding forge as a language from {}", forgePath.toString());
addLanguagePaths(Stream.of(forgePath));
serviceLoader.reload();
loadLanguageProviders();
} else {
LOGGER.debug(CORE, "Skipping adding forge jar - javafml is already present");
}
}
private void addLanguagePaths(final Stream<Path> langPaths) {
languageProviders.clear();
languageProviderMap.clear();
// langPaths.peek(languagePaths::add).map(Path::toFile).map(File::toURI).map(rethrowFunction(URI::toURL)).forEach(languageClassLoader::addURL);
}
public void addAdditionalLanguages(List<ModFile> modFiles)
{
if (modFiles==null) return;
Stream<Path> langPaths = modFiles.stream().map(ModFile::getFilePath);
addLanguagePaths(langPaths);
serviceLoader.reload();
loadLanguageProviders();
}
Stream<Path> getLibraries() {
return languagePaths.stream();
}
public IModLanguageProvider findLanguage(ModFile mf, String modLoader, VersionRange modLoaderVersion) {
final String languageFileName = mf.getProvider() instanceof ExplodedDirectoryLocator ? "in-development" : mf.getFileName();
final ModLanguageWrapper mlw = languageProviderMap.get(modLoader);
if (mlw == null) {
LOGGER.error(LOADING,"Missing language {} version {} wanted by {}", modLoader, modLoaderVersion, languageFileName);
throw new EarlyLoadingException("Missing language "+modLoader, null, Collections.singletonList(new EarlyLoadingException.ExceptionData("fml.language.missingversion", modLoader, modLoaderVersion, languageFileName, "null")));
}
if (!VersionSupportMatrix.testVersionSupportMatrix(modLoaderVersion, modLoader, "languageloader", (llid, range) -> range.containsVersion(mlw.getVersion()))) {
LOGGER.error(LOADING,"Missing language {} version {} wanted by {}, found {}", modLoader, modLoaderVersion, languageFileName, mlw.getVersion());
throw new EarlyLoadingException("Missing language "+ modLoader + " matching range "+modLoaderVersion + " found "+mlw.getVersion(), null, Collections.singletonList(new EarlyLoadingException.ExceptionData("fml.language.missingversion", modLoader, modLoaderVersion, languageFileName, mlw.getVersion())));
}
return mlw.getModLanguageProvider();
}
}

View file

@ -0,0 +1,28 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import com.mojang.logging.LogUtils;
import org.slf4j.Logger;
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
public class LauncherVersion {
private static final Logger LOGGER = LogUtils.getLogger();
private static final String launcherVersion;
static {
String vers = JarVersionLookupHandler.getImplementationVersion(LauncherVersion.class).orElse(System.getenv("LAUNCHER_VERSION"));
if (vers == null) throw new RuntimeException("Missing FMLLauncher version, cannot continue");
launcherVersion = vers;
LOGGER.debug(CORE, "Found FMLLauncher version {}", launcherVersion);
}
public static String getVersion()
{
return launcherVersion;
}
}

View file

@ -0,0 +1,53 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import com.mojang.logging.LogUtils;
import org.slf4j.Logger;
import java.nio.file.Files;
import java.nio.file.Path;
public class LibraryFinder {
private static final Logger LOGGER = LogUtils.getLogger();
private static Path libsPath;
static Path findLibsPath() {
if (libsPath == null) {
libsPath = Path.of(System.getProperty("libraryDirectory","crazysnowmannonsense/cheezwhizz"));
if (!Files.isDirectory(libsPath)) {
throw new IllegalStateException("Missing libraryDirectory system property, cannot continue");
}
}
return libsPath;
}
static Path getForgeLibraryPath(final String mcVersion, final String forgeVersion, final String forgeGroup) {
Path forgePath = findLibsPath().resolve(MavenCoordinateResolver.get(forgeGroup, "forge", "", "universal", mcVersion+"-"+forgeVersion));
LOGGER.debug(LogMarkers.CORE, "Found forge path {} is {}", forgePath, pathStatus(forgePath));
return forgePath;
}
static String pathStatus(final Path path) {
return Files.exists(path) ? "present" : "missing";
}
static Path[] getMCPaths(final String mcVersion, final String mcpVersion, final String forgeVersion, final String forgeGroup, final String type) {
Path srgMcPath = findLibsPath().resolve(MavenCoordinateResolver.get("net.minecraft", type, "", "srg", mcVersion+"-"+mcpVersion));
Path mcExtrasPath = findLibsPath().resolve(MavenCoordinateResolver.get("net.minecraft", type, "", "extra", mcVersion+"-"+mcpVersion));
Path patchedBinariesPath = findLibsPath().resolve(MavenCoordinateResolver.get(forgeGroup, "forge", "", type, mcVersion+"-"+forgeVersion));
LOGGER.debug(LogMarkers.CORE,"SRG MC at {} is {}", srgMcPath.toString(), pathStatus(srgMcPath));
LOGGER.debug(LogMarkers.CORE,"MC Extras at {} is {}", mcExtrasPath.toString(), pathStatus(mcExtrasPath));
LOGGER.debug(LogMarkers.CORE,"Forge patches at {} is {}", patchedBinariesPath.toString(), pathStatus(patchedBinariesPath));
return new Path[] { patchedBinariesPath, mcExtrasPath, srgMcPath };
}
public static Path findPathForMaven(final String group, final String artifact, final String extension, final String classifier, final String version) {
return findLibsPath().resolve(MavenCoordinateResolver.get(group, artifact, extension, classifier, version));
}
public static Path findPathForMaven(final String maven) {
return findLibsPath().resolve(MavenCoordinateResolver.get(maven));
}
}

View file

@ -0,0 +1,171 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
import net.minecraftforge.fml.loading.moddiscovery.BackgroundScanHandler;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
import net.minecraftforge.forgespi.locating.IModFile;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.stream.Collectors;
/**
* Master list of all mods <em>in the loading context. This class cannot refer outside the
* loading package</em>
*/
public class LoadingModList
{
private static LoadingModList INSTANCE;
private final List<ModFileInfo> modFiles;
private final List<ModInfo> sortedList;
private final Map<String, ModFileInfo> fileById;
private final List<EarlyLoadingException> preLoadErrors;
private List<IModFile> brokenFiles;
private LoadingModList(final List<ModFile> modFiles, final List<ModInfo> sortedList)
{
this.modFiles = modFiles.stream()
.map(ModFile::getModFileInfo)
.map(ModFileInfo.class::cast)
.collect(Collectors.toList());
this.sortedList = sortedList.stream()
.map(ModInfo.class::cast)
.collect(Collectors.toList());
this.fileById = this.modFiles.stream()
.map(ModFileInfo::getMods)
.flatMap(Collection::stream)
.map(ModInfo.class::cast)
.collect(Collectors.toMap(ModInfo::getModId, ModInfo::getOwningFile));
this.preLoadErrors = new ArrayList<>();
}
public static LoadingModList of(List<ModFile> modFiles, List<ModInfo> sortedList, final EarlyLoadingException earlyLoadingException)
{
INSTANCE = new LoadingModList(modFiles, sortedList);
if (earlyLoadingException != null)
{
INSTANCE.preLoadErrors.add(earlyLoadingException);
}
return INSTANCE;
}
public static LoadingModList get() {
return INSTANCE;
}
public void addCoreMods()
{
modFiles.stream()
.map(ModFileInfo::getFile)
.map(ModFile::getCoreMods)
.flatMap(List::stream)
.forEach(FMLLoader.getCoreModProvider()::addCoreMod);
}
public void addAccessTransformers()
{
modFiles.stream()
.map(ModFileInfo::getFile)
.forEach(mod -> mod.getAccessTransformer().ifPresent(path -> FMLLoader.addAccessTransformer(path, mod)));
}
public void addForScanning(BackgroundScanHandler backgroundScanHandler)
{
backgroundScanHandler.setLoadingModList(this);
modFiles.stream()
.map(ModFileInfo::getFile)
.forEach(backgroundScanHandler::submitForScanning);
}
public List<ModFileInfo> getModFiles()
{
return modFiles;
}
public Path findResource(final String className)
{
for (ModFileInfo mf : modFiles) {
final Path resource = mf.getFile().findResource(className);
if (Files.exists(resource)) return resource;
}
return null;
}
public Enumeration<URL> findAllURLsForResource(final String resName) {
final String resourceName;
// strip a leading slash
if (resName.startsWith("/")) {
resourceName = resName.substring(1);
} else {
resourceName = resName;
}
return new Enumeration<URL>() {
private final Iterator<ModFileInfo> modFileIterator = modFiles.iterator();
private URL next;
@Override
public boolean hasMoreElements() {
if (next!=null) return true;
next = findNextURL();
return next != null;
}
@Override
public URL nextElement() {
if (next == null) {
next = findNextURL();
if (next == null) throw new NoSuchElementException();
}
URL result = next;
next = null;
return result;
}
private URL findNextURL() {
while (modFileIterator.hasNext()) {
final ModFileInfo next = modFileIterator.next();
final Path resource = next.getFile().findResource(resourceName);
if (Files.exists(resource)) {
return LamdbaExceptionUtils.uncheck(()->new URL("modjar://" + next.getMods().get(0).getModId() + "/" + resourceName));
}
}
return null;
}
};
}
public ModFileInfo getModFileById(String modid)
{
return this.fileById.get(modid);
}
public List<ModInfo> getMods()
{
return this.sortedList;
}
public List<EarlyLoadingException> getErrors() {
return preLoadErrors;
}
public void setBrokenFiles(final List<IModFile> brokenFiles) {
this.brokenFiles = brokenFiles;
}
public List<IModFile> getBrokenFiles() {
return this.brokenFiles;
}
}

View file

@ -0,0 +1,16 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
public class LogMarkers {
public static final Marker CORE = MarkerFactory.getMarker("CORE");
public static final Marker LOADING = MarkerFactory.getMarker("LOADING");
public static final Marker SCAN = MarkerFactory.getMarker("SCAN");
public static final Marker SPLASH = MarkerFactory.getMarker("SPLASH");
}

View file

@ -0,0 +1,90 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.api.INameMappingService;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
public class MCPNamingService implements INameMappingService {
private static final Logger LOGGER = LogUtils.getLogger();
private HashMap<String, String> methods;
private HashMap<String, String> fields;
@Override
public String mappingName() {
return "srgtomcp";
}
@Override
public String mappingVersion() {
return "1234"; //TODO: Minecraft Version?
}
@Override
public Map.Entry<String, String> understanding() {
return Pair.of("srg", "mcp");
}
@Override
public BiFunction<Domain, String, String> namingFunction() {
return this::findMapping;
}
private String findMapping(final Domain domain, final String srgName) {
switch (domain) {
case CLASS:
return srgName;
case FIELD:
return findFieldMapping(srgName);
case METHOD:
return findMethodMapping(srgName);
}
return srgName;
}
private String findMethodMapping(final String origin) {
if (methods == null) {
HashMap<String,String> tmpmethods = new HashMap<>(1000);
loadMappings("methods.csv", tmpmethods::put);
methods = tmpmethods;
LOGGER.debug(LogMarkers.CORE, "Loaded {} method mappings from methods.csv", methods.size());
}
return methods.getOrDefault(origin, origin);
}
private String findFieldMapping(final String origin) {
if (fields == null) {
HashMap<String,String> tmpfields = new HashMap<>(1000);
loadMappings("fields.csv", tmpfields::put);
fields = tmpfields;
LOGGER.debug(LogMarkers.CORE, "Loaded {} field mappings from fields.csv", fields.size());
}
return fields.getOrDefault(origin, origin);
}
private static void loadMappings(final String mappingFileName, BiConsumer<String, String> mapStore) {
URL path = ClassLoader.getSystemResource(mappingFileName); //We EXPLICITLY go through the SystemClassLoader here because this is dev-time only. And will be on the root classpath.
if (path == null)
return;
try (BufferedReader reader = new BufferedReader(new InputStreamReader(path.openStream()))) {
reader.lines().skip(1).map(e -> e.split(",")).forEach(e -> mapStore.accept(e[0], e[1]));
} catch (IOException e1) {
LOGGER.error(LogMarkers.CORE, "Error reading mappings", e1);
}
}
}

View file

@ -0,0 +1,44 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* Convert a maven coordinate into a Path.
*
* This is gradle standard not maven standard coordinate formatting
* {@code <groupId>:<artifactId>[:<classifier>]:<version>[@extension]}, must not be {@code null}.
*/
public class MavenCoordinateResolver {
public static Path get(final String coordinate) {
final String[] parts = coordinate.split(":");
final String groupId = parts[0];
final String artifactId = parts[1];
final String classifier = parts.length > 3 ? parts[2] : "";
final String[] versext = parts[parts.length-1].split("@");
final String version = versext[0];
final String extension = versext.length > 1 ? versext[1] : "";
return get(groupId, artifactId, extension, classifier, version);
}
public static Path get(final String groupId, final String artifactId, final String extension, final String classifier, final String version)
{
final String fileName = artifactId + "-" + version +
(!classifier.isEmpty() ? "-"+ classifier : "") +
(!extension.isEmpty() ? "." + extension : ".jar");
String[] groups = groupId.split("\\.");
Path result = Paths.get(groups[0]);
for (int i = 1; i < groups.length; i++) {
result = result.resolve(groups[i]);
}
return result.resolve(artifactId).resolve(version).resolve(fileName);
}
}

View file

@ -0,0 +1,82 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import com.mojang.logging.LogUtils;
import cpw.mods.jarhandling.SecureJar;
import cpw.mods.modlauncher.Launcher;
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
import cpw.mods.modlauncher.api.NamedPath;
import cpw.mods.modlauncher.serviceapi.ITransformerDiscoveryService;
import org.slf4j.Logger;
import java.io.IOException;
import java.lang.module.ModuleDescriptor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.ServiceLoader;
import java.util.zip.ZipFile;
import java.util.Set;
public class ModDirTransformerDiscoverer implements ITransformerDiscoveryService {
private static final Logger LOGGER = LogUtils.getLogger();
private static final Set<String> SERVICES = Set.of(
"cpw.mods.modlauncher.api.ITransformationService",
"net.minecraftforge.forgespi.locating.IModLocator",
"net.minecraftforge.forgespi.locating.IDependencyLocator"
);
@Override
public List<NamedPath> candidates(final Path gameDirectory, final String launchTarget) {
FMLPaths.loadAbsolutePaths(gameDirectory);
FMLConfig.load();
return candidates(gameDirectory);
}
@Override
public void earlyInitialization(final String launchTarget, final String[] arguments) {
ImmediateWindowHandler.load(launchTarget, arguments);
}
@Override
public List<NamedPath> candidates(final Path gameDirectory) {
ModDirTransformerDiscoverer.scan(gameDirectory);
return List.copyOf(found);
}
private final static List<NamedPath> found = new ArrayList<>();
public static List<Path> allExcluded() {
return found.stream().map(np->np.paths()[0]).toList();
}
private static void scan(final Path gameDirectory) {
final Path modsDir = gameDirectory.resolve(FMLPaths.MODSDIR.relative()).toAbsolutePath().normalize();
if (!Files.exists(modsDir)) {
// Skip if the mods dir doesn't exist yet.
return;
}
try (var walk = Files.walk(modsDir, 1)){
walk.forEach(ModDirTransformerDiscoverer::visitFile);
} catch (IOException | IllegalStateException ioe) {
LOGGER.error("Error during early discovery", ioe);
}
}
private static void visitFile(Path path) {
if (!Files.isRegularFile(path)) return;
if (!path.toString().endsWith(".jar")) return;
if (LamdbaExceptionUtils.uncheck(() -> Files.size(path)) == 0) return;
SecureJar jar = SecureJar.from(path);
jar.moduleDataProvider().descriptor().provides().stream()
.map(ModuleDescriptor.Provides::service)
.filter(SERVICES::contains)
.forEach(s -> found.add(new NamedPath(s, path)));
}
}

View file

@ -0,0 +1,96 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
import org.slf4j.Logger;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
import java.util.Optional;
import java.util.jar.Manifest;
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
public class ModJarURLHandler extends URLStreamHandler
{
private static final Logger LOGGER = LogUtils.getLogger();
// modjar://modid/path/to/file
@Override
protected URLConnection openConnection(URL url) {
return new ModJarURLConnection(url);
}
@Override
protected int hashCode(final URL u) {
return Objects.hash(u.getHost(), u.getFile());
}
@Override
protected boolean equals(final URL u1, final URL u2) {
return Objects.equals(u1.getProtocol(), u2.getProtocol())
&& Objects.equals(u1.getHost(), u2.getHost())
&& Objects.equals(u1.getFile(), u2.getFile());
}
static class ModJarURLConnection extends URLConnection {
private Path resource;
private String modpath;
private String modid;
private Optional<Manifest> manifest;
public ModJarURLConnection(final URL url) {
super(url);
}
@Override
public void connect()
{
// if (resource == null) {
// modid = url.getHost();
// // trim first char
// modpath = url.getPath().substring(1);
// resource = FMLLoader.getLoadingModList().getModFileById(modid).getFile().findResource(modpath);
// manifest = FMLLoader.getLoadingModList().getModFileById(modid).getManifest();
// }
}
@Override
public InputStream getInputStream() throws IOException
{
connect();
LOGGER.trace(CORE, "Loading modjar URL {} got resource {} {}", url, resource, resource != null ? Files.exists(resource) : "missing");
return Files.newInputStream(resource);
}
@Override
public long getContentLengthLong() {
try {
connect();
return Files.size(resource);
} catch (IOException e) {
return -1L;
}
}
// Used to cache protectiondomains by "top level object" aka the modid
@Override
public URL getURL() {
return LamdbaExceptionUtils.uncheck(()->new URL("modjar://"+modid));
}
public Optional<Manifest> getManifest() {
return manifest;
}
}
}

View file

@ -0,0 +1,259 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import com.google.common.graph.GraphBuilder;
import com.google.common.graph.MutableGraph;
import com.mojang.logging.LogUtils;
import cpw.mods.jarhandling.SecureJar;
import net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator;
import net.minecraftforge.forgespi.language.IModFileInfo;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.fml.loading.EarlyLoadingException.ExceptionData;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
import net.minecraftforge.fml.loading.toposort.CyclePresentException;
import net.minecraftforge.fml.loading.toposort.TopologicalSort;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.slf4j.Logger;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.jar.Manifest;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.stream.Collectors.*;
import static net.minecraftforge.fml.loading.LogMarkers.LOADING;
public class ModSorter
{
private static final Logger LOGGER = LogUtils.getLogger();
private final UniqueModListBuilder uniqueModListBuilder;
private List<ModFile> modFiles;
private List<ModInfo> sortedList;
private Map<String, IModInfo> modIdNameLookup;
private List<ModFile> systemMods;
private ModSorter(final List<ModFile> modFiles)
{
this.uniqueModListBuilder = new UniqueModListBuilder(modFiles);
}
public static LoadingModList sort(List<ModFile> mods, final List<ExceptionData> errors)
{
final ModSorter ms = new ModSorter(mods);
try {
ms.buildUniqueList();
} catch (EarlyLoadingException e) {
// We cannot build any list with duped mods. We have to abort immediately and report it
return LoadingModList.of(ms.systemMods, ms.systemMods.stream().map(mf->(ModInfo)mf.getModInfos().get(0)).collect(toList()), e);
}
// try and validate dependencies
final List<ExceptionData> failedList = Stream.concat(ms.verifyDependencyVersions().stream(), errors.stream()).toList();
// if we miss one or the other, we abort now
if (!failedList.isEmpty()) {
return LoadingModList.of(ms.systemMods, ms.systemMods.stream().map(mf->(ModInfo)mf.getModInfos().get(0)).collect(toList()), new EarlyLoadingException("failure to validate mod list", null, failedList));
} else {
// Otherwise, lets try and sort the modlist and proceed
EarlyLoadingException earlyLoadingException = null;
try {
ms.sort();
} catch (EarlyLoadingException e) {
earlyLoadingException = e;
}
return LoadingModList.of(ms.modFiles, ms.sortedList, earlyLoadingException);
}
}
@SuppressWarnings("UnstableApiUsage")
private void sort()
{
// lambdas are identity based, so sorting them is impossible unless you hold reference to them
final MutableGraph<ModFileInfo> graph = GraphBuilder.directed().build();
AtomicInteger counter = new AtomicInteger();
Map<ModFileInfo, Integer> infos = modFiles.stream()
.map(ModFile::getModFileInfo)
.filter(ModFileInfo.class::isInstance)
.map(ModFileInfo.class::cast)
.collect(toMap(Function.identity(), e -> counter.incrementAndGet()));
infos.keySet().forEach(graph::addNode);
modFiles.stream()
.map(ModFile::getModInfos)
.<IModInfo>mapMulti(Iterable::forEach)
.map(IModInfo::getDependencies)
.<IModInfo.ModVersion>mapMulti(Iterable::forEach)
.forEach(dep -> addDependency(graph, dep));
final List<ModFileInfo> sorted;
try
{
sorted = TopologicalSort.topologicalSort(graph, Comparator.comparing(infos::get));
}
catch (CyclePresentException e)
{
Set<Set<ModFileInfo>> cycles = e.getCycles();
if (LOGGER.isErrorEnabled(LOADING))
{
LOGGER.error(LOADING, "Mod Sorting failed.\nDetected Cycles: {}\n", cycles);
}
var dataList = cycles.stream()
.<ModFileInfo>mapMulti(Iterable::forEach)
.<IModInfo>mapMulti((mf,c)->mf.getMods().forEach(c))
.map(IModInfo::getModId)
.map(list -> new ExceptionData("fml.modloading.cycle", list))
.toList();
throw new EarlyLoadingException("Sorting error", e, dataList);
}
this.sortedList = sorted.stream()
.map(ModFileInfo::getMods)
.<IModInfo>mapMulti(Iterable::forEach)
.map(ModInfo.class::cast)
.collect(toList());
this.modFiles = sorted.stream()
.map(ModFileInfo::getFile)
.collect(toList());
}
@SuppressWarnings("UnstableApiUsage")
private void addDependency(MutableGraph<ModFileInfo> topoGraph, IModInfo.ModVersion dep)
{
final ModFileInfo self = (ModFileInfo)dep.getOwner().getOwningFile();
final IModInfo targetModInfo = modIdNameLookup.get(dep.getModId());
// soft dep that doesn't exist. Just return. No edge required.
if (targetModInfo == null || !(targetModInfo.getOwningFile() instanceof final ModFileInfo target)) return;
if (self == target)
return; // in case a jar has two mods that have dependencies between
switch (dep.getOrdering()) {
case BEFORE -> topoGraph.putEdge(self, target);
case AFTER -> topoGraph.putEdge(target, self);
}
}
private void buildUniqueList()
{
final UniqueModListBuilder.UniqueModListData uniqueModListData = uniqueModListBuilder.buildUniqueList();
this.modFiles = uniqueModListData.modFiles();
detectSystemMods(uniqueModListData.modFilesByFirstId());
modIdNameLookup = uniqueModListData.modFilesByFirstId().entrySet().stream()
.filter(e -> !e.getValue().get(0).getModInfos().isEmpty())
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> e.getValue().get(0).getModInfos().get(0)
));
}
private void detectSystemMods(final Map<String, List<ModFile>> modFilesByFirstId)
{
// Capture system mods (ex. MC, Forge) here, so we can keep them for later
final Set<String> systemMods = new HashSet<>();
// The minecraft mod is always a system mod
systemMods.add("minecraft");
// Find mod file from MinecraftLocator to define the system mods
modFiles.stream()
.filter(modFile -> modFile.getProvider().getClass() == MinecraftLocator.class)
.map(ModFile::getSecureJar)
.map(SecureJar::moduleDataProvider)
.map(SecureJar.ModuleDataProvider::getManifest)
.map(Manifest::getMainAttributes)
.map(mf -> mf.getValue("FML-System-Mods"))
.filter(Objects::nonNull)
.findFirst()
.ifPresent(value -> systemMods.addAll(Arrays.asList(value.split(","))));
LOGGER.debug("Configured system mods: {}", systemMods);
this.systemMods = new ArrayList<>();
for (String systemMod : systemMods) {
var container = modFilesByFirstId.get(systemMod);
if (container != null && !container.isEmpty()) {
LOGGER.debug("Found system mod: {}", systemMod);
this.systemMods.add((ModFile) container.get(0));
} else {
throw new IllegalStateException("Failed to find system mod: " + systemMod);
}
}
}
private List<EarlyLoadingException.ExceptionData> verifyDependencyVersions()
{
final var modVersions = modFiles.stream()
.map(ModFile::getModInfos)
.<IModInfo>mapMulti(Iterable::forEach)
.collect(toMap(IModInfo::getModId, IModInfo::getVersion));
final var modVersionDependencies = modFiles.stream()
.map(ModFile::getModInfos)
.<IModInfo>mapMulti(Iterable::forEach)
.collect(groupingBy(Function.identity(), flatMapping(e -> e.getDependencies().stream(), toList())));
final var modRequirements = modVersionDependencies.values().stream()
.<IModInfo.ModVersion>mapMulti(Iterable::forEach)
.filter(mv -> mv.getSide().isCorrectSide())
.collect(toSet());
final long mandatoryRequired = modRequirements.stream().filter(IModInfo.ModVersion::isMandatory).count();
LOGGER.debug(LOADING, "Found {} mod requirements ({} mandatory, {} optional)", modRequirements.size(), mandatoryRequired, modRequirements.size() - mandatoryRequired);
final var missingVersions = modRequirements.stream()
.filter(mv -> (mv.isMandatory() || modVersions.containsKey(mv.getModId())) && this.modVersionNotContained(mv, modVersions))
.collect(toSet());
final long mandatoryMissing = missingVersions.stream().filter(IModInfo.ModVersion::isMandatory).count();
LOGGER.debug(LOADING, "Found {} mod requirements missing ({} mandatory, {} optional)", missingVersions.size(), mandatoryMissing, missingVersions.size() - mandatoryMissing);
if (!missingVersions.isEmpty()) {
if (mandatoryMissing > 0) {
LOGGER.error(
LOADING,
"Missing or unsupported mandatory dependencies:\n{}",
missingVersions.stream()
.filter(IModInfo.ModVersion::isMandatory)
.map(ver -> formatDependencyError(ver, modVersions))
.collect(Collectors.joining("\n"))
);
}
if (missingVersions.size() - mandatoryMissing > 0) {
LOGGER.error(
LOADING,
"Unsupported installed optional dependencies:\n{}",
missingVersions.stream()
.filter(ver -> !ver.isMandatory())
.map(ver -> formatDependencyError(ver, modVersions))
.collect(Collectors.joining("\n"))
);
}
return missingVersions.stream()
.map(mv -> new ExceptionData(mv.isMandatory() ? "fml.modloading.missingdependency" : "fml.modloading.missingdependency.optional",
mv.getOwner(), mv.getModId(), mv.getOwner().getModId(), mv.getVersionRange(),
modVersions.getOrDefault(mv.getModId(), new DefaultArtifactVersion("null"))))
.toList();
}
return Collections.emptyList();
}
private static String formatDependencyError(IModInfo.ModVersion dependency, Map<String, ArtifactVersion> modVersions)
{
ArtifactVersion installed = modVersions.get(dependency.getModId());
return String.format(
"\tMod ID: '%s', Requested by: '%s', Expected range: '%s', Actual version: '%s'",
dependency.getModId(),
dependency.getOwner().getModId(),
dependency.getVersionRange(),
installed != null ? installed.toString() : "[MISSING]"
);
}
private boolean modVersionNotContained(final IModInfo.ModVersion mv, final Map<String, ArtifactVersion> modVersions)
{
return !(VersionSupportMatrix.testVersionSupportMatrix(mv.getVersionRange(), mv.getModId(), "mod", (modId, range) -> modVersions.containsKey(modId) &&
(range.containsVersion(modVersions.get(modId)) || modVersions.get(modId).toString().equals("0.0NONE"))));
}
}

View file

@ -0,0 +1,209 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import com.google.common.collect.Streams;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.api.distmarker.OnlyIns;
import org.objectweb.asm.Handle;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.MethodNode;
import org.slf4j.Logger;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
public class RuntimeDistCleaner implements ILaunchPluginService
{
private static final Logger LOGGER = LogUtils.getLogger();
private static final Marker DISTXFORM = MarkerFactory.getMarker("DISTXFORM");
private static String DIST;
private static final String ONLYIN = Type.getDescriptor(OnlyIn.class);
private static final String ONLYINS = Type.getDescriptor(OnlyIns.class);
@Override
public String name()
{
return "runtimedistcleaner";
}
@Override
public int processClassWithFlags(final Phase phase, final ClassNode classNode, final Type classType, final String reason)
{
AtomicBoolean changes = new AtomicBoolean();
if (remove(classNode.visibleAnnotations, DIST))
{
LOGGER.error(DISTXFORM, "Attempted to load class {} for invalid dist {}", classNode.name, DIST);
throw new RuntimeException("Attempted to load class "+ classNode.name + " for invalid dist "+ DIST);
}
if (classNode.interfaces != null )
{
unpack(classNode.visibleAnnotations).stream()
.filter(ann->Objects.equals(ann.desc, ONLYIN))
.filter(ann->ann.values.indexOf("_interface") != -1)
.filter(ann->!Objects.equals(((String[])ann.values.get(ann.values.indexOf("value") + 1))[1], DIST))
.map(ann -> ((Type)ann.values.get(ann.values.indexOf("_interface") + 1)).getInternalName())
.forEach(intf -> {
if (classNode.interfaces.remove(intf)) {
LOGGER.debug(DISTXFORM,"Removing Interface: {} implements {}", classNode.name, intf);
changes.compareAndSet(false, true);
}
});
//Remove Class level @OnlyIn/@OnlyIns annotations, this is important if anyone gets ambitious and tries to reflect an annotation with _interface set.
if (classNode.visibleAnnotations != null) {
Iterator<AnnotationNode> itr = classNode.visibleAnnotations.iterator();
while (itr.hasNext()) {
AnnotationNode ann = itr.next();
if (Objects.equals(ann.desc, ONLYIN) || Objects.equals(ann.desc, ONLYINS)) {
LOGGER.debug(DISTXFORM,"Removing Class Annotation: {} @{}", classNode.name, ann.desc);
itr.remove();
changes.compareAndSet(false, true);
}
}
}
}
Iterator<FieldNode> fields = classNode.fields.iterator();
while(fields.hasNext())
{
FieldNode field = fields.next();
if (remove(field.visibleAnnotations, DIST))
{
LOGGER.debug(DISTXFORM,"Removing field: {}.{}", classNode.name, field.name);
fields.remove();
changes.compareAndSet(false, true);
}
}
LambdaGatherer lambdaGatherer = new LambdaGatherer();
Iterator<MethodNode> methods = classNode.methods.iterator();
while(methods.hasNext())
{
MethodNode method = methods.next();
if (remove(method.visibleAnnotations, DIST))
{
LOGGER.debug(DISTXFORM,"Removing method: {}.{}{}", classNode.name, method.name, method.desc);
methods.remove();
lambdaGatherer.accept(method);
changes.compareAndSet(false, true);
}
}
// remove dynamic synthetic lambda methods that are inside of removed methods
for (List<Handle> dynamicLambdaHandles = lambdaGatherer.getDynamicLambdaHandles();
!dynamicLambdaHandles.isEmpty(); dynamicLambdaHandles = lambdaGatherer.getDynamicLambdaHandles())
{
lambdaGatherer = new LambdaGatherer();
methods = classNode.methods.iterator();
while (methods.hasNext())
{
MethodNode method = methods.next();
if ((method.access & Opcodes.ACC_SYNTHETIC) == 0) continue;
for (Handle dynamicLambdaHandle : dynamicLambdaHandles)
{
if (method.name.equals(dynamicLambdaHandle.getName()) && method.desc.equals(dynamicLambdaHandle.getDesc()))
{
LOGGER.debug(DISTXFORM,"Removing lambda method: {}.{}{}", classNode.name, method.name, method.desc);
methods.remove();
lambdaGatherer.accept(method);
changes.compareAndSet(false, true);
}
}
}
}
return changes.get() ? ComputeFlags.SIMPLE_REWRITE : ComputeFlags.NO_REWRITE;
}
@SuppressWarnings("unchecked")
private static List<AnnotationNode> unpack(final List<AnnotationNode> anns) {
if (anns == null) return Collections.emptyList();
List<AnnotationNode> ret = anns.stream().filter(ann->Objects.equals(ann.desc, ONLYIN)).collect(Collectors.toList());
anns.stream().filter(ann->Objects.equals(ann.desc, ONLYINS) && ann.values != null)
.map( ann -> (List<AnnotationNode>)ann.values.get(ann.values.indexOf("value") + 1))
.filter(v -> v != null)
.forEach(v -> v.forEach(ret::add));
return ret;
}
private boolean remove(final List<AnnotationNode> anns, final String side)
{
return unpack(anns).stream().
filter(ann->Objects.equals(ann.desc, ONLYIN)).
filter(ann->ann.values.indexOf("_interface") == -1).
anyMatch(ann -> !Objects.equals(((String[])ann.values.get(ann.values.indexOf("value")+1))[1], side));
}
@SuppressWarnings("unchecked")
@Override
public Consumer<Dist> getExtension()
{
return (s)-> {
DIST = s.name();
LOGGER.debug(DISTXFORM, "Configuring for Dist {}", DIST);
};
}
private static final EnumSet<Phase> YAY = EnumSet.of(Phase.AFTER);
private static final EnumSet<Phase> NAY = EnumSet.noneOf(Phase.class);
@Override
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty)
{
return isEmpty ? NAY : YAY;
}
private static class LambdaGatherer extends MethodVisitor {
private static final Handle META_FACTORY = new Handle(Opcodes.H_INVOKESTATIC,
"java/lang/invoke/LambdaMetafactory", "metafactory",
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;",
false);
private final List<Handle> dynamicLambdaHandles = new ArrayList<>();
public LambdaGatherer() {
super(Opcodes.ASM9);
}
public void accept(MethodNode method) {
Streams.stream(method.instructions.iterator()).
filter(insnNode->insnNode.getType() == AbstractInsnNode.INVOKE_DYNAMIC_INSN).
forEach(insnNode->insnNode.accept(this));
}
@Override
public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs)
{
if (META_FACTORY.equals(bsm))
{
Handle dynamicLambdaHandle = (Handle) bsmArgs[1];
dynamicLambdaHandles.add(dynamicLambdaHandle);
}
}
public List<Handle> getDynamicLambdaHandles()
{
return dynamicLambdaHandles;
}
}
}

View file

@ -0,0 +1,48 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import com.google.common.collect.ImmutableMap;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import org.apache.commons.lang3.text.StrLookup;
import org.apache.commons.lang3.text.StrSubstitutor;
import java.util.Map;
@SuppressWarnings("deprecation")
public class StringSubstitutor
{
private static final Map<String,String> globals = ImmutableMap.of(
"mcVersion", FMLLoader.versionInfo().mcVersion(),
"forgeVersion", FMLLoader.versionInfo().forgeVersion()
);
public static String replace(final String in, final ModFile file) {
return new StrSubstitutor(getStringLookup(file)).replace(in);
}
private static StrLookup<String> getStringLookup(final ModFile file) {
return new StrLookup<String>()
{
@Override
public String lookup(String key)
{
final String[] parts = key.split("\\.");
if (parts.length == 1) return key;
final String pfx = parts[0];
if ("global".equals(pfx))
{
return globals.get(parts[1]);
}
else if ("file".equals(pfx) && file != null)
{
return String.valueOf(file.getSubstitutionMap().get().get(parts[1]));
}
return key;
}
};
}
}

View file

@ -0,0 +1,52 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import org.apache.commons.lang3.text.StrSubstitutor;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Locale;
import java.util.Map;
public class StringUtils {
public static String toLowerCase(final String str) {
return str.toLowerCase(Locale.ROOT);
}
public static String toUpperCase(final String str) {
return str.toUpperCase(Locale.ROOT);
}
public static boolean endsWith(final String search, final String... endings) {
String lowerSearch = toLowerCase(search);
return java.util.stream.Stream.of(endings).anyMatch(lowerSearch::endsWith);
}
public static URL toURL(final String string) {
if (string == null || string.trim().isEmpty() || string.contains("myurl.me") || string.contains("example.invalid"))
return null;
try {
return new URL(string); }
catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
public static String parseStringFormat(final String input, final Map<String, String> properties) {
return StrSubstitutor.replace(input, properties);
}
public static String binToHex(final byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
sb.append(Integer.toHexString((bytes[i]&0xf0) >>4));
sb.append(Integer.toHexString(bytes[i]&0x0f));
}
return sb.toString();
}
}

View file

@ -0,0 +1,100 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import org.slf4j.Logger;
import java.io.PrintStream;
/**
* PrintStream which redirects it's output to a given logger.
*
*/
public class TracingPrintStream extends PrintStream
{
private static final int BASE_DEPTH = 4;
private final Logger logger;
public TracingPrintStream(Logger logger, PrintStream original)
{
super(original);
this.logger = logger;
}
private void log(String s)
{
logger.info("{}{}", getPrefix(), s);
}
private static String getPrefix()
{
StackTraceElement[] elems = Thread.currentThread().getStackTrace();
StackTraceElement elem = elems[Math.min(BASE_DEPTH, elems.length - 1)]; // The caller is always at BASE_DEPTH, including this call.
if (elem.getClassName().startsWith("kotlin.io."))
{
elem = elems[Math.min(BASE_DEPTH + 2, elems.length - 1)]; // Kotlins IoPackage masks origins 2 deeper in the stack.
}
else if (elem.getClassName().startsWith("java.lang.Throwable"))
{
elem = elems[Math.min(BASE_DEPTH + 4, elems.length - 1)];
}
return "[" + elem.getClassName() + ":" + elem.getMethodName() + ":" + elem.getLineNumber() + "]: ";
}
@Override
public void println(Object o)
{
log(String.valueOf(o));
}
@Override
public void println(String s)
{
log(s);
}
@Override
public void println(boolean x)
{
log(String.valueOf(x));
}
@Override
public void println(char x)
{
log(String.valueOf(x));
}
@Override
public void println(int x)
{
log(String.valueOf(x));
}
@Override
public void println(long x)
{
log(String.valueOf(x));
}
@Override
public void println(float x)
{
log(String.valueOf(x));
}
@Override
public void println(double x)
{
log(String.valueOf(x));
}
@Override
public void println(char[] x)
{
log(String.valueOf(x));
}
}

View file

@ -0,0 +1,146 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import net.minecraftforge.forgespi.language.IModInfo;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.slf4j.Logger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.joining;
import static net.minecraftforge.fml.loading.LogMarkers.LOADING;
public class UniqueModListBuilder
{
private final static Logger LOGGER = LogUtils.getLogger();
private final List<ModFile> modFiles;
public UniqueModListBuilder(final List<ModFile> modFiles) {this.modFiles = modFiles;}
public UniqueModListData buildUniqueList()
{
List<ModFile> uniqueModList;
List<ModFile> uniqueLibListWithVersion;
// Collect mod files by module name. This will be used for deduping purposes
final Map<String, List<ModFile>> modFilesByFirstId = modFiles.stream()
.filter(mf -> mf.getModFileInfo() != null)
.collect(groupingBy(UniqueModListBuilder::getModId));
final Map<String, List<ModFile>> libFilesWithVersionByModuleName = modFiles.stream()
.filter(mf -> mf.getModFileInfo() == null)
.collect(groupingBy(UniqueModListBuilder::getModId));
// Select the newest by artifact version sorting of non-unique files thus identified
uniqueModList = modFilesByFirstId.entrySet().stream()
.map(this::selectNewestModInfo)
.toList();
// Select the newest by artifact version sorting of non-unique files thus identified
uniqueLibListWithVersion = libFilesWithVersionByModuleName.entrySet().stream()
.map(this::selectNewestModInfo)
.toList();
// Transform to the full mod id list
final Map<String, List<IModInfo>> modIds = uniqueModList.stream()
.filter(mf -> mf.getModFileInfo() != null) //Filter out non-mod files, we don't care about those for now.....
.map(ModFile::getModInfos)
.flatMap(Collection::stream)
.collect(groupingBy(IModInfo::getModId));
// Transform to the full lib id list
final Map<String, List<ModFile>> versionedLibIds = uniqueLibListWithVersion.stream()
.map(UniqueModListBuilder::getModId)
.collect(Collectors.toMap(
Function.identity(),
libFilesWithVersionByModuleName::get
));
// Its theoretically possible that some mod has somehow moved an id to a secondary place, thus causing a dupe.
// We can't handle this
final List<String> dupedModErrors = modIds.values().stream()
.filter(modInfos -> modInfos.size() > 1)
.map(mods -> String.format("\tMod ID: '%s' from mod files: %s",
mods.get(0).getModId(),
mods.stream()
.map(modInfo -> modInfo.getOwningFile().getFile().getFileName()).collect(joining(", "))
)).toList();
if (!dupedModErrors.isEmpty()) {
LOGGER.error(LOADING, "Found duplicate mods:\n{}", dupedModErrors.stream().collect(joining("\n")));
throw new EarlyLoadingException("Duplicate mods found", null, dupedModErrors.stream()
.map(s -> new EarlyLoadingException.ExceptionData(s))
.toList());
}
final List<String> dupedLibErrors = versionedLibIds.values().stream()
.filter(modFiles -> modFiles.size() > 1)
.map(mods -> String.format("\tLibrary: '%s' from files: %s",
getModId(mods.get(0)),
mods.stream()
.map(modFile -> modFile.getFileName()).collect(joining(", "))
)).toList();
if (!dupedLibErrors.isEmpty()) {
LOGGER.error(LOADING, "Found duplicate plugins or libraries:\n{}", dupedLibErrors.stream().collect(joining("\n")));
throw new EarlyLoadingException("Duplicate plugins or libraries found", null, dupedLibErrors.stream()
.map(s -> new EarlyLoadingException.ExceptionData(s))
.toList());
}
// Collect unique mod files by module name. This will be used for deduping purposes
final Map<String, List<ModFile>> uniqueModFilesByFirstId = uniqueModList.stream()
.collect(groupingBy(UniqueModListBuilder::getModId));
final List<ModFile> loadedList = new ArrayList<>();
loadedList.addAll(uniqueModList);
loadedList.addAll(uniqueLibListWithVersion);
return new UniqueModListData(loadedList, uniqueModFilesByFirstId);
}
private ModFile selectNewestModInfo(Map.Entry<String, List<ModFile>> fullList) {
List<ModFile> modInfoList = fullList.getValue();
if (modInfoList.size() > 1) {
LOGGER.debug("Found {} mods for first modid {}, selecting most recent based on version data", modInfoList.size(), fullList.getKey());
modInfoList.sort(Comparator.comparing(this::getVersion).reversed());
LOGGER.debug("Selected file {} for modid {} with version {}", modInfoList.get(0).getFileName(), fullList.getKey(), this.getVersion(modInfoList.get(0)));
}
return modInfoList.get(0);
}
private ArtifactVersion getVersion(final ModFile mf)
{
if (mf.getModFileInfo() == null || mf.getModInfos() == null || mf.getModInfos().isEmpty()) {
return mf.getJarVersion();
}
return mf.getModInfos().get(0).getVersion();
}
private static String getModId(ModFile modFile) {
if (modFile.getModFileInfo() == null || modFile.getModFileInfo().getMods().isEmpty()) {
return modFile.getSecureJar().name();
}
return modFile.getModFileInfo().moduleName();
}
public record UniqueModListData(List<ModFile> modFiles, Map<String, List<ModFile>> modFilesByFirstId) {}
}

View file

@ -0,0 +1,22 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import java.util.Map;
public record VersionInfo(String forgeVersion, String mcVersion, String mcpVersion, String forgeGroup) {
VersionInfo(Map<String, ?> arguments) {
this((String) arguments.get("forgeVersion"), (String) arguments.get("mcVersion"), (String) arguments.get("mcpVersion"), (String) arguments.get("forgeGroup"));
}
public String mcAndForgeVersion() {
return mcVersion+"-"+forgeVersion;
}
public String mcAndMCPVersion() {
return mcVersion + "-" + mcpVersion;
}
}

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import net.minecraftforge.forgespi.language.MavenVersionAdapter;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.artifact.versioning.VersionRange;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.function.BiPredicate;
public class VersionSupportMatrix {
private static final HashMap<String, List<ArtifactVersion>> overrideVersions = new HashMap<>();
static {
final ArtifactVersion version = new DefaultArtifactVersion(FMLLoader.versionInfo().mcVersion());
if (MavenVersionAdapter.createFromVersionSpec("[1.19.2]").containsVersion(version)) {
// 1.19.2 is Compatible with 1.19.1
add("languageloader.javafml", "42");
add("mod.minecraft", "1.19.1");
add("mod.forge", "42.0.9");
}
}
private static void add(String key, String value) {
overrideVersions.computeIfAbsent(key, k -> new ArrayList<>()).add(new DefaultArtifactVersion(value));
}
public static <T> boolean testVersionSupportMatrix(VersionRange declaredRange, String lookupId, String type, BiPredicate<String, VersionRange> standardLookup) {
if (standardLookup.test(lookupId, declaredRange)) return true;
List<ArtifactVersion> custom = overrideVersions.get(type +"." +lookupId);
return custom == null ? false : custom.stream().anyMatch(declaredRange::containsVersion);
}
}

View file

@ -0,0 +1,42 @@
<html>
<head><title>Index of /mirror/src/Magma-1-20-x/fmlloader/src/main/java/net/minecraftforge/fml/loading/</title></head>
<body>
<h1>Index of /mirror/src/Magma-1-20-x/fmlloader/src/main/java/net/minecraftforge/fml/loading/</h1><hr><pre><a href="../">../</a>
<a href="log4j/">log4j/</a> 07-Oct-2023 14:12 -
<a href="moddiscovery/">moddiscovery/</a> 06-Nov-2023 18:00 -
<a href="progress/">progress/</a> 07-Oct-2023 14:12 -
<a href="targets/">targets/</a> 07-Oct-2023 14:12 -
<a href="toposort/">toposort/</a> 07-Oct-2023 14:12 -
<a href="BackgroundWaiter.java">BackgroundWaiter.java</a> 07-Oct-2023 14:12 1156
<a href="ClasspathLocatorUtils.java">ClasspathLocatorUtils.java</a> 07-Oct-2023 14:12 1505
<a href="ClasspathTransformerDiscoverer.java">ClasspathTransformerDiscoverer.java</a> 07-Oct-2023 14:12 2286
<a href="EarlyLoadingException.java">EarlyLoadingException.java</a> 07-Oct-2023 14:12 1495
<a href="FMLConfig.java">FMLConfig.java</a> 07-Oct-2023 14:12 6750
<a href="FMLEnvironment.java">FMLEnvironment.java</a> 07-Oct-2023 14:12 1165
<a href="FMLLoader.java">FMLLoader.java</a> 07-Oct-2023 14:12 12K
<a href="FMLPaths.java">FMLPaths.java</a> 07-Oct-2023 14:12 2678
<a href="FMLServiceProvider.java">FMLServiceProvider.java</a> 07-Oct-2023 14:12 6068
<a href="FileUtils.java">FileUtils.java</a> 07-Oct-2023 14:12 949
<a href="ImmediateWindowHandler.java">ImmediateWindowHandler.java</a> 07-Oct-2023 14:12 6771
<a href="ImmediateWindowProvider.java">ImmediateWindowProvider.java</a> 07-Oct-2023 14:12 6370
<a href="JarVersionLookupHandler.java">JarVersionLookupHandler.java</a> 07-Oct-2023 14:12 1857
<a href="LanguageLoadingProvider.java">LanguageLoadingProvider.java</a> 07-Oct-2023 14:12 6981
<a href="LauncherVersion.java">LauncherVersion.java</a> 07-Oct-2023 14:12 868
<a href="LibraryFinder.java">LibraryFinder.java</a> 07-Oct-2023 14:12 2720
<a href="LoadingModList.java">LoadingModList.java</a> 07-Oct-2023 14:12 5583
<a href="LogMarkers.java">LogMarkers.java</a> 07-Oct-2023 14:12 527
<a href="MCPNamingService.java">MCPNamingService.java</a> 07-Oct-2023 14:12 3045
<a href="MavenCoordinateResolver.java">MavenCoordinateResolver.java</a> 07-Oct-2023 14:12 1617
<a href="ModDirTransformerDiscoverer.java">ModDirTransformerDiscoverer.java</a> 07-Oct-2023 14:12 2928
<a href="ModJarURLHandler.java">ModJarURLHandler.java</a> 07-Oct-2023 14:12 2908
<a href="ModSorter.java">ModSorter.java</a> 07-Oct-2023 14:12 12K
<a href="RuntimeDistCleaner.java">RuntimeDistCleaner.java</a> 07-Oct-2023 14:12 8788
<a href="StringSubstitutor.java">StringSubstitutor.java</a> 07-Oct-2023 14:12 1536
<a href="StringUtils.java">StringUtils.java</a> 07-Oct-2023 14:12 1626
<a href="TracingPrintStream.java">TracingPrintStream.java</a> 07-Oct-2023 14:12 2190
<a href="UniqueModListBuilder.java">UniqueModListBuilder.java</a> 07-Oct-2023 14:12 6437
<a href="VersionInfo.java">VersionInfo.java</a> 07-Oct-2023 14:12 674
<a href="VersionSupportMatrix.java">VersionSupportMatrix.java</a> 07-Oct-2023 14:12 1686
</pre><hr><script defer src="https://static.cloudflareinsights.com/beacon.min.js/v84a3a4012de94ce1a686ba8c167c359c1696973893317" integrity="sha512-euoFGowhlaLqXsPWQ48qSkBSCFs3DPRyiwVu3FjR96cMPx+Fr+gpWRhIafcHwqwCqWS42RZhIudOvEI+Ckf6MA==" nonce="c51c6597050ae55131088111efbfde9d" data-cf-beacon='{"rayId":"85f026fd08161c5c","version":"2024.2.4","r":1,"token":"583109dda43e47a593fd006526a81120","b":1}' crossorigin="anonymous"></script>
</body>
</html>

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.log4j;
import net.minecrell.terminalconsole.HighlightErrorConverter;
import net.minecrell.terminalconsole.TerminalConsoleAppender;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.pattern.ConverterKeys;
import org.apache.logging.log4j.core.pattern.HighlightConverter;
import org.apache.logging.log4j.core.pattern.PatternConverter;
import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.util.PerformanceSensitive;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* A wrapper for {@link HighlightConverter} that auto-disables ANSI when the terminal doesn't support it.
* Ansi support is determined by TerminalConsoleAppender
*/
@Plugin(name = "highlightForge", category = PatternConverter.CATEGORY)
@ConverterKeys("highlightForge")
@PerformanceSensitive("allocation")
public class ForgeHighlight {
protected static final Logger LOGGER = StatusLogger.getLogger();
/**
* Gets a new instance of the {@link HighlightErrorConverter} with the
* specified options.
*
* @param config The current configuration
* @param options The pattern options
* @return The new instance
*/
public static @Nullable HighlightConverter newInstance(Configuration config, String[] options) {
try {
Method method = TerminalConsoleAppender.class.getDeclaredMethod("initializeTerminal");
method.setAccessible(true);
method.invoke(null);
} catch (ReflectiveOperationException e) {
LOGGER.warn("Failed to invoke initializeTerminal on TCA", e);
}
if (!TerminalConsoleAppender.isAnsiSupported() && Arrays.stream(options).noneMatch(s -> s.equals("disableAnsi=true"))) {
List<String> optionList = new ArrayList<>();
optionList.add(options[0]);
optionList.add("disableAnsi=true");
options = optionList.toArray(new String[0]);
}
return HighlightConverter.newInstance(config, options);
}
}

View file

@ -0,0 +1,42 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.log4j;
import cpw.mods.modlauncher.api.NamedPath;
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
import org.objectweb.asm.Type;
import org.slf4j.LoggerFactory;
import java.util.EnumSet;
public class SLF4JFixerLaunchPluginService implements ILaunchPluginService {
private static final EnumSet<Phase> NO_PHASES = EnumSet.noneOf(Phase.class);
@Override
public String name() {
return "slf4jfixer";
}
@Override
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty) {
return NO_PHASES;
}
@Override
public void initializeLaunch(ITransformerLoader transformerLoader, NamedPath[] specialPaths) {
Thread curThread = Thread.currentThread();
ClassLoader contextClassLoader = curThread.getContextClassLoader();
// Set the CCL of the current thread to MC-BOOTSTRAP ModuleClassLoader
curThread.setContextClassLoader(this.getClass().getClassLoader());
// Force SLF4J to bind the service providers while we manually set the context classloader to be correct
LoggerFactory.getILoggerFactory();
// Set CCL back to TransformingClassLoader
curThread.setContextClassLoader(contextClassLoader);
}
}

View file

@ -0,0 +1,9 @@
<html>
<head><title>Index of /mirror/src/Magma-1-20-x/fmlloader/src/main/java/net/minecraftforge/fml/loading/log4j/</title></head>
<body>
<h1>Index of /mirror/src/Magma-1-20-x/fmlloader/src/main/java/net/minecraftforge/fml/loading/log4j/</h1><hr><pre><a href="../">../</a>
<a href="ForgeHighlight.java">ForgeHighlight.java</a> 07-Oct-2023 14:12 2381
<a href="SLF4JFixerLaunchPluginService.java">SLF4JFixerLaunchPluginService.java</a> 07-Oct-2023 14:12 1378
</pre><hr><script defer src="https://static.cloudflareinsights.com/beacon.min.js/v84a3a4012de94ce1a686ba8c167c359c1696973893317" integrity="sha512-euoFGowhlaLqXsPWQ48qSkBSCFs3DPRyiwVu3FjR96cMPx+Fr+gpWRhIafcHwqwCqWS42RZhIudOvEI+Ckf6MA==" nonce="b0add6c76185906a0b8a7eee19a1acea" data-cf-beacon='{"rayId":"85f039a8cf931c4c","version":"2024.2.4","r":1,"token":"583109dda43e47a593fd006526a81120","b":1}' crossorigin="anonymous"></script>
</body>
</html>

View file

@ -0,0 +1,52 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.moddiscovery;
import com.mojang.logging.LogUtils;
import net.minecraftforge.forgespi.locating.IDependencyLocator;
import net.minecraftforge.forgespi.locating.IModFile;
import net.minecraftforge.forgespi.locating.ModFileLoadingException;
import org.slf4j.Logger;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
public abstract class AbstractJarFileDependencyLocator extends AbstractJarFileModProvider implements IDependencyLocator
{
private static final Logger LOGGER = LogUtils.getLogger();
protected Optional<InputStream> loadResourceFromModFile(final IModFile modFile, final Path path) {
try {
return Optional.of(Files.newInputStream(modFile.findResource(path.toString())));
}
catch (final FileNotFoundException e) {
LOGGER.debug("Failed to load resource {} from {}, it does not contain dependency information.", path, modFile.getFileName());
return Optional.empty();
}
catch (final Exception e) {
LOGGER.error("Failed to load resource {} from mod {}, cause {}", path, modFile.getFileName(), e);
return Optional.empty();
}
}
protected Optional<IModFile> loadModFileFrom(final IModFile file, final Path path) {
try {
final Path pathInModFile = file.findResource(path.toString());
return Optional.of(createMod(pathInModFile).file());
}
catch (Exception e) {
LOGGER.error("Failed to load mod file {} from {}", path, file.getFileName());
throw new ModFileLoadingException("Failed to load mod file "+file.getFileName());
}
}
protected String identifyMod(final IModFile modFile) {
return modFile.getFileName();
}
}

View file

@ -0,0 +1,23 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.moddiscovery;
import net.minecraftforge.forgespi.locating.IModLocator;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Stream;
public abstract class AbstractJarFileModLocator extends AbstractJarFileModProvider implements IModLocator
{
@Override
public List<IModLocator.ModFileOrException> scanMods()
{
return scanCandidates().map(this::createMod).toList();
}
public abstract Stream<Path> scanCandidates();
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.moddiscovery;
import com.mojang.logging.LogUtils;
import cpw.mods.jarhandling.SecureJar;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.forgespi.locating.IModFile;
import org.slf4j.Logger;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream;
public abstract class AbstractJarFileModProvider extends AbstractModProvider
{
private static final Logger LOGGER = LogUtils.getLogger();
@Override
public void scanFile(final IModFile file, final Consumer<Path> pathConsumer) {
LOGGER.debug(LogMarkers.SCAN,"Scan started: {}", file);
final Function<Path, SecureJar.Status> status = p->file.getSecureJar().verifyPath(p);
try (Stream<Path> files = Files.find(file.getSecureJar().getRootPath(), Integer.MAX_VALUE, (p, a) -> p.getNameCount() > 0 && p.getFileName().toString().endsWith(".class"))) {
file.setSecurityStatus(files.peek(pathConsumer).map(status).reduce((s1, s2)-> SecureJar.Status.values()[Math.min(s1.ordinal(), s2.ordinal())]).orElse(SecureJar.Status.INVALID));
} catch (IOException e) {
e.printStackTrace();
}
LOGGER.debug(LogMarkers.SCAN,"Scan finished: {}", file);
}
}

View file

@ -0,0 +1,123 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.moddiscovery;
import com.mojang.logging.LogUtils;
import cpw.mods.jarhandling.JarMetadata;
import cpw.mods.jarhandling.SecureJar;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.forgespi.language.IConfigurable;
import net.minecraftforge.forgespi.language.IModFileInfo;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.forgespi.locating.IModFile;
import net.minecraftforge.forgespi.locating.IModLocator;
import net.minecraftforge.forgespi.locating.IModProvider;
import net.minecraftforge.forgespi.locating.ModFileLoadingException;
import org.slf4j.Logger;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.jar.Manifest;
public abstract class AbstractModProvider implements IModProvider
{
private static final Logger LOGGER = LogUtils.getLogger();
protected static final String MODS_TOML = "META-INF/mods.toml";
protected static final String MANIFEST = "META-INF/MANIFEST.MF";
protected IModLocator.ModFileOrException createMod(Path... path) {
var mjm = new ModJarMetadata();
var sj = SecureJar.from(
Manifest::new,
jar -> jar.moduleDataProvider().findFile(MODS_TOML).isPresent() ? mjm : JarMetadata.from(jar, path),
(root, p) -> true,
path
);
IModFile mod;
var type = sj.moduleDataProvider().getManifest().getMainAttributes().getValue(ModFile.TYPE);
if (type == null) {
type = getDefaultJarModType();
}
if (sj.moduleDataProvider().findFile(MODS_TOML).isPresent()) {
LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", MODS_TOML, type, path);
mod = new ModFile(sj, this, ModFileParser::modsTomlParser);
} else if (type != null) {
LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", MANIFEST, type, path);
mod = new ModFile(sj, this, this::manifestParser, type);
} else {
return new IModLocator.ModFileOrException(null, new ModFileLoadingException("Invalid mod file found "+ Arrays.toString(path)));
}
mjm.setModFile(mod);
return new IModLocator.ModFileOrException(mod, null);
}
protected IModFileInfo manifestParser(final IModFile mod) {
Function<String, Optional<String>> cfg = name -> Optional.ofNullable(mod.getSecureJar().moduleDataProvider().getManifest().getMainAttributes().getValue(name));
var license = cfg.apply("LICENSE").orElse("");
var dummy = new IConfigurable() {
@Override
public <T> Optional<T> getConfigElement(String... key) {
return Optional.empty();
}
@Override
public List<? extends IConfigurable> getConfigList(String... key) {
return Collections.emptyList();
}
};
return new DefaultModFileInfo(mod, license, dummy);
}
@Override
public boolean isValid(final IModFile modFile) {
return true;
}
protected String getDefaultJarModType() {
return null;
}
private record DefaultModFileInfo(IModFile mod, String license, IConfigurable configurable) implements IModFileInfo, IConfigurable {
@Override
public <T> Optional<T> getConfigElement(final String... strings)
{
return Optional.empty();
}
@Override
public List<? extends IConfigurable> getConfigList(final String... strings)
{
return null;
}
@Override public List<IModInfo> getMods() { return Collections.emptyList(); }
@Override public List<LanguageSpec> requiredLanguageLoaders() { return Collections.emptyList(); }
@Override public boolean showAsResourcePack() { return false; }
@Override public Map<String, Object> getFileProperties() { return Collections.emptyMap(); }
@Override
public String getLicense() { return license; }
@Override public IModFile getFile() { return mod; }
@Override public IConfigurable getConfig() { return configurable; }
// These Should never be called as it's only called from ModJarMetadata.version and we bypass that
@Override public String moduleName() { return mod.getSecureJar().name(); }
@Override public String versionString() { return null; }
@Override public List<String> usesServices() { return null; }
@Override
public String toString() {
return "IModFileInfo(" + mod.getFilePath() + ")";
}
}
}

View file

@ -0,0 +1,109 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.moddiscovery;
import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.loading.ImmediateWindowHandler;
import net.minecraftforge.fml.loading.LoadingModList;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
import net.minecraftforge.forgespi.language.ModFileScanData;
import org.slf4j.Logger;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class BackgroundScanHandler
{
private enum ScanStatus {
NOT_STARTED,
RUNNING,
COMPLETE,
TIMED_OUT,
INTERRUPTED,
ERRORED
}
private static final Logger LOGGER = LogUtils.getLogger();
private final ExecutorService modContentScanner;
private final List<ModFile> pendingFiles;
private final List<ModFile> scannedFiles;
private final List<ModFile> allFiles;
private final List<ModFile> modFiles;
private ScanStatus status;
private LoadingModList loadingModList;
public BackgroundScanHandler(final List<ModFile> modFiles) {
this.modFiles = modFiles;
modContentScanner = Executors.newSingleThreadExecutor(r -> {
final Thread thread = Executors.defaultThreadFactory().newThread(r);
thread.setDaemon(true);
return thread;
});
scannedFiles = new ArrayList<>();
pendingFiles = new ArrayList<>();
allFiles = new ArrayList<>();
status = ScanStatus.NOT_STARTED;
}
public List<ModFile> getModFiles() {
return modFiles;
}
public void submitForScanning(final ModFile file) {
if (modContentScanner.isShutdown()) {
status = ScanStatus.ERRORED;
throw new IllegalStateException("Scanner has shutdown");
}
status = ScanStatus.RUNNING;
ImmediateWindowHandler.updateProgress("Scanning mod candidates");
allFiles.add(file);
pendingFiles.add(file);
final CompletableFuture<ModFileScanData> future = CompletableFuture.supplyAsync(file::compileContent, modContentScanner)
.whenComplete(file::setScanResult)
.whenComplete((r,t)-> this.addCompletedFile(file,r,t));
file.setFutureScanResult(future);
}
private void addCompletedFile(final ModFile file, final ModFileScanData modFileScanData, final Throwable throwable) {
if (throwable != null) {
status = ScanStatus.ERRORED;
LOGGER.error(LogMarkers.SCAN,"An error occurred scanning file {}", file, throwable);
}
pendingFiles.remove(file);
scannedFiles.add(file);
}
public void setLoadingModList(LoadingModList loadingModList)
{
this.loadingModList = loadingModList;
}
public LoadingModList getLoadingModList()
{
return loadingModList;
}
public void waitForScanToComplete(final Runnable ticker) {
boolean timeoutActive = System.getProperty("fml.disableScanTimeout") == null;
Instant deadline = Instant.now().plus(Duration.ofMinutes(10));
modContentScanner.shutdown();
do {
ticker.run();
try {
status = modContentScanner.awaitTermination(50, TimeUnit.MILLISECONDS) ? ScanStatus.COMPLETE : ScanStatus.RUNNING;
} catch (InterruptedException e) {
status = ScanStatus.INTERRUPTED;
}
if (timeoutActive && Instant.now().isAfter(deadline)) status = ScanStatus.TIMED_OUT;
} while (status == ScanStatus.RUNNING);
if (status == ScanStatus.INTERRUPTED) Thread.currentThread().interrupt();
if (status != ScanStatus.COMPLETE) throw new IllegalStateException("Failed to complete mod scan");
}
}

View file

@ -0,0 +1,94 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.moddiscovery;
import net.minecraftforge.fml.loading.ClasspathLocatorUtils;
import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.loading.LogMarkers;
import org.slf4j.Logger;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
public class ClasspathLocator extends AbstractJarFileModLocator
{
private static final Logger LOGGER = LogUtils.getLogger();
private final List<Path> legacyClasspath = Arrays.stream(System.getProperty("legacyClassPath", "").split(File.pathSeparator)).map(Path::of).toList();
private boolean enabled = false;
@Override
public String name() {
return "userdev classpath";
}
@Override
public Stream<Path> scanCandidates() {
if (!enabled)
return Stream.of();
try {
var claimed = new ArrayList<>(legacyClasspath);
var paths = Stream.<Path>builder();
findPaths(claimed, MODS_TOML).forEach(paths::add);
findPaths(claimed, MANIFEST).forEach(paths::add);
return paths.build();
} catch (IOException e) {
LOGGER.error(LogMarkers.SCAN, "Error trying to find resources", e);
throw new RuntimeException(e);
}
}
private List<Path> findPaths(List<Path> claimed, String resource) throws IOException {
var ret = new ArrayList<Path>();
final Enumeration<URL> resources = ClassLoader.getSystemClassLoader().getResources(resource);
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
Path path = ClasspathLocatorUtils.findJarPathFor(resource, resource, url);
if (claimed.stream().anyMatch(path::equals) || !Files.exists(path) || Files.isDirectory(path))
continue;
ret.add(path);
}
return ret;
}
@Override
public void initArguments(Map<String, ?> arguments) {
var launchTarget = (String) arguments.get("launchTarget");
enabled = launchTarget != null && launchTarget.contains("dev");
}
private Path findJarPathFor(final String resourceName, final String jarName, final URL resource) {
try {
Path path;
final URI uri = resource.toURI();
if (uri.getScheme().equals("jar") && uri.getRawSchemeSpecificPart().contains("!/")) {
int lastExcl = uri.getRawSchemeSpecificPart().lastIndexOf("!/");
path = Paths.get(new URI(uri.getRawSchemeSpecificPart().substring(0, lastExcl)));
} else {
path = Paths.get(new URI("file://"+uri.getRawSchemeSpecificPart().substring(0, uri.getRawSchemeSpecificPart().length()-resourceName.length())));
}
//LOGGER.debug(CORE, "Found JAR {} at path {}", jarName, path.toString());
return path;
} catch (NullPointerException | URISyntaxException e) {
LOGGER.error(LogMarkers.SCAN, "Failed to find JAR for class {} - {}", resourceName, jarName);
throw new RuntimeException("Unable to locate "+resourceName+" - "+jarName, e);
}
}
}

View file

@ -0,0 +1,50 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.moddiscovery;
import net.minecraftforge.forgespi.coremod.ICoreModFile;
import java.io.IOException;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Path;
public class CoreModFile implements ICoreModFile {
private final Path internalPath;
private final ModFile file;
private final String name;
CoreModFile(final String name, final Path path, final ModFile file) {
this.name = name;
this.internalPath = path;
this.file = file;
}
@Override
public Reader readCoreMod() throws IOException {
return Files.newBufferedReader(this.internalPath);
}
@Override
public Path getPath() {
return this.internalPath;
}
@Override
public Reader getAdditionalFile(final String fileName) throws IOException {
return Files.newBufferedReader(file.findResource(fileName));
}
@Override
public String getOwnerId() {
return this.file.getModInfos().get(0).getModId();
}
@Override
public String toString() {
return "{Name: " + name + ", Owner: " + getOwnerId() + " @ " + getPath() + "}";
}
}

View file

@ -0,0 +1,78 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.moddiscovery;
import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.forgespi.locating.IModFile;
import net.minecraftforge.forgespi.locating.IModLocator;
import org.slf4j.Logger;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Stream;
public class ExplodedDirectoryLocator implements IModLocator {
private static final Logger LOGGER = LogUtils.getLogger();
public record ExplodedMod(String modid, List<Path> paths) {}
private final List<ExplodedMod> explodedMods = new ArrayList<>();
private final Map<ExplodedMod, IModFile> mods = new HashMap<>();
@Override
public List<IModLocator.ModFileOrException> scanMods() {
explodedMods.forEach(explodedMod ->
ModJarMetadata.buildFile(this,
jar->jar.moduleDataProvider().findFile("/META-INF/mods.toml").isPresent(),
(a,b) -> true,
explodedMod.paths().toArray(Path[]::new))
.ifPresentOrElse(f->mods.put(explodedMod, f), () -> LOGGER.warn(LogMarkers.LOADING, "Failed to find exploded resource mods.toml in directory {}", explodedMod.paths().get(0).toString())));
return mods.values().stream().map(mf->new IModLocator.ModFileOrException(mf, null)).toList();
}
@Override
public String name() {
return "exploded directory";
}
@Override
public void scanFile(final IModFile file, final Consumer<Path> pathConsumer) {
LOGGER.debug(LogMarkers.SCAN,"Scanning exploded directory {}", file.getFilePath().toString());
try (Stream<Path> files = Files.find(file.getSecureJar().getRootPath(), Integer.MAX_VALUE, (p, a) -> p.getNameCount() > 0 && p.getFileName().toString().endsWith(".class"))) {
files.forEach(pathConsumer);
} catch (IOException e) {
e.printStackTrace();
}
LOGGER.debug(LogMarkers.SCAN,"Exploded directory scan complete {}", file.getFilePath().toString());
}
@Override
public String toString()
{
return "{ExplodedDir locator}";
}
@SuppressWarnings("unchecked")
@Override
public void initArguments(final Map<String, ?> arguments) {
final var explodedTargets = ((Map<String, List<ExplodedMod>>) arguments).get("explodedTargets");
if (explodedTargets != null && !explodedTargets.isEmpty()) {
explodedMods.addAll(explodedTargets);
}
}
@Override
public boolean isValid(final IModFile modFile) {
return true;
}
}

View file

@ -0,0 +1,27 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.moddiscovery;
import net.minecraftforge.forgespi.language.IModFileInfo;
import net.minecraftforge.forgespi.locating.ModFileLoadingException;
import java.util.Locale;
import java.util.Optional;
public class InvalidModFileException extends ModFileLoadingException
{
private final IModFileInfo modFileInfo;
public InvalidModFileException(String message, IModFileInfo modFileInfo)
{
super(String.format(Locale.ROOT, "%s (%s)", message, Optional.ofNullable(modFileInfo).map(mf->mf.getFile().getFileName()).orElse("MISSING FILE NAME")));
this.modFileInfo = modFileInfo;
}
public IModFileInfo getBrokenFile() {
return modFileInfo;
}
}

View file

@ -0,0 +1,68 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.moddiscovery;
import cpw.mods.modlauncher.api.LamdbaExceptionUtils.Supplier_WithExceptions;
import net.minecraftforge.fml.loading.StringUtils;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Optional;
import java.util.function.BiPredicate;
import java.util.zip.ZipFile;
import static cpw.mods.modlauncher.api.LamdbaExceptionUtils.*;
public enum InvalidModIdentifier {
OLDFORGE(filePresent("mcmod.info")),
FABRIC(filePresent("fabric.mod.json")),
LITELOADER(filePresent("litemod.json")),
OPTIFINE(filePresent("optifine/Installer.class")),
BUKKIT(filePresent("plugin.yml")),
INVALIDZIP((f,zf) -> !zf.isPresent());
private BiPredicate<Path, Optional<ZipFile>> ident;
InvalidModIdentifier(BiPredicate<Path, Optional<ZipFile>> identifier)
{
this.ident = identifier;
}
private String getReason()
{
return "fml.modloading.brokenfile." + StringUtils.toLowerCase(name());
}
public static Optional<String> identifyJarProblem(Path path)
{
Optional<ZipFile> zfo = optionalFromException(() -> new ZipFile(path.toFile()));
Optional<String> result = Arrays.stream(values()).
filter(i -> i.ident.test(path, zfo)).
map(InvalidModIdentifier::getReason).
findAny();
zfo.ifPresent(rethrowConsumer(ZipFile::close));
return result;
}
private static BiPredicate<Path, Optional<ZipFile>> filePresent(String filename)
{
return (f, zfo) -> zfo.map(zf -> zf.getEntry(filename) != null).orElse(false);
}
private static <T> Optional<T> optionalFromException(Supplier_WithExceptions<T, ? extends Exception> supp)
{
try
{
return Optional.of(supp.get());
}
catch (Exception e)
{
return Optional.empty();
}
}
}

View file

@ -0,0 +1,158 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.moddiscovery;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.loading.EarlyLoadingException;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.forgespi.locating.IModFile;
import net.minecraftforge.forgespi.locating.ModFileLoadingException;
import net.minecraftforge.jarjar.selection.JarSelector;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.VersionRange;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class JarInJarDependencyLocator extends AbstractJarFileDependencyLocator
{
private static final Logger LOGGER = LogUtils.getLogger();
@Override
public String name()
{
return "JarInJar";
}
@Override
public List<IModFile> scanMods(final Iterable<IModFile> loadedMods)
{
final List<IModFile> sources = Lists.newArrayList();
loadedMods.forEach(sources::add);
final List<IModFile> dependenciesToLoad = JarSelector.detectAndSelect(sources, this::loadResourceFromModFile, this::loadModFileFrom, this::identifyMod, this::exception);
if (dependenciesToLoad.isEmpty())
{
LOGGER.info("No dependencies to load found. Skipping!");
return Collections.emptyList();
}
LOGGER.info("Found {} dependencies adding them to mods collection", dependenciesToLoad.size());
return dependenciesToLoad;
}
@Override
public void initArguments(final Map<String, ?> arguments)
{
// NO-OP, for now
}
@Override
protected String getDefaultJarModType()
{
return IModFile.Type.GAMELIBRARY.name();
}
@SuppressWarnings("resource")
@Override
protected Optional<IModFile> loadModFileFrom(final IModFile file, final Path path)
{
try
{
final Path pathInModFile = file.findResource(path.toString());
final URI filePathUri = new URI("jij:" + (pathInModFile.toAbsolutePath().toUri().getRawSchemeSpecificPart())).normalize();
final Map<String, ?> outerFsArgs = ImmutableMap.of("packagePath", pathInModFile);
final FileSystem zipFS = FileSystems.newFileSystem(filePathUri, outerFsArgs);
final Path pathInFS = zipFS.getPath("/");
return Optional.of(createMod(pathInFS).file());
}
catch (Exception e)
{
LOGGER.error("Failed to load mod file {} from {}", path, file.getFileName());
final RuntimeException exception = new ModFileLoadingException("Failed to load mod file " + file.getFileName());
exception.initCause(e);
throw exception;
}
}
protected EarlyLoadingException exception(Collection<JarSelector.ResolutionFailureInformation<IModFile>> failedDependencies)
{
final List<EarlyLoadingException.ExceptionData> errors = failedDependencies.stream()
.filter(entry -> !entry.sources().isEmpty()) //Should never be the case, but just to be sure
.map(this::buildExceptionData)
.toList();
return new EarlyLoadingException(failedDependencies.size() + " Dependency restrictions were not met.", null, errors);
}
@NotNull
private EarlyLoadingException.ExceptionData buildExceptionData(final JarSelector.ResolutionFailureInformation<IModFile> entry)
{
return new EarlyLoadingException.ExceptionData(
getErrorTranslationKey(entry),
entry.identifier().group() + ":" + entry.identifier().artifact(),
entry.sources()
.stream()
.flatMap(this::getModWithVersionRangeStream)
.map(this::formatError)
.collect(Collectors.joining(", "))
);
}
@NotNull
private String getErrorTranslationKey(final JarSelector.ResolutionFailureInformation<IModFile> entry)
{
return entry.failureReason() == JarSelector.FailureReason.VERSION_RESOLUTION_FAILED ?
"fml.dependencyloading.conflictingdependencies" :
"fml.dependencyloading.mismatchedcontaineddependencies";
}
@NotNull
private Stream<ModWithVersionRange> getModWithVersionRangeStream(final JarSelector.SourceWithRequestedVersionRange<IModFile> file)
{
return file.sources()
.stream()
.map(IModFile::getModFileInfo)
.flatMap(modFileInfo -> modFileInfo.getMods().stream())
.map(modInfo -> new ModWithVersionRange(modInfo, file.requestedVersionRange(), file.includedVersion()));
}
@NotNull
private String formatError(final ModWithVersionRange modWithVersionRange)
{
return "\u00a7e" + modWithVersionRange.modInfo().getModId() + "\u00a7r - \u00a74" + modWithVersionRange.versionRange().toString() + "\u00a74 - \u00a72" + modWithVersionRange.artifactVersion().toString() + "\u00a72";
}
@Override
protected String identifyMod(final IModFile modFile)
{
if (modFile.getModFileInfo() == null || modFile.getModInfos().isEmpty())
{
return modFile.getFileName();
}
return modFile.getModInfos().stream().map(IModInfo::getModId).collect(Collectors.joining());
}
private record ModWithVersionRange(IModInfo modInfo, VersionRange versionRange, ArtifactVersion artifactVersion)
{}
}

View file

@ -0,0 +1,49 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.moddiscovery;
import net.minecraftforge.fml.loading.FMLPaths;
import net.minecraftforge.fml.loading.MavenCoordinateResolver;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class MavenDirectoryLocator extends AbstractJarFileModLocator
{
private List<Path> modCoords;
@Override
public Stream<Path> scanCandidates() {
return modCoords.stream();
}
@Override
public String name() {
return "maven libs";
}
public String toString() {
return "{Maven Directory locator for mods "+this.modCoords+"}";
}
@SuppressWarnings("unchecked")
@Override
public void initArguments(final Map<String, ?> arguments) {
final List<String> mavenRoots = (List<String>) arguments.get("mavenRoots");
final List<Path> mavenRootPaths = mavenRoots.stream().map(n -> FMLPaths.GAMEDIR.get().resolve(n)).collect(Collectors.toList());
final List<String> mods = (List<String>) arguments.get("mods");
final List<String> listedMods = ModListHandler.processModLists((List<String>) arguments.get("modLists"), mavenRootPaths);
List<Path> localModCoords = Stream.concat(mods.stream(),listedMods.stream()).map(MavenCoordinateResolver::get).collect(Collectors.toList());
// find the modCoords path in each supplied maven path, and turn it into a mod file. (skips not found files)
this.modCoords = localModCoords.stream().map(mc -> mavenRootPaths.stream().map(root -> root.resolve(mc)).filter(path -> Files.exists(path)).findFirst().orElseThrow(() -> new IllegalArgumentException("Failed to locate requested mod coordinate " + mc))).collect(Collectors.toList());
}
}

View file

@ -0,0 +1,122 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.moddiscovery;
import com.electronwill.nightconfig.core.Config;
import com.mojang.logging.LogUtils;
import cpw.mods.jarhandling.SecureJar;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.forgespi.language.IModFileInfo;
import net.minecraftforge.forgespi.locating.IModFile;
import net.minecraftforge.forgespi.locating.IModLocator;
import net.minecraftforge.forgespi.locating.ModFileFactory;
import org.slf4j.Logger;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class MinecraftLocator extends AbstractModProvider implements IModLocator
{
private static final Logger LOGGER = LogUtils.getLogger();
@Override
public List<IModLocator.ModFileOrException> scanMods() {
final var launchHandler = FMLLoader.getLaunchHandler();
var baseMC = launchHandler.getMinecraftPaths();
var mcjar = ModJarMetadata.buildFile(j->ModFileFactory.FACTORY.build(j, this, this::buildMinecraftTOML), j->true, baseMC.minecraftFilter(), baseMC.minecraftPaths().toArray(Path[]::new)).orElseThrow();
var artifacts = baseMC.otherArtifacts().stream()
.map(SecureJar::from)
.map(sj -> new ModFile(sj, this, ModFileParser::modsTomlParser))
.collect(Collectors.<IModFile>toList());
var othermods = baseMC.otherModPaths().stream()
.map(p -> createMod(p.toArray(Path[]::new)))
.filter(Objects::nonNull);
artifacts.add(mcjar);
return Stream.concat(artifacts.stream().map(f -> new ModFileOrException(f, null)), othermods).toList();
}
private IModFileInfo buildMinecraftTOML(final IModFile iModFile) {
final ModFile modFile = (ModFile) iModFile;
/*
final Path mcmodtoml = modFile.findResource("META-INF", "minecraftmod.toml");
if (Files.notExists(mcmodtoml)) {
LOGGER.fatal(LOADING, "Mod file {} is missing minecraftmod.toml file", modFile.getFilePath());
return null;
}
final FileConfig mcmodstomlfile = FileConfig.builder(mcmodtoml).build();
mcmodstomlfile.load();
mcmodstomlfile.close();
*/
// We haven't changed this in years, and I can't be asked right now to special case this one file in the path.
final var conf = Config.inMemory();
conf.set("modLoader", "minecraft");
conf.set("loaderVersion", "1");
conf.set("license", "Mojang Studios, All Rights Reserved");
final var mods = Config.inMemory();
mods.set("modId", "minecraft");
mods.set("version", FMLLoader.versionInfo().mcVersion());
mods.set("displayName", "Minecraft");
mods.set("logoFile", "mcplogo.png");
mods.set("credits", "Mojang, deobfuscated by MCP");
mods.set("authors", "MCP: Searge,ProfMobius,IngisKahn,Fesh0r,ZeuX,R4wk,LexManos,Bspkrs");
mods.set("description", "Minecraft, decompiled and deobfuscated with MCP technology");
conf.set("mods", List.of(mods));
/*
conf.putAll(mcmodstomlfile);
final var extralangs = Stream.<IModFileInfo.LanguageSpec>builder();
final Path forgemodtoml = modFile.findResource("META-INF", "mods.toml");
if (Files.notExists(forgemodtoml)) {
LOGGER.info("No forge mods.toml file found, not loading forge mod");
} else {
final FileConfig forgemodstomlfile = FileConfig.builder(forgemodtoml).build();
forgemodstomlfile.load();
forgemodstomlfile.close();
conf.putAll(forgemodstomlfile);
conf.<List<Object>>get("mods").add(0, mcmodstomlfile.<List<Object>>get("mods").get(0)); // Add MC as a sub-mod
extralangs.add(new IModFileInfo.LanguageSpec(mcmodstomlfile.get("modLoader"), MavenVersionAdapter.createFromVersionSpec(mcmodstomlfile.get("loaderVersion"))));
}
*/
final NightConfigWrapper configWrapper = new NightConfigWrapper(conf);
//final ModFileInfo modFileInfo = new ModFileInfo(modFile, configWrapper, extralangs.build().toList());
return new ModFileInfo(modFile, configWrapper, configWrapper::setFile, List.of());
}
@Override
public String name() {
return "minecraft";
}
@Override
public void scanFile(final IModFile modFile, final Consumer<Path> pathConsumer) {
LOGGER.debug(LogMarkers.SCAN, "Scan started: {}", modFile);
try (Stream<Path> files = Files.find(modFile.getSecureJar().getRootPath(), Integer.MAX_VALUE, (p, a) -> p.getNameCount() > 0 && p.getFileName().toString().endsWith(".class"))) {
files.forEach(pathConsumer);
} catch (IOException e) {
e.printStackTrace();
}
LOGGER.debug(LogMarkers.SCAN, "Scan finished: {}", modFile);
}
@Override
public void initArguments(final Map<String, ?> arguments) {
// no op
}
}

View file

@ -0,0 +1,126 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.moddiscovery;
import java.lang.annotation.ElementType;
import java.util.ArrayList;
import java.util.Map;
import net.minecraftforge.forgespi.language.ModFileScanData;
import org.objectweb.asm.Type;
import com.google.common.base.MoreObjects;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
public class ModAnnotation
{
public static ModFileScanData.AnnotationData fromModAnnotation(final Type clazz, final ModAnnotation annotation) {
return new ModFileScanData.AnnotationData(annotation.asmType, annotation.type, clazz, annotation.member, annotation.values);
}
public static class EnumHolder
{
private final String desc;
private final String value;
public EnumHolder(String desc, String value)
{
this.desc = desc;
this.value = value;
}
public String getDesc()
{
return desc;
}
public String getValue()
{
return value;
}
}
private final ElementType type;
private final Type asmType;
private final String member;
private final Map<String,Object> values = Maps.newHashMap();
private ArrayList<Object> arrayList;
private String arrayName;
public ModAnnotation(ElementType type, Type asmType, String member)
{
this.type = type;
this.asmType = asmType;
this.member = member;
}
public ModAnnotation(Type asmType, ModAnnotation parent)
{
this.type = parent.type;
this.asmType = asmType;
this.member = parent.member;
}
@Override
public String toString()
{
return MoreObjects.toStringHelper("Annotation")
.add("type",type)
.add("name",asmType.getClassName())
.add("member",member)
.add("values", values)
.toString();
}
public ElementType getType()
{
return type;
}
public Type getASMType()
{
return asmType;
}
public String getMember()
{
return member;
}
public Map<String, Object> getValues()
{
return values;
}
public void addArray(String name)
{
this.arrayList = Lists.newArrayList();
this.arrayName = name;
}
public void addProperty(String key, Object value)
{
if (this.arrayList != null)
{
arrayList.add(value);
}
else
{
values.put(key, value);
}
}
public void addEnumProperty(String key, String enumName, String value)
{
addProperty(key, new EnumHolder(enumName, value));
}
public void endArray()
{
values.put(arrayName, arrayList);
arrayList = null;
}
public ModAnnotation addChildAnnotation(String name, String desc)
{
ModAnnotation child = new ModAnnotation(Type.getType(desc), this);
addProperty(name, child.getValues());
return child;
}
}

View file

@ -0,0 +1,81 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.moddiscovery;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Opcodes;
import java.util.LinkedList;
public class ModAnnotationVisitor extends AnnotationVisitor
{
private final ModAnnotation annotation;
private LinkedList<ModAnnotation> annotations;
private boolean array;
private String name;
private boolean isSubAnnotation;
public ModAnnotationVisitor(LinkedList<ModAnnotation> annotations, ModAnnotation annotation)
{
super(Opcodes.ASM9);
this.annotations = annotations;
this.annotation = annotation;
}
public ModAnnotationVisitor(LinkedList<ModAnnotation> annotations, ModAnnotation annotation, String name)
{
this(annotations, annotation);
this.array = true;
this.name = name;
annotation.addArray(name);
}
public ModAnnotationVisitor(LinkedList<ModAnnotation> annotations, ModAnnotation annotation, boolean isSubAnnotation)
{
this(annotations, annotation);
this.isSubAnnotation = true;
}
@Override
public void visit(String key, Object value)
{
annotation.addProperty(key, value);
}
@Override
public void visitEnum(String name, String desc, String value)
{
annotation.addEnumProperty(name, desc, value);
}
@Override
public AnnotationVisitor visitArray(String name)
{
return new ModAnnotationVisitor(annotations, annotation, name);
}
@Override
public AnnotationVisitor visitAnnotation(String name, String desc)
{
ModAnnotation ma = annotations.getFirst();
final ModAnnotation childAnnotation = ma.addChildAnnotation(name, desc);
annotations.addFirst(childAnnotation);
return new ModAnnotationVisitor(annotations, childAnnotation,true);
}
@Override
public void visitEnd()
{
if (array)
{
annotation.endArray();
}
if (isSubAnnotation)
{
ModAnnotation child = annotations.removeFirst();
annotations.addLast(child);
}
}
}

View file

@ -0,0 +1,72 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.moddiscovery;
import net.minecraftforge.forgespi.language.ModFileScanData;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import java.lang.annotation.ElementType;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class ModClassVisitor extends ClassVisitor
{
private Type asmType;
private Type asmSuperType;
private Set<Type> interfaces;
private final LinkedList<ModAnnotation> annotations = new LinkedList<>();
public ModClassVisitor()
{
super(Opcodes.ASM9);
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces)
{
this.asmType = Type.getObjectType(name);
this.asmSuperType = superName != null && superName.length() > 0 ? Type.getObjectType(superName) : null;
this.interfaces = Stream.of(interfaces).map(Type::getObjectType).collect(Collectors.toSet());
}
@Override
public AnnotationVisitor visitAnnotation(final String annotationName, final boolean runtimeVisible)
{
ModAnnotation ann = new ModAnnotation(ElementType.TYPE, Type.getType(annotationName), this.asmType.getClassName());
annotations.addFirst(ann);
return new ModAnnotationVisitor(annotations, ann);
}
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value)
{
return new ModFieldVisitor(name, annotations);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions)
{
return new ModMethodVisitor(name, desc, annotations);
}
public void buildData(final Set<ModFileScanData.ClassData> classes, final Set<ModFileScanData.AnnotationData> annotations) {
classes.add(new ModFileScanData.ClassData(this.asmType, this.asmSuperType, this.interfaces));
final List<ModFileScanData.AnnotationData> collect = this.annotations.stream().
filter(ma->ModFileScanData.interestingAnnotations().test(ma.getASMType())).
map(a -> ModAnnotation.fromModAnnotation(this.asmType, a)).collect(Collectors.toList());
annotations.addAll(collect);
}
}

View file

@ -0,0 +1,175 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.moddiscovery;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.Launcher;
import cpw.mods.modlauncher.api.IModuleLayerManager;
import cpw.mods.modlauncher.util.ServiceLoaderUtils;
import net.minecraftforge.fml.loading.EarlyLoadingException;
import net.minecraftforge.fml.loading.ImmediateWindowHandler;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.fml.loading.UniqueModListBuilder;
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
import net.minecraftforge.forgespi.Environment;
import net.minecraftforge.forgespi.language.IModFileInfo;
import net.minecraftforge.forgespi.locating.IDependencyLocator;
import net.minecraftforge.forgespi.locating.IModFile;
import net.minecraftforge.forgespi.locating.IModLocator;
import org.slf4j.Logger;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.stream.Collectors;
public class ModDiscoverer {
private static final Logger LOGGER = LogUtils.getLogger();
private final ServiceLoader<IModLocator> modLocators;
private final ServiceLoader<IDependencyLocator> dependencyLocators;
private final List<IModLocator> modLocatorList;
private final List<IDependencyLocator> dependencyLocatorList;
public ModDiscoverer(Map<String, ?> arguments) {
Launcher.INSTANCE.environment().computePropertyIfAbsent(Environment.Keys.MODDIRECTORYFACTORY.get(), v->ModsFolderLocator::new);
Launcher.INSTANCE.environment().computePropertyIfAbsent(Environment.Keys.PROGRESSMESSAGE.get(), v-> StartupNotificationManager.locatorConsumer().orElseGet(()-> s->{}));
final var moduleLayerManager = Launcher.INSTANCE.environment().findModuleLayerManager().orElseThrow();
modLocators = ServiceLoader.load(moduleLayerManager.getLayer(IModuleLayerManager.Layer.SERVICE).orElseThrow(), IModLocator.class);
dependencyLocators = ServiceLoader.load(moduleLayerManager.getLayer(IModuleLayerManager.Layer.SERVICE).orElseThrow(), IDependencyLocator.class);
modLocatorList = ServiceLoaderUtils.streamServiceLoader(()-> modLocators, sce->LOGGER.error("Failed to load mod locator list", sce)).collect(Collectors.toList());
modLocatorList.forEach(l->l.initArguments(arguments));
dependencyLocatorList = ServiceLoaderUtils.streamServiceLoader(()-> dependencyLocators, sce->LOGGER.error("Failed to load dependency locator list", sce)).collect(Collectors.toList());
dependencyLocatorList.forEach(l->l.initArguments(arguments));
if (LOGGER.isDebugEnabled(LogMarkers.CORE))
{
LOGGER.debug(LogMarkers.CORE, "Found Mod Locators : {}", modLocatorList.stream()
.map(modLocator -> "(%s:%s)".formatted(modLocator.name(),
modLocator.getClass().getPackage().getImplementationVersion())).collect(Collectors.joining(",")));
}
if (LOGGER.isDebugEnabled(LogMarkers.CORE))
{
LOGGER.debug(LogMarkers.CORE, "Found Dependency Locators : {}", dependencyLocatorList.stream()
.map(dependencyLocator -> "(%s:%s)".formatted(dependencyLocator.name(),
dependencyLocator.getClass().getPackage().getImplementationVersion())).collect(Collectors.joining(",")));
}
}
public ModValidator discoverMods() {
LOGGER.debug(LogMarkers.SCAN,"Scanning for mods and other resources to load. We know {} ways to find mods", modLocatorList.size());
List<ModFile> loadedFiles = new ArrayList<>();
List<EarlyLoadingException.ExceptionData> discoveryErrorData = new ArrayList<>();
boolean successfullyLoadedMods = true;
List<IModFileInfo> brokenFiles = new ArrayList<>();
ImmediateWindowHandler.updateProgress("Discovering mod files");
//Loop all mod locators to get the prime mods to load from.
for (IModLocator locator : modLocatorList) {
try {
LOGGER.debug(LogMarkers.SCAN, "Trying locator {}", locator);
var candidates = locator.scanMods();
LOGGER.debug(LogMarkers.SCAN, "Locator {} found {} candidates or errors", locator, candidates.size());
var exceptions = candidates.stream().map(IModLocator.ModFileOrException::ex).filter(Objects::nonNull).toList();
if (!exceptions.isEmpty()) {
LOGGER.debug(LogMarkers.SCAN, "Locator {} found {} invalid mod files", locator, exceptions.size());
brokenFiles.addAll(exceptions.stream().map(e->e instanceof InvalidModFileException ime ? ime.getBrokenFile() : null).filter(Objects::nonNull).toList());
}
var locatedFiles = candidates.stream().map(IModLocator.ModFileOrException::file).filter(Objects::nonNull).collect(Collectors.toList());
var badModFiles = locatedFiles.stream().filter(file -> !(file instanceof ModFile)).toList();
if (!badModFiles.isEmpty()) {
LOGGER.error(LogMarkers.SCAN, "Locator {} returned {} files which is are not ModFile instances! They will be skipped!", locator, badModFiles.size());
brokenFiles.addAll(badModFiles.stream().map(IModFile::getModFileInfo).toList());
}
locatedFiles.removeAll(badModFiles);
LOGGER.debug(LogMarkers.SCAN, "Locator {} found {} valid mod files", locator, locatedFiles.size());
handleLocatedFiles(loadedFiles, locatedFiles);
} catch (InvalidModFileException imfe) {
// We don't generally expect this exception, since it should come from the candidates stream above and be handled in the Locator, but just in case.
LOGGER.error(LogMarkers.SCAN, "Locator {} found an invalid mod file {}", locator, imfe.getBrokenFile(), imfe);
brokenFiles.add(imfe.getBrokenFile());
} catch (EarlyLoadingException exception) {
LOGGER.error(LogMarkers.SCAN, "Failed to load mods with locator {}", locator, exception);
discoveryErrorData.addAll(exception.getAllData());
}
}
//First processing run of the mod list. Any duplicates will cause resolution failure and dependency loading will be skipped.
Map<IModFile.Type, List<ModFile>> modFilesMap = Maps.newHashMap();
try {
final UniqueModListBuilder modsUniqueListBuilder = new UniqueModListBuilder(loadedFiles);
final UniqueModListBuilder.UniqueModListData uniqueModsData = modsUniqueListBuilder.buildUniqueList();
//Grab the temporary results.
//This allows loading to continue to a base state, in case dependency loading fails.
modFilesMap = uniqueModsData.modFiles().stream()
.collect(Collectors.groupingBy(IModFile::getType));
loadedFiles = uniqueModsData.modFiles();
}
catch (EarlyLoadingException exception) {
LOGGER.error(LogMarkers.SCAN, "Failed to build unique mod list after mod discovery.", exception);
discoveryErrorData.addAll(exception.getAllData());
successfullyLoadedMods = false;
}
//We can continue loading if prime mods loaded successfully.
if (successfullyLoadedMods) {
LOGGER.debug(LogMarkers.SCAN, "Successfully Loaded {} mods. Attempting to load dependencies...", loadedFiles.size());
for (IDependencyLocator locator : dependencyLocatorList) {
try {
LOGGER.debug(LogMarkers.SCAN,"Trying locator {}", locator);
final List<IModFile> locatedMods = ImmutableList.copyOf(loadedFiles);
var locatedFiles = locator.scanMods(locatedMods);
if (locatedFiles.stream().anyMatch(file -> !(file instanceof ModFile))) {
LOGGER.error(LogMarkers.SCAN, "A dependency locator returned a file which is not a ModFile instance!. They will be skipped!");
}
handleLocatedFiles(loadedFiles, locatedFiles);
}
catch (EarlyLoadingException exception) {
LOGGER.error(LogMarkers.SCAN, "Failed to load dependencies with locator {}", locator, exception);
discoveryErrorData.addAll(exception.getAllData());
}
}
//Second processing run of the mod list. Any duplicates will cause resolution failure and only the mods list will be loaded.
try {
final UniqueModListBuilder modsAndDependenciesUniqueListBuilder = new UniqueModListBuilder(loadedFiles);
final UniqueModListBuilder.UniqueModListData uniqueModsAndDependenciesData = modsAndDependenciesUniqueListBuilder.buildUniqueList();
//We now only need the mod files map, not the list.
modFilesMap = uniqueModsAndDependenciesData.modFiles().stream()
.collect(Collectors.groupingBy(IModFile::getType));
} catch (EarlyLoadingException exception) {
LOGGER.error(LogMarkers.SCAN, "Failed to build unique mod list after dependency discovery.", exception);
discoveryErrorData.addAll(exception.getAllData());
modFilesMap = loadedFiles.stream().collect(Collectors.groupingBy(IModFile::getType));
}
}
else {
//Failure notify the listeners.
LOGGER.error(LogMarkers.SCAN, "Mod Discovery failed. Skipping dependency discovery.");
}
//Validate the loading. With a deduplicated list, we can now successfully process the artifacts and load
//transformer plugins.
var validator = new ModValidator(modFilesMap, brokenFiles, discoveryErrorData);
validator.stage1Validation();
return validator;
}
private void handleLocatedFiles(final List<ModFile> loadedFiles, final List<IModFile> locatedFiles)
{
var locatedModFiles = locatedFiles.stream().filter(ModFile.class::isInstance).map(ModFile.class::cast).toList();
for (IModFile mf : locatedModFiles) {
LOGGER.info(LogMarkers.SCAN, "Found mod file {} of type {} with provider {}", mf.getFileName(), mf.getType(), mf.getProvider());
}
loadedFiles.addAll(locatedModFiles);
}
}

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.moddiscovery;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import java.lang.annotation.ElementType;
import java.util.LinkedList;
public class ModFieldVisitor extends FieldVisitor
{
private final LinkedList<ModAnnotation> annotations;
private final String fieldName;
public ModFieldVisitor(String name, final LinkedList<ModAnnotation> annotations)
{
super(Opcodes.ASM9);
this.fieldName = name;
this.annotations = annotations;
}
@Override
public AnnotationVisitor visitAnnotation(String annotationName, boolean runtimeVisible)
{
ModAnnotation ann = new ModAnnotation(ElementType.FIELD, Type.getType(annotationName), fieldName);
annotations.addFirst(ann);
return new ModAnnotationVisitor(annotations, ann);
}
}

View file

@ -0,0 +1,220 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.moddiscovery;
import com.google.common.collect.ImmutableMap;
import com.mojang.logging.LogUtils;
import cpw.mods.jarhandling.SecureJar;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
import net.minecraftforge.forgespi.language.IModFileInfo;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.forgespi.language.IModLanguageProvider;
import net.minecraftforge.forgespi.language.ModFileScanData;
import net.minecraftforge.forgespi.locating.IModFile;
import net.minecraftforge.forgespi.locating.IModProvider;
import net.minecraftforge.forgespi.locating.ModFileFactory;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.slf4j.Logger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
public class ModFile implements IModFile {
// Mods either must have a mods.toml or a manifest. We can no longer just put any jar on the classpath.
@Deprecated(forRemoval = true, since = "1.18")
public static final Manifest DEFAULTMANIFEST;
private static final Logger LOGGER = LogUtils.getLogger();
static {
DEFAULTMANIFEST = new Manifest();
DEFAULTMANIFEST.getMainAttributes().putValue("FMLModType", "MOD");
}
private final String jarVersion;
private final ModFileFactory.ModFileInfoParser parser;
private Map<String, Object> fileProperties;
private List<IModLanguageProvider> loaders;
private Throwable scanError;
private final SecureJar jar;
private final Type modFileType;
private final Manifest manifest;
private final IModProvider provider;
private IModFileInfo modFileInfo;
private ModFileScanData fileModFileScanData;
private CompletableFuture<ModFileScanData> futureScanResult;
private List<CoreModFile> coreMods;
private Path accessTransformer;
static final Attributes.Name TYPE = new Attributes.Name("FMLModType");
private SecureJar.Status securityStatus;
public ModFile(final SecureJar jar, final IModProvider provider, final ModFileFactory.ModFileInfoParser parser) {
this(jar, provider, parser, parseType(jar));
}
public ModFile(final SecureJar jar, final IModProvider provider, final ModFileFactory.ModFileInfoParser parser, String type) {
this.provider = provider;
this.jar = jar;
this.parser = parser;
manifest = this.jar.moduleDataProvider().getManifest();
modFileType = Type.valueOf(type);
jarVersion = Optional.ofNullable(manifest.getMainAttributes().getValue(Attributes.Name.IMPLEMENTATION_VERSION)).orElse("0.0NONE");
this.modFileInfo = ModFileParser.readModList(this, this.parser);
}
@Override
public Supplier<Map<String,Object>> getSubstitutionMap() {
return () -> ImmutableMap.<String,Object>builder().put("jarVersion", jarVersion).putAll(fileProperties).build();
}
@Override
public Type getType() {
return modFileType;
}
@Override
public Path getFilePath() {
return jar.getPrimaryPath();
}
@Override
public SecureJar getSecureJar() {
return this.jar;
}
@Override
public List<IModInfo> getModInfos() {
return modFileInfo.getMods();
}
public Optional<Path> getAccessTransformer() {
return Optional.ofNullable(Files.exists(accessTransformer) ? accessTransformer : null);
}
public boolean identifyMods() {
this.modFileInfo = ModFileParser.readModList(this, this.parser);
if (this.modFileInfo == null) return this.getType() != Type.MOD;
LOGGER.debug(LogMarkers.LOADING,"Loading mod file {} with languages {}", this.getFilePath(), this.modFileInfo.requiredLanguageLoaders());
this.coreMods = ModFileParser.getCoreMods(this);
this.coreMods.forEach(mi-> LOGGER.debug(LogMarkers.LOADING,"Found coremod {}", mi.getPath()));
this.accessTransformer = findResource("META-INF", "accesstransformer.cfg");
return true;
}
public List<CoreModFile> getCoreMods() {
return coreMods;
}
/**
* Run in an executor thread to harvest the class and annotation list
*/
public ModFileScanData compileContent() {
return new Scanner(this).scan();
}
public void scanFile(Consumer<Path> pathConsumer) {
provider.scanFile(this, pathConsumer);
}
public void setFutureScanResult(CompletableFuture<ModFileScanData> future) {
this.futureScanResult = future;
}
@Override
public ModFileScanData getScanResult() {
if (this.futureScanResult != null) {
try {
this.futureScanResult.get();
} catch (InterruptedException | ExecutionException e) {
LOGGER.error("Caught unexpected exception processing scan results", e);
}
}
if (this.scanError != null) {
throw new RuntimeException(this.scanError);
}
return this.fileModFileScanData;
}
public void setScanResult(final ModFileScanData modFileScanData, final Throwable throwable) {
this.futureScanResult = null;
this.fileModFileScanData = modFileScanData;
if (throwable != null) {
this.scanError = throwable;
}
}
public void setFileProperties(Map<String, Object> fileProperties) {
this.fileProperties = fileProperties;
}
@Override
public List<IModLanguageProvider> getLoaders() {
return loaders;
}
@Override
public Path findResource(String... path) {
if (path.length < 1) {
throw new IllegalArgumentException("Missing path");
}
return getSecureJar().getPath(String.join("/", path));
}
public void identifyLanguage() {
this.loaders = this.modFileInfo.requiredLanguageLoaders().stream()
.map(spec-> FMLLoader.getLanguageLoadingProvider().findLanguage(this, spec.languageName(), spec.acceptedVersions()))
.toList();
}
@Override
public String toString() {
return "Mod File: " + Objects.toString(this.jar.getPrimaryPath());
}
@Override
public String getFileName() {
return getFilePath().getFileName().toString();
}
@Override
public IModProvider getProvider() {
return provider;
}
@Override
public IModFileInfo getModFileInfo() {
return modFileInfo;
}
@Override
public void setSecurityStatus(final SecureJar.Status status) {
this.securityStatus = status;
}
public ArtifactVersion getJarVersion()
{
return new DefaultArtifactVersion(this.jarVersion);
}
private static String parseType(final SecureJar jar) {
final Manifest m = jar.moduleDataProvider().getManifest();
final Optional<String> value = Optional.ofNullable(m.getMainAttributes().getValue(TYPE));
return value.orElse("MOD");
}
}

View file

@ -0,0 +1,211 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.moddiscovery;
import com.google.common.base.Strings;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.fml.loading.StringUtils;
import net.minecraftforge.forgespi.language.IConfigurable;
import net.minecraftforge.forgespi.language.IModFileInfo;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.forgespi.language.MavenVersionAdapter;
import org.slf4j.Logger;
import javax.security.auth.x500.X500Principal;
import java.net.URL;
import java.security.CodeSigner;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class ModFileInfo implements IModFileInfo, IConfigurable
{
private static final Logger LOGGER = LogUtils.getLogger();
private final IConfigurable config;
private final ModFile modFile;
private final URL issueURL;
private final List<LanguageSpec> languageSpecs;
private final boolean showAsResourcePack;
private final List<IModInfo> mods;
private final Map<String,Object> properties;
private final String license;
private final List<String> usesServices;
ModFileInfo(final ModFile modFile, final IConfigurable config, Consumer<IModFileInfo> configFileConsumer)
{
this.modFile = modFile;
this.config = config;
configFileConsumer.accept(this);
// modloader is essential
var modLoader = config.<String>getConfigElement("modLoader")
.orElseThrow(()->new InvalidModFileException("Missing ModLoader in file", this));
// as is modloader version
var modLoaderVersion = config.<String>getConfigElement("loaderVersion")
.map(MavenVersionAdapter::createFromVersionSpec)
.orElseThrow(()->new InvalidModFileException("Missing ModLoader version in file", this));
this.languageSpecs = new ArrayList<>(List.of(new LanguageSpec(modLoader, modLoaderVersion)));
// the remaining properties are optional with sensible defaults
this.license = config.<String>getConfigElement("license")
.orElse("");
this.showAsResourcePack = config.<Boolean>getConfigElement("showAsResourcePack")
.orElse(false);
this.usesServices = config.<List<String>>getConfigElement("services")
.orElse(List.of());
this.properties = config.<Map<String, Object>>getConfigElement("properties")
.orElse(Collections.emptyMap());
this.modFile.setFileProperties(this.properties);
this.issueURL = config.<String>getConfigElement("issueTrackerURL")
.map(StringUtils::toURL)
.orElse(null);
final List<? extends IConfigurable> modConfigs = config.getConfigList("mods");
if (modConfigs.isEmpty())
{
throw new InvalidModFileException("Missing mods list", this);
}
this.mods = modConfigs.stream()
.map(mi-> (IModInfo)new ModInfo(this, mi))
.toList();
if (LOGGER.isDebugEnabled(LogMarkers.LOADING))
{
LOGGER.debug(LogMarkers.LOADING, "Found valid mod file {} with {} mods - versions {}",
this.modFile.getFileName(),
this.mods.stream().map(IModInfo::getModId).collect(Collectors.joining(",", "{", "}")),
this.mods.stream().map(IModInfo::getVersion).map(Objects::toString).collect(Collectors.joining(",", "{", "}")));
}
}
public ModFileInfo(final ModFile file, final IConfigurable config, Consumer<IModFileInfo> configFileConsumer, final List<LanguageSpec> languageSpecs) {
this(file, config, configFileConsumer);
this.languageSpecs.addAll(languageSpecs);
}
@Override
public List<IModInfo> getMods()
{
return mods;
}
public ModFile getFile()
{
return this.modFile;
}
@Override
public List<LanguageSpec> requiredLanguageLoaders() {
return this.languageSpecs;
}
@Override
public Map<String, Object> getFileProperties()
{
return this.properties;
}
@Override
public boolean showAsResourcePack()
{
return this.showAsResourcePack;
}
@Override
public <T> Optional<T> getConfigElement(final String... key)
{
return this.config.getConfigElement(key);
}
@Override
public List<? extends IConfigurable> getConfigList(final String... key)
{
return this.config.getConfigList(key);
}
@Override
public String getLicense()
{
return license;
}
@Override
public IConfigurable getConfig() {
return this;
}
public URL getIssueURL()
{
return issueURL;
}
public boolean missingLicense()
{
return Strings.isNullOrEmpty(license);
}
public Optional<String> getCodeSigningFingerprint() {
var signers = this.modFile.getSecureJar().getManifestSigners();
return (signers == null ? Stream.<CodeSigner>of() : Arrays.stream(signers))
.flatMap(csa->csa.getSignerCertPath().getCertificates().stream())
.findFirst()
.map(LamdbaExceptionUtils.rethrowFunction(Certificate::getEncoded))
.map(bytes->LamdbaExceptionUtils.uncheck(()->MessageDigest.getInstance("SHA-256")).digest(bytes))
.map(StringUtils::binToHex)
.map(str-> String.join(":", str.split("(?<=\\G.{2})")));
}
public Optional<String> getTrustData() {
return Arrays.stream(this.modFile.getSecureJar().getManifestSigners())
.flatMap(csa->csa.getSignerCertPath().getCertificates().stream())
.findFirst()
.map(X509Certificate.class::cast)
.map(c->{
StringBuffer sb = new StringBuffer();
sb.append(c.getSubjectX500Principal().getName(X500Principal.RFC2253).split(",")[0]);
boolean selfSigned = false;
try {
c.verify(c.getPublicKey());
selfSigned = true;
} catch (CertificateException | NoSuchAlgorithmException | InvalidKeyException | SignatureException | NoSuchProviderException e) {
// not self signed
}
if (selfSigned) {
sb.append(" self-signed");
} else {
sb.append(" signed by ").append(c.getIssuerX500Principal().getName(X500Principal.RFC2253).split(",")[0]);
};
return sb.toString();
});
}
@Override
public String moduleName() {
return getMods().get(0).getModId();
}
@Override
public String versionString() {
return getMods().get(0).getVersion().toString();
}
@Override
public List<String> usesServices() {
return usesServices;
}
}

View file

@ -0,0 +1,70 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.moddiscovery;
import com.electronwill.nightconfig.core.file.FileConfig;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.forgespi.language.IModFileInfo;
import net.minecraftforge.forgespi.locating.IModFile;
import net.minecraftforge.forgespi.locating.ModFileFactory;
import org.slf4j.Logger;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class ModFileParser {
private static final Logger LOGGER = LogUtils.getLogger();
public static IModFileInfo readModList(final ModFile modFile, final ModFileFactory.ModFileInfoParser parser) {
return parser.build(modFile);
}
public static IModFileInfo modsTomlParser(final IModFile imodFile) {
ModFile modFile = (ModFile) imodFile;
LOGGER.debug(LogMarkers.LOADING,"Considering mod file candidate {}", modFile.getFilePath());
final Path modsjson = modFile.findResource("META-INF", "mods.toml");
if (!Files.exists(modsjson)) {
LOGGER.warn(LogMarkers.LOADING, "Mod file {} is missing mods.toml file", modFile.getFilePath());
return null;
}
final FileConfig fileConfig = FileConfig.builder(modsjson).build();
fileConfig.load();
fileConfig.close();
final NightConfigWrapper configWrapper = new NightConfigWrapper(fileConfig);
return new ModFileInfo(modFile, configWrapper, configWrapper::setFile);
}
protected static List<CoreModFile> getCoreMods(final ModFile modFile) {
Map<String,String> coreModPaths;
try {
final Path coremodsjson = modFile.findResource("META-INF", "coremods.json");
if (!Files.exists(coremodsjson)) {
return Collections.emptyList();
}
final Type type = new TypeToken<Map<String, String>>() {}.getType();
final Gson gson = new Gson();
coreModPaths = gson.fromJson(Files.newBufferedReader(coremodsjson), type);
} catch (IOException e) {
LOGGER.debug(LogMarkers.LOADING,"Failed to read coremod list coremods.json", e);
return Collections.emptyList();
}
return coreModPaths.entrySet().stream()
.peek(e-> LOGGER.debug(LogMarkers.LOADING,"Found coremod {} with Javascript path {}", e.getKey(), e.getValue()))
.map(e -> new CoreModFile(e.getKey(), modFile.findResource(e.getValue()),modFile))
.toList();
}
}

View file

@ -0,0 +1,279 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.moddiscovery;
import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.loading.StringSubstitutor;
import net.minecraftforge.fml.loading.StringUtils;
import net.minecraftforge.forgespi.language.IConfigurable;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.forgespi.language.MavenVersionAdapter;
import net.minecraftforge.forgespi.locating.ForgeFeature;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.artifact.versioning.VersionRange;
import org.slf4j.Logger;
import java.net.URL;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Pattern;
public class ModInfo implements IModInfo, IConfigurable
{
private static final Logger LOGGER = LogUtils.getLogger();
private static final DefaultArtifactVersion DEFAULT_VERSION = new DefaultArtifactVersion("1");
private static final Pattern VALID_MODID = Pattern.compile("^[a-z][a-z0-9_]{1,63}$");
private static final Pattern VALID_NAMESPACE = Pattern.compile("^[a-z][a-z0-9_.-]{1,63}$");
private static final Pattern VALID_VERSION = Pattern.compile("^\\d+.*");
private final ModFileInfo owningFile;
private final String modId;
private final String namespace;
private final ArtifactVersion version;
private final String displayName;
private final String description;
private final Optional<String> logoFile;
private final boolean logoBlur;
private final Optional<URL> updateJSONURL;
private final List<? extends IModInfo.ModVersion> dependencies;
private final List<ForgeFeature.Bound> features;
private final Map<String,Object> properties;
private final IConfigurable config;
private final Optional<URL> modUrl;
public ModInfo(final ModFileInfo owningFile, final IConfigurable config)
{
Optional<ModFileInfo> ownFile = Optional.ofNullable(owningFile);
this.owningFile = owningFile;
this.config = config;
// These are sourced from the mod specific [[mod]] entry
this.modId = config.<String>getConfigElement("modId")
.orElseThrow(() -> new InvalidModFileException("Missing modId", owningFile));
// verify we have a valid modid
if (!VALID_MODID.matcher(this.modId).matches()) {
LOGGER.error(LogUtils.FATAL_MARKER, "Invalid modId found in file {} - {} does not match the standard: {}", this.owningFile.getFile().getFilePath(), this.modId, VALID_MODID.pattern());
throw new InvalidModFileException("Invalid modId found : " + this.modId, owningFile);
}
this.namespace = config.<String>getConfigElement("namespace")
.orElse(this.modId);
// verify our namespace is valid
if (!VALID_NAMESPACE.matcher(this.namespace).matches()) {
LOGGER.error(LogUtils.FATAL_MARKER, "Invalid override namespace found in file {} - {} does not match the standard: {}", this.owningFile.getFile().getFilePath(), this.namespace, VALID_NAMESPACE.pattern());
throw new InvalidModFileException("Invalid override namespace found : " + this.namespace, owningFile);
}
this.version = config.<String>getConfigElement("version")
.map(s -> StringSubstitutor.replace(s, ownFile.map(ModFileInfo::getFile).orElse(null)))
.map(DefaultArtifactVersion::new)
.orElse(DEFAULT_VERSION);
// verify we have a valid mod version
if (!VALID_VERSION.matcher(this.version.toString()).matches()) {
throw new InvalidModFileException("Illegal version number specified "+this.version, this.getOwningFile());
}
// The remaining properties all default to sensible values and are not essential
this.displayName = config.<String>getConfigElement("displayName")
.orElse(this.modId);
this.description = config.<String>getConfigElement("description")
.orElse("MISSING DESCRIPTION")
.replace("\r\n", "\n").stripIndent();
this.logoFile = Optional.ofNullable(config.<String>getConfigElement("logoFile")
.orElseGet(() -> ownFile.flatMap(mf -> mf.<String>getConfigElement("logoFile"))
.orElse(null)));
this.logoBlur = config.<Boolean>getConfigElement("logoBlur")
.orElseGet(() -> ownFile.flatMap(f -> f.<Boolean>getConfigElement("logoBlur"))
.orElse(true));
this.updateJSONURL = config.<String>getConfigElement("updateJSONURL")
.map(StringUtils::toURL);
this.modUrl = config.<String>getConfigElement("modUrl")
.map(StringUtils::toURL);
// These are sourced from the file rather than the mod-specific block, but with a modid tag
this.dependencies = ownFile.map(mfi -> mfi.getConfigList("dependencies", this.modId))
.orElse(Collections.emptyList())
.stream()
.map(dep -> new ModVersion(this, dep))
.toList();
this.features = ownFile.flatMap(mfi -> mfi.<Map<String, Object>>getConfigElement("features", this.modId))
.stream()
.flatMap(m->m.entrySet().stream())
.map(this::makeBound)
.toList();
this.properties = ownFile.flatMap(mfi -> mfi.<Map<String, Object>>getConfigElement("modproperties", this.modId))
.orElse(Collections.emptyMap());
}
@Override
public ModFileInfo getOwningFile() {
return owningFile;
}
@Override
public String getModId() {
return modId;
}
@Override
public String getDisplayName()
{
return this.displayName;
}
@Override
public String getDescription()
{
return this.description;
}
@Override
public ArtifactVersion getVersion() {
return version;
}
@Override
public List<? extends IModInfo.ModVersion> getDependencies() {
return this.dependencies;
}
@Override
public String getNamespace() {
return this.namespace;
}
@Override
public Map<String, Object> getModProperties() {
return this.properties;
}
@Override
public Optional<URL> getUpdateURL() {
return this.updateJSONURL;
}
@Override
public Optional<String> getLogoFile()
{
return this.logoFile;
}
@Override
public boolean getLogoBlur()
{
return this.logoBlur;
}
@Override
public IConfigurable getConfig() {
return this;
}
@Override
public List<? extends ForgeFeature.Bound> getForgeFeatures() {
return this.features;
}
@Override
public <T> Optional<T> getConfigElement(final String... key) {
return this.config.getConfigElement(key);
}
@Override
public List<? extends IConfigurable> getConfigList(final String... key) {
return null;
}
@Override
public Optional<URL> getModURL() {
return modUrl;
}
private ForgeFeature.Bound makeBound(Map.Entry<String, Object> e) {
if (e.getValue() instanceof String val) {
return new ForgeFeature.Bound(e.getKey(), val, this);
} else {
throw new InvalidModFileException("Invalid feature bound {" + e.getValue() + "} for key {" + e.getKey() + "} only strings are accepted", this.owningFile);
}
}
class ModVersion implements net.minecraftforge.forgespi.language.IModInfo.ModVersion {
private IModInfo owner;
private final String modId;
private final VersionRange versionRange;
private final boolean mandatory;
private final Ordering ordering;
private final DependencySide side;
private final Optional<URL> referralUrl;
public ModVersion(final IModInfo owner, final IConfigurable config) {
this.owner = owner;
this.modId = config.<String>getConfigElement("modId")
.orElseThrow(()->new InvalidModFileException("Missing required field modid in dependency", getOwningFile()));
this.mandatory = config.<Boolean>getConfigElement("mandatory")
.orElseThrow(()->new InvalidModFileException("Missing required field mandatory in dependency", getOwningFile()));
this.versionRange = config.<String>getConfigElement("versionRange")
.map(MavenVersionAdapter::createFromVersionSpec)
.orElse(UNBOUNDED);
this.ordering = config.<String>getConfigElement("ordering")
.map(Ordering::valueOf)
.orElse(Ordering.NONE);
this.side = config.<String>getConfigElement("side")
.map(DependencySide::valueOf)
.orElse(DependencySide.BOTH);
this.referralUrl = config.<String>getConfigElement("referralUrl")
.map(StringUtils::toURL);
}
@Override
public String getModId()
{
return modId;
}
@Override
public VersionRange getVersionRange()
{
return versionRange;
}
@Override
public boolean isMandatory()
{
return mandatory;
}
@Override
public Ordering getOrdering()
{
return ordering;
}
@Override
public DependencySide getSide()
{
return side;
}
@Override
public void setOwner(final IModInfo owner)
{
this.owner = owner;
}
@Override
public IModInfo getOwner()
{
return owner;
}
@Override
public Optional<URL> getReferralURL() {
return referralUrl;
}
}
}

View file

@ -0,0 +1,103 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.moddiscovery;
import cpw.mods.jarhandling.JarMetadata;
import cpw.mods.jarhandling.SecureJar;
import net.minecraftforge.forgespi.locating.IModFile;
import net.minecraftforge.forgespi.locating.IModLocator;
import java.lang.module.ModuleDescriptor;
import java.nio.file.Path;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Predicate;
public final class ModJarMetadata implements JarMetadata {
private IModFile modFile;
private ModuleDescriptor descriptor;
// TODO: Remove helper functions to cleanup api
@Deprecated(forRemoval = true, since="1.18")
static Optional<IModFile> buildFile(IModLocator locator, Predicate<SecureJar> jarTest, BiPredicate<String, String> filter, Path... files) {
return buildFile(j->new ModFile(j, locator, ModFileParser::modsTomlParser), jarTest, filter, files);
}
// TODO: Remove helper functions to cleanup api
@Deprecated(forRemoval = true, since="1.18")
static IModFile buildFile(IModLocator locator, Path... files) {
return buildFile(locator, j->true, (a,b) -> true, files).orElseThrow(()->new IllegalArgumentException("Failed to find valid JAR file"));
}
// TODO: Remove helper functions to cleanup api
@Deprecated(forRemoval = true, since="1.18")
static Optional<IModFile> buildFile(Function<SecureJar, IModFile> mfConstructor, Predicate<SecureJar> jarTest, BiPredicate<String, String> filter, Path... files) {
var mjm = new ModJarMetadata();
var sj = SecureJar.from(()->ModFile.DEFAULTMANIFEST, j->mjm, filter, files);
if (jarTest.test(sj)) {
var mf = mfConstructor.apply(sj);
mjm.setModFile(mf);
return Optional.of(mf);
} else {
return Optional.empty();
}
}
ModJarMetadata() {
}
public void setModFile(IModFile file) {
this.modFile = file;
}
@Override
public String name() {
return modFile.getModFileInfo().moduleName();
}
@Override
public String version() {
return modFile.getModFileInfo().versionString();
}
@Override
public ModuleDescriptor descriptor() {
if (descriptor != null) return descriptor;
var bld = ModuleDescriptor.newAutomaticModule(name())
.version(version())
.packages(modFile.getSecureJar().getPackages());
modFile.getSecureJar().getProviders().stream()
.filter(p -> !p.providers().isEmpty())
.forEach(p -> bld.provides(p.serviceName(), p.providers()));
modFile.getModFileInfo().usesServices().forEach(bld::uses);
descriptor = bld.build();
return descriptor;
}
public IModFile modFile() {
return modFile;
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj == null || obj.getClass() != this.getClass()) return false;
var that = (ModJarMetadata) obj;
return Objects.equals(this.modFile, that.modFile);
}
@Override
public int hashCode() {
return Objects.hash(modFile);
}
@Override
public String toString() {
return "ModJarMetadata[" +"modFile=" + modFile + ']';
}
}

View file

@ -0,0 +1,88 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.moddiscovery;
import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.loading.FMLPaths;
import net.minecraftforge.fml.loading.FileUtils;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.fml.loading.MavenCoordinateResolver;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public class ModListHandler {
private static final Logger LOGGER = LogUtils.getLogger();
/**
* Reads the modList paths specified, and searches each maven root for mods matching. Returns a list of mods
* found.
*
* @param modListPaths Paths to search for mod file lists
* @param mavenRootPaths Roots to look for mods listed
* @return list of found mod coordinates
*/
public static List<String> processModLists(final List<String> modListPaths, final List<Path> mavenRootPaths) {
final List<String> modCoordinates = modListPaths.stream().map(ModListHandler::transformPathToList).
flatMap(Collection::stream).
collect(Collectors.toList());
List<Pair<Path,String>> localModCoords = modCoordinates.stream().map(mc->Pair.of(MavenCoordinateResolver.get(mc), mc)).collect(Collectors.toList());
final List<Pair<Path, String>> foundCoordinates = localModCoords.stream().
map(mc -> mavenRootPaths.stream().
map(root -> Pair.of(root.resolve(mc.getLeft()), mc.getRight())).
filter(path -> Files.exists(path.getLeft())).
findFirst().
orElseGet(()->{
LOGGER.warn(LogMarkers.CORE, "Failed to find coordinate {}", mc);
return null;
})).
filter(Objects::nonNull).
collect(Collectors.toList());
final List<String> found = foundCoordinates.stream().map(Pair::getRight).collect(Collectors.toList());
LOGGER.debug(LogMarkers.CORE, "Found mod coordinates from lists: {}", found);
return found;
}
private static List<String> transformPathToList(final String path) {
LOGGER.debug(LogMarkers.CORE, "Reading mod list {}", path);
Path filePath = FMLPaths.GAMEDIR.get().resolve(path);
if (!Files.exists(filePath)) {
LOGGER.warn(LogMarkers.CORE, "Failed to find modlist file at {}", filePath);
return Collections.emptyList();
}
String extension = FileUtils.fileExtension(filePath);
if (Objects.equals("list",extension)) {
return readListFile(filePath).stream().filter(s -> !s.isEmpty()).collect(Collectors.toList());
} else {
LOGGER.warn(LogMarkers.CORE, "Failed to read unknown file list type {} for file {}", extension, filePath);
}
return Collections.emptyList();
}
/**
* Simple list file, ending in ".list" with one mod coordinate per line
* @param filePath path
* @return list
*/
private static List<String> readListFile(final Path filePath) {
try {
return Files.readAllLines(filePath);
} catch (IOException e) {
LOGGER.warn(LogMarkers.CORE, "Failed to read file list {}", filePath, e);
return Collections.emptyList();
}
}
}

View file

@ -0,0 +1,38 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.moddiscovery;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import java.lang.annotation.ElementType;
import java.util.LinkedList;
public class ModMethodVisitor extends MethodVisitor {
private final LinkedList<ModAnnotation> annotations;
private String methodName;
private String methodDescriptor;
public ModMethodVisitor(String name, String desc, final LinkedList<ModAnnotation> annotations)
{
super(Opcodes.ASM9);
this.methodName = name;
this.methodDescriptor = desc;
this.annotations = annotations;
}
@Override
public AnnotationVisitor visitAnnotation(String annotationName, boolean runtimeVisible)
{
ModAnnotation ann = new ModAnnotation(ElementType.METHOD, Type.getType(annotationName), methodName+methodDescriptor);
annotations.addFirst(ann);
return new ModAnnotationVisitor(annotations, ann);
}
}

View file

@ -0,0 +1,106 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.moddiscovery;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.api.IModuleLayerManager;
import cpw.mods.modlauncher.api.ITransformationService;
import net.minecraftforge.fml.loading.*;
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
import net.minecraftforge.forgespi.language.IModFileInfo;
import net.minecraftforge.forgespi.locating.IModFile;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class ModValidator {
private static final Logger LOGGER = LogUtils.getLogger();
private final Map<IModFile.Type, List<ModFile>> modFiles;
private final List<ModFile> candidatePlugins;
private final List<ModFile> candidateMods;
private LoadingModList loadingModList;
private List<IModFile> brokenFiles;
private final List<EarlyLoadingException.ExceptionData> discoveryErrorData;
public ModValidator(final Map<IModFile.Type, List<ModFile>> modFiles, final List<IModFileInfo> brokenFiles, final List<EarlyLoadingException.ExceptionData> discoveryErrorData) {
this.modFiles = modFiles;
this.candidateMods = lst(modFiles.get(IModFile.Type.MOD));
this.candidateMods.addAll(lst(modFiles.get(IModFile.Type.GAMELIBRARY)));
this.candidatePlugins = lst(modFiles.get(IModFile.Type.LANGPROVIDER));
this.candidatePlugins.addAll(lst(modFiles.get(IModFile.Type.LIBRARY)));
this.discoveryErrorData = discoveryErrorData;
this.brokenFiles = brokenFiles.stream().map(IModFileInfo::getFile).collect(Collectors.toList()); // mutable list
}
private static List<ModFile> lst(List<ModFile> files) {
return files == null ? new ArrayList<>() : new ArrayList<>(files);
}
public void stage1Validation() {
brokenFiles.addAll(validateFiles(candidateMods));
if (LOGGER.isDebugEnabled(LogMarkers.SCAN)) {
LOGGER.debug(LogMarkers.SCAN, "Found {} mod files with {} mods", candidateMods.size(), candidateMods.stream().mapToInt(mf -> mf.getModInfos().size()).sum());
}
ImmediateWindowHandler.updateProgress("Found "+candidateMods.size()+" mod candidates");
}
@NotNull
private List<ModFile> validateFiles(final List<ModFile> mods) {
final List<ModFile> brokenFiles = new ArrayList<>();
for (Iterator<ModFile> iterator = mods.iterator(); iterator.hasNext(); )
{
ModFile modFile = iterator.next();
if (!modFile.getProvider().isValid(modFile) || !modFile.identifyMods()) {
LOGGER.warn(LogMarkers.SCAN, "File {} has been ignored - it is invalid", modFile.getFilePath());
iterator.remove();
brokenFiles.add(modFile);
}
}
return brokenFiles;
}
public ITransformationService.Resource getPluginResources() {
return new ITransformationService.Resource(IModuleLayerManager.Layer.PLUGIN, this.candidatePlugins.stream().map(IModFile::getSecureJar).toList());
}
public ITransformationService.Resource getModResources() {
return new ITransformationService.Resource(IModuleLayerManager.Layer.GAME, this.candidateMods.stream().map(IModFile::getSecureJar).toList());
}
private List<EarlyLoadingException.ExceptionData> validateLanguages() {
List<EarlyLoadingException.ExceptionData> errorData = new ArrayList<>();
for (Iterator<ModFile> iterator = this.candidateMods.iterator(); iterator.hasNext(); ) {
final ModFile modFile = iterator.next();
try {
modFile.identifyLanguage();
} catch (EarlyLoadingException e) {
errorData.addAll(e.getAllData());
iterator.remove();
}
}
return errorData;
}
public BackgroundScanHandler stage2Validation() {
var errors = validateLanguages();
var allErrors = new ArrayList<>(errors);
allErrors.addAll(this.discoveryErrorData);
loadingModList = ModSorter.sort(candidateMods, allErrors);
loadingModList.addCoreMods();
loadingModList.addAccessTransformers();
loadingModList.setBrokenFiles(brokenFiles);
BackgroundScanHandler backgroundScanHandler = new BackgroundScanHandler(candidateMods);
loadingModList.addForScanning(backgroundScanHandler);
return backgroundScanHandler;
}
}

View file

@ -0,0 +1,69 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.moddiscovery;
import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.loading.FMLPaths;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.fml.loading.ModDirTransformerDiscoverer;
import net.minecraftforge.fml.loading.StringUtils;
import org.slf4j.Logger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Comparator;
import java.util.Map;
import java.util.stream.Stream;
import static cpw.mods.modlauncher.api.LamdbaExceptionUtils.uncheck;
/**
* Support loading mods located in JAR files in the mods folder
*/
public class ModsFolderLocator extends AbstractJarFileModLocator
{
private static final String SUFFIX = ".jar";
private static final Logger LOGGER = LogUtils.getLogger();
private final Path modFolder;
private final String customName;
public ModsFolderLocator() {
this(FMLPaths.MODSDIR.get());
}
ModsFolderLocator(Path modFolder) {
this(modFolder, "mods folder");
}
ModsFolderLocator(Path modFolder, String name) {
this.modFolder = modFolder;
this.customName = name;
}
@Override
public Stream<Path> scanCandidates() {
LOGGER.debug(LogMarkers.SCAN,"Scanning mods dir {} for mods", this.modFolder);
var excluded = ModDirTransformerDiscoverer.allExcluded();
return uncheck(()-> Files.list(this.modFolder))
.filter(p-> !excluded.contains(p) && StringUtils.toLowerCase(p.getFileName().toString()).endsWith(SUFFIX))
.sorted(Comparator.comparing(path-> StringUtils.toLowerCase(path.getFileName().toString())));
}
@Override
public String name() {
return customName;
}
@Override
public String toString() {
return "{"+customName+" locator at "+this.modFolder+"}";
}
@Override
public void initArguments(final Map<String, ?> arguments) {
}
}

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.moddiscovery;
import com.electronwill.nightconfig.core.UnmodifiableConfig;
import net.minecraftforge.forgespi.language.IConfigurable;
import net.minecraftforge.forgespi.language.IModFileInfo;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import static java.util.Arrays.asList;
public class NightConfigWrapper implements IConfigurable {
private final UnmodifiableConfig config;
private IModFileInfo file;
public NightConfigWrapper(final UnmodifiableConfig config) {
this.config = config;
}
NightConfigWrapper setFile(IModFileInfo file) {
this.file = file;
return this;
}
@Override
@SuppressWarnings("unchecked")
public <T> Optional<T> getConfigElement(final String... key) {
var path = asList(key);
return this.config.getOptional(path).map(value -> {
if (value instanceof UnmodifiableConfig) {
return (T) ((UnmodifiableConfig) value).valueMap();
} else if (value instanceof ArrayList<?> al && al.size() > 0 && al.get(0) instanceof UnmodifiableConfig) {
throw new InvalidModFileException("The configuration path " + path + " is invalid. I wasn't expecting a multi-object list - remove one of the [[ ]]", file);
}
return (T) value;
});
}
@Override
public List<? extends IConfigurable> getConfigList(final String... key) {
final List<String> path = asList(key);
if (this.config.contains(path) && !(this.config.get(path) instanceof Collection)) {
throw new InvalidModFileException("The configuration path "+path+" is invalid. Expecting a collection!", file);
}
final Collection<UnmodifiableConfig> nestedConfigs = this.config.getOrElse(path, ArrayList::new);
return nestedConfigs.stream()
.map(NightConfigWrapper::new)
.map(cw->cw.setFile(file))
.collect(Collectors.toList());
}
}

View file

@ -0,0 +1,54 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.moddiscovery;
import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.forgespi.language.IModLanguageProvider;
import net.minecraftforge.forgespi.language.ModFileScanData;
import org.objectweb.asm.ClassReader;
import org.slf4j.Logger;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
public class Scanner {
private static final Logger LOGGER = LogUtils.getLogger();
private final ModFile fileToScan;
public Scanner(final ModFile fileToScan) {
this.fileToScan = fileToScan;
}
public ModFileScanData scan() {
ModFileScanData result = new ModFileScanData();
result.addModFileInfo(fileToScan.getModFileInfo());
fileToScan.scanFile(p -> fileVisitor(p, result));
final List<IModLanguageProvider> loaders = fileToScan.getLoaders();
if (loaders != null) {
loaders.forEach(loader -> {
LOGGER.debug(LogMarkers.SCAN, "Scanning {} with language loader {}", fileToScan.getFilePath(), loader.name());
loader.getFileVisitor().accept(result);
});
}
return result;
}
private void fileVisitor(final Path path, final ModFileScanData result) {
LOGGER.debug(LogMarkers.SCAN,"Scanning {} path {}", fileToScan, path);
try (InputStream in = Files.newInputStream(path)){
ModClassVisitor mcv = new ModClassVisitor();
ClassReader cr = new ClassReader(in);
cr.accept(mcv, 0);
mcv.buildData(result.getClasses(), result.getAnnotations());
} catch (IOException | IllegalArgumentException e) {
// mark path bad
}
}
}

View file

@ -0,0 +1,36 @@
<html>
<head><title>Index of /mirror/src/Magma-1-20-x/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/</title></head>
<body>
<h1>Index of /mirror/src/Magma-1-20-x/fmlloader/src/main/java/net/minecraftforge/fml/loading/moddiscovery/</h1><hr><pre><a href="../">../</a>
<a href="AbstractJarFileDependencyLocator.java">AbstractJarFileDependencyLocator.java</a> 07-Oct-2023 14:12 1986
<a href="AbstractJarFileModLocator.java">AbstractJarFileModLocator.java</a> 07-Oct-2023 14:12 611
<a href="AbstractJarFileModProvider.java">AbstractJarFileModProvider.java</a> 07-Oct-2023 14:12 1474
<a href="AbstractModProvider.java">AbstractModProvider.java</a> 07-Oct-2023 14:12 4882
<a href="BackgroundScanHandler.java">BackgroundScanHandler.java</a> 07-Oct-2023 14:12 3963
<a href="ClasspathLocator.java">ClasspathLocator.java</a> 07-Oct-2023 14:12 3557
<a href="CoreModFile.java">CoreModFile.java</a> 07-Oct-2023 14:12 1283
<a href="ExplodedDirectoryLocator.java">ExplodedDirectoryLocator.java</a> 07-Oct-2023 14:12 2897
<a href="InvalidModFileException.java">InvalidModFileException.java</a> 07-Oct-2023 14:12 826
<a href="InvalidModIdentifier.java">InvalidModIdentifier.java</a> 07-Oct-2023 14:12 2094
<a href="JarInJarDependencyLocator.java">JarInJarDependencyLocator.java</a> 07-Oct-2023 14:12 6010
<a href="MavenDirectoryLocator.java">MavenDirectoryLocator.java</a> 07-Oct-2023 14:12 1934
<a href="MinecraftLocator.java">MinecraftLocator.java</a> 07-Oct-2023 14:12 5298
<a href="ModAnnotation.java">ModAnnotation.java</a> 07-Oct-2023 14:12 3171
<a href="ModAnnotationVisitor.java">ModAnnotationVisitor.java</a> 07-Oct-2023 14:12 2244
<a href="ModClassVisitor.java">ModClassVisitor.java</a> 07-Oct-2023 14:12 2635
<a href="ModDiscoverer.java">ModDiscoverer.java</a> 07-Oct-2023 14:12 11K
<a href="ModFieldVisitor.java">ModFieldVisitor.java</a> 07-Oct-2023 14:12 1049
<a href="ModFile.java">ModFile.java</a> 07-Oct-2023 14:12 7564
<a href="ModFileInfo.java">ModFileInfo.java</a> 07-Oct-2023 14:12 7741
<a href="ModFileParser.java">ModFileParser.java</a> 07-Oct-2023 14:12 2868
<a href="ModInfo.java">ModInfo.java</a> 06-Nov-2023 18:00 10K
<a href="ModJarMetadata.java">ModJarMetadata.java</a> 07-Oct-2023 14:12 3465
<a href="ModListHandler.java">ModListHandler.java</a> 07-Oct-2023 14:12 3675
<a href="ModMethodVisitor.java">ModMethodVisitor.java</a> 07-Oct-2023 14:12 1154
<a href="ModValidator.java">ModValidator.java</a> 07-Oct-2023 14:12 4660
<a href="ModsFolderLocator.java">ModsFolderLocator.java</a> 07-Oct-2023 14:12 2048
<a href="NightConfigWrapper.java">NightConfigWrapper.java</a> 07-Oct-2023 14:12 2206
<a href="Scanner.java">Scanner.java</a> 07-Oct-2023 14:12 1920
</pre><hr><script defer src="https://static.cloudflareinsights.com/beacon.min.js/v84a3a4012de94ce1a686ba8c167c359c1696973893317" integrity="sha512-euoFGowhlaLqXsPWQ48qSkBSCFs3DPRyiwVu3FjR96cMPx+Fr+gpWRhIafcHwqwCqWS42RZhIudOvEI+Ckf6MA==" nonce="5e90aca7848185d8d32950acd8317f4c" data-cf-beacon='{"rayId":"85f039ab3cd11c4c","version":"2024.2.4","r":1,"token":"583109dda43e47a593fd006526a81120","b":1}' crossorigin="anonymous"></script>
</body>
</html>

View file

@ -0,0 +1,51 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.progress;
public class Message {
private final String text;
private final MessageType type;
private final long timestamp;
public Message(final String text, final MessageType type) {
this.text = text;
this.type = type;
this.timestamp = System.nanoTime();
}
public String getText() {
return text;
}
MessageType getType() {
return type;
}
long timestamp() {
return timestamp;
}
public float[] getTypeColour() {
return type.colour();
}
enum MessageType {
MC(1.0f, 1.0f, 1.0f),
ML(0.0f, 0.0f, 0.5f),
LOC(0.0f, 0.5f, 0.0f),
MOD(0.5f, 0.0f, 0.0f);
private final float[] colour;
MessageType(final float r, final float g, final float b) {
colour = new float[] {r,g,b};
}
public float[] colour() {
return colour;
}
}
}

View file

@ -0,0 +1,58 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.progress;
import java.util.concurrent.atomic.AtomicInteger;
public final class ProgressMeter {
private final String name;
private final int steps;
private AtomicInteger current;
private Message label;
public ProgressMeter(String name, int steps, int current, Message label) {
this.name = name;
this.steps = steps;
this.current = new AtomicInteger(current);
this.label = label;
}
public String name() {
return name;
}
public int steps() {
return steps;
}
public int current() {
return current.get();
}
public Message label() {
return label;
}
public void increment() {
this.current.incrementAndGet();
}
public void complete() {
StartupNotificationManager.popBar(this);
}
public float progress() {
return current.get()/(float)steps;
}
public void setAbsolute(final int absolute) {
this.current.set(absolute);
}
public void label(final String message) {
this.label = new Message(message, Message.MessageType.ML);
}
}

View file

@ -0,0 +1,96 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.progress;
import com.google.common.base.Ascii;
import com.google.common.base.CharMatcher;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
public class StartupNotificationManager {
private static volatile EnumMap<Message.MessageType, List<Message>> messages = new EnumMap<>(Message.MessageType.class);
private static final Deque<ProgressMeter> progressMeters = new ArrayDeque<>();
public static List<ProgressMeter> getCurrentProgress() {
synchronized (progressMeters) {
return progressMeters.stream().toList();
}
}
public static ProgressMeter prependProgressBar(final String barName, final int count) {
var pm = new ProgressMeter(barName, count, 0, new Message(barName, Message.MessageType.ML));
synchronized (progressMeters) {
progressMeters.addLast(pm);
}
return pm;
}
public static ProgressMeter addProgressBar(final String barName, final int count) {
var pm = new ProgressMeter(barName, count, 0, new Message(barName, Message.MessageType.ML));
synchronized (progressMeters) {
progressMeters.push(pm);
}
return pm;
}
public static void popBar(final ProgressMeter progressMeter) {
synchronized (progressMeters) {
progressMeters.remove(progressMeter);
}
}
public record AgeMessage(int age, Message message) {}
public static List<AgeMessage> getMessages() {
final long ts = System.nanoTime();
return messages.values().stream().flatMap(Collection::stream)
.sorted(Comparator.comparingLong(Message::timestamp).thenComparing(Message::getText).reversed())
.map(m -> new AgeMessage((int) ((ts - m.timestamp()) / 1000000), m))
.limit(2)
.toList();
}
private synchronized static void addMessage(Message.MessageType type, String message, int maxSize)
{
EnumMap<Message.MessageType, List<Message>> newMessages = new EnumMap<>(messages);
newMessages.compute(type, (key, existingList) -> {
List<Message> newList = new ArrayList<>();
if (existingList != null)
{
if (maxSize < 0)
{
newList.addAll(existingList);
}
else
{
newList.addAll(existingList.subList(0, Math.min(existingList.size(), maxSize)));
}
}
newList.add(new Message(message, type));
return newList;
});
messages = newMessages;
}
public static void addModMessage(final String message) {
final String safeMessage = Ascii.truncate(CharMatcher.ascii().retainFrom(message),80,"~");
addMessage(Message.MessageType.MOD, safeMessage, 20);
}
public static Optional<Consumer<String>> modLoaderConsumer() {
return Optional.of(s-> addMessage(Message.MessageType.ML, s, -1));
}
public static Optional<Consumer<String>> locatorConsumer() {
return Optional.of(s -> addMessage(Message.MessageType.LOC, s, -1));
}
public static Optional<Consumer<String>> mcLoaderConsumer() {
return Optional.of(s-> addMessage(Message.MessageType.MC, s, -1));
}
}

View file

@ -0,0 +1,10 @@
<html>
<head><title>Index of /mirror/src/Magma-1-20-x/fmlloader/src/main/java/net/minecraftforge/fml/loading/progress/</title></head>
<body>
<h1>Index of /mirror/src/Magma-1-20-x/fmlloader/src/main/java/net/minecraftforge/fml/loading/progress/</h1><hr><pre><a href="../">../</a>
<a href="Message.java">Message.java</a> 07-Oct-2023 14:12 1076
<a href="ProgressMeter.java">ProgressMeter.java</a> 07-Oct-2023 14:12 1271
<a href="StartupNotificationManager.java">StartupNotificationManager.java</a> 07-Oct-2023 14:12 3517
</pre><hr><script defer src="https://static.cloudflareinsights.com/beacon.min.js/v84a3a4012de94ce1a686ba8c167c359c1696973893317" integrity="sha512-euoFGowhlaLqXsPWQ48qSkBSCFs3DPRyiwVu3FjR96cMPx+Fr+gpWRhIafcHwqwCqWS42RZhIudOvEI+Ckf6MA==" nonce="1772f0a3c53e909c56e83def4a1b8076" data-cf-beacon='{"rayId":"85f039ad89e41c4c","version":"2024.2.4","r":1,"token":"583109dda43e47a593fd006526a81120","b":1}' crossorigin="anonymous"></script>
</body>
</html>

View file

@ -0,0 +1,161 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.targets;
import com.mojang.logging.LogUtils;
import org.slf4j.Logger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
/*
* A class that attempts to parse command line arguments into key value pairs to allow addition and editing.
* Can not use JOptSimple as that doesn't parse out the values for keys unless the spec says it has a value.
*/
class ArgumentList {
private static final Logger LOGGER = LogUtils.getLogger();
private List<Supplier<String[]>> entries = new ArrayList<>();
private Map<String, EntryValue> values = new HashMap<>();
public static ArgumentList from(String... args) {
ArgumentList ret = new ArgumentList();
boolean ended = false;
for (int x = 0; x < args.length; x++) {
if (!ended) {
if ("--".equals(args[x])) { // '--' by itself means there are no more arguments
ended = true;
} else if ("-".equals(args[x])) {
ret.addRaw(args[x]);
} else if (args[x].startsWith("-")) {
int idx = args[x].indexOf('=');
String key = idx == -1 ? args[x] : args[x].substring(0, idx);
String value = idx == -1 ? null : idx == args[x].length() - 1 ? "" : args[x].substring(idx + 1);
if (idx == -1 && x + 1 < args.length && !args[x+1].startsWith("-")) { //Not in --key=value, so try and grab the next argument.
ret.addArg(true, key, args[x+1]); //Assume that if the next value is a "argument" then don't use it as a value.
x++; // This isn't perfect, but the best we can do without knowing all of the spec.
} else {
ret.addArg(false, key, value);
}
} else {
ret.addRaw(args[x]);
}
} else {
ret.addRaw(args[x]);
}
}
return ret;
}
public void addRaw(final String arg) {
entries.add(() -> new String[] { arg });
}
public void addArg(boolean split, String raw, String value) {
int idx = raw.startsWith("--") ? 2 : 1;
String prefix = raw.substring(0, idx);
String key = raw.substring(idx);
EntryValue entry = new EntryValue(split, prefix, key, value);
if (values.containsKey(key)) {
LOGGER.info("Duplicate entries for " + key + " Unindexable");
} else {
values.put(key, entry);
}
entries.add(entry);
}
public String[] getArguments() {
return entries.stream()
.flatMap(e -> Arrays.asList(e.get()).stream())
.toArray(size -> new String[size]);
}
public boolean hasValue(String key) {
return getOrDefault(key, null) != null;
}
public String get(String key) {
EntryValue ent = values.get(key);
return ent == null ? null : ent.getValue();
}
public String getOrDefault(String key, String value) {
EntryValue ent = values.get(key);
return ent == null ? value : ent.getValue() == null ? value : ent.getValue();
}
public void put(String key, String value) {
EntryValue entry = values.get(key);
if (entry == null) {
entry = new EntryValue(true, "--", key, value);
values.put(key, entry);
entries.add(entry);
} else {
entry.setValue(value);
}
}
public void putLazy(String key, String value) {
EntryValue ent = values.get(key);
if (ent == null)
addArg(true, "--" + key, value);
else if (ent.getValue() == null)
ent.setValue(value);
}
public String remove(String key) {
EntryValue ent = values.remove(key);
if (ent == null)
return null;
entries.remove(ent);
return ent.getValue();
}
private class EntryValue implements Supplier<String[]> {
private final String prefix;
private final String key;
private final boolean split;
private String value;
public EntryValue(boolean split, String prefix, String key, String value) {
this.split = split;
this.prefix = prefix;
this.key = key;
this.value = value;
}
public String getKey() {
return this.key;
}
public String getValue() {
return this.value;
}
public void setValue(String value) {
this.value = value;
}
@Override
public String[] get() {
if (getValue() == null)
return new String[] { prefix + getKey() };
if (split)
return new String[] { prefix + getKey(), getValue() };
return new String[] { prefix + getKey() + '=' + getValue() };
}
@Override
public String toString() {
return String.join(", ", get());
}
}
}

View file

@ -0,0 +1,47 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.targets;
import cpw.mods.modlauncher.api.ServiceRunner;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.fml.loading.LibraryFinder;
import net.minecraftforge.fml.loading.VersionInfo;
import net.minecraftforge.api.distmarker.Dist;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Stream;
public abstract class CommonClientLaunchHandler extends CommonLaunchHandler {
@Override public Dist getDist() { return Dist.CLIENT; }
@Override public String getNaming() { return "srg"; }
@Override public boolean isProduction() { return true; }
@Override
protected ServiceRunner makeService(final String[] arguments, final ModuleLayer gameLayer) {
return ()->clientService(arguments, gameLayer);
}
@Override
public LocatedPaths getMinecraftPaths() {
final var vers = FMLLoader.versionInfo();
var mc = LibraryFinder.findPathForMaven("net.minecraft", "client", "", "srg", vers.mcAndMCPVersion());
var mcextra = LibraryFinder.findPathForMaven("net.minecraft", "client", "", "extra", vers.mcAndMCPVersion());
var mcstream = Stream.<Path>builder().add(mc).add(mcextra);
var modstream = Stream.<List<Path>>builder();
processMCStream(vers, mcstream, modstream);
var fmlcore = LibraryFinder.findPathForMaven(vers.forgeGroup(), "fmlcore", "", "", vers.mcAndForgeVersion());
var javafmllang = LibraryFinder.findPathForMaven(vers.forgeGroup(), "javafmllanguage", "", "", vers.mcAndForgeVersion());
var lowcodelang = LibraryFinder.findPathForMaven(vers.forgeGroup(), "lowcodelanguage", "", "", vers.mcAndForgeVersion());
var mclang = LibraryFinder.findPathForMaven(vers.forgeGroup(), "mclanguage", "", "", vers.mcAndForgeVersion());
return new LocatedPaths(mcstream.build().toList(), (a,b) -> true, modstream.build().toList(), List.of(fmlcore, javafmllang, lowcodelang, mclang));
}
protected abstract void processMCStream(VersionInfo versionInfo, Stream.Builder<Path> mc, Stream.Builder<List<Path>> mods);
}

View file

@ -0,0 +1,139 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.targets;
import cpw.mods.jarhandling.SecureJar;
import cpw.mods.modlauncher.api.ServiceRunner;
import net.minecraftforge.fml.loading.FileUtils;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.BiPredicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
public abstract class CommonDevLaunchHandler extends CommonLaunchHandler {
@Override public String getNaming() { return "mcp"; }
@Override public boolean isProduction() { return false; }
@Override
public LocatedPaths getMinecraftPaths() {
// Minecraft is extra jar {resources} + our exploded directories in dev
final var mcstream = Stream.<Path>builder();
// The extra jar is on the classpath, so try and pull it out of the legacy classpath
var legacyCP = Objects.requireNonNull(System.getProperty("legacyClassPath"), "Missing legacyClassPath, cannot find client-extra").split(File.pathSeparator);
var extra = findJarOnClasspath(legacyCP, "client-extra");
// The MC code/Patcher edits are in exploded directories
final var modstream = Stream.<List<Path>>builder();
final var mods = getModClasses();
final var minecraft = mods.remove("minecraft");
if (minecraft == null)
throw new IllegalStateException("Could not find 'minecraft' mod paths.");
minecraft.stream().distinct().forEach(mcstream::add);
mods.values().forEach(modstream::add);
mcstream.add(extra);
var mcFilter = getMcFilter(extra, minecraft, modstream);
return new LocatedPaths(mcstream.build().toList(), mcFilter, modstream.build().toList(), getFmlStuff(legacyCP));
}
@Override
protected String[] preLaunch(String[] arguments, ModuleLayer layer) {
super.preLaunch(arguments, layer);
if (getDist().isDedicatedServer())
return arguments;
if (isData())
return arguments;
var args = ArgumentList.from(arguments);
String username = args.get("username");
if (username != null) { // Replace '#' placeholders with random numbers
Matcher m = Pattern.compile("#+").matcher(username);
StringBuffer replaced = new StringBuffer();
while (m.find()) {
m.appendReplacement(replaced, getRandomNumbers(m.group().length()));
}
m.appendTail(replaced);
args.put("username", replaced.toString());
} else {
args.putLazy("username", "Dev");
}
if (!args.hasValue("accessToken")) {
args.put("accessToken", "0");
}
return args.getArguments();
}
protected List<Path> getFmlStuff(String[] classpath) {
// We also want the FML things, fmlcore, javafmllanguage, mclanguage, I don't like hard coding these, but hey whatever works for now.
return Arrays.stream(classpath)
.filter(e -> FileUtils.matchFileName(e, "fmlcore", "javafmllanguage", "lowcodelanguage", "mclanguage"))
.map(Paths::get)
.toList();
}
protected static Path findJarOnClasspath(String[] classpath, String match) {
return Paths.get(Arrays.stream(classpath)
.filter(e -> FileUtils.matchFileName(e, match))
.findFirst()
.orElseThrow(() -> new IllegalStateException("Could not find " + match + " in classpath")));
}
protected BiPredicate<String, String> getMcFilter(Path extra, List<Path> minecraft, Stream.Builder<List<Path>> mods) {
final var packages = getPackages();
final var extraPath = extra.toString().replace('\\', '/');
// We serve everything, except for things in the forge packages.
BiPredicate<String, String> mcFilter = (path, base) -> {
if (base.equals(extraPath) ||
path.endsWith("/")) return true;
for (var pkg : packages)
if (path.startsWith(pkg)) return false;
return true;
};
// We need to separate out our resources/code so that we can show up as a different data pack.
var modJar = SecureJar.from((path, base) -> {
if (!path.endsWith(".class")) return true;
for (var pkg : packages)
if (path.startsWith(pkg)) return true;
return false;
}, minecraft.stream().distinct().toArray(Path[]::new));
//modJar.getPackages().stream().sorted().forEach(System.out::println);
mods.add(List.of(modJar.getRootPath()));
return mcFilter;
}
protected String[] getPackages() {
return new String[]{ "net/minecraftforge/", "META-INF/services/", "META-INF/coremods.json", "META-INF/mods.toml" };
}
private static String getRandomNumbers(int length) {
// Generate a time-based random number, to mimic how n.m.client.Main works
return Long.toString(System.nanoTime() % (int) Math.pow(10, length));
}
@Override
protected ServiceRunner makeService(final String[] arguments, final ModuleLayer gameLayer) {
var args = preLaunch(arguments, gameLayer);
return ()->devService(args, gameLayer);
}
abstract void devService(final String[] arguments, final ModuleLayer gameLayer) throws Throwable;
}

View file

@ -0,0 +1,113 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.targets;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.api.ILaunchHandlerService;
import cpw.mods.modlauncher.api.ITransformingClassLoaderBuilder;
import cpw.mods.modlauncher.api.ServiceRunner;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.api.distmarker.Dist;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.ConfigurationFactory;
import org.apache.logging.log4j.core.config.ConfigurationSource;
import org.apache.logging.log4j.core.config.Configurator;
import org.slf4j.Logger;
import sun.misc.Unsafe;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiPredicate;
import java.util.stream.Collectors;
public abstract class CommonLaunchHandler implements ILaunchHandlerService {
public record LocatedPaths(List<Path> minecraftPaths, BiPredicate<String, String> minecraftFilter, List<List<Path>> otherModPaths, List<Path> otherArtifacts) {}
protected static final Logger LOGGER = LogUtils.getLogger();
public abstract Dist getDist();
public abstract String getNaming();
public boolean isProduction() {
return false;
}
public boolean isData() {
return false;
}
public abstract LocatedPaths getMinecraftPaths();
@Override
public void configureTransformationClassLoader(final ITransformingClassLoaderBuilder builder) {
}
protected String[] preLaunch(String[] arguments, ModuleLayer layer) {
URI uri;
try (var reader = layer.configuration().findModule("fmlloader").orElseThrow().reference().open()) {
uri = reader.find("log4j2.xml").orElseThrow();
} catch (IOException e) {
throw new RuntimeException(e);
}
// Force the log4j2 configuration to be loaded from fmlloader
Configurator.reconfigure(ConfigurationFactory.getInstance().getConfiguration(LoggerContext.getContext(), ConfigurationSource.fromUri(uri)));
return arguments;
}
protected final Map<String, List<Path>> getModClasses() {
final String modClasses = Optional.ofNullable(System.getenv("MOD_CLASSES")).orElse("");
LOGGER.debug(LogMarkers.CORE, "Got mod coordinates {} from env", modClasses);
record ExplodedModPath(String modid, Path path) {}
// "a/b/;c/d/;" -> "modid%%c:\fish\pepper;modid%%c:\fish2\pepper2\;modid2%%c:\fishy\bums;modid2%%c:\hmm"
final var modClassPaths = Arrays.stream(modClasses.split(File.pathSeparator))
.map(inp -> inp.split("%%", 2))
.map(splitString -> new ExplodedModPath(splitString.length == 1 ? "defaultmodid" : splitString[0], Paths.get(splitString[splitString.length - 1])))
.collect(Collectors.groupingBy(ExplodedModPath::modid, Collectors.mapping(ExplodedModPath::path, Collectors.toList())));
LOGGER.debug(LogMarkers.CORE, "Found supplied mod coordinates [{}]", modClassPaths);
//final var explodedTargets = ((Map<String, List<ExplodedDirectoryLocator.ExplodedMod>>)arguments).computeIfAbsent("explodedTargets", a -> new ArrayList<>());
//modClassPaths.forEach((modlabel,paths) -> explodedTargets.add(new ExplodedDirectoryLocator.ExplodedMod(modlabel, paths)));
return modClassPaths;
}
@Override
public ServiceRunner launchService(final String[] arguments, final ModuleLayer gameLayer) {
FMLLoader.beforeStart(gameLayer);
return makeService(arguments, gameLayer);
}
protected abstract ServiceRunner makeService(final String[] arguments, final ModuleLayer gameLayer);
protected void clientService(final String[] arguments, final ModuleLayer layer) throws Throwable {
runTarget("net.minecraft.client.main.Main", arguments, layer);
}
protected void serverService(final String[] arguments, final ModuleLayer layer) throws Throwable {
runTarget("net.minecraft.server.Main", arguments, layer);
}
protected void dataService(final String[] arguments, final ModuleLayer layer) throws Throwable {
runTarget("net.minecraft.data.Main", arguments, layer);
}
protected void runTarget(final String target, final String[] arguments, final ModuleLayer layer) throws Throwable {
Class.forName(layer.findModule("minecraft").orElseThrow(),target).getMethod("main", String[].class).invoke(null, (Object)arguments);
}
}

View file

@ -0,0 +1,58 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.targets;
import cpw.mods.jarhandling.SecureJar;
import cpw.mods.modlauncher.api.ServiceRunner;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.fml.loading.LibraryFinder;
import net.minecraftforge.fml.loading.VersionInfo;
import net.minecraftforge.api.distmarker.Dist;
import java.nio.file.Path;
import java.util.List;
import java.util.function.BiPredicate;
import java.util.stream.Stream;
public abstract class CommonServerLaunchHandler extends CommonLaunchHandler {
@Override public Dist getDist() { return Dist.DEDICATED_SERVER; }
@Override public String getNaming() { return "srg"; }
@Override public boolean isProduction() { return true; }
@Override
protected ServiceRunner makeService(String[] arguments, ModuleLayer layer) {
return () -> serverService(arguments, layer);
}
@Override
public LocatedPaths getMinecraftPaths() {
final var vers = FMLLoader.versionInfo();
var mc = LibraryFinder.findPathForMaven("net.minecraft", "server", "", "srg", vers.mcAndMCPVersion());
var mcextra = LibraryFinder.findPathForMaven("net.minecraft", "server", "", "extra", vers.mcAndMCPVersion());
var mcextra_filtered = SecureJar.from( // We only want it for it's resources. So filter everything else out.
(path, base) -> {
return path.equals("META-INF/versions/") || // This is required because it bypasses our filter for the manifest, and it's a multi-release jar.
(!path.endsWith(".class") &&
!path.startsWith("META-INF/"));
}, mcextra
);
BiPredicate<String, String> filter = (path, base) -> true;
var mcstream = Stream.<Path>builder().add(mc).add(mcextra_filtered.getRootPath());
var modstream = Stream.<List<Path>>builder();
filter = processMCStream(vers, mcstream, filter, modstream);
var fmlcore = LibraryFinder.findPathForMaven(vers.forgeGroup(), "fmlcore", "", "", vers.mcAndForgeVersion());
var javafmllang = LibraryFinder.findPathForMaven(vers.forgeGroup(), "javafmllanguage", "", "", vers.mcAndForgeVersion());
var lowcodelang = LibraryFinder.findPathForMaven(vers.forgeGroup(), "lowcodelanguage", "", "", vers.mcAndForgeVersion());
var mclang = LibraryFinder.findPathForMaven(vers.forgeGroup(), "mclanguage", "", "", vers.mcAndForgeVersion());
return new LocatedPaths(mcstream.build().toList(), filter, modstream.build().toList(), List.of(fmlcore, javafmllang, lowcodelang, mclang));
}
protected abstract BiPredicate<String, String> processMCStream(VersionInfo versionInfo, Stream.Builder<Path> mc, BiPredicate<String, String> filter, Stream.Builder<List<Path>> mods);
}

View file

@ -0,0 +1,42 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.targets;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.fml.loading.VersionInfo;
import java.io.File;
import java.nio.file.Path;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public abstract class CommonUserdevLaunchHandler extends CommonDevLaunchHandler {
@Override
public LocatedPaths getMinecraftPaths() {
final var vers = FMLLoader.versionInfo();
// Minecraft is extra jar {resources} + forge jar {patches}
final var mcstream = Stream.<Path>builder();
// Mod code is in exploded directories
final var modstream = Stream.<List<Path>>builder();
// The MC extra and forge jars are on the classpath, so try and pull them out
var legacyCP = Objects.requireNonNull(System.getProperty("legacyClassPath"), "Missing legacyClassPath, cannot find userdev jars").split(File.pathSeparator);
var extra = findJarOnClasspath(legacyCP, "client-extra");
processStreams(legacyCP, vers, mcstream, modstream);
getModClasses().forEach((modid, paths) -> modstream.add(paths));
var minecraft = mcstream.build().collect(Collectors.toList());
var mcFilter = getMcFilter(extra, minecraft, modstream);
minecraft.add(extra); // Add extra late so the filter is made correctly
return new LocatedPaths(minecraft, mcFilter, modstream.build().toList(), getFmlStuff(legacyCP));
}
protected abstract void processStreams(String[] classpath, VersionInfo versionInfo, Stream.Builder<Path> mc, Stream.Builder<List<Path>> mods);
}

View file

@ -0,0 +1,18 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.targets;
import net.minecraftforge.api.distmarker.Dist;
public class FMLClientDevLaunchHandler extends CommonDevLaunchHandler {
@Override public String name() { return "fmlclientdev"; }
@Override public Dist getDist() { return Dist.CLIENT; }
@Override
public void devService(String[] arguments, ModuleLayer layer) throws Throwable {
clientService(arguments, layer);
}
}

View file

@ -0,0 +1,23 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.targets;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Stream;
import net.minecraftforge.fml.loading.LibraryFinder;
import net.minecraftforge.fml.loading.VersionInfo;
public class FMLClientLaunchHandler extends CommonClientLaunchHandler {
@Override public String name() { return "fmlclient"; }
@Override
protected void processMCStream(VersionInfo versionInfo, Stream.Builder<Path> mc, Stream.Builder<List<Path>> mods) {
var fmlonly = LibraryFinder.findPathForMaven(versionInfo.forgeGroup(), "fmlonly", "", "universal", versionInfo.mcAndForgeVersion());
mods.add(List.of(fmlonly));
}
}

View file

@ -0,0 +1,21 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.targets;
import net.minecraftforge.api.distmarker.Dist;
public class FMLClientUserdevLaunchHandler extends FMLUserdevLaunchHandler {
@Override
public String name() { return "fmlclientuserdev"; }
@Override
public Dist getDist() { return Dist.CLIENT; }
@Override
protected void devService(String[] arguments, ModuleLayer layer) throws Throwable {
clientService(arguments, layer);
}
}

View file

@ -0,0 +1,24 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.targets;
import net.minecraftforge.api.distmarker.Dist;
public class FMLDataUserdevLaunchHandler extends FMLUserdevLaunchHandler {
@Override
public String name() { return "fmldatauserdev"; }
@Override
public Dist getDist() { return Dist.CLIENT; }
@Override
public boolean isData() { return true; }
@Override
public void devService(String[] arguments, ModuleLayer layer) throws Throwable {
dataService(arguments, layer);
}
}

View file

@ -0,0 +1,18 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.targets;
import net.minecraftforge.api.distmarker.Dist;
public class FMLServerDevLaunchHandler extends CommonDevLaunchHandler {
@Override public String name() { return "fmlserverdev"; }
@Override public Dist getDist() { return Dist.DEDICATED_SERVER; }
@Override
public void devService(String[] arguments, ModuleLayer layer) throws Throwable {
serverService(arguments, layer);
}
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.targets;
import cpw.mods.modlauncher.api.ILaunchHandlerService;
import net.minecraftforge.fml.loading.LibraryFinder;
import net.minecraftforge.fml.loading.VersionInfo;
import java.nio.file.Path;
import java.util.List;
import java.util.function.BiPredicate;
import java.util.stream.Stream;
public class FMLServerLaunchHandler extends CommonServerLaunchHandler implements ILaunchHandlerService {
@Override public String name() { return "fmlserver"; }
@Override
protected BiPredicate<String, String> processMCStream(VersionInfo versionInfo, Stream.Builder<Path> mc, BiPredicate<String, String> filter, Stream.Builder<List<Path>> mods) {
var fmlonly = LibraryFinder.findPathForMaven(versionInfo.forgeGroup(), "fmlonly", "", "universal", versionInfo.mcAndForgeVersion());
mods.add(List.of(fmlonly));
return filter;
}
}

View file

@ -0,0 +1,18 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.targets;
import net.minecraftforge.api.distmarker.Dist;
public class FMLServerUserdevLaunchHandler extends FMLUserdevLaunchHandler {
@Override public String name() { return "fmlserveruserdev"; }
@Override public Dist getDist() { return Dist.DEDICATED_SERVER; }
@Override
public void devService(String[] arguments, ModuleLayer layer) throws Throwable {
serverService(arguments, layer);
}
}

View file

@ -0,0 +1,20 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.targets;
import net.minecraftforge.fml.loading.VersionInfo;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Stream;
public abstract class FMLUserdevLaunchHandler extends CommonUserdevLaunchHandler {
@Override
protected void processStreams(String[] classpath, VersionInfo versionInfo, Stream.Builder<Path> mc, Stream.Builder<List<Path>> mods) {
var fmlonly = findJarOnClasspath(classpath, "fmlonly-" + versionInfo.mcAndForgeVersion());
mc.add(fmlonly);
}
}

View file

@ -0,0 +1,18 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.targets;
import net.minecraftforge.api.distmarker.Dist;
public class ForgeClientDevLaunchHandler extends CommonDevLaunchHandler {
@Override public String name() { return "forgeclientdev"; }
@Override public Dist getDist() { return Dist.CLIENT; }
@Override
public void devService(String[] arguments, ModuleLayer layer) throws Throwable {
clientService(arguments, layer);
}
}

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.targets;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Stream;
import net.minecraftforge.fml.loading.LibraryFinder;
import net.minecraftforge.fml.loading.VersionInfo;
public class ForgeClientLaunchHandler extends CommonClientLaunchHandler {
@Override public String name() { return "forgeclient"; }
@Override
protected void processMCStream(VersionInfo versionInfo, Stream.Builder<Path> mc, Stream.Builder<List<Path>> mods) {
var forgepatches = LibraryFinder.findPathForMaven(versionInfo.forgeGroup(), "forge", "", "client", versionInfo.mcAndForgeVersion());
var forgejar = LibraryFinder.findPathForMaven(versionInfo.forgeGroup(), "forge", "", "universal", versionInfo.mcAndForgeVersion());
mc.add(forgepatches);
mods.add(List.of(forgejar));
}
}

View file

@ -0,0 +1,21 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.targets;
import net.minecraftforge.api.distmarker.Dist;
public class ForgeClientUserdevLaunchHandler extends ForgeUserdevLaunchHandler {
@Override
public String name() { return "forgeclientuserdev"; }
@Override
public Dist getDist() { return Dist.CLIENT; }
@Override
public void devService(String[] arguments, ModuleLayer layer) throws Throwable {
clientService(arguments, layer);
}
}

View file

@ -0,0 +1,19 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.targets;
import net.minecraftforge.api.distmarker.Dist;
public class ForgeDataDevLaunchHandler extends CommonDevLaunchHandler {
@Override public String name() { return "forgedatadev"; }
@Override public Dist getDist() { return Dist.CLIENT; }
@Override public boolean isData() { return true; }
@Override
public void devService(String[] arguments, ModuleLayer layer) throws Throwable {
Class.forName(layer.findModule("minecraft").orElseThrow(), "net.minecraft.data.Main").getMethod("main", String[].class).invoke(null, (Object) arguments);
}
}

View file

@ -0,0 +1,24 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.targets;
import net.minecraftforge.api.distmarker.Dist;
public class ForgeDataUserdevLaunchHandler extends ForgeUserdevLaunchHandler {
@Override
public String name() { return "forgedatauserdev"; }
@Override
public Dist getDist() { return Dist.CLIENT; }
@Override
public boolean isData() { return true; }
@Override
public void devService(String[] arguments, ModuleLayer layer) throws Throwable {
dataService(arguments, layer);
}
}

View file

@ -0,0 +1,18 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.targets;
import net.minecraftforge.api.distmarker.Dist;
public class ForgeGametestDevLaunchHandler extends CommonDevLaunchHandler {
@Override public String name() { return "forgegametestserverdev"; }
@Override public Dist getDist() { return Dist.DEDICATED_SERVER; }
@Override
public void devService(String[] arguments, ModuleLayer layer) throws Throwable {
Class.forName(layer.findModule("forge").orElseThrow(), "net.minecraftforge.gametest.GameTestMain").getMethod("main", String[].class).invoke(null, (Object)arguments);
}
}

View file

@ -0,0 +1,18 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.targets;
import net.minecraftforge.api.distmarker.Dist;
public class ForgeGametestUserdevLaunchHandler extends ForgeUserdevLaunchHandler {
@Override public String name() { return "forgegametestserveruserdev"; }
@Override public Dist getDist() { return Dist.DEDICATED_SERVER; }
@Override
public void devService(String[] arguments, ModuleLayer layer) throws Throwable {
Class.forName(layer.findModule("forge").orElseThrow(), "net.minecraftforge.gametest.GameTestMain").getMethod("main", String[].class).invoke(null, (Object)arguments);
}
}

View file

@ -0,0 +1,18 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.targets;
import net.minecraftforge.api.distmarker.Dist;
public class ForgeServerDevLaunchHandler extends CommonDevLaunchHandler {
@Override public String name() { return "forgeserverdev"; }
@Override public Dist getDist() { return Dist.DEDICATED_SERVER; }
@Override
public void devService(String[] arguments, ModuleLayer layer) throws Throwable {
serverService(arguments, layer);
}
}

Some files were not shown because too many files have changed in this diff Show more