Upload magma
This commit is contained in:
commit
dfa9ee0b24
5008 changed files with 653442 additions and 0 deletions
8
fmlloader/src/index.html
Normal file
8
fmlloader/src/index.html
Normal 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>
|
9
fmlloader/src/main/index.html
Normal file
9
fmlloader/src/main/index.html
Normal 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>
|
9
fmlloader/src/main/java/index.html
Normal file
9
fmlloader/src/main/java/index.html
Normal 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>
|
8
fmlloader/src/main/java/net/index.html
Normal file
8
fmlloader/src/main/java/net/index.html
Normal 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>
|
|
@ -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<String>(){}</code>
|
||||
* Has the signature <code>"CapabilityToken<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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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) {}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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>
|
|
@ -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>
|
10
fmlloader/src/main/java/net/minecraftforge/fml/index.html
Normal file
10
fmlloader/src/main/java/net/minecraftforge/fml/index.html
Normal 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>
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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)));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"))));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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) {}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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() + ")";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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() + "}";
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
{}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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 + ']';
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue