mirror of
https://github.com/MinecraftForge/MinecraftForge.git
synced 2026-11-14 17:14:44 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
beb5ccc378 | ||
|
|
8199135df7 | ||
|
|
32359d7e76 | ||
|
|
abf2889b11 | ||
|
|
82f7d4311e | ||
|
|
a11d1936c5 | ||
|
|
75f340f2a4 | ||
|
|
8c50fdebc1 | ||
|
|
0675635b0c | ||
|
|
96a894d3ec | ||
|
|
e1461a3185 | ||
|
|
cc24b60942 | ||
|
|
879a5d3245 | ||
|
|
0cf398c509 | ||
|
|
d2f441ed41 | ||
|
|
444d4a94da | ||
|
|
86a00f6122 | ||
|
|
fe8684891c | ||
|
|
e2a81d278c | ||
|
|
86e2a11c8e | ||
|
|
927edb8533 | ||
|
|
0bca349d64 | ||
|
|
7a44626d5e | ||
|
|
30ec79b19e | ||
|
|
0794e51f62 | ||
|
|
c86172db31 | ||
|
|
ee14c0c911 | ||
|
|
b3a4ac864f | ||
|
|
f9139a3002 | ||
|
|
784693b7e5 | ||
|
|
774048154f | ||
|
|
490e35ba8e | ||
|
|
667a164430 | ||
|
|
0434d67016 | ||
|
|
02229557ed | ||
|
|
79224a70e6 | ||
|
|
5c9c8ce539 | ||
|
|
c186c38113 | ||
|
|
1507717fae | ||
|
|
12c4233fac | ||
|
|
f331376d9d | ||
|
|
b5b5064a4f | ||
|
|
f4801960e7 | ||
|
|
68c60c17e5 | ||
|
|
4a5f977691 | ||
|
|
896cfb38a3 | ||
|
|
185273022c | ||
|
|
eb65e1db51 | ||
|
|
6a5c413a13 | ||
|
|
1b7b2b6ff1 | ||
|
|
cb290f23f5 | ||
|
|
6e8eaeeec9 | ||
|
|
56c41f3ca8 | ||
|
|
0084b8cf48 | ||
|
|
eb6c564ce4 | ||
|
|
618da273dc | ||
|
|
074c0cdfd6 |
No files matched your search
@@ -3,7 +3,6 @@
|
||||
*.patch text eol=lf
|
||||
*.java text eol=lf
|
||||
*.gradle text eol=crlf
|
||||
**/MANIFEST.MF text eol=crlf
|
||||
*.png binary
|
||||
*.gif binary
|
||||
*.exe binary
|
||||
|
||||
@@ -10,4 +10,4 @@ assignees: ''
|
||||
<!-- Please note, feature requests that are submitted as GitHub issues here are rarely implemented. -->
|
||||
<!-- If you'd like to see something in Forge, you should be ready to implement it as a PR. -->
|
||||
<!-- If you're not sure, open a Draft PR or discuss it in the Discord server. -->
|
||||
<!-- Discord: https://discord..minecraftforge.net/ -->
|
||||
<!-- Discord: https://discord.gg/UvedJ9m -->
|
||||
@@ -11,7 +11,6 @@ assignees: ''
|
||||
|
||||
<!-- For support with crashes and issues, please create a post on the forum instead: -->
|
||||
<!-- https://www.minecraftforge.net/forum/forum/18-support-bug-reports/ -->
|
||||
<!-- You can also use the discord for support, https://discord.minecraftforge.net/ -->
|
||||
|
||||
**Minecraft Version:** {Minecraft version}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ name: Publish
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ '1.21.11' ]
|
||||
branches: [ '1.21.1' ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
+12
-6
@@ -26,6 +26,7 @@
|
||||
/projects/mcp/
|
||||
/projects/clean/
|
||||
/projects/forge/
|
||||
#/patches/*
|
||||
|
||||
#occupational hazards
|
||||
/projects/**/build/
|
||||
@@ -34,15 +35,20 @@
|
||||
/projects/**/*.launch
|
||||
/repo/
|
||||
src/*/generated/**/.cache/
|
||||
|
||||
# Generated by gradle every import
|
||||
**/src/main/resources/META-INF/MANIFEST.MF
|
||||
/fmlloader/src/main/resources/forge_version.json
|
||||
|
||||
#Patch rejects
|
||||
#/patches-/
|
||||
#*.patch.rej
|
||||
#/projects/*/rejects/
|
||||
|
||||
# FML changelog
|
||||
changelog.txt
|
||||
*.py
|
||||
*.7z
|
||||
/logs/
|
||||
|
||||
/ForgeRoot.ipr
|
||||
/ForgeRoot.iws
|
||||
|
||||
# APT stuff
|
||||
/*/.gitignore
|
||||
/*/.factorypath
|
||||
/*/.apt_generated/
|
||||
+6
-5
@@ -4,11 +4,11 @@ import net.minecraftforge.gradleutils.PomUtils
|
||||
plugins {
|
||||
id 'net.minecraftforge.licenser' version '1.0.1'
|
||||
id 'com.github.ben-manes.versions' version '0.46.0'
|
||||
id 'net.minecraftforge.gradleutils' version '[2.6.4,2.7)'
|
||||
id 'net.minecraftforge.gradleutils' version '[2.3.0,2.4.0)'
|
||||
id 'eclipse'
|
||||
id 'de.undercouch.download' version '5.4.0'
|
||||
id 'net.minecraftforge.gradle.patcher' version '[6.0.46,6.2)' apply false
|
||||
id 'net.minecraftforge.gradle.mcp' version '[6.0.46,6.2)' apply false
|
||||
id 'net.minecraftforge.gradle.patcher' version '[6.0.22,6.2)' apply false
|
||||
id 'net.minecraftforge.gradle.mcp' version '[6.0.22,6.2)' apply false
|
||||
id 'net.minecraftforge.gradlejarsigner' version '1.0.4'
|
||||
id 'org.barfuin.gradle.taskinfo' version '2.1.0'
|
||||
}
|
||||
@@ -16,12 +16,13 @@ plugins {
|
||||
Util.init() //Init all our extension methods!
|
||||
|
||||
ext {
|
||||
VERSION = gitversion.getMCTagOffsetBranch(MC_VERSION)
|
||||
GIT_INFO = gradleutils.gitInfo
|
||||
VERSION = gradleutils.getFilteredMCTagOffsetBranchVersion(true, '[0-9]', MC_VERSION)
|
||||
FORGE_VERSION = VERSION.substring(MC_VERSION.length() + 1)
|
||||
}
|
||||
|
||||
changelog {
|
||||
from '47.999'
|
||||
from '51.0' // Since 1.21
|
||||
}
|
||||
|
||||
tasks.register('setup') {
|
||||
|
||||
@@ -4,10 +4,9 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.ow2.asm:asm:9.8'
|
||||
implementation 'org.ow2.asm:asm-tree:9.8'
|
||||
implementation 'net.minecraftforge:srgutils:0.5.10'
|
||||
implementation 'net.minecraftforge:JarJarMetadata:0.3.27'
|
||||
implementation 'org.ow2.asm:asm:9.9.1'
|
||||
implementation 'org.ow2.asm:asm-tree:9.9.1'
|
||||
implementation 'net.minecraftforge:srgutils:0.6.2'
|
||||
implementation 'commons-io:commons-io:2.13.0'
|
||||
implementation 'com.google.code.gson:gson:2.10.1'
|
||||
implementation 'org.eclipse.jgit:org.eclipse.jgit:6.7.0.202309050840-r'
|
||||
|
||||
@@ -62,10 +62,6 @@ abstract class BundleList extends DefaultTask {
|
||||
def (group, name, other) = artifact.split(':', 3)
|
||||
//println("Group: $group Name: $name")
|
||||
def key = "$group:$name"
|
||||
if (other.indexOf(':') != -1){
|
||||
key += ':' + other.split(':', 2)[1]
|
||||
//println("\tKey: $key")
|
||||
}
|
||||
if (!entries.containsKey(key))
|
||||
entries.put(key, line)
|
||||
}
|
||||
|
||||
+3
-19
@@ -3,7 +3,6 @@ package net.minecraftforge.forge.tasks
|
||||
import groovy.transform.CompileStatic
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.Internal
|
||||
import org.objectweb.asm.Opcodes
|
||||
import org.objectweb.asm.tree.ClassNode
|
||||
import org.objectweb.asm.tree.MethodNode
|
||||
|
||||
@@ -13,19 +12,13 @@ abstract class BytecodePredicateFinder extends BytecodeFinder {
|
||||
@Internal
|
||||
abstract Property<Closure<Boolean>> getPredicate()
|
||||
|
||||
private final Map<String, List<String>> matches = new TreeMap<>(Comparator.naturalOrder())
|
||||
private final Set<String> matches = new HashSet<>()
|
||||
|
||||
@Override
|
||||
protected process(ClassNode parent, MethodNode node) {
|
||||
for (final current : node.instructions) {
|
||||
if (predicate.get().call(parent, node, current)) {
|
||||
var methods = matches.compute(parent.name, { k, v -> v ?: new ArrayList<>() })
|
||||
|
||||
// only add non-synthetic methods
|
||||
if ((node.access & Opcodes.ACC_SYNTHETIC) == 0) {
|
||||
methods.add(node.name + node.desc)
|
||||
}
|
||||
|
||||
matches.add(parent.name)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -34,15 +27,6 @@ abstract class BytecodePredicateFinder extends BytecodeFinder {
|
||||
@Internal
|
||||
@Override
|
||||
protected Object getData() {
|
||||
var array = new ArrayList<Object>()
|
||||
matches.forEach { c, m ->
|
||||
{
|
||||
array.add(
|
||||
'class': c,
|
||||
'methods': m
|
||||
)
|
||||
}
|
||||
}
|
||||
return array ?: { throw new RuntimeException('Failed to find any targets, please ensure that method names and descriptors are correct.') }()
|
||||
return matches.toSorted(Comparator.<String>naturalOrder()) ?: {throw new RuntimeException('Failed to find any targets, please ensure that method names and descriptors are correct.')}()
|
||||
}
|
||||
}
|
||||
@@ -1,415 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.forge.tasks;
|
||||
|
||||
import com.google.common.reflect.TypeToken;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import net.minecraftforge.jarjar.metadata.ContainedJarIdentifier;
|
||||
import net.minecraftforge.jarjar.metadata.ContainedJarMetadata;
|
||||
import net.minecraftforge.jarjar.metadata.ContainedVersion;
|
||||
import net.minecraftforge.jarjar.metadata.Metadata;
|
||||
import net.minecraftforge.jarjar.metadata.MetadataIOHandler;
|
||||
import net.minecraftforge.jarjar.metadata.json.ArtifactVersionSerializer;
|
||||
import net.minecraftforge.jarjar.metadata.json.ContainedJarIdentifierSerializer;
|
||||
import net.minecraftforge.jarjar.metadata.json.ContainedJarMetadataSerializer;
|
||||
import net.minecraftforge.jarjar.metadata.json.ContainedVersionSerializer;
|
||||
import net.minecraftforge.jarjar.metadata.json.MetadataSerializer;
|
||||
import net.minecraftforge.jarjar.metadata.json.VersionRangeSerializer;
|
||||
import org.apache.maven.artifact.versioning.ArtifactVersion;
|
||||
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
|
||||
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
|
||||
import org.apache.maven.artifact.versioning.VersionRange;
|
||||
import org.codehaus.groovy.runtime.InvokerHelper;
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.DefaultTask;
|
||||
import org.gradle.api.artifacts.ConfigurationContainer;
|
||||
import org.gradle.api.artifacts.Dependency;
|
||||
import org.gradle.api.artifacts.ExternalModuleDependency;
|
||||
import org.gradle.api.artifacts.FileCollectionDependency;
|
||||
import org.gradle.api.artifacts.MinimalExternalModuleDependency;
|
||||
import org.gradle.api.artifacts.ModuleDependency;
|
||||
import org.gradle.api.artifacts.ModuleIdentifier;
|
||||
import org.gradle.api.artifacts.ModuleVersionIdentifier;
|
||||
import org.gradle.api.artifacts.ResolvedArtifact;
|
||||
import org.gradle.api.file.ProjectLayout;
|
||||
import org.gradle.api.file.RegularFileProperty;
|
||||
import org.gradle.api.provider.Provider;
|
||||
import org.gradle.api.provider.SetProperty;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.OutputFile;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
// TODO SUPER SUPER SUPER UGLY, CLEAN UP IN FORGEDEV 7
|
||||
@Deprecated(forRemoval = true) // Will be moved to JarJar plugin in ForgeDev 7
|
||||
public abstract class JarJarMetadataOptions extends DefaultTask {
|
||||
private static final Gson GSON = new GsonBuilder()
|
||||
.registerTypeAdapter(VersionRange.class, new VersionRangeSerializer())
|
||||
.registerTypeAdapter(ArtifactVersion.class, new ArtifactVersionSerializer())
|
||||
.registerTypeAdapter(DefaultArtifactVersion.class, new ArtifactVersionSerializer())
|
||||
.registerTypeAdapter(ContainedJarIdentifier.class, new ContainedJarIdentifierSerializer())
|
||||
.registerTypeAdapter(ContainedJarMetadata.class, new ContainedJarMetadataSerializer())
|
||||
.registerTypeAdapter(ContainedVersion.class, new ContainedVersionSerializer())
|
||||
.registerTypeAdapter(Metadata.class, new MetadataSerializer())
|
||||
.setPrettyPrinting()
|
||||
.create();
|
||||
|
||||
protected abstract @Input SetProperty<ResolvedDependencyInfoImpl> getResolvedDependencies();
|
||||
|
||||
protected abstract @OutputFile RegularFileProperty getMetadataFile();
|
||||
|
||||
protected abstract @Inject ProjectLayout getLayout();
|
||||
|
||||
// NOTE: I'm not adding a non-provider version. please just use the version catalog entries for now.
|
||||
public void add(Provider<MinimalExternalModuleDependency> dependency, Action<? super ResolvedDependencyInfo> action) {
|
||||
this.getResolvedDependencies().add(dependency.map(d -> {
|
||||
var ret = ResolvedDependencyInfoImpl.from(this.getProject().getConfigurations(), d);
|
||||
action.execute(ret);
|
||||
return ret;
|
||||
}));
|
||||
}
|
||||
|
||||
@Inject
|
||||
public JarJarMetadataOptions() {
|
||||
this.getMetadataFile().convention(this.getLayout().getBuildDirectory().file(this.getName() + "/options.json"));
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
protected void exec() {
|
||||
record ForgeLocaterOptions(String resource, String layer, String id, List<ContainedJarMetadata> deps, ContainedJarMetadata meta, boolean nested) { }
|
||||
|
||||
var resolved = this.getResolvedDependencies().get();
|
||||
var jars = new ArrayList<ForgeLocaterOptions>(resolved.size());
|
||||
for (var dependency : resolved) {
|
||||
var deps = new ArrayList<ContainedJarMetadata>();
|
||||
try (var zip = new ZipFile(dependency.artifact)) {
|
||||
var entry = zip.getEntry("META-INF/jarjar/metadata.json");
|
||||
if (entry != null) {
|
||||
try (var stream = zip.getInputStream(entry)) {
|
||||
var meta = MetadataIOHandler.fromStream(stream).orElse(null);
|
||||
if (meta == null)
|
||||
throw new IllegalStateException("Corrupt metadata.json in " + dependency.artifact.getAbsolutePath());
|
||||
for (var dep : meta.jars())
|
||||
deps.add(new ContainedJarMetadata(dep.identifier(), dep.version(), "", dep.isObfuscated()));
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
jars.add(new ForgeLocaterOptions(
|
||||
dependency.resource,
|
||||
dependency.layer,
|
||||
dependency.identifier,
|
||||
deps,
|
||||
new ContainedJarMetadata(
|
||||
new ContainedJarIdentifier(validateGroup(dependency), dependency.module.getName()),
|
||||
new ContainedVersion(null, parseVersion(dependency)),
|
||||
Objects.requireNonNull(dependency.path, "Dependency path is unspecified: " + dependency.asString),
|
||||
false
|
||||
),
|
||||
dependency.nested
|
||||
));
|
||||
}
|
||||
|
||||
try {
|
||||
record Meta(List<ForgeLocaterOptions> options){}
|
||||
|
||||
Files.writeString(
|
||||
this.getMetadataFile().getAsFile().get().toPath(),
|
||||
GSON.toJson(new Meta(jars), Meta.class)
|
||||
);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private String validateGroup(ResolvedDependencyInfoImpl dependency) {
|
||||
try {
|
||||
return Objects.requireNonNull(dependency.module.getGroup());
|
||||
} catch (NullPointerException e) {
|
||||
throw new IllegalArgumentException("Module dependency has no group: " + dependency.asString, e);
|
||||
}
|
||||
}
|
||||
|
||||
private ArtifactVersion parseVersion(ResolvedDependencyInfoImpl resolved) {
|
||||
try {
|
||||
return VersionRange.createFromVersionSpec(Objects.requireNonNull(resolved.version)).getRecommendedVersion();
|
||||
} catch (InvalidVersionSpecificationException e) {
|
||||
throw new IllegalArgumentException("Version is invalid for: " + resolved.asString, e);
|
||||
} catch (NullPointerException e) {
|
||||
throw new IllegalArgumentException("Version is unspecified for: " + resolved.asString, e);
|
||||
}
|
||||
}
|
||||
|
||||
private VersionRange parseVersionRange(ResolvedDependencyInfoImpl dependency) {
|
||||
if (dependency.hasManuallySpecifiedRange) {
|
||||
try {
|
||||
return VersionRange.createFromVersionSpec(dependency.versionRange);
|
||||
} catch (InvalidVersionSpecificationException e) {
|
||||
throw new IllegalArgumentException("Version is invalid for: " + dependency.asString, e);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return VersionRange.createFromVersionSpec("[%s,)".formatted(Objects.requireNonNull(dependency.versionRange)));
|
||||
} catch (InvalidVersionSpecificationException e) {
|
||||
throw new IllegalArgumentException("Version range is invalid for: " + dependency.asString, e);
|
||||
} catch (NullPointerException e) {
|
||||
throw new IllegalArgumentException("Version is unspecified for: " + dependency.asString, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface ResolvedDependencyInfo {
|
||||
void containedJarMetadata(Action<? super ContainedJarMetadataInfo> action);
|
||||
|
||||
void setResource(String resource);
|
||||
|
||||
void setLayer(String layer);
|
||||
|
||||
void setId(String identifier);
|
||||
|
||||
void setNested(boolean nested);
|
||||
|
||||
interface ContainedJarMetadataInfo {
|
||||
void setGroup(String group);
|
||||
|
||||
void setName(String name);
|
||||
|
||||
void setPath(String version);
|
||||
}
|
||||
}
|
||||
|
||||
static final class ResolvedDependencyInfoImpl implements ResolvedDependencyInfo, ResolvedDependencyInfo.ContainedJarMetadataInfo, Serializable {
|
||||
private static final @Serial long serialVersionUID = -7577318115877822993L;
|
||||
|
||||
final MinimalModuleVersionIdentifier module;
|
||||
final String version;
|
||||
String versionRange;
|
||||
boolean hasManuallySpecifiedRange;
|
||||
boolean nested;
|
||||
final File artifact;
|
||||
String path;
|
||||
String resource;
|
||||
String layer;
|
||||
String identifier;
|
||||
final String asString;
|
||||
|
||||
public ResolvedDependencyInfoImpl(MinimalModuleVersionIdentifier module, String version, File artifact, String asString) {
|
||||
this.module = module;
|
||||
this.version = version;
|
||||
this.artifact = artifact;
|
||||
this.asString = asString;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void containedJarMetadata(Action<? super ContainedJarMetadataInfo> action) {
|
||||
action.execute(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGroup(String group) {
|
||||
this.module.group = group;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
this.module.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setResource(String resource) {
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLayer(String layer) {
|
||||
this.layer = layer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setId(String identifier) {
|
||||
this.identifier = identifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNested(boolean nested) {
|
||||
this.nested = nested;
|
||||
}
|
||||
|
||||
static Set<File> getFiles(Set<ResolvedDependencyInfoImpl> resolvedDependencies) {
|
||||
var ret = new HashSet<File>(resolvedDependencies.size());
|
||||
for (var dependency : resolvedDependencies) {
|
||||
ret.add(dependency.artifact);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ResolvedDependencyInfoImpl from(ConfigurationContainer configurations, Dependency dependency) {
|
||||
var group = dependency.getGroup();
|
||||
var name = dependency.getName();
|
||||
var version = dependency.getVersion();
|
||||
|
||||
if (dependency instanceof FileCollectionDependency filesDependency) {
|
||||
File artifact;
|
||||
try {
|
||||
artifact = filesDependency.getFiles().getSingleFile();
|
||||
} catch (IllegalStateException e) {
|
||||
// TODO fileCollectionDependencyIsNotSingleFile
|
||||
throw e;
|
||||
}
|
||||
|
||||
return new ResolvedDependencyInfoImpl(
|
||||
new MinimalModuleVersionIdentifier(group, name, version),
|
||||
version,
|
||||
artifact,
|
||||
filesDependency.toString()
|
||||
);
|
||||
} else if (dependency instanceof ModuleDependency moduleDependency) {
|
||||
moduleDependency = moduleDependency.copy();
|
||||
if (moduleDependency instanceof ExternalModuleDependency externalModuleDependency) {
|
||||
externalModuleDependency.version(v -> v.strictly(version.toString()));
|
||||
}
|
||||
|
||||
var detachedConfiguration = configurations.detachedConfiguration(moduleDependency);
|
||||
detachedConfiguration.setTransitive(false);
|
||||
|
||||
ResolvedDependencyInfoImpl ret = null;
|
||||
for (var artifact : detachedConfiguration.getResolvedConfiguration().getFirstLevelModuleDependencies().iterator().next().getModuleArtifacts()) {
|
||||
var fileName = getFileName(artifact);
|
||||
if (!fileName.endsWith(".jar"))
|
||||
continue;
|
||||
|
||||
if (ret != null)
|
||||
throw new IllegalArgumentException("Module dependency has too many Jar artifacts: " + moduleDependency);
|
||||
|
||||
ret = new ResolvedDependencyInfoImpl(
|
||||
new MinimalModuleVersionIdentifier(group, name, artifact.getModuleVersion().getId().getVersion()),
|
||||
version,
|
||||
artifact.getFile(),
|
||||
moduleDependency.toString()
|
||||
);
|
||||
}
|
||||
if (ret == null)
|
||||
throw new IllegalArgumentException("Module dependency has no Jar artifacts: " + moduleDependency);
|
||||
|
||||
return ret;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported dependency type: " + dependency.getClass().getName() + " -- " + dependency);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getFileName(ResolvedArtifact artifact) {
|
||||
try {
|
||||
return InvokerHelper.getProperty(artifact.getId(), "fileName").toString();
|
||||
} catch (Throwable e) {
|
||||
// NOTE: Why not just use this to begin with?
|
||||
// ComponentArtifactIdentifier can have a getFileName() method, which doesn't necessarily resolve the file itself.
|
||||
// This allows us to get the name of the file to be used without asking Gradle to download the file.
|
||||
// So, if a file is not a JAR file, we can check the name without actually downloading it.
|
||||
return artifact.getFile().getName();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) return true;
|
||||
if (obj == null || obj.getClass() != this.getClass()) return false;
|
||||
var that = (ResolvedDependencyInfoImpl) obj;
|
||||
return Objects.equals(this.module, that.module) &&
|
||||
Objects.equals(this.version, that.version) &&
|
||||
Objects.equals(this.versionRange, that.versionRange) &&
|
||||
Objects.equals(this.artifact, that.artifact);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(module, version, versionRange, artifact);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ResolvedDependencyInfo[" +
|
||||
"module=" + module + ", " +
|
||||
"fixedVersion=" + version + ", " +
|
||||
"versionRange=" + versionRange + ", " +
|
||||
"artifact=" + artifact + ']';
|
||||
}
|
||||
|
||||
static final class MinimalModuleVersionIdentifier implements ModuleIdentifier, ModuleVersionIdentifier {
|
||||
private static final @Serial long serialVersionUID = -955346236759069739L;
|
||||
|
||||
private String group;
|
||||
private String name;
|
||||
private final String version;
|
||||
|
||||
@Inject
|
||||
public MinimalModuleVersionIdentifier(String group, String name, String version) {
|
||||
this.group = group;
|
||||
this.name = name;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModuleIdentifier getModule() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getGroup() {
|
||||
return this.group;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return this.version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return this == obj || obj instanceof MinimalModuleVersionIdentifier o
|
||||
&& Objects.equals(this.group, o.group)
|
||||
&& Objects.equals(this.name, o.name)
|
||||
&& Objects.equals(this.version, o.version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(group, name, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MinimalModuleVersionIdentifier[" +
|
||||
"group=" + group + ", " +
|
||||
"name=" + name + ", " +
|
||||
"version=" + version + ']';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -289,7 +289,6 @@ class ATParser {
|
||||
final String comment = idx === -1 ? null : line.substring(idx)
|
||||
if (idx !== -1) line = line.substring(0, idx - 1)
|
||||
final data = (line.trim() + ' ').split(' ', -1)
|
||||
data[1] = data[1].replaceAll('/', '.') // Convert to Source names, internal names are fine by spec, but not supported by old AST based AT implementations.
|
||||
new Entry(data[0], data[1], data[2], comment)
|
||||
}
|
||||
|
||||
|
||||
+26
-77
@@ -47,7 +47,6 @@ jarSigner.autoDetect('forge')
|
||||
applyPatches {
|
||||
level 'WARNING'
|
||||
failOnError = UPDATING != 'true'
|
||||
rejects = rootProject.layout.projectDirectory.dir('rejects').asFile
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
@@ -71,7 +70,7 @@ sourceSets {
|
||||
}
|
||||
}
|
||||
|
||||
final String SPEC_VERSION = gitversion.info.tag
|
||||
final String SPEC_VERSION = gradleutils.gitInfo.tag
|
||||
|
||||
// The new versioning sceme is <MCVersion>-<ForgeMC>.<RB>.<CommitsSinceRB>
|
||||
// ForgeMC is a unique identifier for every MC version we have supported.
|
||||
@@ -91,18 +90,18 @@ final String MAVEN_PATH = ext.MAVEN_PATH
|
||||
final String BINPATCH_TOOL = 'net.minecraftforge:binarypatcher:1.2.0:fatjar'
|
||||
final String INSTALLER_TOOLS = 'net.minecraftforge:installertools:1.4.3'
|
||||
final String FART = 'net.minecraftforge:ForgeAutoRenamingTool:1.0.6'
|
||||
final String S2S_TOOL = 'net.minecraftforge:Srg2Source:8.2.0:fatjar'
|
||||
final String S2S_TOOL = 'net.minecraftforge:Srg2Source:8.1.0:fatjar'
|
||||
|
||||
configurations {
|
||||
// Don't pull all libraries, if we're missing something, add it to the installer list so the installer knows to download it.
|
||||
bootstrap { transitive = false }
|
||||
installer {
|
||||
installer {
|
||||
extendsFrom(bootstrap)
|
||||
transitive = false
|
||||
transitive = false
|
||||
}
|
||||
installerextra {
|
||||
installerextra {
|
||||
extendsFrom(bootstrap)
|
||||
transitive = false
|
||||
transitive = false
|
||||
}
|
||||
api.extendsFrom(installer)
|
||||
}
|
||||
@@ -112,30 +111,28 @@ dependencies {
|
||||
// And this is only needed because custom file systems are REQUIRED to be on the boot classloader.
|
||||
// This has ASM/BootStrap/Unsafe all because I haven't gotten around to moving UnionFileSystem out to its own project.
|
||||
bootstrap(libs.jarjar.fs) // JarInJar file system
|
||||
bootstrap(libs.roimfs) // JarInJar File System - FileSystems need to be on boot loader.
|
||||
bootstrap(libs.bundles.jimfs) // In memory file system used for ForgeDev launches
|
||||
bootstrap(libs.securemodules) // Has Union file system in it
|
||||
bootstrap(libs.unsafe) // Needed by securemodules
|
||||
bootstrap(libs.bundles.asm) // Needed by securemodules
|
||||
|
||||
|
||||
implementation(libs.jopt.simple)
|
||||
|
||||
|
||||
installer(libs.bootstrap)
|
||||
installer(libs.bootstrap.api) // Needed by securemodules
|
||||
installer(libs.accesstransformers)
|
||||
installer(libs.eventbus)
|
||||
installer(libs.jspecify) // Dep of EventBus
|
||||
installer(libs.typetools) // Dep of EventBus
|
||||
installer(libs.forgespi)
|
||||
installer(libs.coremods.api)
|
||||
installer(libs.coremods)
|
||||
installer(libs.nashorn) // Dep of Coremods
|
||||
installer(libs.modlauncher)
|
||||
installer(libs.mergetool.api)
|
||||
installer(libs.bundles.night.config)
|
||||
installer(libs.maven.artifact)
|
||||
installer(libs.bundles.terminalconsoleappender)
|
||||
installer(libs.mixin)
|
||||
installer(libs.mixinextras.forge)
|
||||
installer(libs.bundles.jarjar)
|
||||
installer(libs.roimfs)
|
||||
|
||||
installer(project(':fmlcore'))
|
||||
installer(project(':fmlloader'))
|
||||
@@ -143,25 +140,16 @@ dependencies {
|
||||
installer(project(':javafmllanguage'))
|
||||
installer(project(':lowcodelanguage'))
|
||||
installer(project(':mclanguage'))
|
||||
installer(project(':forge-transformers'))
|
||||
|
||||
installerextra("${MCP_ARTIFACT.group}:${MCP_ARTIFACT.name}:${MCP_ARTIFACT.version}:srg2off")
|
||||
|
||||
runtimeOnly(libs.bootstrap)
|
||||
runtimeOnly(libs.bootstrap.dev)
|
||||
|
||||
annotationProcessor(libs.eventbus.validator)
|
||||
}
|
||||
|
||||
tasks.named('extractRangeMap').configure {
|
||||
tool = S2S_TOOL
|
||||
}
|
||||
tasks.named('applyRangeMap').configure {
|
||||
tool = S2S_TOOL
|
||||
}
|
||||
tasks.named('applyRangeMapBase').configure {
|
||||
tool = S2S_TOOL
|
||||
}
|
||||
|
||||
patcher {
|
||||
excs.from file("$rootDir/src/main/resources/forge.exc")
|
||||
@@ -176,7 +164,7 @@ patcher {
|
||||
|
||||
runs {
|
||||
forge_client {
|
||||
property 'eventbus.api.strictRuntimeChecks', 'true'
|
||||
property 'eventbus.checkTypesOnDispatch', 'true'
|
||||
property 'org.lwjgl.system.SharedLibraryExtractDirectory', 'lwjgl_dll'
|
||||
|
||||
args '--launchTarget', 'forge_dev_client',
|
||||
@@ -219,6 +207,7 @@ patcher {
|
||||
'--all',
|
||||
'--validate',
|
||||
'--output', rootProject.file('src/main/generated/'),
|
||||
'--validate',
|
||||
'--existing', sourceSets.main.resources.srcDirs[0],
|
||||
'--assetsDir', downloadAssets.output,
|
||||
'--assetIndex', "{asset_index}"
|
||||
@@ -236,30 +225,6 @@ patcher {
|
||||
'--assetsDir', downloadAssets.output,
|
||||
'--assetIndex', "{asset_index}"
|
||||
}
|
||||
|
||||
forge_client_data {
|
||||
args '--launchTarget', 'forge_dev_client_data',
|
||||
'--mod', 'forge',
|
||||
'--all',
|
||||
'--output', rootProject.file('src/main/generated/'),
|
||||
'--existing', sourceSets.main.resources.srcDirs[0],
|
||||
'--assetsDir', downloadAssets.output,
|
||||
'--assetIndex', "{asset_index}"
|
||||
}
|
||||
|
||||
forge_client_data_test {
|
||||
source sourceSets.test
|
||||
args '--launchTarget', 'forge_dev',
|
||||
'--launchEntry', 'minecraft/net.minecraft.client.data.Main',
|
||||
'--launchData',
|
||||
'--mod', '.+',
|
||||
'--all',
|
||||
'--output', rootProject.file('src/test/generated/'),
|
||||
'--existing', sourceSets.main.resources.srcDirs[0],
|
||||
'--existing', sourceSets.test.resources.srcDirs[0],
|
||||
'--assetsDir', downloadAssets.output,
|
||||
'--assetIndex', "{asset_index}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,16 +266,11 @@ tasks.register('downloadCrowdin', Download) {
|
||||
tasks.userdevConfig.configure {
|
||||
configurations.installer.allDependencies.forEach {
|
||||
def dep = it.toString()
|
||||
if (it instanceof ProjectDependency)
|
||||
if (it instanceof ProjectDependency)
|
||||
dep = "net.minecraftforge:$it.dependencyProject.name:$it.dependencyProject.version"
|
||||
libraries.add(dep)
|
||||
}
|
||||
|
||||
// TODO [ForgeDev][UserDev] Split API/Runtime elements in userdev config
|
||||
// so we don't gotta keep making exceptions for MixinExtras
|
||||
addCompileDependency(libs.mixinextras.common)
|
||||
addAnnotationProcessorDependency(libs.mixinextras.common)
|
||||
|
||||
|
||||
inject = '' // We don't have a userdev sourceset anymore. Empty as a gradle workaround...
|
||||
runs {
|
||||
client {
|
||||
@@ -322,13 +282,6 @@ tasks.userdevConfig.configure {
|
||||
args '--assetsDir', '{assets_root}'
|
||||
}
|
||||
|
||||
clientData {
|
||||
environment 'MCP_MAPPINGS', '{mcp_mappings}'
|
||||
args '--launchTarget', "forge_userdev_client_data"
|
||||
args '--assetIndex', '{asset_index}'
|
||||
args '--assetsDir', '{assets_root}'
|
||||
}
|
||||
|
||||
server {
|
||||
environment 'MCP_MAPPINGS', '{mcp_mappings}'
|
||||
property 'forge.enableGameTest', 'true'
|
||||
@@ -426,14 +379,14 @@ tasks.register('extractInheritance', ExtractInheritance) {
|
||||
}
|
||||
}
|
||||
tasks.register("findFinalizeSpawnTargets", BytecodePredicateFinder) {
|
||||
jar = createClientOfficial.output
|
||||
output = rootProject.file('forge-transformers/src/main/resources/coremods/finalize_spawn_targets.json')
|
||||
jar = createJoinedSRG.output
|
||||
output = rootProject.file('src/main/resources/coremods/finalize_spawn_targets.json')
|
||||
predicate = {
|
||||
parent, node, insn ->
|
||||
return 'net/minecraft/world/level/BaseSpawner' != parent.name // Ignore this class as we special case it.
|
||||
&& insn.getOpcode().equals(Opcodes.INVOKEVIRTUAL)
|
||||
&& insn.name == 'finalizeSpawn'
|
||||
&& insn.desc == '(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/EntitySpawnReason;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;';
|
||||
&& insn.name == 'm_6518_'
|
||||
&& insn.desc == '(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/MobSpawnType;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;';
|
||||
}
|
||||
}
|
||||
tasks.register('validateDeprecations', ValidateDeprecations) {
|
||||
@@ -538,7 +491,7 @@ tasks.register('serverShimJar', Jar) {
|
||||
from {
|
||||
zipTree(downloadServerShim.output).find { it.name == 'MANIFEST.MF' }
|
||||
}
|
||||
|
||||
|
||||
attributes('Class-Path': configurations.bootstrap.resolvedConfiguration.resolvedArtifacts.collect { "libraries/${Util.getMavenInfoFromDep(it).path}" }.join(' '))
|
||||
}
|
||||
archiveClassifier = 'shim'
|
||||
@@ -631,7 +584,7 @@ tasks.register('checkJarCompatibility', CheckJarCompatibility) {
|
||||
baseLibraries.from(project.tasks.named('createJoinedSRG').flatMap { it.output })
|
||||
|
||||
inputJar = project.tasks.named('reobfJar').flatMap { it.output }
|
||||
|
||||
|
||||
commonLibraries.from(project.configurations.minecraftImplementation)
|
||||
commonLibraries.from(project.configurations.installer)
|
||||
}
|
||||
@@ -652,11 +605,11 @@ tasks.register('launcherJson', LauncherJson).configure {
|
||||
|
||||
tasks.register('installerJson', InstallerJson) {
|
||||
icon = rootProject.file('icon.ico')
|
||||
|
||||
|
||||
// Json to install into the client's launcher
|
||||
dependsOn(launcherJson)
|
||||
input.from(launcherJson.output)
|
||||
|
||||
|
||||
// Download Official Mappings
|
||||
dependsOn(downloadClientMappings, downloadServerMappings)
|
||||
input.from(downloadClientMappings.output, downloadServerMappings.output)
|
||||
@@ -971,15 +924,11 @@ tasks.register('mdkZip', Zip) {
|
||||
MAPPING_CHANNEL: MAPPING_CHANNEL,
|
||||
MAPPING_VERSION: MAPPING_VERSION,
|
||||
FORGE_SPEC_VERSION: SPEC_VERSION.split("\\.")[0],
|
||||
MC_NEXT_VERSION: MC_NEXT_VERSION,
|
||||
EVENTBUS_VERSION: libs.versions.eventbus.get()
|
||||
MC_NEXT_VERSION: MC_NEXT_VERSION
|
||||
])
|
||||
rename 'gitignore\\.txt', '.gitignore'
|
||||
rename 'gitattributes\\.txt', '.gitattributes'
|
||||
}
|
||||
from(rootProject.file('src/test/java/com/example/examplemod/')) {
|
||||
into('src/main/java/com/example/examplemod/')
|
||||
}
|
||||
from(rootProject.file('src/test/generated/mdk_datagen/')) {
|
||||
into('src/main/resources/')
|
||||
exclude '**/.cache/'
|
||||
@@ -1019,7 +968,7 @@ if (project.hasProperty('UPDATE_MAPPINGS')) {
|
||||
|
||||
tasks.named('javadoc', Javadoc).configure {
|
||||
description 'Generates the combined javadocs for the FML projects and the main Forge project'
|
||||
var includedProjects = [ ':fmlcore', ':fmlloader', ':javafmllanguage', ':mclanguage', ':forge-transformers' ]
|
||||
var includedProjects = [ ':fmlcore', ':fmlloader', ':javafmllanguage', ':mclanguage' ]
|
||||
source includedProjects.collect { project(it).sourceSets.main.allJava }
|
||||
classpath = classpath + files(includedProjects.collect { project(it).sourceSets.main.compileClasspath })
|
||||
|
||||
@@ -1113,7 +1062,7 @@ publishing {
|
||||
license gradleutils.pom.Licenses.LGPLv2_1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
repositories {
|
||||
maven gradleutils.publishingForgeMaven
|
||||
}
|
||||
|
||||
+1
-5
@@ -38,17 +38,13 @@ tasks.register('generateResources') {
|
||||
dependsOn('writeManifest')
|
||||
}
|
||||
|
||||
tasks.named('processResources') {
|
||||
dependsOn(generateResources)
|
||||
}
|
||||
|
||||
// Make sure out manifests get written before compiling the code, IDEA calls this task if you tell it to use the gradle build.
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
dependsOn 'generateResources'
|
||||
dependsOn 'processResources' // Needed because we merge the output of this with the output of the compile task. And gradle detects downstream tasks using the output without a hard dep
|
||||
options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation
|
||||
options.warnings = false // Shutup deprecated for removal warnings
|
||||
options.forkOptions.jvmArgs += '-Xmx6G' // Needed to make compiling faster, and not run out of heap space in some cases.
|
||||
options.forkOptions.jvmArgs += '-Xmx3G' // Needed to make compiling faster, and not run out of heap space in some cases.
|
||||
}
|
||||
|
||||
// Merge the resources and classes into the same directory. We'll need to split them at runtime because
|
||||
|
||||
+2
-8
@@ -2,8 +2,8 @@
|
||||
|
||||
MinecraftForge
|
||||
=============
|
||||
[][Download]
|
||||
[][Download]
|
||||
[][Download] [][Discord] [][Patreon]
|
||||
|
||||
Forge is a free, open-source modding API all of your favourite mods use!
|
||||
@@ -70,11 +70,6 @@ Please ensure you have a valid email address associated with your GitHub account
|
||||
to use and modify. However, it costs money to run such a large project as this, so please consider
|
||||
[becoming a patron][Patreon].*
|
||||
|
||||
### Thanks
|
||||
[](https://www.yourkit.com/)
|
||||
|
||||
YourKit for providing us accesss to their [YourKit Java Profiler] which helps identify bottlenecks and places for improvment.
|
||||
|
||||
[Contributing]: ./CONTRIBUTING.md
|
||||
[CLA]: https://cla-assistant.io/MinecraftForge/MinecraftForge
|
||||
|
||||
@@ -98,4 +93,3 @@ YourKit for providing us accesss to their [YourKit Java Profiler] which helps id
|
||||
[Pull-Requests]: https://docs.minecraftforge.net/en/latest/forgedev/#making-changes-and-pull-requests
|
||||
|
||||
[Patreon]: https://www.patreon.com/LexManos
|
||||
[YourKit Java Profiler]: https://www.yourkit.com/java/profiler/
|
||||
@@ -10,10 +10,9 @@ plugins {
|
||||
apply from: rootProject.file('build_shared.gradle')
|
||||
|
||||
dependencies {
|
||||
compileOnly(libs.jetbrains.annotations)
|
||||
compileOnly(libs.nulls)
|
||||
|
||||
api(libs.eventbus)
|
||||
annotationProcessor(libs.eventbus.validator)
|
||||
|
||||
implementation(project(':fmlloader'))
|
||||
implementation(libs.commons.io)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.eventbus.api.bus.BusGroup;
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import net.minecraftforge.fml.config.IConfigEvent;
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
|
||||
@@ -29,7 +29,7 @@ public class Bindings {
|
||||
/**
|
||||
* @return A supplier of net.minecraftforge.common.MinecraftForge#EVENT_BUS
|
||||
*/
|
||||
public static Supplier<BusGroup> getForgeBus() {
|
||||
public static Supplier<IEventBus> getForgeBus() {
|
||||
return PROVIDER.getForgeBusSupplier();
|
||||
}
|
||||
|
||||
|
||||
@@ -25,18 +25,7 @@ import java.util.function.Supplier;
|
||||
* <li>When you want to call one thing on one side, another thing on the other and return a result {@link #safeRunForDist(Supplier, Supplier)}</li>
|
||||
* <li>When you want to run something on one side {@link #safeRunWhenOn(Dist, Supplier)}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @deprecated Historically, this allowed for safe execution of code on the correct side by exploiting a low-level
|
||||
* detail in Java 8, but in newer versions a simple if check is enough.
|
||||
* <br>
|
||||
* For example:
|
||||
* <pre>{@code
|
||||
* if (FMLEnvironment.dist.isClient()) {
|
||||
* // client code here
|
||||
* }
|
||||
* }</pre>
|
||||
*/
|
||||
@Deprecated(since = "1.21.5", forRemoval = true)
|
||||
public final class DistExecutor
|
||||
{
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.eventbus.api.bus.BusGroup;
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import net.minecraftforge.fml.config.IConfigEvent;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public interface IBindingsProvider {
|
||||
Supplier<BusGroup> getForgeBusSupplier();
|
||||
Supplier<IEventBus> getForgeBusSupplier();
|
||||
Supplier<I18NParser> getMessageParser();
|
||||
Supplier<IConfigEvent.ConfigConfig> getConfigConfiguration();
|
||||
}
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.fml.loading.progress.ProgressMeter;
|
||||
|
||||
import java.util.Optional;
|
||||
@@ -59,27 +61,27 @@ public interface IModLoadingState {
|
||||
/**
|
||||
* Builds the transition task for this state with a blank pre-sync and post-sync task.
|
||||
*
|
||||
* @param <T> a type of event fired on the mod-specific event bus
|
||||
* @param syncExecutor a synchronous executor
|
||||
* @param parallelExecutor a parallel executor
|
||||
* @param progressBar a progress meter for tracking progress
|
||||
* @return a transition task for this state
|
||||
* @see #buildTransition(Executor, Executor, ProgressMeter, Function, Function)
|
||||
*/
|
||||
default Optional<CompletableFuture<Void>> buildTransition(
|
||||
final Executor syncExecutor,
|
||||
final Executor parallelExecutor,
|
||||
final ProgressMeter progressBar
|
||||
) {
|
||||
default <T extends Event & IModBusEvent>
|
||||
Optional<CompletableFuture<Void>> buildTransition(final Executor syncExecutor,
|
||||
final Executor parallelExecutor,
|
||||
final ProgressMeter progressBar) {
|
||||
return buildTransition(syncExecutor, parallelExecutor, progressBar,
|
||||
e -> CompletableFuture.completedFuture(null),
|
||||
e -> CompletableFuture.completedFuture(null)
|
||||
);
|
||||
e -> CompletableFuture.runAsync(() -> {}, e),
|
||||
e -> CompletableFuture.runAsync(() -> {}, e));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the transition task for this state. The pre-sync and post-sync task functions allow the transition builder
|
||||
* to run these tasks on the same executor as the actual event dispatch and pre/post hooks.
|
||||
*
|
||||
* @param <T> a type of event fired on the mod-specific event bus
|
||||
* @param syncExecutor a synchronous executor
|
||||
* @param parallelExecutor a parallel executor
|
||||
* @param progressBar a progress meter for tracking progress
|
||||
@@ -87,6 +89,7 @@ public interface IModLoadingState {
|
||||
* @param postSyncTask a function which returns a task to run after event post-dispatch hook
|
||||
* @return a transition task for this state
|
||||
*/
|
||||
<T extends Event & IModBusEvent>
|
||||
Optional<CompletableFuture<Void>> buildTransition(final Executor syncExecutor,
|
||||
final Executor parallelExecutor,
|
||||
final ProgressMeter progressBar,
|
||||
|
||||
@@ -5,9 +5,12 @@
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.fml.loading.progress.ProgressMeter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.BiFunction;
|
||||
@@ -15,67 +18,103 @@ import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public interface IModStateTransition {
|
||||
/** Magic value to allow me to optimize the futures list by ignoring the default value without making old methods nullable. */
|
||||
BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> NULL_HOOK = (e, g) -> CompletableFuture.completedFuture(null);
|
||||
|
||||
static IModStateTransition buildNoopTransition() {
|
||||
return ModStateTransitionHelper.NOOP;
|
||||
return new NoopTransition();
|
||||
}
|
||||
|
||||
default CompletableFuture<Void> build(
|
||||
final String name,
|
||||
final Executor syncExecutor,
|
||||
final Executor parallelExecutor,
|
||||
final ProgressMeter progressBar,
|
||||
final Function<Executor, CompletableFuture<Void>> preSyncTask,
|
||||
final Function<Executor, CompletableFuture<Void>> postSyncTask
|
||||
) {
|
||||
return ModStateTransitionHelper.build(this, name, syncExecutor, parallelExecutor, progressBar, preSyncTask, postSyncTask);
|
||||
default <T extends Event & IModBusEvent>
|
||||
CompletableFuture<Void> build(final String name,
|
||||
final Executor syncExecutor,
|
||||
final Executor parallelExecutor,
|
||||
final ProgressMeter progressBar,
|
||||
final Function<Executor, CompletableFuture<Void>> preSyncTask,
|
||||
final Function<Executor, CompletableFuture<Void>> postSyncTask) {
|
||||
List<CompletableFuture<Void>> futures = new ArrayList<>();
|
||||
|
||||
this.eventFunctionStream().get()
|
||||
.map(f -> (EventGenerator<T>) f)
|
||||
.reduce((head, tail) -> addCompletableFutureTaskForModDispatch(syncExecutor, parallelExecutor, futures, progressBar, head, ModLoadingStage::currentState, tail))
|
||||
.ifPresent(last -> addCompletableFutureTaskForModDispatch(syncExecutor, parallelExecutor, futures, progressBar, last, nextModLoadingStage(), null));
|
||||
|
||||
final CompletableFuture<Void> preSyncTaskCF = preSyncTask.apply(syncExecutor);
|
||||
final CompletableFuture<Void> eventDispatchCF = ModList.gather(futures).thenCompose(ModList::completableFutureFromExceptionList);
|
||||
final CompletableFuture<Void> postEventDispatchCF = preSyncTaskCF
|
||||
.thenApplyAsync(n -> {
|
||||
progressBar.label(progressBar.name() + ": dispatching "+name);
|
||||
return null;
|
||||
}, parallelExecutor)
|
||||
.thenComposeAsync(n -> eventDispatchCF, parallelExecutor)
|
||||
.thenApply(r -> {
|
||||
postSyncTask.apply(syncExecutor);
|
||||
return null;
|
||||
});
|
||||
return this.finalActivityGenerator().apply(syncExecutor, postEventDispatchCF);
|
||||
}
|
||||
|
||||
default BiFunction<ModLoadingStage, Throwable, ModLoadingStage> nextModLoadingStage() {
|
||||
return ModLoadingStage::nextState;
|
||||
}
|
||||
|
||||
/**
|
||||
* This used to allow you to fire multiple events during the transition. However, in doing so it would cause issues with the default
|
||||
* ModContainer's event handlers causing issues such as mod classes being constructed multiple times.
|
||||
*/
|
||||
@Deprecated(since = "1.21.3", forRemoval = true)
|
||||
default Supplier<Stream<EventGenerator<?>>> eventFunctionStream() {
|
||||
return () -> Stream.ofNullable(eventFunction());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
default <T extends IModBusEvent> EventGenerator<T> eventFunction() {
|
||||
return null;
|
||||
private <T extends Event & IModBusEvent>
|
||||
EventGenerator<T> addCompletableFutureTaskForModDispatch(final Executor syncExecutor,
|
||||
final Executor parallelExecutor,
|
||||
final List<CompletableFuture<Void>> completableFutures,
|
||||
final ProgressMeter progressBar,
|
||||
final EventGenerator<T> eventGenerator,
|
||||
final BiFunction<ModLoadingStage, Throwable, ModLoadingStage> nextState,
|
||||
final EventGenerator<T> nextGenerator) {
|
||||
final Executor selectedExecutor = threadSelector().apply(syncExecutor, parallelExecutor);
|
||||
|
||||
var preDispatchHook = (BiFunction<Executor, EventGenerator<T>, CompletableFuture<Void>>) preDispatchHook();
|
||||
completableFutures.add(preDispatchHook.apply(selectedExecutor, eventGenerator));
|
||||
|
||||
completableFutures.add(ModList.get().futureVisitor(eventGenerator, progressBar, nextState).apply(threadSelector().apply(syncExecutor, parallelExecutor)));
|
||||
|
||||
var postDispatchHook = (BiFunction<Executor, EventGenerator<T>, CompletableFuture<Void>>) postDispatchHook();
|
||||
completableFutures.add(postDispatchHook.apply(selectedExecutor, eventGenerator));
|
||||
|
||||
return nextGenerator;
|
||||
}
|
||||
|
||||
Supplier<Stream<EventGenerator<?>>> eventFunctionStream();
|
||||
ThreadSelector threadSelector();
|
||||
BiFunction<Executor, CompletableFuture<Void>, CompletableFuture<Void>> finalActivityGenerator();
|
||||
BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> preDispatchHook();
|
||||
BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> postDispatchHook();
|
||||
|
||||
/**
|
||||
* I think this was meant as a way to do some things for each mod container beforge/after the transition had been sent to the container.
|
||||
* However, the Future returned by this was never linked to the main transition future in any way. Which means that it was run
|
||||
* in parallel and couldn't guarantee the state of the ModContainer.
|
||||
* <p>
|
||||
* Plus all existing code that I could find returned a completedFuture, so I don't think anyone ever used this.
|
||||
* <p>
|
||||
* If I were to add this back it would be a CompletableFuture wrap(ModContainer, CompletableFuture)
|
||||
* <p>
|
||||
* Added magic value NULL_HOOK to allow me to optimize the futures list by ignoring the default value without making this method nullable.
|
||||
*/
|
||||
@Deprecated(since = "1.21.3", forRemoval = true)
|
||||
default BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> preDispatchHook() { return NULL_HOOK; }
|
||||
@Deprecated(since = "1.21.3", forRemoval = true)
|
||||
default BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> postDispatchHook() { return NULL_HOOK; }
|
||||
|
||||
interface EventGenerator<T extends IModBusEvent> extends Function<ModContainer, T> {
|
||||
static <FN extends IModBusEvent> EventGenerator<FN> fromFunction(Function<ModContainer, FN> fn) {
|
||||
interface EventGenerator<T extends Event & IModBusEvent> extends Function<ModContainer, T> {
|
||||
static <FN extends Event & IModBusEvent> EventGenerator<FN> fromFunction(Function<ModContainer, FN> fn) {
|
||||
return fn::apply;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
record NoopTransition() implements IModStateTransition {
|
||||
|
||||
@Override
|
||||
public Supplier<Stream<EventGenerator<?>>> eventFunctionStream() {
|
||||
return Stream::of;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThreadSelector threadSelector() {
|
||||
return ThreadSelector.SYNC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BiFunction<Executor, CompletableFuture<Void>, CompletableFuture<Void>> finalActivityGenerator() {
|
||||
return (e, t) -> t.thenApplyAsync(Function.identity(), e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> preDispatchHook() {
|
||||
return (t, f)-> CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> postDispatchHook() {
|
||||
return (t, f)-> CompletableFuture.completedFuture(null);
|
||||
}
|
||||
}
|
||||
@@ -5,19 +5,22 @@
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.eventbus.api.bus.BusGroup;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.config.IConfigEvent;
|
||||
import net.minecraftforge.fml.config.ModConfig;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.fml.loading.progress.ProgressMeter;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
@@ -30,8 +33,13 @@ import java.util.function.Supplier;
|
||||
* a mechanism by which we can wrap actual mod code so that the loader and other
|
||||
* facilities can treat mods at arms length.
|
||||
* </p>
|
||||
*
|
||||
* @author cpw
|
||||
*
|
||||
*/
|
||||
public abstract class ModContainer {
|
||||
|
||||
public abstract class ModContainer
|
||||
{
|
||||
protected final String modId;
|
||||
protected final String namespace;
|
||||
protected final IModInfo modInfo;
|
||||
@@ -40,14 +48,11 @@ public abstract class ModContainer {
|
||||
protected final Map<ModLoadingStage, Runnable> activityMap = new EnumMap<>(ModLoadingStage.class);
|
||||
protected final Map<Class<? extends IExtensionPoint<?>>, Supplier<?>> extensionPoints = new IdentityHashMap<>();
|
||||
protected final EnumMap<ModConfig.Type, ModConfig> configs = new EnumMap<>(ModConfig.Type.class);
|
||||
final Set<ModContainer> dependencies = new HashSet<>();
|
||||
/**
|
||||
* If you want to handle the event, override {@link #dispatchConfigEvent(IConfigEvent)}
|
||||
*/
|
||||
@Deprecated(since = "1.21.3", forRemoval = true)
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
protected Optional<Consumer<IConfigEvent>> configHandler = Optional.empty();
|
||||
|
||||
public ModContainer(IModInfo info) {
|
||||
public ModContainer(IModInfo info)
|
||||
{
|
||||
this.modId = info.getModId();
|
||||
// TODO: Currently not reading namespace from configuration..
|
||||
this.namespace = this.modId;
|
||||
@@ -77,35 +82,58 @@ public abstract class ModContainer {
|
||||
/**
|
||||
* Errored container state, used for filtering. Does nothing.
|
||||
*/
|
||||
ModContainer() {
|
||||
ModContainer()
|
||||
{
|
||||
this.modLoadingStage = ModLoadingStage.ERROR;
|
||||
modId = "BROKEN";
|
||||
namespace = "BROKEN";
|
||||
modInfo = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the modid for this mod
|
||||
*/
|
||||
public final String getModId() {
|
||||
public final String getModId()
|
||||
{
|
||||
return modId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the resource prefix for the mod
|
||||
*/
|
||||
public final String getNamespace() {
|
||||
public final String getNamespace()
|
||||
{
|
||||
return namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The current loading stage for this mod
|
||||
*/
|
||||
public ModLoadingStage getCurrentState() {
|
||||
public ModLoadingStage getCurrentState()
|
||||
{
|
||||
return modLoadingStage;
|
||||
}
|
||||
|
||||
public IModInfo getModInfo() {
|
||||
public static <T extends Event & IModBusEvent> CompletableFuture<Void> buildTransitionHandler(
|
||||
final ModContainer target,
|
||||
final IModStateTransition.EventGenerator<T> eventGenerator,
|
||||
final ProgressMeter progressBar,
|
||||
final BiFunction<ModLoadingStage, Throwable, ModLoadingStage> stateChangeHandler,
|
||||
final Executor executor) {
|
||||
return CompletableFuture
|
||||
.runAsync(() -> {
|
||||
ModLoadingContext.get().setActiveContainer(target);
|
||||
target.activityMap.getOrDefault(target.modLoadingStage, ()->{}).run();
|
||||
target.acceptEvent(eventGenerator.apply(target));
|
||||
}, executor)
|
||||
.whenComplete((mc, exception) -> {
|
||||
target.modLoadingStage = stateChangeHandler.apply(target.modLoadingStage, exception);
|
||||
progressBar.increment();
|
||||
ModLoadingContext.get().setActiveContainer(null);
|
||||
});
|
||||
}
|
||||
|
||||
public IModInfo getModInfo()
|
||||
{
|
||||
return modInfo;
|
||||
}
|
||||
|
||||
@@ -114,7 +142,8 @@ public abstract class ModContainer {
|
||||
return Optional.ofNullable((T)extensionPoints.getOrDefault(point,()-> null).get());
|
||||
}
|
||||
|
||||
public <T extends Record & IExtensionPoint<T>> void registerExtensionPoint(Class<? extends IExtensionPoint<T>> point, Supplier<T> extension) {
|
||||
public <T extends Record & IExtensionPoint<T>> void registerExtensionPoint(Class<? extends IExtensionPoint<T>> point, Supplier<T> extension)
|
||||
{
|
||||
extensionPoints.put(point, extension);
|
||||
}
|
||||
|
||||
@@ -158,17 +187,9 @@ public abstract class ModContainer {
|
||||
*/
|
||||
public abstract Object getMod();
|
||||
|
||||
/**
|
||||
* @return the mod bus group for this specific mod, or null if there is none.
|
||||
* Leave null if you use {@link BusGroup#DEFAULT} for your mod lifecycle events.
|
||||
*/
|
||||
public BusGroup getModBusGroup() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept an arbitrary event for processing by the mod. Probably posted to an event bus in the lower level container.
|
||||
* @param e Event to accept
|
||||
*/
|
||||
protected <T extends IModBusEvent> void acceptEvent(T e) {}
|
||||
protected <T extends Event & IModBusEvent> void acceptEvent(T e) {}
|
||||
}
|
||||
@@ -5,6 +5,9 @@
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.fml.loading.progress.ProgressMeter;
|
||||
import net.minecraftforge.forgespi.language.IModFileInfo;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import net.minecraftforge.forgespi.language.ModFileScanData;
|
||||
@@ -12,15 +15,27 @@ 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.util.Collections;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.concurrent.ForkJoinWorkerThread;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -30,25 +45,27 @@ import java.util.stream.Stream;
|
||||
* Master list of all mods - game-side version. This is classloaded in the game scope and
|
||||
* can dispatch game level events as a result.
|
||||
*/
|
||||
public class ModList {
|
||||
public class ModList
|
||||
{
|
||||
//private static final Logger LOGGER = LogManager.getLogger();
|
||||
private static ModList INSTANCE;
|
||||
private final List<IModFileInfo> modFiles;
|
||||
private final List<IModInfo> sortedList;
|
||||
private final Map<String, IModFileInfo> fileById;
|
||||
private final Map<String, ModFileInfo> fileById;
|
||||
private List<ModContainer> mods;
|
||||
private Map<String, ModContainer> indexedMods;
|
||||
private List<ModFileScanData> modFileScanData;
|
||||
private List<ModContainer> sortedContainers;
|
||||
|
||||
private ModList(final List<ModFile> modFiles, final List<ModInfo> sortedList) {
|
||||
var modFileInfos = this.modFiles = modFiles.stream().map(ModFile::getModFileInfo).toList();
|
||||
this.sortedList = sortedList.stream().map(IModInfo.class::cast).toList();
|
||||
var byId = new HashMap<String, IModFileInfo>();
|
||||
for (var file : modFileInfos) {
|
||||
for (var mod : file.getMods())
|
||||
byId.put(mod.getModId(), mod.getOwningFile());
|
||||
}
|
||||
this.fileById = Map.copyOf(byId);
|
||||
private ModList(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(IModFileInfo::getMods).flatMap(Collection::stream).
|
||||
map(ModInfo.class::cast).
|
||||
collect(Collectors.toMap(ModInfo::getModId, ModInfo::getOwningFile));
|
||||
CrashReportCallables.registerCrashCallable("Mod List", this::crashReport);
|
||||
}
|
||||
|
||||
@@ -65,12 +82,12 @@ public class ModList {
|
||||
getModContainerState(mainMod.getModId()),
|
||||
((ModFileInfo)mf.getModFileInfo()).getCodeSigningFingerprint().orElse("NOSIGNATURE"));
|
||||
}
|
||||
|
||||
private String crashReport() {
|
||||
return "\n" + applyForEachModFile(this::fileToLine).collect(Collectors.joining("\n\t\t", "\t\t", ""));
|
||||
return "\n"+applyForEachModFile(this::fileToLine).collect(Collectors.joining("\n\t\t", "\t\t", ""));
|
||||
}
|
||||
|
||||
public static ModList of(List<ModFile> modFiles, List<ModInfo> sortedList) {
|
||||
public static ModList of(List<ModFile> modFiles, List<ModInfo> sortedList)
|
||||
{
|
||||
INSTANCE = new ModList(modFiles, sortedList);
|
||||
return INSTANCE;
|
||||
}
|
||||
@@ -79,59 +96,117 @@ public class ModList {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public List<IModFileInfo> getModFiles() {
|
||||
public List<IModFileInfo> getModFiles()
|
||||
{
|
||||
return modFiles;
|
||||
}
|
||||
|
||||
public IModFileInfo getModFileById(String modid) {
|
||||
public IModFileInfo getModFileById(String modid)
|
||||
{
|
||||
return this.fileById.get(modid);
|
||||
}
|
||||
|
||||
void setLoadedMods(final List<ModContainer> modContainers) {
|
||||
<T extends Event & IModBusEvent> Function<Executor, CompletableFuture<Void>> futureVisitor(
|
||||
final IModStateTransition.EventGenerator<T> eventGenerator,
|
||||
final ProgressMeter progressBar,
|
||||
final BiFunction<ModLoadingStage, Throwable, ModLoadingStage> stateChange) {
|
||||
return executor -> gather(
|
||||
this.mods.stream()
|
||||
.map(mod -> ModContainer.buildTransitionHandler(mod, eventGenerator, progressBar, stateChange, executor))
|
||||
.toList()
|
||||
).thenComposeAsync(ModList::completableFutureFromExceptionList, executor);
|
||||
}
|
||||
static CompletionStage<Void> completableFutureFromExceptionList(List<? extends Map.Entry<?, Throwable>> t) {
|
||||
if (t.stream().noneMatch(e->e.getValue()!=null)) {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
} else {
|
||||
final List<Throwable> throwables = t.stream().filter(e -> e.getValue() != null).map(Map.Entry::getValue).toList();
|
||||
CompletableFuture<Void> cf = new CompletableFuture<>();
|
||||
final RuntimeException accumulator = new RuntimeException();
|
||||
cf.completeExceptionally(accumulator);
|
||||
for (Throwable exception : throwables) {
|
||||
if (exception instanceof CompletionException) {
|
||||
exception = exception.getCause();
|
||||
}
|
||||
if (exception.getSuppressed().length != 0) {
|
||||
for (Throwable throwable : exception.getSuppressed()) {
|
||||
accumulator.addSuppressed(throwable);
|
||||
}
|
||||
} else {
|
||||
accumulator.addSuppressed(exception);
|
||||
}
|
||||
}
|
||||
return cf;
|
||||
}
|
||||
}
|
||||
|
||||
static <V> CompletableFuture<List<Map.Entry<V, Throwable>>> gather(List<? extends CompletableFuture<? extends V>> futures) {
|
||||
List<Map.Entry<V, Throwable>> list = new ArrayList<>(futures.size());
|
||||
CompletableFuture<?>[] results = new CompletableFuture[futures.size()];
|
||||
for (var future : futures) {
|
||||
int i = list.size();
|
||||
list.add(null);
|
||||
results[i] = future.whenComplete((result, exception) -> list.set(i, new AbstractMap.SimpleImmutableEntry<>(result, exception)));
|
||||
}
|
||||
return CompletableFuture.allOf(results).handle((r, th)->null).thenApply(res -> list);
|
||||
}
|
||||
|
||||
void setLoadedMods(final List<ModContainer> modContainers)
|
||||
{
|
||||
this.mods = modContainers;
|
||||
this.sortedContainers = modContainers.stream().sorted(Comparator.comparingInt(c -> sortedList.indexOf(c.getModInfo()))).toList();
|
||||
this.sortedContainers = modContainers.stream().sorted(Comparator.comparingInt(c->sortedList.indexOf(c.getModInfo()))).toList();
|
||||
this.indexedMods = modContainers.stream().collect(Collectors.toMap(ModContainer::getModId, Function.identity()));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Optional<T> getModObjectById(String modId) {
|
||||
public <T> Optional<T> getModObjectById(String modId)
|
||||
{
|
||||
return getModContainerById(modId).map(ModContainer::getMod).map(o -> (T) o);
|
||||
}
|
||||
|
||||
public Optional<? extends ModContainer> getModContainerById(String modId) {
|
||||
public Optional<? extends ModContainer> getModContainerById(String modId)
|
||||
{
|
||||
return Optional.ofNullable(this.indexedMods.get(modId));
|
||||
}
|
||||
|
||||
public Optional<? extends ModContainer> getModContainerByObject(Object obj) {
|
||||
public Optional<? extends ModContainer> getModContainerByObject(Object obj)
|
||||
{
|
||||
return mods.stream().filter(mc -> mc.getMod() == obj).findFirst();
|
||||
}
|
||||
|
||||
public List<IModInfo> getMods() {
|
||||
public List<IModInfo> getMods()
|
||||
{
|
||||
return this.sortedList;
|
||||
}
|
||||
|
||||
public boolean isLoaded(String modTarget) {
|
||||
public boolean isLoaded(String modTarget)
|
||||
{
|
||||
return this.indexedMods.containsKey(modTarget);
|
||||
}
|
||||
|
||||
public int size() {
|
||||
public int size()
|
||||
{
|
||||
return mods.size();
|
||||
}
|
||||
|
||||
public List<ModFileScanData> getAllScanData() {
|
||||
if (modFileScanData == null) {
|
||||
public List<ModFileScanData> getAllScanData()
|
||||
{
|
||||
if (modFileScanData == null)
|
||||
{
|
||||
modFileScanData = this.sortedList.stream().
|
||||
map(IModInfo::getOwningFile).
|
||||
filter(Objects::nonNull).
|
||||
map(IModFileInfo::getFile).
|
||||
distinct().
|
||||
map(IModFile::getScanResult).
|
||||
toList();
|
||||
collect(Collectors.toList());
|
||||
}
|
||||
return modFileScanData;
|
||||
|
||||
}
|
||||
|
||||
public void forEachModFile(Consumer<IModFile> fileConsumer) {
|
||||
public void forEachModFile(Consumer<IModFile> fileConsumer)
|
||||
{
|
||||
modFiles.stream().map(IModFileInfo::getFile).forEach(fileConsumer);
|
||||
}
|
||||
|
||||
@@ -147,11 +222,13 @@ public class ModList {
|
||||
this.sortedContainers.forEach(containerConsumer);
|
||||
}
|
||||
|
||||
public List<ModContainer> getLoadedMods() {
|
||||
return this.sortedContainers;
|
||||
}
|
||||
|
||||
public <T> Stream<T> applyForEachModContainer(Function<ModContainer, T> function) {
|
||||
return indexedMods.values().stream().map(function);
|
||||
}
|
||||
|
||||
private static class UncaughtModLoadingException extends ModLoadingException {
|
||||
public UncaughtModLoadingException(ModLoadingStage stage, Throwable originalException) {
|
||||
super(null, stage, "fml.modloading.uncaughterror", originalException);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,8 @@
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import net.minecraftforge.eventbus.api.bus.BusGroup;
|
||||
import com.google.common.collect.Streams;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.fml.loading.FMLEnvironment;
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
@@ -32,6 +33,8 @@ import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static net.minecraftforge.fml.Logging.CORE;
|
||||
import static net.minecraftforge.fml.Logging.LOADING;
|
||||
|
||||
@@ -71,7 +74,8 @@ import static net.minecraftforge.fml.Logging.LOADING;
|
||||
* and completes the mod loading sequence.</dd>
|
||||
* </dl>
|
||||
*/
|
||||
public class ModLoader {
|
||||
public class ModLoader
|
||||
{
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
private final LoadingModList loadingModList;
|
||||
|
||||
@@ -80,25 +84,27 @@ public class ModLoader {
|
||||
private final List<ModLoadingWarning> loadingWarnings;
|
||||
private final ModStateManager stateManager;
|
||||
private static boolean loadingStateValid;
|
||||
private final Consumer<String> statusConsumer = StartupNotificationManager.modLoaderConsumer().orElse(msg -> {});
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
private final Optional<Consumer<String>> statusConsumer = StartupNotificationManager.modLoaderConsumer();
|
||||
private final Set<IModLoadingState> completedStates = new HashSet<>();
|
||||
private ModList modList;
|
||||
|
||||
private ModLoader() {
|
||||
var loadingModList = this.loadingModList = FMLLoader.getLoadingModList();
|
||||
var loadingExceptions = this.loadingExceptions = loadingModList.getErrors().stream()
|
||||
private ModLoader()
|
||||
{
|
||||
this.loadingModList = FMLLoader.getLoadingModList();
|
||||
this.loadingExceptions = this.loadingModList.getErrors().stream()
|
||||
.flatMap(ModLoadingException::fromEarlyException)
|
||||
.collect(Collectors.toList());
|
||||
this.loadingWarnings = loadingModList.getBrokenFiles().stream()
|
||||
this.loadingWarnings = this.loadingModList.getBrokenFiles().stream()
|
||||
.map(file -> new ModLoadingWarning(null, ModLoadingStage.VALIDATE, InvalidModIdentifier.identifyJarProblem(file.getFilePath()).orElse("fml.modloading.brokenfile"), file.getFileName()))
|
||||
.collect(Collectors.toList());
|
||||
if (loadingExceptions.isEmpty()) {
|
||||
if (this.loadingExceptions.isEmpty()) {
|
||||
this.erroredModInfos = Collections.emptySet();
|
||||
} else {
|
||||
this.erroredModInfos = Collections.newSetFromMap(new IdentityHashMap<>());
|
||||
this.erroredModInfos.addAll(loadingExceptions.stream().map(ModLoadingException::getModInfo).toList());
|
||||
this.erroredModInfos.addAll(this.loadingExceptions.stream().map(ModLoadingException::getModInfo).toList());
|
||||
}
|
||||
loadingModList.getModFiles().stream()
|
||||
this.loadingModList.getModFiles().stream()
|
||||
.filter(ModFileInfo::missingLicense)
|
||||
.filter(modFileInfo -> modFileInfo.getMods().stream().noneMatch(this.erroredModInfos::contains)) //Ignore files where any other mod already encountered an error
|
||||
.map(modFileInfo -> new ModLoadingException(null, ModLoadingStage.VALIDATE, "fml.modloading.missinglicense", null, modFileInfo.getFile()))
|
||||
@@ -113,16 +119,16 @@ public class ModLoader {
|
||||
|
||||
private static String computeLanguageList() {
|
||||
return "\n" + FMLLoader.getLanguageLoadingProvider()
|
||||
.applyForEach(lp -> lp.name() + '@' + lp.getClass().getPackage().getImplementationVersion())
|
||||
.applyForEach(lp -> lp.name() + "@" + lp.getClass().getPackage().getImplementationVersion())
|
||||
.collect(Collectors.joining("\n\t\t", "\t\t", ""));
|
||||
}
|
||||
|
||||
private static String computeModLauncherServiceList() {
|
||||
final List<Map<String, String>> mods = FMLLoader.modLauncherModList();
|
||||
return "\n" + mods.stream().map(mod -> mod.getOrDefault("file","nofile") +
|
||||
' ' + mod.getOrDefault("name", "missing") +
|
||||
' ' + mod.getOrDefault("type","NOTYPE") +
|
||||
' ' + mod.getOrDefault("description", "")).
|
||||
return "\n"+mods.stream().map(mod->mod.getOrDefault("file","nofile")+
|
||||
" "+mod.getOrDefault("name", "missing")+
|
||||
" "+mod.getOrDefault("type","NOTYPE")+
|
||||
" "+mod.getOrDefault("description", "")).
|
||||
collect(Collectors.joining("\n\t\t","\t\t",""));
|
||||
}
|
||||
|
||||
@@ -150,22 +156,20 @@ public class ModLoader {
|
||||
loadingModList.getMods());
|
||||
if (!this.loadingExceptions.isEmpty()) {
|
||||
LOGGER.fatal(CORE, "Error during pre-loading phase", loadingExceptions.getFirst());
|
||||
statusConsumer.accept("ERROR DURING MOD LOADING");
|
||||
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
|
||||
modList.setLoadedMods(Collections.emptyList());
|
||||
loadingStateValid = false;
|
||||
throw new LoadingFailedException(loadingExceptions);
|
||||
}
|
||||
var failedBounds = new ArrayList<ForgeFeature.Bound>();
|
||||
for (var mod : loadingModList.getMods()) {
|
||||
for (var feature : mod.getForgeFeatures()) {
|
||||
if (!ForgeFeature.testFeature(FMLEnvironment.dist, feature))
|
||||
failedBounds.add(feature);
|
||||
}
|
||||
}
|
||||
List<? extends ForgeFeature.Bound> failedBounds = loadingModList.getMods().stream()
|
||||
.map(ModInfo::getForgeFeatures)
|
||||
.flatMap(List::stream)
|
||||
.filter(bound -> !ForgeFeature.testFeature(FMLEnvironment.dist, bound))
|
||||
.toList();
|
||||
|
||||
if (!failedBounds.isEmpty()) {
|
||||
LOGGER.fatal(CORE, "Failed to validate feature bounds for mods: {}", failedBounds);
|
||||
statusConsumer.accept("ERROR DURING MOD LOADING");
|
||||
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
|
||||
modList.setLoadedMods(Collections.emptyList());
|
||||
loadingStateValid = false;
|
||||
throw new LoadingFailedException(failedBounds.stream()
|
||||
@@ -173,42 +177,19 @@ public class ModLoader {
|
||||
.toList());
|
||||
}
|
||||
|
||||
final var modContainers = new HashMap<String, ModContainer>();
|
||||
for (var file : loadingModList.getModFiles()) {
|
||||
var containers = this.buildMods(file.getFile());
|
||||
for (var container : containers)
|
||||
modContainers.put(container.getModId(), container);
|
||||
}
|
||||
|
||||
final List<ModContainer> modContainers = loadingModList.getModFiles().stream()
|
||||
.map(ModFileInfo::getFile)
|
||||
.map(this::buildMods)
|
||||
.flatMap(List::stream)
|
||||
.toList();
|
||||
if (!loadingExceptions.isEmpty()) {
|
||||
LOGGER.fatal(CORE, "Failed to initialize mod containers", loadingExceptions.getFirst());
|
||||
statusConsumer.accept("ERROR DURING MOD LOADING");
|
||||
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
|
||||
modList.setLoadedMods(Collections.emptyList());
|
||||
loadingStateValid = false;
|
||||
throw new LoadingFailedException(loadingExceptions);
|
||||
}
|
||||
|
||||
// Gather all dependencies, so we can make sure they have finished their transition events before we fire ours
|
||||
for (var mod : modContainers.values()) {
|
||||
for (var dep : mod.getModInfo().getDependencies()) {
|
||||
var target = modContainers.get(dep.getModId());
|
||||
if (target == null)
|
||||
continue;
|
||||
|
||||
switch (dep.getOrdering()) {
|
||||
case AFTER:
|
||||
mod.dependencies.add(target);
|
||||
break;
|
||||
case BEFORE:
|
||||
target.dependencies.add(mod);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
modList.setLoadedMods(modContainers.values().stream().toList());
|
||||
modList.setLoadedMods(modContainers);
|
||||
this.modList = modList;
|
||||
var stateList = stateManager.getStates(ModLoadingPhase.GATHER);
|
||||
var progress = StartupMessageManager.addProgressBar("Mod Gather", stateList.stream().mapToInt(mls -> mls.size().applyAsInt(this.modList)).sum());
|
||||
@@ -233,19 +214,7 @@ public class ModLoader {
|
||||
for (IModLoadingState mls : stateList) {
|
||||
dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress);
|
||||
}
|
||||
|
||||
var modBusGroups = Set.of(
|
||||
modList.getLoadedMods().stream()
|
||||
.map(ModContainer::getModBusGroup)
|
||||
.filter(Objects::nonNull)
|
||||
.toArray(BusGroup[]::new)
|
||||
);
|
||||
for (var modBusGroup : modBusGroups) {
|
||||
parallelExecutor.execute(modBusGroup::trim);
|
||||
}
|
||||
parallelExecutor.execute(BusGroup.DEFAULT::trim);
|
||||
|
||||
statusConsumer.accept("Mod loading complete - %d mods loaded".formatted(this.modList.size()));
|
||||
statusConsumer.ifPresent(c->c.accept(String.format("Mod loading complete - %d mods loaded", this.modList.size())));
|
||||
progress.complete();
|
||||
}
|
||||
|
||||
@@ -267,19 +236,17 @@ public class ModLoader {
|
||||
}
|
||||
|
||||
private void handleInlineTransition(final Consumer<ModList> transition, final IModLoadingState state, final ModWorkManager.DrivenExecutor syncExecutor, final Runnable ticker) {
|
||||
var pb = StartupMessageManager.addProgressBar("State transition " + state.name() + " running", 0);
|
||||
var pb = StartupMessageManager.addProgressBar("State transition " +state.name()+" running", 0);
|
||||
syncExecutor.drive(ticker);
|
||||
transition.accept(this.modList);
|
||||
syncExecutor.drive(ticker);
|
||||
pb.complete();
|
||||
syncExecutor.drive(ticker);
|
||||
}
|
||||
|
||||
private void waitForTransition(final IModLoadingState state, final ModWorkManager.DrivenExecutor syncExecutor, final Runnable ticker, final CompletableFuture<Void> transition) {
|
||||
while (!transition.isDone()) {
|
||||
syncExecutor.drive(ticker);
|
||||
}
|
||||
|
||||
try {
|
||||
transition.join();
|
||||
} catch (CompletionException e) {
|
||||
@@ -289,38 +256,36 @@ public class ModLoader {
|
||||
.anyMatch(obj -> !(obj instanceof ModLoadingException));
|
||||
if (hasNotModLoadingEx) {
|
||||
LOGGER.fatal("Encountered non-modloading exceptions!", e);
|
||||
statusConsumer.accept("ERROR DURING MOD LOADING");
|
||||
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
|
||||
throw e;
|
||||
}
|
||||
|
||||
final List<ModLoadingException> modLoadingExceptions = Arrays.stream(t.getSuppressed())
|
||||
.filter(ModLoadingException.class::isInstance)
|
||||
.map(ModLoadingException.class::cast)
|
||||
.toList();
|
||||
.collect(Collectors.toList());
|
||||
LOGGER.fatal(LOADING,"Failed to complete lifecycle event {}, {} errors found", state.name(), modLoadingExceptions.size());
|
||||
statusConsumer.accept("ERROR DURING MOD LOADING");
|
||||
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
|
||||
throw new LoadingFailedException(modLoadingExceptions);
|
||||
}
|
||||
}
|
||||
|
||||
private List<ModContainer> buildMods(final IModFile modFile) {
|
||||
final Map<String, IModInfo> modInfoMap = new HashMap<>();
|
||||
for (var mod : modFile.getModFileInfo().getMods())
|
||||
modInfoMap.put(mod.getModId(), mod);
|
||||
private List<ModContainer> buildMods(final IModFile modFile)
|
||||
{
|
||||
final Map<String, IModInfo> modInfoMap = modFile.getModFileInfo().getMods().stream().collect(Collectors.toMap(IModInfo::getModId, Function.identity()));
|
||||
|
||||
LOGGER.trace(LOADING, "ModContainer is {}", ModContainer.class.getClassLoader());
|
||||
final List<ModContainer> containers = new ArrayList<>();
|
||||
for (var entry : modFile.getScanResult().getTargets().entrySet()) {
|
||||
var container = buildModContainerFromTOML(modFile, modInfoMap, entry.getKey(), entry.getValue());
|
||||
if (container != null)
|
||||
containers.add(container);
|
||||
}
|
||||
|
||||
final List<ModContainer> containers = modFile.getScanResult().getTargets()
|
||||
.entrySet()
|
||||
.stream()
|
||||
.map(e -> buildModContainerFromTOML(modFile, modInfoMap, e))
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
if (containers.size() != modInfoMap.size()) {
|
||||
var modIds = modInfoMap.values().stream().map(IModInfo::getModId).sorted().toList();
|
||||
var containerIds = containers.stream().map(c -> c != null ? c.getModId() : "(null)").sorted().toList();
|
||||
|
||||
LOGGER.fatal(LOADING, "File {} constructed {} mods: {}, but had {} mods specified: {}",
|
||||
LOGGER.fatal(LOADING,"File {} constructed {} mods: {}, but had {} mods specified: {}",
|
||||
modFile.getFilePath(),
|
||||
containers.size(), containerIds,
|
||||
modInfoMap.size(), modIds);
|
||||
@@ -335,15 +300,15 @@ public class ModLoader {
|
||||
|
||||
loadingExceptions.add(new ModLoadingException(null, ModLoadingStage.CONSTRUCT, "fml.modloading.missingclasses", null, modFile.getFilePath()));
|
||||
}
|
||||
|
||||
// remove errored mod containers
|
||||
return containers.stream().filter(mc -> mc.modLoadingStage != ModLoadingStage.ERROR).toList();
|
||||
}
|
||||
|
||||
private ModContainer buildModContainerFromTOML(final IModFile modFile, final Map<String, IModInfo> modInfoMap,
|
||||
final String modId, final IModLanguageProvider.IModLanguageLoader languageLoader
|
||||
) {
|
||||
private ModContainer buildModContainerFromTOML(final IModFile modFile, final Map<String, IModInfo> modInfoMap, final Map.Entry<String, ? extends IModLanguageProvider.IModLanguageLoader> idToProviderEntry) {
|
||||
try {
|
||||
final String modId = idToProviderEntry.getKey();
|
||||
final IModLanguageProvider.IModLanguageLoader languageLoader = idToProviderEntry.getValue();
|
||||
|
||||
IModInfo info = modInfoMap.get(modId);
|
||||
// throw a missing metadata error if there is no matching modid in the modInfoMap from the mods.toml file
|
||||
if (info == null)
|
||||
@@ -366,70 +331,56 @@ public class ModLoader {
|
||||
return loadingStateValid;
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link #hasCompletedState(IModLoadingState)} instead */
|
||||
@Deprecated(forRemoval = true)
|
||||
public boolean hasCompletedState(final String stateName) {
|
||||
IModLoadingState state = stateManager.findState(stateName);
|
||||
return completedStates.contains(state);
|
||||
}
|
||||
|
||||
public boolean hasCompletedState(IModLoadingState state) {
|
||||
return completedStates.contains(state);
|
||||
}
|
||||
|
||||
public static <T extends IModBusEvent> void runEventGenerator(Function<ModContainer, T> generator) {
|
||||
public <T extends Event & IModBusEvent> void runEventGenerator(Function<ModContainer, T> generator) {
|
||||
if (!loadingStateValid) {
|
||||
LOGGER.error("Cowardly refusing to send event generator to a broken mod state");
|
||||
return;
|
||||
}
|
||||
for (var mod : ModList.get().getLoadedMods())
|
||||
mod.acceptEvent(generator.apply(mod));
|
||||
ModList.get().forEachModInOrder(mc -> mc.acceptEvent(generator.apply(mc)));
|
||||
}
|
||||
|
||||
public static <T extends IModBusEvent> void postEvent(T e) {
|
||||
public <T extends Event & IModBusEvent> void postEvent(T e) {
|
||||
if (!loadingStateValid) {
|
||||
LOGGER.error("Cowardly refusing to send event {} to a broken mod state", e.getClass().getName());
|
||||
return;
|
||||
}
|
||||
for (var mod : ModList.get().getLoadedMods())
|
||||
mod.acceptEvent(e);
|
||||
ModList.get().forEachModInOrder(mc -> mc.acceptEvent(e));
|
||||
}
|
||||
|
||||
public static <T extends IModBusEvent> T postEventWithReturn(T e) {
|
||||
public <T extends Event & IModBusEvent> T postEventWithReturn(T e) {
|
||||
if (!loadingStateValid) {
|
||||
LOGGER.error("Cowardly refusing to send event {} to a broken mod state", e.getClass().getName());
|
||||
return e;
|
||||
}
|
||||
for (var mod : ModList.get().getLoadedMods())
|
||||
mod.acceptEvent(e);
|
||||
ModList.get().forEachModInOrder(mc -> mc.acceptEvent(e));
|
||||
return e;
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
public static <T extends IModBusEvent> void postEventWrapContainerInModOrder(T event) {
|
||||
postEventWithWrapInModOrder(event,
|
||||
(mc, e) -> ModLoadingContext.get().setActiveContainer(mc),
|
||||
(mc, e) -> ModLoadingContext.get().setActiveContainer(null)
|
||||
);
|
||||
public <T extends Event & IModBusEvent> void postEventWrapContainerInModOrder(T event) {
|
||||
postEventWithWrapInModOrder(event, (mc, e) -> ModLoadingContext.get().setActiveContainer(mc), (mc, e) -> ModLoadingContext.get().setActiveContainer(null));
|
||||
}
|
||||
|
||||
public static <T extends IModBusEvent> void postEventWithWrapInModOrder(T e, BiConsumer<ModContainer, T> pre, BiConsumer<ModContainer, T> post) {
|
||||
public <T extends Event & IModBusEvent> void postEventWithWrapInModOrder(T e, BiConsumer<ModContainer, T> pre, BiConsumer<ModContainer, T> post) {
|
||||
if (!loadingStateValid) {
|
||||
LOGGER.error("Cowardly refusing to send event {} to a broken mod state", e.getClass().getName());
|
||||
return;
|
||||
}
|
||||
for (var mod : ModList.get().getLoadedMods()) {
|
||||
pre.accept(mod, e);
|
||||
mod.acceptEvent(e);
|
||||
post.accept(mod, e);
|
||||
}
|
||||
ModList.get().forEachModInOrder(mc -> {
|
||||
pre.accept(mc, e);
|
||||
mc.acceptEvent(e);
|
||||
post.accept(mc, e);
|
||||
});
|
||||
}
|
||||
|
||||
public List<ModLoadingWarning> getWarnings() {
|
||||
public List<ModLoadingWarning> getWarnings()
|
||||
{
|
||||
return ImmutableList.copyOf(this.loadingWarnings);
|
||||
}
|
||||
|
||||
public void addWarning(ModLoadingWarning warning) {
|
||||
public void addWarning(ModLoadingWarning warning)
|
||||
{
|
||||
this.loadingWarnings.add(warning);
|
||||
}
|
||||
|
||||
@@ -439,7 +390,7 @@ public class ModLoader {
|
||||
return runningDataGen;
|
||||
}
|
||||
|
||||
private static final class ErroredModContainer extends ModContainer {
|
||||
private static class ErroredModContainer extends ModContainer {
|
||||
public ErroredModContainer() {
|
||||
super();
|
||||
}
|
||||
|
||||
@@ -8,24 +8,23 @@ package net.minecraftforge.fml;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraftforge.fml.config.IConfigSpec;
|
||||
import net.minecraftforge.fml.config.ModConfig;
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class ModLoadingContext {
|
||||
public class ModLoadingContext
|
||||
{
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static final ThreadLocal<ModLoadingContext> context = ThreadLocal.withInitial(ModLoadingContext::new);
|
||||
private ModContainer activeContainer;
|
||||
private Object languageExtension;
|
||||
private ModLoadingStage stage;
|
||||
|
||||
/**
|
||||
* @deprecated Use the context provided by your language loader in your mod's constructor. Example for Java mods: {@snippet :
|
||||
* @Mod("examplemod")
|
||||
* public final class ExampleMod {
|
||||
* public ExampleMod(FMLJavaModLoadingContext context) {
|
||||
* // Use context from the param instead of FMLJavaModLoadingContext.get() or ModLoadingContext.get()
|
||||
* }
|
||||
* }}
|
||||
* @deprecated Use the context provided by your language loader in your mod's constructor
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since="1.21.1")
|
||||
public static ModLoadingContext get() {
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.fml.loading.progress.ProgressMeter;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -25,23 +26,19 @@ import java.util.function.ToIntFunction;
|
||||
* @param inlineRunnable an optional runnable, which runs before starting the transition from this state to the next
|
||||
* @param transition optional state transition information
|
||||
*/
|
||||
public record ModLoadingState(
|
||||
String name,
|
||||
String previous,
|
||||
Function<ModList, String> message,
|
||||
ToIntFunction<ModList> size,
|
||||
ModLoadingPhase phase,
|
||||
Optional<Consumer<ModList>> inlineRunnable,
|
||||
Optional<IModStateTransition> transition
|
||||
) implements IModLoadingState {
|
||||
public record ModLoadingState(String name, String previous,
|
||||
Function<ModList, String> message,
|
||||
ToIntFunction<ModList> size,
|
||||
ModLoadingPhase phase,
|
||||
Optional<Consumer<ModList>> inlineRunnable,
|
||||
Optional<IModStateTransition> transition) implements IModLoadingState {
|
||||
@Override
|
||||
public Optional<CompletableFuture<Void>> buildTransition(
|
||||
final Executor syncExecutor,
|
||||
final Executor parallelExecutor,
|
||||
final ProgressMeter progressBar,
|
||||
final Function<Executor, CompletableFuture<Void>> preSyncTask,
|
||||
final Function<Executor, CompletableFuture<Void>> postSyncTask
|
||||
) {
|
||||
public <T extends Event & IModBusEvent>
|
||||
Optional<CompletableFuture<Void>> buildTransition(final Executor syncExecutor,
|
||||
final Executor parallelExecutor,
|
||||
final ProgressMeter progressBar,
|
||||
final Function<Executor, CompletableFuture<Void>> preSyncTask,
|
||||
final Function<Executor, CompletableFuture<Void>> postSyncTask) {
|
||||
var transition = this.transition.orElse(null);
|
||||
return transition == null
|
||||
? Optional.empty()
|
||||
@@ -55,12 +52,9 @@ public record ModLoadingState(
|
||||
* @param name the name of the state
|
||||
* @param previous the name of the immediately previous state to this state
|
||||
* @param phase the mod loading phase the state belongs to
|
||||
*
|
||||
* @deprecated Use the builder
|
||||
*/
|
||||
@Deprecated(since = "1.21.3", forRemoval = true)
|
||||
public static ModLoadingState empty(final String name, final String previous, final ModLoadingPhase phase) {
|
||||
return new ModLoadingState(name, previous, ml -> "", ml -> 0, phase, Optional.empty(), Optional.empty());
|
||||
return new ModLoadingState(name, previous, ml -> "", f->0, phase, Optional.empty(), Optional.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,13 +65,11 @@ public record ModLoadingState(
|
||||
* @param previous the name of the immediately previous state to this state
|
||||
* @param phase the mod loading phase the state belongs to
|
||||
* @param transition the state transition information
|
||||
*
|
||||
* @deprecated Use the builder
|
||||
* @return a mod loading state with state transition information and a default message
|
||||
*/
|
||||
@Deprecated(since = "1.21.3", forRemoval = true)
|
||||
public static ModLoadingState withTransition(final String name, final String previous, final ModLoadingPhase phase,
|
||||
final IModStateTransition transition) {
|
||||
return of(name, phase).after(previous).withTransition(transition);
|
||||
return new ModLoadingState(name, previous, ml -> "Processing transition " + name, ModList::size, phase, Optional.empty(), Optional.of(transition));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,14 +81,11 @@ public record ModLoadingState(
|
||||
* @param phase the mod loading phase the state belongs to
|
||||
* @param transition the state transition information
|
||||
* @return a mod loading state with state transition information and a custom message
|
||||
*
|
||||
* @deprecated Use the builder
|
||||
*/
|
||||
@Deprecated(since = "1.21.3", forRemoval = true)
|
||||
public static ModLoadingState withTransition(final String name, final String previous,
|
||||
final Function<ModList, String> message, final ModLoadingPhase phase,
|
||||
final IModStateTransition transition) {
|
||||
return of(name, phase).after(previous).message(message).withTransition(transition);
|
||||
return new ModLoadingState(name, previous, message, ModList::size, phase, Optional.empty(), Optional.of(transition));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,75 +97,9 @@ public record ModLoadingState(
|
||||
* @param phase the mod loading phase the state belongs to
|
||||
* @param inline an optional runnable, which runs before starting the transition from this state to the next
|
||||
* @return a mod loading state with an inline runnable and default message
|
||||
*
|
||||
* @deprecated Use the builder
|
||||
*/
|
||||
@Deprecated(since = "1.21.3", forRemoval = true)
|
||||
public static ModLoadingState withInline(final String name, final String previous, final ModLoadingPhase phase,
|
||||
final Consumer<ModList> inline) {
|
||||
return of(name, phase).after(previous).withInline(inline);
|
||||
}
|
||||
|
||||
public static Builder of(final String name, final ModLoadingPhase phase) {
|
||||
return new Builder(name, phase);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof ModLoadingState that
|
||||
&& this.phase == that.phase
|
||||
&& this.name.equals(that.name)
|
||||
&& Objects.equals(this.previous, that.previous);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = phase.hashCode();
|
||||
result = 31 * result + name.hashCode();
|
||||
result = 31 * result + Objects.hashCode(previous);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private final String name;
|
||||
private final ModLoadingPhase phase;
|
||||
private String after;
|
||||
private Function<ModList, String> message = null;
|
||||
private ToIntFunction<ModList> size = null;
|
||||
|
||||
private Builder(final String name, final ModLoadingPhase phase) {
|
||||
this.name = name;
|
||||
this.phase = phase;
|
||||
}
|
||||
|
||||
public Builder after(final ModLoadingState value) { return after(value.name()); }
|
||||
public Builder after(final String value) {
|
||||
this.after = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder message(final String value) { return message(ml -> value); }
|
||||
public Builder message(final Function<ModList, String> value) {
|
||||
this.message = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder size(final int value) { return size(ml -> value); }
|
||||
public Builder size(final ToIntFunction<ModList> value) {
|
||||
this.size = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ModLoadingState empty() {
|
||||
return new ModLoadingState(name, after, message != null ? message : ml -> "", size != null ? size : ml -> 0, phase, Optional.empty(), Optional.empty());
|
||||
}
|
||||
|
||||
public ModLoadingState withTransition(final IModStateTransition transition) {
|
||||
return new ModLoadingState(name, after, message != null ? message : ml -> "Processing transition " + name, size != null ? size : ModList::size, phase, Optional.empty(), Optional.of(transition));
|
||||
}
|
||||
|
||||
public ModLoadingState withInline(final Consumer<ModList> inline) {
|
||||
return new ModLoadingState(name, after, message != null ? message : ml -> "Processing work " + name, size != null ? size : ml -> 0, phase, Optional.of(inline), Optional.empty());
|
||||
}
|
||||
return new ModLoadingState(name, previous, ml -> "Processing work " + name, ml->0, phase, Optional.of(inline), Optional.empty());
|
||||
}
|
||||
}
|
||||
@@ -41,13 +41,11 @@ public class ModStateManager {
|
||||
}
|
||||
|
||||
public IModLoadingState findState(final String stateName) {
|
||||
var result = stateMap.values()
|
||||
return stateMap.values()
|
||||
.stream()
|
||||
.flatMap(List::stream)
|
||||
.filter(mls -> mls.name().equals(stateName))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (result != null) return result;
|
||||
else throw new IllegalArgumentException("Unknown IModLoadingState: " + stateName);
|
||||
.orElseThrow(() -> new IllegalArgumentException("Unknown IModLoadingState: " + stateName));
|
||||
}
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import net.minecraftforge.fml.IModStateTransition.EventGenerator;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.fml.loading.progress.ProgressMeter;
|
||||
|
||||
@ApiStatus.Internal
|
||||
class ModStateTransitionHelper {
|
||||
static final IModStateTransition NOOP = new NoopTransition();
|
||||
record NoopTransition() implements IModStateTransition {
|
||||
@Override
|
||||
public ThreadSelector threadSelector() {
|
||||
return ThreadSelector.SYNC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BiFunction<Executor, CompletableFuture<Void>, CompletableFuture<Void>> finalActivityGenerator() {
|
||||
return (e, t) -> t.thenApplyAsync(Function.identity(), e);
|
||||
}
|
||||
}
|
||||
|
||||
static <V> CompletionStage<Void> completableFutureFromExceptionList(List<FutureResult<V>> t) {
|
||||
if (t.stream().noneMatch(e -> e.exception() != null)) {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
} else {
|
||||
final var throwables = t.stream().map(FutureResult::exception).filter(Objects::nonNull).toList();
|
||||
CompletableFuture<Void> cf = new CompletableFuture<>();
|
||||
final RuntimeException accumulator = new RuntimeException();
|
||||
cf.completeExceptionally(accumulator);
|
||||
for (Throwable exception : throwables) {
|
||||
if (exception instanceof CompletionException) {
|
||||
exception = exception.getCause();
|
||||
}
|
||||
if (exception.getSuppressed().length != 0) {
|
||||
for (Throwable throwable : exception.getSuppressed()) {
|
||||
accumulator.addSuppressed(throwable);
|
||||
}
|
||||
} else {
|
||||
accumulator.addSuppressed(exception);
|
||||
}
|
||||
}
|
||||
return cf;
|
||||
}
|
||||
}
|
||||
|
||||
record FutureResult<V>(V value, Throwable exception){}
|
||||
|
||||
static <V> CompletableFuture<List<FutureResult<V>>> gather(Collection<? extends CompletableFuture<? extends V>> futures) {
|
||||
var list = new ArrayList<FutureResult<V>>(futures.size());
|
||||
var results = new CompletableFuture[futures.size()];
|
||||
|
||||
for (var future : futures) {
|
||||
int i = list.size();
|
||||
list.add(null);
|
||||
@SuppressWarnings("unchecked") // Bypass a weird javac error with generic by forcing it to be the exact type we want.
|
||||
var raw = ((CompletableFuture<V>)future);
|
||||
results[i] = raw.whenComplete((result, exception) -> list.set(i, new FutureResult<>(result, exception)));
|
||||
}
|
||||
|
||||
return CompletableFuture.allOf(results).handle((r, th)->null).thenApply(res -> list);
|
||||
}
|
||||
|
||||
private static <T extends IModBusEvent> void addCompletableFutureTaskForModDispatch(
|
||||
final IModStateTransition transition,
|
||||
final Executor executor,
|
||||
final List<CompletableFuture<Void>> completableFutures,
|
||||
final ProgressMeter progressBar,
|
||||
final EventGenerator<T> eventGenerator,
|
||||
final BiFunction<ModLoadingStage, Throwable, ModLoadingStage> nextState
|
||||
) {
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
var preDispatchHook = getHook(transition.preDispatchHook(), executor, eventGenerator);
|
||||
if (preDispatchHook != null)
|
||||
completableFutures.add(preDispatchHook);
|
||||
|
||||
var modFutures = new LinkedHashMap<String, CompletableFuture<Void>>();
|
||||
for (var mod : ModList.get().getLoadedMods()) {
|
||||
|
||||
CompletableFuture<Void> parent = null;
|
||||
if (mod.dependencies.isEmpty()) {
|
||||
parent = CompletableFuture.allOf();
|
||||
} else {
|
||||
var deps = new CompletableFuture[mod.dependencies.size()];
|
||||
int idx = 0;
|
||||
for (var depContainer : mod.dependencies) {
|
||||
var future = modFutures.get(depContainer.getModId());
|
||||
if (future == null)
|
||||
throw new IllegalStateException("Could not find dependency future " + depContainer.getModId() + " for " + mod.getModId());
|
||||
deps[idx++] = future;
|
||||
}
|
||||
parent = CompletableFuture.allOf(deps);
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
var dispatch = parent
|
||||
.thenRunAsync(() -> {
|
||||
ModLoadingContext.get().setActiveContainer(mod);
|
||||
var handler = mod.activityMap.get(mod.modLoadingStage);
|
||||
if (handler != null)
|
||||
handler.run();
|
||||
mod.acceptEvent(eventGenerator.apply(mod));
|
||||
}, executor)
|
||||
.whenComplete((mc, exception) -> {
|
||||
mod.modLoadingStage = nextState.apply(mod.modLoadingStage, exception);
|
||||
progressBar.increment();
|
||||
ModLoadingContext.get().setActiveContainer(null);
|
||||
});
|
||||
|
||||
modFutures.put(mod.getModId(), dispatch);
|
||||
}
|
||||
|
||||
var dispatch = gather(modFutures.values()).thenComposeAsync(ModStateTransitionHelper::completableFutureFromExceptionList, executor);
|
||||
completableFutures.add(dispatch);
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
var postDispatchHook = getHook(transition.preDispatchHook(), executor, eventGenerator);
|
||||
if (postDispatchHook != null)
|
||||
completableFutures.add(postDispatchHook);
|
||||
}
|
||||
|
||||
private static <T extends IModBusEvent> CompletableFuture<Void> getHook(BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> hook, Executor executor, EventGenerator<T> eventGenerator) {
|
||||
if (hook == null || hook == IModStateTransition.NULL_HOOK) return null;
|
||||
@SuppressWarnings("unchecked")
|
||||
var hookTyped = (BiFunction<Executor, EventGenerator<T>, CompletableFuture<Void>>)hook;
|
||||
return hookTyped.apply(executor, eventGenerator);
|
||||
}
|
||||
|
||||
static <T extends IModBusEvent> CompletableFuture<Void> build(
|
||||
final IModStateTransition transition,
|
||||
final String name,
|
||||
final Executor syncExecutor,
|
||||
final Executor parallelExecutor,
|
||||
final ProgressMeter progressBar,
|
||||
final Function<Executor, CompletableFuture<Void>> preSyncTask,
|
||||
final Function<Executor, CompletableFuture<Void>> postSyncTask
|
||||
) {
|
||||
List<CompletableFuture<Void>> futures = new ArrayList<>();
|
||||
final var executor = transition.threadSelector().apply(syncExecutor, parallelExecutor);
|
||||
|
||||
@SuppressWarnings({ "removal", "unchecked" })
|
||||
var events = transition.eventFunctionStream().get().map(f -> (EventGenerator<T>)f).toList();
|
||||
for (int x = 0; x < events.size(); x++) {
|
||||
var gen = events.get(x);
|
||||
BiFunction<ModLoadingStage, Throwable, ModLoadingStage> state = x == events.size() - 1
|
||||
? transition.nextModLoadingStage()
|
||||
: ModLoadingStage::currentState;
|
||||
addCompletableFutureTaskForModDispatch(transition, executor, futures, progressBar, gen, state);
|
||||
}
|
||||
|
||||
final CompletableFuture<Void> preSyncTaskCF = preSyncTask.apply(syncExecutor);
|
||||
final CompletableFuture<Void> eventDispatchCF = gather(futures).thenCompose(ModStateTransitionHelper::completableFutureFromExceptionList);
|
||||
final CompletableFuture<Void> postEventDispatchCF = preSyncTaskCF
|
||||
.thenApplyAsync(n -> {
|
||||
progressBar.label(progressBar.name() + ": dispatching " + name);
|
||||
return null;
|
||||
}, parallelExecutor)
|
||||
.thenComposeAsync(n -> eventDispatchCF, parallelExecutor)
|
||||
.thenApply(r -> {
|
||||
postSyncTask.apply(syncExecutor);
|
||||
return null;
|
||||
});
|
||||
return transition.finalActivityGenerator().apply(syncExecutor, postEventDispatchCF);
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
package net.minecraftforge.fml.config;
|
||||
|
||||
import net.minecraftforge.eventbus.api.event.InheritableEvent;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.Bindings;
|
||||
|
||||
import java.util.function.Function;
|
||||
@@ -29,7 +29,7 @@ public interface IConfigEvent {
|
||||
ModConfig getConfig();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
default <T extends InheritableEvent & IConfigEvent> T self() {
|
||||
default <T extends Event & IConfigEvent> T self() {
|
||||
return (T) this;
|
||||
}
|
||||
}
|
||||
@@ -5,33 +5,8 @@
|
||||
|
||||
package net.minecraftforge.fml.event;
|
||||
|
||||
import net.minecraftforge.eventbus.api.bus.BusGroup;
|
||||
import net.minecraftforge.eventbus.api.bus.EventBus;
|
||||
import net.minecraftforge.eventbus.api.event.InheritableEvent;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
|
||||
/**
|
||||
* Marker interface for mod lifecycle events dispatched on the
|
||||
* {@linkplain net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext#getModBusGroup() mod BusGroup} instead
|
||||
* of the {@linkplain BusGroup#DEFAULT default BusGroup}.
|
||||
*
|
||||
* @apiNote Each mod gets its own unique mod BusGroup instance, obtained from the mod loading context. Most events
|
||||
* should go on the default BusGroup.
|
||||
* Marker interface for events dispatched on the ModLifecycle event bus instead of the primary event bus
|
||||
*/
|
||||
public interface IModBusEvent extends InheritableEvent {
|
||||
/**
|
||||
* @param modEventBusGroup Obtained from {@link net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext#getModBusGroup()} or your language provider's equivalent.
|
||||
* @param eventClass The event type you want to get the associated bus for.
|
||||
*/
|
||||
static <T extends IModBusEvent> @NonNull EventBus<@NonNull T> getBus(@NonNull BusGroup modEventBusGroup, @NonNull Class<T> eventClass) {
|
||||
// Do not copy! Temporary solution until the FML rewrite is complete. May throw in future
|
||||
// without prior notice.
|
||||
//
|
||||
// Mods creating their own events should directly assign the result of the create method to a
|
||||
// static final field in their event class, without wrapping or calling multiple times.
|
||||
//
|
||||
// Mods using existing events should instead use the existing static final BUS field in the
|
||||
// event class (for most events) or getBus(BusGroup) method (for mod bus events).
|
||||
return EventBus.create(modEventBusGroup, eventClass);
|
||||
}
|
||||
public interface IModBusEvent {
|
||||
}
|
||||
@@ -7,11 +7,8 @@ package net.minecraftforge.fml.util.thread;
|
||||
|
||||
import net.minecraftforge.fml.LogicalSide;
|
||||
|
||||
/**
|
||||
* Attempts to determine the effect side of the current thread, based on know threads in networking.
|
||||
* This is not guaranteed to be correct, and modders should use other methods to determine the logical side when possible.
|
||||
*/
|
||||
public class EffectiveSide {
|
||||
public class EffectiveSide
|
||||
{
|
||||
public static LogicalSide get() {
|
||||
final ThreadGroup group = Thread.currentThread().getThreadGroup();
|
||||
return group instanceof SidedThreadGroup ? ((SidedThreadGroup) group).getSide() : LogicalSide.CLIENT;
|
||||
|
||||
@@ -7,13 +7,11 @@ package net.minecraftforge.fml.util.thread;
|
||||
|
||||
import net.minecraftforge.fml.LogicalSide;
|
||||
|
||||
public final class SidedThreadGroups {
|
||||
public final class SidedThreadGroups
|
||||
{
|
||||
public static final SidedThreadGroup CLIENT = new SidedThreadGroup(LogicalSide.CLIENT);
|
||||
public static final SidedThreadGroup SERVER = new SidedThreadGroup(LogicalSide.SERVER);
|
||||
|
||||
public static SidedThreadGroup get(boolean client) {
|
||||
return client ? CLIENT : SERVER;
|
||||
private SidedThreadGroups() {
|
||||
}
|
||||
|
||||
private SidedThreadGroups() { }
|
||||
}
|
||||
@@ -28,7 +28,7 @@ java {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(libs.jetbrains.annotations)
|
||||
compileOnly(libs.nulls)
|
||||
implementation(project(':fmlloader'))
|
||||
implementation(project(':fmlcore'))
|
||||
implementation(libs.bundles.lwjgl)
|
||||
|
||||
+18
-17
@@ -85,17 +85,11 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
private ScheduledFuture<?> initializationFuture;
|
||||
|
||||
private PerformanceInfo performanceInfo;
|
||||
@SuppressWarnings("unused")
|
||||
private ScheduledFuture<?> performanceTick;
|
||||
// The GL ID of the window. Used for all operations
|
||||
private long window;
|
||||
// The thread that contains and ticks the window while Forge is loading mods
|
||||
private static final ScheduledExecutorService renderScheduler = Executors.newSingleThreadScheduledExecutor(r -> {
|
||||
final var thread = Executors.defaultThreadFactory().newThread(r);
|
||||
thread.setName("EarlyDisplay");
|
||||
thread.setDaemon(true);
|
||||
return thread;
|
||||
});
|
||||
private ScheduledExecutorService renderScheduler;
|
||||
private int fbWidth;
|
||||
private int fbHeight;
|
||||
private int fbScale;
|
||||
@@ -154,7 +148,7 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
}
|
||||
this.maximized = parsed.has(maximizedopt) || FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_MAXIMIZED);
|
||||
|
||||
StartupNotificationManager.modLoaderConsumer().ifPresent(c->c.accept("Forge loading " + FMLLoader.versionInfo().forgeVersion()));
|
||||
StartupNotificationManager.modLoaderConsumer().ifPresent(c->c.accept("Forge loading " + forgeVersion));
|
||||
performanceInfo = new PerformanceInfo();
|
||||
return start(mcVersion, forgeVersion);
|
||||
}
|
||||
@@ -243,7 +237,7 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
|
||||
var date = Calendar.getInstance();
|
||||
if (FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_SQUIR) || (date.get(Calendar.MONTH) == Calendar.APRIL && date.get(Calendar.DAY_OF_MONTH) == 1))
|
||||
this.elements.addFirst(RenderElement.squir());
|
||||
this.elements.add(0, RenderElement.squir());
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
@@ -263,11 +257,7 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
for (var itr = this.elements.iterator(); itr.hasNext(); ) {
|
||||
var element = itr.next();
|
||||
if (!element.render(context, framecount))
|
||||
itr.remove();
|
||||
}
|
||||
this.elements.removeIf(element -> !element.render(context, framecount));
|
||||
if (animationTimerTrigger.compareAndSet(true, false)) // we only increment the framecount on a periodic basis
|
||||
framecount++;
|
||||
}
|
||||
@@ -292,6 +282,12 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
* Start the window and Render Thread; we're ready to go.
|
||||
*/
|
||||
public Runnable start(@Nullable String mcVersion, final String forgeVersion) {
|
||||
renderScheduler = Executors.newSingleThreadScheduledExecutor(r -> {
|
||||
final var thread = Executors.defaultThreadFactory().newThread(r);
|
||||
thread.setName("EarlyDisplay");
|
||||
thread.setDaemon(true);
|
||||
return thread;
|
||||
});
|
||||
initWindow(mcVersion);
|
||||
this.initializationFuture = renderScheduler.schedule(() -> initRender(mcVersion, forgeVersion), 1, TimeUnit.MILLISECONDS);
|
||||
return this::periodicTick;
|
||||
@@ -303,7 +299,7 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
return this.glVersion;
|
||||
}
|
||||
|
||||
private static void crashElegantly(String errorDetails) {
|
||||
private void crashElegantly(String errorDetails) {
|
||||
StringBuilder msgBuilder = new StringBuilder(2000);
|
||||
msgBuilder.append("Failed to initialize graphics window with current settings.\n");
|
||||
msgBuilder.append("\n\n");
|
||||
@@ -492,6 +488,10 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
glfwPollEvents();
|
||||
}
|
||||
|
||||
private void badWindowHandler(final int code, final long desc) {
|
||||
LOGGER.error("Got error from GLFW window init: "+code+ " "+MemoryUtil.memUTF8(desc));
|
||||
}
|
||||
|
||||
private void winResize(long window, int width, int height) {
|
||||
if (window == this.window && width != 0 && height != 0) {
|
||||
this.winWidth = width;
|
||||
@@ -511,7 +511,7 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
this.winY = y;
|
||||
}
|
||||
}
|
||||
private static void handleLastGLFWError(BiConsumer<Integer, String> handler) {
|
||||
private void handleLastGLFWError(BiConsumer<Integer, String> handler) {
|
||||
try (MemoryStack memorystack = MemoryStack.stackPush()) {
|
||||
PointerBuffer pointerbuffer = memorystack.mallocPointer(1);
|
||||
int error = glfwGetError(pointerbuffer);
|
||||
@@ -639,7 +639,8 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
}
|
||||
|
||||
public void addMojangTexture(final int textureId) {
|
||||
this.elements.addFirst(RenderElement.mojang(textureId, framecount));
|
||||
this.elements.add(0, RenderElement.mojang(textureId, framecount));
|
||||
// this.elements.get(0).retire(framecount + 1);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
|
||||
+39
-30
@@ -22,8 +22,8 @@ public class RenderElement {
|
||||
static int globalAlpha = 255;
|
||||
private int retireCount;
|
||||
|
||||
@FunctionalInterface
|
||||
interface Renderer {
|
||||
|
||||
void accept(SimpleBufferBuilder bb, DisplayContext context, int frame);
|
||||
|
||||
default Renderer then(Renderer r) {
|
||||
@@ -38,9 +38,6 @@ public class RenderElement {
|
||||
interface TextureRenderer {
|
||||
void accept(SimpleBufferBuilder bb, DisplayContext context, int[] size, int frame);
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link Renderer} directly. This was always eagerly resolved, making it an unnecessary wrapper. */
|
||||
@Deprecated(since = "1.21.5", forRemoval = true)
|
||||
@FunctionalInterface
|
||||
interface Initializer extends Supplier<Renderer> {}
|
||||
|
||||
@@ -59,18 +56,11 @@ public class RenderElement {
|
||||
}
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link RenderElement#RenderElement(Renderer)} instead */
|
||||
@Deprecated(since = "1.21.5", forRemoval = true)
|
||||
public RenderElement(final Initializer rendererInitializer) {
|
||||
this.bb = new SimpleBufferBuilder(1);
|
||||
this.renderer = rendererInitializer.get();
|
||||
}
|
||||
|
||||
public RenderElement(Renderer renderer) {
|
||||
this.bb = new SimpleBufferBuilder(1);
|
||||
this.renderer = renderer;
|
||||
}
|
||||
|
||||
public boolean render(DisplayContext ctx, int count) {
|
||||
this.renderer.accept(bb, ctx, count);
|
||||
return this.retireCount == 0 || this.retireCount < count;
|
||||
@@ -85,7 +75,7 @@ public class RenderElement {
|
||||
List<SimpleFont.DisplayText> texts = new ArrayList<>();
|
||||
for (int i = messages.size() - 1; i >= 0; i--) {
|
||||
final StartupNotificationManager.AgeMessage pair = messages.get(i);
|
||||
final float fade = Math.clamp((4000.0f - (float) pair.age() - ( i - 4 ) * 1000.0f) / 5000.0f, 0.0f, 1.0f);
|
||||
final float fade = clamp((4000.0f - (float) pair.age() - ( i - 4 ) * 1000.0f) / 5000.0f, 0.0f, 1.0f);
|
||||
if (fade <0.01f) continue;
|
||||
Message msg = pair.message();
|
||||
int colour = Math.min((int)(fade * 255f), globalAlpha) << 24 | 0xFFFFFF;
|
||||
@@ -94,9 +84,19 @@ public class RenderElement {
|
||||
|
||||
font.generateVerticesForTexts(10, context.scaledHeight() - texts.size() * font.lineSpacing() + font.descent() - 10, bb, texts.toArray(SimpleFont.DisplayText[]::new));
|
||||
}
|
||||
public static RenderElement monag() {
|
||||
return new RenderElement(RenderElement.initializeTexture("monagstudios.png", 45000, 4, (bb, ctx, sz, frame) -> {
|
||||
var size = 256;
|
||||
var x0 = (ctx.width() - 2 * size) / 2;
|
||||
var y0 = 64;
|
||||
QuadHelper.loadQuad(bb, x0, x0+size, y0, y0+size/2f, 0f, 1f, 0f, 0.5f, 0xFFFFFFFF);
|
||||
QuadHelper.loadQuad(bb, x0+size, x0+2*size, y0, y0+size/2f, 0f, 1f, 0.5f, 1f, 0xFFFFFFFF);
|
||||
}));
|
||||
|
||||
}
|
||||
|
||||
public static RenderElement mojang(final int textureId, final int frameStart) {
|
||||
return new RenderElement((bb, ctx, frame) -> {
|
||||
return new RenderElement(()->(bb, ctx, frame) -> {
|
||||
var size = 256 * ctx.scale();
|
||||
var x0 = (ctx.scaledWidth() - 2 * size) / 2;
|
||||
var y0 = 64 * ctx.scale() + 32;
|
||||
@@ -148,11 +148,11 @@ public class RenderElement {
|
||||
}));
|
||||
}
|
||||
public static RenderElement progressBars(SimpleFont font) {
|
||||
return new RenderElement((bb, ctx, frame) -> RenderElement.startupProgressBars(font, bb, ctx, frame));
|
||||
return new RenderElement(() -> (bb, ctx, frame) -> RenderElement.startupProgressBars(font, bb, ctx, frame));
|
||||
}
|
||||
|
||||
public static RenderElement performanceBar(SimpleFont font) {
|
||||
return new RenderElement((bb, ctx, frame) -> RenderElement.memoryInfo(font, bb, ctx, frame));
|
||||
return new RenderElement(() -> (bb, ctx, frame) -> RenderElement.memoryInfo(font, bb, ctx, frame));
|
||||
}
|
||||
|
||||
public static void startupProgressBars(SimpleFont font, final SimpleBufferBuilder buffer, final DisplayContext context, final int frameNumber) {
|
||||
@@ -187,10 +187,10 @@ public class RenderElement {
|
||||
}
|
||||
private static float[] indeterminateBar(int frame, boolean isActive) {
|
||||
if (RenderElement.globalAlpha != 0xFF || !isActive) {
|
||||
return new float[] { 0f, 1f };
|
||||
return new float[] {0f,1f};
|
||||
} else {
|
||||
int progress = frame % 100;
|
||||
return new float[] { Math.clamp((progress - 2) / 100f, 0f, 1f), Math.clamp((progress + 2) / 100f, 0f, 1f) };
|
||||
var progress = frame % 100;
|
||||
return new float[]{clamp((progress - 2) / 100f, 0f, 1f), clamp((progress + 2) / 100f, 0f, 1f)};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,8 +249,8 @@ public class RenderElement {
|
||||
};
|
||||
}
|
||||
|
||||
private static Renderer initializeText(SimpleFont font, TextGenerator textGenerator) {
|
||||
return (bb, context, frame) -> renderText(font, textGenerator, bb, context);
|
||||
private static Initializer initializeText(SimpleFont font, TextGenerator textGenerator) {
|
||||
return () -> (bb, context, frame) -> renderText(font, textGenerator, bb, context);
|
||||
}
|
||||
|
||||
private static void renderText(final SimpleFont font, final TextGenerator textGenerator, final SimpleBufferBuilder bb, final DisplayContext context) {
|
||||
@@ -264,13 +264,14 @@ public class RenderElement {
|
||||
private static TextGenerator text(int x, int y, String text, int colour) {
|
||||
return (bb, font, context) -> font.generateVerticesForTexts(x, y, bb, new SimpleFont.DisplayText(text, colour));
|
||||
}
|
||||
|
||||
private static Renderer initializeTexture(final String textureFileName, int size, int textureNumber, TextureRenderer positionAndColour) {
|
||||
int[] imgSize = STBHelper.loadTextureFromClasspath(textureFileName, size, GL_TEXTURE0 + textureNumber + INDEX_TEXTURE_OFFSET);
|
||||
return (bb, ctx, frame) -> {
|
||||
ctx.elementShader().updateTextureUniform(textureNumber + INDEX_TEXTURE_OFFSET);
|
||||
ctx.elementShader().updateRenderTypeUniform(ElementShader.RenderType.TEXTURE);
|
||||
renderTexture(bb, ctx, frame, imgSize, positionAndColour);
|
||||
private static Initializer initializeTexture(final String textureFileName, int size, int textureNumber, TextureRenderer positionAndColour) {
|
||||
return ()->{
|
||||
int[] imgSize = STBHelper.loadTextureFromClasspath(textureFileName, size, GL_TEXTURE0 + textureNumber + INDEX_TEXTURE_OFFSET);
|
||||
return (bb, ctx, frame) -> {
|
||||
ctx.elementShader().updateTextureUniform(textureNumber + INDEX_TEXTURE_OFFSET);
|
||||
ctx.elementShader().updateRenderTypeUniform(ElementShader.RenderType.TEXTURE);
|
||||
renderTexture(bb, ctx, frame, imgSize, positionAndColour);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -280,13 +281,21 @@ public class RenderElement {
|
||||
bb.draw();
|
||||
}
|
||||
|
||||
@Deprecated(since = "1.21.5", forRemoval = true)
|
||||
|
||||
public static float clamp(float num, float min, float max) {
|
||||
return Math.clamp(num, min, max);
|
||||
if (num < min) {
|
||||
return min;
|
||||
} else {
|
||||
return Math.min(num, max);
|
||||
}
|
||||
}
|
||||
|
||||
public static int clamp(int num, int min, int max) {
|
||||
return Math.min(max, Math.max(num, min));
|
||||
if (num < min) {
|
||||
return min;
|
||||
} else {
|
||||
return Math.min(num, max);
|
||||
}
|
||||
}
|
||||
|
||||
public static int hsvToRGB(float hue, float saturation, float value) {
|
||||
|
||||
@@ -11,6 +11,8 @@ import org.lwjgl.stb.STBTTFontinfo;
|
||||
import org.lwjgl.stb.STBTTPackContext;
|
||||
import org.lwjgl.stb.STBTTPackRange;
|
||||
import org.lwjgl.stb.STBTTPackedchar;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@@ -22,7 +24,7 @@ public class SimpleFont {
|
||||
private final int textureNumber;
|
||||
private final int lineSpacing;
|
||||
private final int descent;
|
||||
private static final int GLYPH_COUNT = 127 - 32;
|
||||
private final int GLYPH_COUNT = 127-32;
|
||||
private final Glyph[] glyphs;
|
||||
|
||||
private record Glyph(char c, int charwidth, int[] pos, float[] uv) {
|
||||
@@ -120,7 +122,7 @@ public class SimpleFont {
|
||||
case '\n', '\t' -> 0;
|
||||
case ' ' -> glyphs[0].charwidth();
|
||||
default -> {
|
||||
if (c - 32 < GLYPH_COUNT && c > 32) {
|
||||
if (c - 32 < this.GLYPH_COUNT && c > 32) {
|
||||
yield this.glyphs[c - 32].charwidth();
|
||||
} else {
|
||||
yield 0;
|
||||
@@ -151,7 +153,7 @@ public class SimpleFont {
|
||||
case '\t' -> new Pos(pos.x()+font.glyphs[0].charwidth() * 4, pos.y(), pos.minx());
|
||||
case ' ' -> new Pos(pos.x()+font.glyphs[0].charwidth(), pos.y(), pos.minx());
|
||||
default -> {
|
||||
if (c-32 < GLYPH_COUNT && c > 32) {
|
||||
if (c-32 < font.GLYPH_COUNT && c > 32) {
|
||||
pos = font.glyphs[c - 32].loadQuad(pos, colour(), bb);
|
||||
}
|
||||
yield pos;
|
||||
|
||||
+4
-44
@@ -5,7 +5,6 @@ plugins {
|
||||
id 'maven-publish'
|
||||
id 'net.minecraftforge.licenser'
|
||||
id 'net.minecraftforge.gradleutils'
|
||||
alias(libs.plugins.apt)
|
||||
}
|
||||
|
||||
apply from: rootProject.file('build_shared.gradle')
|
||||
@@ -13,7 +12,7 @@ apply from: rootProject.file('build_shared.gradle')
|
||||
configurations.forEach{ it.transitive = false }
|
||||
|
||||
dependencies {
|
||||
compileOnly(libs.jetbrains.annotations)
|
||||
compileOnly(libs.nulls)
|
||||
|
||||
api(libs.bundles.asm) // Needed by all the black magic
|
||||
api(libs.forgespi)
|
||||
@@ -26,6 +25,7 @@ dependencies {
|
||||
api(libs.apache.commons)
|
||||
api(libs.bundles.night.config)
|
||||
api(libs.modlauncher)
|
||||
api(libs.coremods)
|
||||
api(libs.mojang.logging)
|
||||
api(libs.jarjar.selector)
|
||||
api(libs.jarjar.meta)
|
||||
@@ -35,13 +35,11 @@ dependencies {
|
||||
implementation(libs.accesstransformers)
|
||||
implementation(libs.terminalconsoleappender)
|
||||
implementation(libs.jimfs)
|
||||
implementation(libs.roimfs)
|
||||
|
||||
// Needed because we have a custom log4j plugin, and they removed package scanning and require a data file to be generated
|
||||
implementation(libs.log4j.core)
|
||||
annotationProcessor(libs.bundles.log4j)
|
||||
|
||||
testCompileOnly(libs.jetbrains.annotations)
|
||||
testCompileOnly(libs.nulls)
|
||||
testRuntimeOnly(libs.bootstrap)
|
||||
}
|
||||
|
||||
@@ -87,7 +85,7 @@ publishing {
|
||||
license PomUtils.Licenses.LGPLv2_1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
repositories {
|
||||
maven gradleutils.publishingForgeMaven
|
||||
}
|
||||
@@ -103,44 +101,6 @@ tasks.register('writeForgeVersionJson') {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register('jarJarOptionsJson', net.minecraftforge.forge.tasks.JarJarMetadataOptions) {
|
||||
metadataFile = project.file('src/main/resources/jarjar_options.json')
|
||||
// This is resolved too early by cpw.mods.modlauncher.TransformationServicesHandler.discoverServices(DiscoveryData) But eventually...
|
||||
//add(libs.mixin, 'org/spongepowered/asm/mixin/Mixin.class')
|
||||
add(libs.mixinextras.forge) {
|
||||
containedJarMetadata {
|
||||
path = ''
|
||||
}
|
||||
resource = 'com/llamalad7/mixinextras/platform/forge/MixinExtrasConfigPlugin.class'
|
||||
layer = 'GAMELIBRARY'
|
||||
id = 'mixinextras'
|
||||
nested = true
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named('generateResources').configure {
|
||||
dependsOn('eclipseJdt')
|
||||
dependsOn('eclipseJdtApt')
|
||||
dependsOn('eclipseFactorypath')
|
||||
dependsOn('writeForgeVersionJson')
|
||||
dependsOn('jarJarOptionsJson')
|
||||
}
|
||||
|
||||
tasks.named('sourcesJar') {
|
||||
dependsOn('jarJarOptionsJson')
|
||||
}
|
||||
|
||||
eclipse {
|
||||
classpath {
|
||||
// We need to set the default output directory for the log4j annotation processor
|
||||
// Ideally we'd just set the value, but the eclipse plugin deduplicates the output directories
|
||||
// So we have to do a hacky whenMerged
|
||||
//defaultOutputDir = file('bin/main')
|
||||
file.whenMerged {
|
||||
entries.each { entry ->
|
||||
if (entry.kind == 'output' && entry.hasProperty('path'))
|
||||
entry.path = 'bin/main'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
-7
@@ -34,10 +34,11 @@ import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
|
||||
* }</code>
|
||||
* </pre>
|
||||
*/
|
||||
public final class CapabilityTokenSubclass implements ILaunchPluginService {
|
||||
public class CapabilityTokenSubclass implements ILaunchPluginService {
|
||||
|
||||
private static final String FUNC_NAME = "getType";
|
||||
private static final String FUNC_DESC = "()Ljava/lang/String;";
|
||||
private static final String CAP_INJECT = "net/minecraftforge/common/capabilities/CapabilityToken"; // Don't directly reference this to prevent class loading.
|
||||
private static final String CAP_INJECT = "net/minecraftforge/common/capabilities/CapabilityToken"; //Don't directly reference this to prevent class loading.
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
@@ -55,11 +56,8 @@ public final class CapabilityTokenSubclass implements ILaunchPluginService {
|
||||
String internalName = classType.getInternalName();
|
||||
if (internalName.startsWith("net/minecraft/") || internalName.startsWith("com/mojang/"))
|
||||
return NAY;
|
||||
|
||||
if (internalName.startsWith("net/minecraftforge/common/capabilities/"))
|
||||
return YAY;
|
||||
|
||||
return internalName.contains("$") ? YAY : NAY; // Anonymous subclasses only
|
||||
return YAY;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -72,7 +70,7 @@ public final class CapabilityTokenSubclass implements ILaunchPluginService {
|
||||
}
|
||||
return ComputeFlags.SIMPLE_REWRITE;
|
||||
} else if (CAP_INJECT.equals(classNode.superName)) {
|
||||
var cls = new Object() { String value = null; };
|
||||
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) {
|
||||
@@ -108,4 +106,8 @@ public final class CapabilityTokenSubclass implements ILaunchPluginService {
|
||||
return ComputeFlags.NO_REWRITE;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Holder {
|
||||
String value;
|
||||
}
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* 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.Set;
|
||||
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.toUnmodifiableMap(VanillaObjectHolderData::holderClass, Function.identity()));
|
||||
private static final Set<String> VANILLA_OBJECT_HOLDER_CLASSES = VANILLA_OBJECT_HOLDERS.keySet().stream()
|
||||
.map(it -> it.replace('.', '/'))
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
private static final String OBJECT_HOLDER = "Lnet/minecraftforge/registries/ObjectHolder;"; //Don't directly reference this to prevent class loading.
|
||||
private static final int PUBLIC_STATIC_FINAL_FLAGS = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL;
|
||||
|
||||
@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) {
|
||||
if (isEmpty)
|
||||
return NAY;
|
||||
|
||||
// Let's skip processing classes that definitely don't have object holders
|
||||
String internalName = classType.getInternalName();
|
||||
|
||||
// Forge classes that aren't in the debug package (that package is used for tests)
|
||||
if (internalName.startsWith("net/minecraftforge/") && !internalName.substring(18).contains("debug"))
|
||||
return NAY;
|
||||
|
||||
// Vanilla classes that don't have object holders
|
||||
if (internalName.startsWith("com/mojang/"))
|
||||
return NAY;
|
||||
|
||||
// ...except specific ones we added to the VANILLA_OBJECT_HOLDERS map
|
||||
if (internalName.startsWith("net/minecraft/") && !VANILLA_OBJECT_HOLDER_CLASSES.contains(internalName))
|
||||
return NAY;
|
||||
|
||||
return YAY;
|
||||
}
|
||||
|
||||
private static boolean hasHolder(List<AnnotationNode> lst) {
|
||||
return lst != null && lst.stream().anyMatch(n -> n.desc.equals(OBJECT_HOLDER));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
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
|
||||
|
||||
//Fix Annotated Fields before injecting from class level
|
||||
classNode.fields.stream().filter(f -> ((f.access & PUBLIC_STATIC_FINAL_FLAGS) == PUBLIC_STATIC_FINAL_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 & PUBLIC_STATIC_FINAL_FLAGS) == PUBLIC_STATIC_FINAL_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) {}
|
||||
}
|
||||
@@ -7,9 +7,9 @@ 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 net.minecraftforge.fml.loading.LoadingModList;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
@@ -47,21 +47,16 @@ public class RuntimeEnumExtender implements ILaunchPluginService {
|
||||
private static final EnumSet<Phase> YAY = EnumSet.of(Phase.AFTER);
|
||||
private static final EnumSet<Phase> NAY = EnumSet.noneOf(Phase.class);
|
||||
|
||||
private static final class LazyInit {
|
||||
private LazyInit() {}
|
||||
private static final boolean ANY_MODS = LoadingModList.get().getMods().size() > 2; // 2: forge, minecraft
|
||||
}
|
||||
|
||||
@Override
|
||||
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty) {
|
||||
if (isEmpty || !LazyInit.ANY_MODS)
|
||||
if (isEmpty)
|
||||
return NAY;
|
||||
|
||||
String internalName = classType.getInternalName();
|
||||
if (internalName.startsWith("net/minecraft/client/model") || internalName.startsWith("net/minecraft/world"))
|
||||
return YAY;
|
||||
if (internalName.startsWith("net/minecraftforge/") || internalName.startsWith("com/mojang/"))
|
||||
return NAY;
|
||||
|
||||
return NAY;
|
||||
return YAY;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -6,13 +6,14 @@
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
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 final ExecutorService runner = Executors.newSingleThreadExecutor();
|
||||
|
||||
public static void runAndTick(Runnable r, Runnable tick) {
|
||||
var runner = Executors.newSingleThreadExecutor();
|
||||
ImmediateWindowHandler.updateProgress("Loading bootstrap resources");
|
||||
final Future<?> work = runner.submit(r);
|
||||
do {
|
||||
|
||||
@@ -16,6 +16,7 @@ import net.minecraftforge.accesstransformer.service.AccessTransformerService;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.fml.loading.targets.CommonLaunchHandler;
|
||||
import net.minecraftforge.forgespi.Environment;
|
||||
import net.minecraftforge.forgespi.coremod.ICoreModProvider;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.nio.file.Path;
|
||||
@@ -33,6 +34,7 @@ import static net.minecraftforge.fml.loading.LogMarkers.SCAN;
|
||||
public class FMLLoader {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static AccessTransformerService accessTransformer;
|
||||
private static ICoreModProvider coreModProvider;
|
||||
private static LanguageLoadingProvider languageLoadingProvider;
|
||||
private static Dist dist;
|
||||
private static String naming;
|
||||
@@ -52,9 +54,15 @@ public class FMLLoader {
|
||||
LOGGER.debug(CORE, "Detected version data : {}", versionInfo);
|
||||
LOGGER.debug(CORE, "FML {} loading", LauncherVersion.getVersion());
|
||||
|
||||
// Allows us to communicate properties with other services through ModLauncher
|
||||
setupBlackboardKeys();
|
||||
|
||||
checkPackage(ITransformationService.class, "4.0", "ModLauncher");
|
||||
accessTransformer = getPlugin(env, "accesstransformer", "1.0", "AccessTransformer");
|
||||
/*eventBus =*/ getPlugin(env, "eventbus", "1.0", "EventBus");
|
||||
runtimeDistCleaner = getPlugin(env, "runtimedistcleaner", "1.0", "RuntimeDistCleaner");
|
||||
coreModProvider = getSingleService(ICoreModProvider.class, "CoreMod");
|
||||
LOGGER.debug(CORE, "FML found CoreMod version : {}", JarVersionLookupHandler.getInfo(coreModProvider.getClass()).impl().version().orElse("MISSING"));
|
||||
checkPackage(Environment.class, "2.0", "ForgeSPI");
|
||||
|
||||
try {
|
||||
@@ -92,7 +100,6 @@ public class FMLLoader {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static <T> T getSingleService(Class<T> clazz, String name) throws IncompatibleEnvironmentException {
|
||||
var providers = new ArrayList<T>();
|
||||
for (var itr = ServiceLoader.load(FMLLoader.class.getModule().getLayer(), clazz).iterator(); itr.hasNext(); ) {
|
||||
@@ -114,6 +121,12 @@ public class FMLLoader {
|
||||
return providers.get(0);
|
||||
}
|
||||
|
||||
private static void setupBlackboardKeys() {
|
||||
LOGGER.debug(CORE, "Requesting CoreMods to not apply the fix for ASMAPI.findFirstInstructionBefore by default");
|
||||
var blackboardKey = TypesafeMap.Key.getOrCreate(Launcher.INSTANCE.blackboard(), "coremods.use_old_findFirstInstructionBefore", Boolean.class);
|
||||
Launcher.INSTANCE.blackboard().<Boolean>computeIfAbsent(blackboardKey, k -> true);
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -143,8 +156,9 @@ public class FMLLoader {
|
||||
}
|
||||
|
||||
public static List<ITransformationService.Resource> beginModScan(final Map<String,?> arguments) {
|
||||
LOGGER.debug(SCAN, "Scanning for Mod Locators");
|
||||
modValidator = ModDiscoverer.discoverMods(arguments);
|
||||
LOGGER.debug(SCAN,"Scanning for Mod Locators");
|
||||
var modDiscoverer = new ModDiscoverer(arguments);
|
||||
modValidator = modDiscoverer.discoverMods();
|
||||
var pluginResources = modValidator.getPluginResources();
|
||||
return List.of(pluginResources);
|
||||
}
|
||||
@@ -157,6 +171,10 @@ public class FMLLoader {
|
||||
return List.of(modValidator.getModResources());
|
||||
}
|
||||
|
||||
public static ICoreModProvider getCoreModProvider() {
|
||||
return coreModProvider;
|
||||
}
|
||||
|
||||
public static LanguageLoadingProvider getLanguageLoadingProvider() {
|
||||
return languageLoadingProvider;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ package net.minecraftforge.fml.loading;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import cpw.mods.modlauncher.api.*;
|
||||
import joptsimple.OptionSpecBuilder;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
|
||||
import net.minecraftforge.forgespi.Environment;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -18,6 +19,7 @@ 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 {
|
||||
@@ -68,6 +70,7 @@ public class FMLServiceProvider implements ITransformationService {
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public @NotNull List<ITransformer> transformers() {
|
||||
return List.of();
|
||||
LOGGER.debug(CORE, "Loading coremod transformers");
|
||||
return new ArrayList<>(FMLLoader.getCoreModProvider().getCoreModTransformers());
|
||||
}
|
||||
}
|
||||
@@ -5,30 +5,33 @@
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import net.minecraftforge.fml.loading.EarlyLoadingException.ExceptionData;
|
||||
import com.mojang.logging.LogUtils;
|
||||
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 org.slf4j.Logger;
|
||||
|
||||
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;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
|
||||
/**
|
||||
* Master list of all mods <em>in the loading context. This class cannot refer outside the
|
||||
* loading package</em>
|
||||
*/
|
||||
public class LoadingModList {
|
||||
public class LoadingModList
|
||||
{
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static LoadingModList INSTANCE;
|
||||
private final List<ModFileInfo> modFiles;
|
||||
@@ -37,12 +40,15 @@ public class LoadingModList {
|
||||
private final List<EarlyLoadingException> preLoadErrors;
|
||||
private List<IModFile> brokenFiles;
|
||||
|
||||
private LoadingModList(final List<ModFile> modFiles, final List<ModInfo> sortedList) {
|
||||
private LoadingModList(final List<ModFile> modFiles, final List<ModInfo> sortedList)
|
||||
{
|
||||
this.modFiles = modFiles.stream()
|
||||
.map(ModFile::getModFileInfo)
|
||||
.map(ModFileInfo.class::cast)
|
||||
.toList();
|
||||
this.sortedList = sortedList.stream().toList();
|
||||
.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)
|
||||
@@ -51,48 +57,63 @@ public class LoadingModList {
|
||||
this.preLoadErrors = new ArrayList<>();
|
||||
}
|
||||
|
||||
public static LoadingModList of(List<ModFile> modFiles, List<ModInfo> sortedList, final EarlyLoadingException earlyLoadingException) {
|
||||
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 addAccessTransformers() {
|
||||
var errors = new ArrayList<ExceptionData>();
|
||||
|
||||
for (ModFileInfo modFile : modFiles) {
|
||||
ModFile mod = modFile.getFile();
|
||||
for (var at : mod.getAccessTransformers()) {
|
||||
if (!Files.exists(at)) {
|
||||
var message = "Invalid mod file: " + modFile.getFile().getFileName() + ". Missing Access Transformer: " + at;
|
||||
errors.add(new ExceptionData(message));
|
||||
LOGGER.error(message);
|
||||
} else
|
||||
FMLLoader.addAccessTransformer(at, mod);
|
||||
}
|
||||
}
|
||||
|
||||
if (!errors.isEmpty())
|
||||
preLoadErrors.add(new EarlyLoadingException("Invalid Access Transformers", null, errors));
|
||||
public void addCoreMods()
|
||||
{
|
||||
modFiles.stream()
|
||||
.map(ModFileInfo::getFile)
|
||||
.map(ModFile::getCoreMods)
|
||||
.flatMap(List::stream)
|
||||
.forEach(FMLLoader.getCoreModProvider()::addCoreMod);
|
||||
}
|
||||
|
||||
public void addForScanning(BackgroundScanHandler backgroundScanHandler) {
|
||||
public void addAccessTransformers()
|
||||
{
|
||||
var errors = new ArrayList<EarlyLoadingException.ExceptionData>();
|
||||
for (ModFileInfo modFile : modFiles) {
|
||||
ModFile mod = modFile.getFile();
|
||||
for (Path at : mod.getAccessTransformers()) {
|
||||
if (!Files.exists(at)) {
|
||||
var message = "Invalid mod file: " + modFile.getFile().getFileName() + ". Missing Access Transformer: " + at;
|
||||
errors.add(new EarlyLoadingException.ExceptionData(message));
|
||||
LOGGER.error(message);
|
||||
} else {
|
||||
FMLLoader.addAccessTransformer(at, mod);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!errors.isEmpty()) {
|
||||
preLoadErrors.add(new EarlyLoadingException("Invalid Access Transformers", null, errors));
|
||||
}
|
||||
}
|
||||
|
||||
public void addForScanning(BackgroundScanHandler backgroundScanHandler)
|
||||
{
|
||||
backgroundScanHandler.setLoadingModList(this);
|
||||
modFiles.stream()
|
||||
.map(ModFileInfo::getFile)
|
||||
.forEach(backgroundScanHandler::submitForScanning);
|
||||
}
|
||||
|
||||
public List<ModFileInfo> getModFiles() {
|
||||
public List<ModFileInfo> getModFiles()
|
||||
{
|
||||
return modFiles;
|
||||
}
|
||||
|
||||
public Path findResource(final String className) {
|
||||
public Path findResource(final String className)
|
||||
{
|
||||
for (ModFileInfo mf : modFiles) {
|
||||
final Path resource = mf.getFile().findResource(className);
|
||||
if (Files.exists(resource)) return resource;
|
||||
@@ -100,11 +121,55 @@ public class LoadingModList {
|
||||
return null;
|
||||
}
|
||||
|
||||
public ModFileInfo getModFileById(String modid) {
|
||||
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() {
|
||||
public List<ModInfo> getMods()
|
||||
{
|
||||
return this.sortedList;
|
||||
}
|
||||
|
||||
|
||||
-13
@@ -7,10 +7,8 @@ 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.api.TypesafeMap;
|
||||
import cpw.mods.modlauncher.serviceapi.ITransformerDiscoveryService;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
@@ -42,17 +40,6 @@ public class ModDirTransformerDiscoverer implements ITransformerDiscoveryService
|
||||
|
||||
@Override
|
||||
public void earlyInitialization(final String launchTarget, final String[] arguments) {
|
||||
boolean isMixinEnabledInDev = false;
|
||||
for (String arg : arguments) {
|
||||
if (arg.startsWith("-mixin") || arg.startsWith("--mixin")) {
|
||||
isMixinEnabledInDev = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Launcher.INSTANCE.blackboard().putIfAbsent(
|
||||
TypesafeMap.Key.getOrCreate(Launcher.INSTANCE.blackboard(), "isMixinEnabledInDev", Boolean.class),
|
||||
isMixinEnabledInDev
|
||||
);
|
||||
ImmediateWindowHandler.load(launchTarget, arguments);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ package net.minecraftforge.fml.loading;
|
||||
import com.google.common.graph.GraphBuilder;
|
||||
import com.google.common.graph.MutableGraph;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraftforge.forgespi.language.IModInfo.ModVersion;
|
||||
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;
|
||||
@@ -20,250 +20,235 @@ import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Function;
|
||||
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 {
|
||||
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 record State(List<ModFile> files, List<ModInfo> mods) {}
|
||||
|
||||
private ModSorter() {}
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
public static LoadingModList sort(List<ModFile> mods, final List<ExceptionData> errors) {
|
||||
State systemMods = detectSystemMods(mods);
|
||||
List<ModFile> modFiles;
|
||||
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 {
|
||||
modFiles = UniqueModListBuilder.buildUniqueList(mods).modFiles();
|
||||
ms.buildUniqueList();
|
||||
} catch (EarlyLoadingException e) {
|
||||
// We cannot build any list with duped mods. We have to abort immediately and report it
|
||||
// Note this will never actually throw an error because the duplicate checks are done in ModDiscovererer before we get to this phase
|
||||
// So all this is really doing is wasting time.
|
||||
// But i'm leaving it here until I rewrite all of cpw's mod loading code because its such a clusterfuck.
|
||||
return LoadingModList.of(systemMods.files(), systemMods.mods(), e);
|
||||
return LoadingModList.of(ms.systemMods, ms.systemMods.stream().map(mf->(ModInfo)mf.getModInfos().get(0)).toList(), e);
|
||||
}
|
||||
|
||||
var named = new HashMap<String, ModInfo>();
|
||||
for (var file : modFiles) {
|
||||
for (var info : file.getModInfos())
|
||||
named.put(info.getModId(), (ModInfo)info);
|
||||
}
|
||||
|
||||
// try and validate dependencies
|
||||
final List<ExceptionData> failedList = Stream.concat(verifyDependencyVersions(modFiles).stream(), errors.stream()).toList();
|
||||
|
||||
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(systemMods.files(), systemMods.mods(), new EarlyLoadingException("failure to validate mod list", null, failedList));
|
||||
return LoadingModList.of(ms.systemMods, ms.systemMods.stream().map(mf->(ModInfo)mf.getModInfos().get(0)).toList(), new EarlyLoadingException("failure to validate mod list", null, failedList));
|
||||
} else {
|
||||
// Otherwise, lets try and sort the modlist and proceed
|
||||
EarlyLoadingException earlyLoadingException = null;
|
||||
try {
|
||||
var sorted = sort(modFiles, named);
|
||||
return LoadingModList.of(sorted.files(), sorted.mods(), null);
|
||||
ms.sort();
|
||||
} catch (EarlyLoadingException e) {
|
||||
// The only exception that can happen here is a cyclic exception, but fall back to system mods so we can display the nice screen.
|
||||
return LoadingModList.of(systemMods.files(), systemMods.mods(), e);
|
||||
earlyLoadingException = e;
|
||||
}
|
||||
return LoadingModList.of(ms.modFiles, ms.sortedList, earlyLoadingException);
|
||||
}
|
||||
}
|
||||
|
||||
private static State sort(final List<ModFile> modFiles, final Map<String, ModInfo> named) {
|
||||
final MutableGraph<ModInfo> graph = GraphBuilder.directed().build();
|
||||
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)
|
||||
.flatMap(List::stream)
|
||||
.map(IModInfo::getDependencies)
|
||||
.<IModInfo.ModVersion>flatMap(List::stream)
|
||||
.forEach(dep -> addDependency(graph, dep));
|
||||
|
||||
int counter = 0;
|
||||
var infos = new HashMap<ModInfo, Integer>();
|
||||
for (var file : modFiles) {
|
||||
if (file.getModFileInfo() instanceof ModFileInfo info) {
|
||||
for (var imod : info.getMods()) {
|
||||
var mod = (ModInfo)imod;
|
||||
infos.put(mod, counter++);
|
||||
graph.addNode(mod);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var file : modFiles) {
|
||||
for (var info : file.getModInfos()) {
|
||||
for (var dep : info.getDependencies()) {
|
||||
// Ordering isn't effected by sides, should it be?
|
||||
//if (!dep.getSide().isCorrectSide())
|
||||
// continue;
|
||||
|
||||
var target = named.get(dep.getModId());
|
||||
|
||||
// soft dep that doesn't exist. No edge required.
|
||||
if (target == null)
|
||||
continue;
|
||||
|
||||
var self = (ModInfo)dep.getOwner();
|
||||
|
||||
switch (dep.getOrdering()) {
|
||||
case BEFORE -> graph.putEdge(self, target);
|
||||
case AFTER -> graph.putEdge(target, self);
|
||||
default -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final List<ModInfo> sorted;
|
||||
try {
|
||||
final List<ModFileInfo> sorted;
|
||||
try
|
||||
{
|
||||
sorted = TopologicalSort.topologicalSort(graph, Comparator.comparing(infos::get));
|
||||
} catch (CyclePresentException e) {
|
||||
Set<Set<ModInfo>> cycles = e.getCycles();
|
||||
var buf = new StringBuilder();
|
||||
buf.append("Mod Sorting failed - Detected Cycles: \n");
|
||||
|
||||
var dataList = new ArrayList<ExceptionData>();
|
||||
for (var cycle : cycles) {
|
||||
buf.append("\tCycle:\n");
|
||||
for (var mod : cycle) {
|
||||
var modDeps = new StringBuilder()
|
||||
.append(mod.getModId())
|
||||
.append(' ')
|
||||
.append(mod.getDependencies().stream()
|
||||
.filter(v -> cycle.stream().anyMatch(m -> m.getModId().equals(v.getModId())))
|
||||
.map(dep -> dep.getOrdering().name() + " " + dep.getModId())
|
||||
.collect(Collectors.joining(", "))
|
||||
);
|
||||
dataList.add(new ExceptionData("fml.modloading.cycle", modDeps.toString()));
|
||||
buf.append("\t\tMod: ").append(modDeps.toString()).append('\n');
|
||||
}
|
||||
}
|
||||
catch (CyclePresentException e)
|
||||
{
|
||||
Set<Set<ModFileInfo>> cycles = e.getCycles();
|
||||
if (LOGGER.isErrorEnabled(LOADING))
|
||||
{
|
||||
LOGGER.error(LOADING, "Mod Sorting failed.\nDetected Cycles: {}\n", cycles);
|
||||
}
|
||||
|
||||
LOGGER.error(LOADING, buf.toString());
|
||||
|
||||
var dataList = cycles.stream()
|
||||
.flatMap(Set::stream)
|
||||
.<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);
|
||||
}
|
||||
|
||||
var files = new LinkedHashSet<ModFile>();
|
||||
var list = new ArrayList<ModInfo>();
|
||||
|
||||
for (var mod : sorted) {
|
||||
files.add(mod.getOwningFile().getFile());
|
||||
list.add(mod);
|
||||
}
|
||||
|
||||
return new State(files.stream().toList(), list);
|
||||
this.sortedList = sorted.stream()
|
||||
.map(ModFileInfo::getMods)
|
||||
.flatMap(List::stream)
|
||||
.map(ModInfo.class::cast)
|
||||
.collect(toList());
|
||||
this.modFiles = sorted.stream()
|
||||
.map(ModFileInfo::getFile)
|
||||
.collect(toList());
|
||||
}
|
||||
|
||||
private static State detectSystemMods(final List<ModFile> modFiles) {
|
||||
private void addDependency(MutableGraph<ModFileInfo> topoGraph, IModInfo.ModVersion dep)
|
||||
{
|
||||
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;
|
||||
|
||||
final ModFileInfo self = (ModFileInfo)dep.getOwner().getOwningFile();
|
||||
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);
|
||||
default -> {}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
var systemMods = List.of("minecraft", "forge");
|
||||
LOGGER.debug("Configured system mods: {}", systemMods);
|
||||
|
||||
var mods = new ArrayList<ModInfo>();
|
||||
var files = new ArrayList<ModFile>();
|
||||
this.systemMods = new ArrayList<>();
|
||||
for (var systemMod : systemMods) {
|
||||
var mod = findMod(modFiles, systemMod);
|
||||
if (mod == null)
|
||||
var container = modFilesByFirstId.get(systemMod);
|
||||
if (container != null && !container.isEmpty()) {
|
||||
LOGGER.debug("Found system mod: {}", systemMod);
|
||||
this.systemMods.add(container.getFirst());
|
||||
} else {
|
||||
throw new IllegalStateException("Failed to find system mod: " + systemMod);
|
||||
|
||||
LOGGER.debug("Found system mod: {}", systemMod);
|
||||
mods.add(mod.info());
|
||||
files.add(mod.file());
|
||||
}
|
||||
|
||||
return new State(files, mods);
|
||||
}
|
||||
|
||||
private record ModPair(ModFile file, ModInfo info) {}
|
||||
private static ModPair findMod(final List<ModFile> modFiles, String name) {
|
||||
for (var file : modFiles) {
|
||||
for (var mod : file.getModFileInfo().getMods()) {
|
||||
if (name.equals(mod.getModId()))
|
||||
return new ModPair(file, (ModInfo)mod);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static List<ExceptionData> verifyDependencyVersions(final List<ModFile> files) {
|
||||
final var modVersions = new HashMap<String, ArtifactVersion>();
|
||||
final var modRequirements = new HashSet<ModVersion>();
|
||||
int mandatoryRequired = 0;
|
||||
private List<EarlyLoadingException.ExceptionData> verifyDependencyVersions()
|
||||
{
|
||||
final var modVersions = modFiles.stream()
|
||||
.map(ModFile::getModInfos)
|
||||
.flatMap(List::stream)
|
||||
.collect(toMap(IModInfo::getModId, IModInfo::getVersion));
|
||||
|
||||
for (var file : files) {
|
||||
for (var info : file.getModInfos()) {
|
||||
modVersions.put(info.getModId(), info.getVersion());
|
||||
for (var dep : info.getDependencies()) {
|
||||
if (dep.getSide().isCorrectSide()) {
|
||||
if (modRequirements.add(dep) && dep.isMandatory())
|
||||
mandatoryRequired++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
final var modVersionDependencies = modFiles.stream()
|
||||
.map(ModFile::getModInfos)
|
||||
.flatMap(List::stream)
|
||||
.collect(groupingBy(Function.identity(), flatMapping(e -> e.getDependencies().stream(), toList())));
|
||||
|
||||
final var modRequirements = modVersionDependencies.values().stream()
|
||||
.<IModInfo.ModVersion>flatMap(List::stream)
|
||||
.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())) && !modVersionContained(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);
|
||||
|
||||
final var missingMandatory = new HashSet<ModVersion>();
|
||||
final var missingOptional = new HashSet<ModVersion>();
|
||||
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"))
|
||||
);
|
||||
}
|
||||
|
||||
for (var dep : modRequirements) {
|
||||
var modId = dep.getModId();
|
||||
var existing = modVersions.get(modId);
|
||||
|
||||
if (!dep.isMandatory() && existing == null)
|
||||
continue;
|
||||
|
||||
var range = dep.getVersionRange();
|
||||
if (existing != null && (range.containsVersion(existing) || "0.0NONE".equals(existing.toString())))
|
||||
continue;
|
||||
|
||||
if (!VersionSupportMatrix.testVersionSupportMatrix(range, modId, "mod"))
|
||||
(dep.isMandatory() ? missingMandatory : missingOptional).add(dep);
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
LOGGER.debug(LOADING, "Found {} mod requirements missing ({} mandatory, {} optional)", missingMandatory.size() + missingOptional.size(), missingMandatory.size(), missingOptional.size());
|
||||
|
||||
var ret = new ArrayList<ExceptionData>();
|
||||
|
||||
if (!missingMandatory.isEmpty()) {
|
||||
LOGGER.error(LOADING, "Missing or unsupported mandatory dependencies:\n{}", formatDependencyError(missingMandatory, modVersions));
|
||||
for (var mv : missingMandatory)
|
||||
ret.add(data(mv, modVersions, "fml.modloading.missingdependency"));
|
||||
}
|
||||
|
||||
if (!missingOptional.isEmpty()) {
|
||||
LOGGER.error(LOADING, "Unsupported installed optional dependencies:\n{}", formatDependencyError(missingMandatory, modVersions));
|
||||
for (var mv : missingMandatory)
|
||||
ret.add(data(mv, modVersions, "fml.modloading.missingdependency.optional"));
|
||||
}
|
||||
|
||||
return ret;
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private static String formatDependencyError(Collection<ModVersion> missing, Map<String, ArtifactVersion> modVersions) {
|
||||
var ret = new ArrayList<String>();
|
||||
for (var dep : missing) {
|
||||
var installed = modVersions.get(dep.getModId());
|
||||
ret.add(String.format(
|
||||
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'",
|
||||
dep.getModId(),
|
||||
dep.getOwner().getModId(),
|
||||
dep.getVersionRange(),
|
||||
dependency.getModId(),
|
||||
dependency.getOwner().getModId(),
|
||||
dependency.getVersionRange(),
|
||||
installed != null ? installed.toString() : "[MISSING]"
|
||||
));
|
||||
}
|
||||
return String.join("\n", ret);
|
||||
);
|
||||
}
|
||||
|
||||
private static final ArtifactVersion NULL_VERSION = new DefaultArtifactVersion("null");
|
||||
private static ExceptionData data(ModVersion mv, Map<String, ArtifactVersion> modVersions, String key) {
|
||||
return new ExceptionData(key, mv.getOwner(), mv.getModId(), mv.getOwner().getModId(), mv.getVersionRange(), modVersions.getOrDefault(mv.getModId(), NULL_VERSION));
|
||||
private static boolean modVersionContained(IModInfo.ModVersion mv, Map<String, ArtifactVersion> modVersions) {
|
||||
var modId = mv.getModId();
|
||||
var range = mv.getVersionRange();
|
||||
if (modVersions.containsKey(modId)
|
||||
&& (range.containsVersion(modVersions.get(modId)) || modVersions.get(modId).toString().equals("0.0NONE")))
|
||||
return true;
|
||||
|
||||
return VersionSupportMatrix.testVersionSupportMatrix(mv.getVersionRange(), mv.getModId(), "mod");
|
||||
}
|
||||
}
|
||||
@@ -40,54 +40,20 @@ public final class RuntimeDistCleaner implements ILaunchPluginService {
|
||||
return "runtimedistcleaner";
|
||||
}
|
||||
|
||||
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) {
|
||||
// TODO: [FML][RuntimeDistCleaner] In 26.1, only return YAY for MC classes on mod dev server
|
||||
if (isEmpty)
|
||||
return NAY;
|
||||
|
||||
String internalName = classType.getInternalName();
|
||||
if (internalName.startsWith("net/minecraftforge/"))
|
||||
return NAY;
|
||||
|
||||
// No Vanilla classes use @OnlyIn(Dist.SERVER), so we can skip them entirely when we're running on a client
|
||||
if (FMLEnvironment.dist.isClient() && isMinecraftClass(internalName))
|
||||
return NAY;
|
||||
|
||||
return YAY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int processClassWithFlags(final Phase phase, final ClassNode classNode, final Type classType, final String reason) {
|
||||
var changed = false;
|
||||
|
||||
var annotations = unpack(classNode.visibleAnnotations);
|
||||
var isNonMinecraftClass = !isMinecraftClass(classNode.name);
|
||||
final class LazyInit {
|
||||
private LazyInit() {}
|
||||
private static final boolean CAN_EXPLODE = !FMLEnvironment.production && "21.6".equals(FMLLoader.versionInfo().mcVersion());
|
||||
}
|
||||
|
||||
if (remove(annotations, 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);
|
||||
throw new RuntimeException("Attempted to load class "+ classNode.name + " for invalid dist "+ DIST);
|
||||
}
|
||||
|
||||
if (!annotations.isEmpty()) {
|
||||
if (isNonMinecraftClass) {
|
||||
if (LazyInit.CAN_EXPLODE)
|
||||
throw new UnsupportedOperationException("Mod class " + classNode.name + " is annotated with @OnlyIn, this is no longer supported as it slowed down startup times");
|
||||
else
|
||||
LOGGER.warn(DISTXFORM, "Class {} is annotated with @OnlyIn, this is deprecated and won't work in a future MC release.", classNode.name);
|
||||
}
|
||||
|
||||
if (!FMLEnvironment.production && hasModAnnotation(classNode.visibleAnnotations)) {
|
||||
LOGGER.error(DISTXFORM, "Attempted to load class {} with @Mod and @OnlyIn/@OnlyIns annotations", classNode.name);
|
||||
throw new RuntimeException("Found @OnlyIn on @Mod class " + classNode.name + " - this is not allowed as it causes crashes. Remove the OnlyIn and consider setting clientSideOnly=true in the root of your mods.toml instead");
|
||||
}
|
||||
if (!FMLEnvironment.production && !annotations.isEmpty() && hasModAnnotation(classNode.visibleAnnotations)) {
|
||||
LOGGER.error(DISTXFORM, "Attempted to load class {} with @Mod and @OnlyIn/@OnlyIns annotations", classNode.name);
|
||||
throw new RuntimeException("Found @OnlyIn on @Mod class "+ classNode.name + " - this is not allowed as it causes crashes. Remove the OnlyIn and consider setting clientSideOnly=true in the root of your mods.toml instead");
|
||||
}
|
||||
|
||||
if (classNode.interfaces != null && !classNode.interfaces.isEmpty()) {
|
||||
@@ -96,7 +62,7 @@ public final class RuntimeDistCleaner implements ILaunchPluginService {
|
||||
continue;
|
||||
|
||||
if (classNode.interfaces.remove(ann.intf)) {
|
||||
LOGGER.debug(DISTXFORM, "Removing Interface: {} implements {}", classNode.name, ann.intf);
|
||||
LOGGER.debug(DISTXFORM,"Removing Interface: {} implements {}", classNode.name, ann.intf);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
@@ -120,13 +86,6 @@ public final class RuntimeDistCleaner implements ILaunchPluginService {
|
||||
LOGGER.debug(DISTXFORM, "Removing field: {}.{}", classNode.name, field.name);
|
||||
itr.remove();
|
||||
changed = true;
|
||||
|
||||
if (isNonMinecraftClass) {
|
||||
if (LazyInit.CAN_EXPLODE)
|
||||
throw new UnsupportedOperationException("Field " + field.name + " in mod class " + classNode.name + " is annotated with @OnlyIn, this is no longer supported as it slowed down startup times");
|
||||
else
|
||||
LOGGER.warn(DISTXFORM, "Field {} in class {} is annotated with @OnlyIn, this is deprecated and won't work in a future MC release.", field.name, classNode.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,13 +97,6 @@ public final class RuntimeDistCleaner implements ILaunchPluginService {
|
||||
itr.remove();
|
||||
lambdaGatherer.accept(method);
|
||||
changed = true;
|
||||
|
||||
if (isNonMinecraftClass) {
|
||||
if (LazyInit.CAN_EXPLODE)
|
||||
throw new UnsupportedOperationException("Method " + method.name + " in mod class " + classNode.name + " is annotated with @OnlyIn, this is no longer supported as it slowed down startup times");
|
||||
else
|
||||
LOGGER.warn(DISTXFORM, "Method {} in class {} is annotated with @OnlyIn, this is deprecated and won't work in a future MC release.", method.name, classNode.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +123,7 @@ public final class RuntimeDistCleaner implements ILaunchPluginService {
|
||||
return changed ? ComputeFlags.SIMPLE_REWRITE : ComputeFlags.NO_REWRITE;
|
||||
}
|
||||
|
||||
private record Target(String side, String intf) {
|
||||
private static record Target(String side, String intf) {
|
||||
static Target from(final AnnotationNode node) {
|
||||
var idx = node.values.indexOf("value");
|
||||
var value = (String[])node.values.get(idx + 1); // Enums are stored as [Type, Value]
|
||||
@@ -237,10 +189,6 @@ public final class RuntimeDistCleaner implements ILaunchPluginService {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isMinecraftClass(final String internalName) {
|
||||
return internalName.startsWith("net/minecraft/") || internalName.startsWith("com/mojang/");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Consumer<Dist> getExtension() {
|
||||
@@ -250,6 +198,25 @@ public final class RuntimeDistCleaner implements ILaunchPluginService {
|
||||
};
|
||||
}
|
||||
|
||||
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) {
|
||||
if (isEmpty)
|
||||
return NAY;
|
||||
|
||||
String internalName = classType.getInternalName();
|
||||
if (internalName.startsWith("net/minecraftforge/"))
|
||||
return NAY;
|
||||
|
||||
// No Vanilla classes use @OnlyIn(Dist.SERVER), so we can skip them entirely when we're running on a client
|
||||
if (FMLEnvironment.dist.isClient() && (internalName.startsWith("net/minecraft/") || internalName.startsWith("com/mojang/")))
|
||||
return NAY;
|
||||
|
||||
return YAY;
|
||||
}
|
||||
|
||||
private static class LambdaGatherer extends MethodVisitor {
|
||||
private static final Handle META_FACTORY = new Handle(Opcodes.H_INVOKESTATIC,
|
||||
"java/lang/invoke/LambdaMetafactory", "metafactory",
|
||||
|
||||
@@ -9,7 +9,6 @@ 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.jetbrains.annotations.ApiStatus;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -17,19 +16,26 @@ 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;
|
||||
|
||||
@ApiStatus.Internal
|
||||
@Deprecated(since = "1.21.3", forRemoval = true) // TODO: [FML][Loading] Convert to package private in 1.22
|
||||
public final class UniqueModListBuilder {
|
||||
private UniqueModListBuilder() {}
|
||||
public class UniqueModListBuilder
|
||||
{
|
||||
private final static Logger LOGGER = LogUtils.getLogger();
|
||||
|
||||
private static final 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;
|
||||
|
||||
public static UniqueModListData buildUniqueList(List<ModFile> modFiles) {
|
||||
// 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)
|
||||
@@ -40,12 +46,12 @@ public final class UniqueModListBuilder {
|
||||
.collect(groupingBy(UniqueModListBuilder::getModId));
|
||||
|
||||
// Select the newest by artifact version sorting of non-unique files thus identified
|
||||
final List<ModFile> uniqueModList = modFilesByFirstId.entrySet().stream()
|
||||
uniqueModList = modFilesByFirstId.entrySet().stream()
|
||||
.map(UniqueModListBuilder::selectNewestModInfo)
|
||||
.toList();
|
||||
|
||||
// Select the newest by artifact version sorting of non-unique files thus identified
|
||||
final List<ModFile> uniqueLibListWithVersion = libFilesWithVersionByModuleName.entrySet().stream()
|
||||
uniqueLibListWithVersion = libFilesWithVersionByModuleName.entrySet().stream()
|
||||
.map(UniqueModListBuilder::selectNewestModInfo)
|
||||
.toList();
|
||||
|
||||
@@ -57,10 +63,12 @@ public final class UniqueModListBuilder {
|
||||
.collect(groupingBy(IModInfo::getModId));
|
||||
|
||||
// Transform to the full lib id list
|
||||
final List<List<ModFile>> versionedLibs = uniqueLibListWithVersion.stream()
|
||||
final Map<String, List<ModFile>> versionedLibIds = uniqueLibListWithVersion.stream()
|
||||
.map(UniqueModListBuilder::getModId)
|
||||
.map(libFilesWithVersionByModuleName::get)
|
||||
.toList();
|
||||
.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
|
||||
@@ -73,24 +81,24 @@ public final class UniqueModListBuilder {
|
||||
)).toList();
|
||||
|
||||
if (!dupedModErrors.isEmpty()) {
|
||||
LOGGER.error(LOADING, "Found duplicate mods:\n{}", String.join("\n", dupedModErrors));
|
||||
LOGGER.error(LOADING, "Found duplicate mods:\n{}", dupedModErrors.stream().collect(joining("\n")));
|
||||
throw new EarlyLoadingException("Duplicate mods found", null, dupedModErrors.stream()
|
||||
.map(EarlyLoadingException.ExceptionData::new)
|
||||
.map(s -> new EarlyLoadingException.ExceptionData(s))
|
||||
.toList());
|
||||
}
|
||||
|
||||
|
||||
final List<String> dupedLibErrors = versionedLibs.stream()
|
||||
.filter(libModFiles -> libModFiles.size() > 1)
|
||||
final List<String> dupedLibErrors = versionedLibIds.values().stream()
|
||||
.filter(modFiles -> modFiles.size() > 1)
|
||||
.map(mods -> String.format("\tLibrary: '%s' from files: %s",
|
||||
getModId(mods.getFirst()),
|
||||
mods.stream().map(ModFile::getFileName).collect(joining(", "))
|
||||
)).toList();
|
||||
|
||||
if (!dupedLibErrors.isEmpty()) {
|
||||
LOGGER.error(LOADING, "Found duplicate plugins or libraries:\n{}", String.join("\n", dupedLibErrors));
|
||||
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(EarlyLoadingException.ExceptionData::new)
|
||||
.map(s -> new EarlyLoadingException.ExceptionData(s))
|
||||
.toList());
|
||||
}
|
||||
|
||||
@@ -111,7 +119,7 @@ public final class UniqueModListBuilder {
|
||||
modInfoList.sort(Comparator.comparing(UniqueModListBuilder::getVersion).reversed());
|
||||
LOGGER.debug("Selected file {} for modid {} with version {}", modInfoList.getFirst().getFileName(), fullList.getKey(), getVersion(modInfoList.getFirst()));
|
||||
}
|
||||
return modInfoList.getFirst();
|
||||
return modInfoList.get(0);
|
||||
}
|
||||
|
||||
private static ArtifactVersion getVersion(final ModFile mf) {
|
||||
@@ -119,7 +127,7 @@ public final class UniqueModListBuilder {
|
||||
return mf.getJarVersion();
|
||||
}
|
||||
|
||||
return mf.getModInfos().getFirst().getVersion();
|
||||
return mf.getModInfos().get(0).getVersion();
|
||||
}
|
||||
|
||||
private static String getModId(ModFile modFile) {
|
||||
@@ -127,8 +135,9 @@ public final class UniqueModListBuilder {
|
||||
return modFile.getSecureJar().name();
|
||||
}
|
||||
|
||||
return modFile.getModFileInfo().getMods().getFirst().getModId();
|
||||
return modFile.getModFileInfo().getMods().get(0).getModId();
|
||||
}
|
||||
|
||||
public record UniqueModListData(List<ModFile> modFiles, Map<String, List<ModFile>> modFilesByFirstId) {}
|
||||
|
||||
}
|
||||
@@ -18,7 +18,9 @@ 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.
|
||||
|
||||
+5
-15
@@ -54,16 +54,12 @@ public abstract class AbstractModProvider implements IModProvider {
|
||||
|
||||
@Nullable
|
||||
protected IModLocator.ModFileOrException createMod(Path path, boolean ignoreUnknown, String defaultType) {
|
||||
var debug = path.toString();
|
||||
if (debug.equals("/"))
|
||||
debug = path.toUri().toString();
|
||||
|
||||
var mjm = new ModJarMetadata();
|
||||
SecureJar sj = null;
|
||||
try {
|
||||
sj = SecureJar.from(jar -> loadMetaFromJar(jar, mjm), path);
|
||||
} catch (Throwable t) {
|
||||
return new IModLocator.ModFileOrException(null, new ModFileLoadingException("Failed to create secure jar for \"" + debug + "\" - " + t.getMessage()));
|
||||
return new IModLocator.ModFileOrException(null, new ModFileLoadingException("Failed to create secure jar for \"" + path + "\" - " + t.getMessage()));
|
||||
}
|
||||
|
||||
IModFile mod;
|
||||
@@ -72,7 +68,7 @@ public abstract class AbstractModProvider implements IModProvider {
|
||||
type = defaultType;
|
||||
|
||||
if (sj.moduleDataProvider().findFile(MODS_TOML).isPresent()) {
|
||||
LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", MODS_TOML, type, debug);
|
||||
LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", MODS_TOML, type, path);
|
||||
mod = new ModFile(sj, this, ModFileParser::modsTomlParser);
|
||||
// ModJarMetadata is only used when loading mods.toml mods as ModJarMetadata
|
||||
mjm.setModFile(mod);
|
||||
@@ -81,17 +77,17 @@ public abstract class AbstractModProvider implements IModProvider {
|
||||
return new IModLocator.ModFileOrException(null, new ModFileLoadingException("File \"%s\" is not a Forge mod and cannot be loaded. Look for a Forge version of this mod or consider alternative mods.".formatted(mod.getFileName())));
|
||||
}
|
||||
} else if (type != null) {
|
||||
LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", JarFile.MANIFEST_NAME, type, debug);
|
||||
LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", JarFile.MANIFEST_NAME, type, path);
|
||||
mod = new ModFile(sj, this, this::manifestParser, type);
|
||||
} else if (ignoreUnknown) {
|
||||
return null;
|
||||
} else
|
||||
return new IModLocator.ModFileOrException(null, new ModFileLoadingException("Invalid mod file found " + debug));
|
||||
return new IModLocator.ModFileOrException(null, new ModFileLoadingException("Invalid mod file found " + path));
|
||||
|
||||
return new IModLocator.ModFileOrException(mod, null);
|
||||
}
|
||||
|
||||
protected static JarMetadata loadMetaFromJar(SecureJar jar, ModJarMetadata mjm) {
|
||||
private static JarMetadata loadMetaFromJar(SecureJar jar, ModJarMetadata mjm) {
|
||||
var info = jar.moduleDataProvider().open(MODULE_INFO).orElse(null);
|
||||
if (info != null) {
|
||||
try {
|
||||
@@ -149,12 +145,6 @@ public abstract class AbstractModProvider implements IModProvider {
|
||||
public <T> Optional<T> getConfigElement(String... key) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends IConfigurable> getConfigList(String key) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends IConfigurable> getConfigList(String... key) {
|
||||
return Collections.emptyList();
|
||||
|
||||
+2
-3
@@ -74,8 +74,7 @@ public final class ClasspathLocator extends AbstractModProvider implements IModL
|
||||
var ret = new ArrayList<ModFileOrException>();
|
||||
for (var path : claimed) {
|
||||
// Filter out anything found by the ServiceLoader
|
||||
if (!ModDirTransformerDiscoverer.isServiceProvider(path)
|
||||
&& !JarInJarDependencyLocator.isOption(path))
|
||||
if (!ModDirTransformerDiscoverer.isServiceProvider(path))
|
||||
ret.add(createMod(path));
|
||||
}
|
||||
return ret;
|
||||
@@ -108,7 +107,7 @@ public final class ClasspathLocator extends AbstractModProvider implements IModL
|
||||
return Path.of(URI.create(str));
|
||||
}
|
||||
|
||||
static Path getPathFromResource(ClassLoader cl, String resource) {
|
||||
private static Path getPathFromResource(ClassLoader cl, String resource) {
|
||||
var url = cl.getResource(resource);
|
||||
if (url == null)
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public final 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() + "}";
|
||||
}
|
||||
}
|
||||
+1
@@ -9,6 +9,7 @@ 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.stream.Stream;
|
||||
|
||||
+102
-621
@@ -5,39 +5,22 @@
|
||||
|
||||
package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import com.electronwill.nightconfig.core.UnmodifiableConfig;
|
||||
import com.electronwill.nightconfig.core.file.FileConfig;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.mojang.logging.LogUtils;
|
||||
|
||||
import cpw.mods.modlauncher.Launcher;
|
||||
import cpw.mods.modlauncher.api.TypesafeMap;
|
||||
import net.minecraftforge.fml.loading.EarlyLoadingException;
|
||||
import net.minecraftforge.fml.loading.EarlyLoadingException.ExceptionData;
|
||||
import net.minecraftforge.fml.loading.FMLEnvironment;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import net.minecraftforge.forgespi.locating.IDependencyLocator;
|
||||
import net.minecraftforge.forgespi.locating.IModFile;
|
||||
import net.minecraftforge.forgespi.locating.IModFile.Type;
|
||||
import net.minecraftforge.jarjar.metadata.ContainedJarIdentifier;
|
||||
import net.minecraftforge.jarjar.metadata.ContainedJarMetadata;
|
||||
import net.minecraftforge.jarjar.metadata.ContainedVersion;
|
||||
import net.minecraftforge.jarjar.metadata.MetadataIOHandler;
|
||||
import net.minecraftforge.forgespi.locating.ModFileLoadingException;
|
||||
import net.minecraftforge.jarjar.selection.JarSelector;
|
||||
|
||||
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
|
||||
import org.apache.maven.artifact.versioning.ArtifactVersion;
|
||||
import org.apache.maven.artifact.versioning.VersionRange;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.Marker;
|
||||
import org.slf4j.MarkerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.module.ModuleDescriptor;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
@@ -45,20 +28,14 @@ import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.TreeMap;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public class JarInJarDependencyLocator extends AbstractModProvider implements IDependencyLocator {
|
||||
private static final String ROIMFS = "roimfs";
|
||||
private static final String COLOR_CODE = "\u00a7";
|
||||
private static final String RESET = COLOR_CODE + "r";
|
||||
private static final String YELLOW = COLOR_CODE + "e";
|
||||
@@ -66,238 +43,32 @@ public class JarInJarDependencyLocator extends AbstractModProvider implements ID
|
||||
private static final String GREEN = COLOR_CODE + "2";
|
||||
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static final Marker MARKER = MarkerFactory.getMarker("JAR-JAR"); // LogMarkers.SCAN;
|
||||
|
||||
private static final Map<Path, Option> OPTIONS = new HashMap<>();
|
||||
private static volatile boolean optionsLoaded = false;
|
||||
|
||||
private static final Attributes.Name MIXIN_CONFIGS_ATTR = new Attributes.Name("MixinConfigs");
|
||||
private static final Attributes.Name MIXIN_CONNECTOR_ATTR = new Attributes.Name("MixinConnector");
|
||||
private static final ContainedJarMetadata MIXIN_EXTRAS_DEPENDENCY = makeMixinExtraDependency();
|
||||
|
||||
private static ContainedJarMetadata makeMixinExtraDependency() {
|
||||
VersionRange range = null;
|
||||
try {
|
||||
range = VersionRange.createFromVersionSpec("[0,)");
|
||||
} catch (InvalidVersionSpecificationException e) {
|
||||
throw new RuntimeException("Failed to create version range for mixinextras-forge dependency", e);
|
||||
}
|
||||
|
||||
return new ContainedJarMetadata(
|
||||
new ContainedJarIdentifier("io.github.llamalad7", "mixinextras-forge"),
|
||||
new ContainedVersion(range, null),
|
||||
null, false
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "JarInJar";
|
||||
}
|
||||
|
||||
private int fsID = 0;
|
||||
private synchronized int nextId() {
|
||||
return fsID++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IModFile> scanMods(Iterable<IModFile> loadedMods) {
|
||||
var mods = new ArrayList<IModFile>();
|
||||
loadedMods.forEach(mods::add);
|
||||
final List<IModFile> sources = new ArrayList<>();
|
||||
loadedMods.forEach(sources::add);
|
||||
|
||||
var selector = new Selector(mods);
|
||||
var dependenciesToLoad = JarSelector.detectAndSelect(
|
||||
sources,
|
||||
this::loadResourceFromModFile,
|
||||
this::loadModFileFrom,
|
||||
this::identifyMod,
|
||||
this::exception
|
||||
);
|
||||
|
||||
// Add a synthetic Mixin-Extras dependency if we detect anyone using Mixin
|
||||
if (anyMixinsLoaded(mods)) {
|
||||
if (!selector.isRequired(MIXIN_EXTRAS_DEPENDENCY.identifier()))
|
||||
selector.addRequirement(MIXIN_EXTRAS_DEPENDENCY);
|
||||
} else if (selector.entries.size() == mods.size()) {
|
||||
if (dependenciesToLoad.isEmpty()) {
|
||||
LOGGER.info("No dependencies to load found. Skipping!");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
loadOptions();
|
||||
|
||||
// Ideally, the json would be in order so we only have to loop once. But its written this way just in case.
|
||||
var seen = new HashSet<Option>();
|
||||
var added = false;
|
||||
do {
|
||||
added = false;
|
||||
for (var entry : OPTIONS.values()) {
|
||||
if (seen.contains(entry) || !selector.isRequired(entry.meta.identifier()))
|
||||
continue;
|
||||
|
||||
seen.add(entry);
|
||||
|
||||
entry.addTo(selector);
|
||||
|
||||
if (entry.deps != null && !entry.deps.isEmpty()) {
|
||||
added = true;
|
||||
for (var dep : entry.deps)
|
||||
selector.addRequirement(dep);
|
||||
}
|
||||
}
|
||||
} while (added);
|
||||
|
||||
var selected = selector.select();
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
var ids = new TreeMap<String, List<Entry>>();
|
||||
for (var entry : selector.entries.values()) {
|
||||
if (entry != FAILED && entry.coord != null)
|
||||
ids.computeIfAbsent(entry.coord, id -> new ArrayList<>()).add(entry);
|
||||
}
|
||||
|
||||
for (var entry : ids.entrySet()) {
|
||||
LOGGER.info(MARKER, "JarJar Candidated for {}", entry.getKey());
|
||||
Collections.sort(entry.getValue());
|
||||
for (var option : entry.getValue())
|
||||
LOGGER.info(MARKER, "\t{}{}", selected.contains(option) ? '*' : ' ', option);
|
||||
}
|
||||
}
|
||||
|
||||
if (selected.isEmpty()) {
|
||||
LOGGER.info("No dependencies to load found. Skipping!");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/* Since Java 11, the JRE decompresses all zip files to memory when getting a ByteChannel for them.
|
||||
* JarJar only targets Minecraft 1.18+ which is Java17+ Which means we can exploit the fact that
|
||||
* these are Disjointed file systems, and close all unselected libraries.
|
||||
*
|
||||
* https://github.com/openjdk/jdk11u/blob/25a9b9e5594e88c9e1e66f95c7ca45d5a9da8854/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java#L703
|
||||
*
|
||||
* Combined with the fact that the JarJarFileSystem has always simply called into FileSystems.newFileSystem for its wrapped targets.
|
||||
* https://github.com/MinecraftForge/JarJar/blame/b39b2051439c81519d826d38898778bf3081356f/src/main/java/net/minecraftforge/jarjar/nio/pathfs/PathFileSystem.java#L38
|
||||
*
|
||||
* This effectively means that JarJar has ALWAYS been a wrapper around in-memory copies of every jar it scans.
|
||||
*
|
||||
* It also never closes these FileSystems, which potentially means they are leaked if the GC can't inherently GC them.
|
||||
*
|
||||
* Ideally we would extract these contained jars to a temp folder, so that we can use the nice optimizations Java has for ZipFile/JarFile.
|
||||
* But in order to do that we would need to write the file to a place that could potentially have conflicts.
|
||||
* To resolve those conflicts we should take a hint out of maven/gradle/minecraft's book and key the filepath based on a hash of the file being extracted.
|
||||
* However, because that hash is no available in existing metadata we would need to re-calculate it.
|
||||
*
|
||||
* To do it in an efficient way we I needed to be able to share the single opened FileSystem with both the SecureJar implementation, and with our recursive search.
|
||||
* So I use my new ReadOnlyInMemoryFileSystem which is specifically designed to use the same backing array for the JarInJar search as it does with the resulting dependency
|
||||
* selected SecureJars.
|
||||
*
|
||||
* This means that if we wanted to we could rather efficiently calculate a hash for extracting the file. However, it also means that we don't have to extract to file to
|
||||
* support ZipFileSystem caching properly. So as long as we are okay with not using the JRE's optimized ZipFile implementation, or we want to support legacy JarInJar metadata,
|
||||
* using this efficient in memory file system is the best option performance wise. Extracting the files during launch without an efficient caching/unique identifier system
|
||||
* would incur a lot of Disc IO which can be super slow depending on your setup.
|
||||
*
|
||||
* So, it is a stated goal of our future re-write to extract files into a cache directory to gain the benefits of JRE optimizations, using a FileSystem much simpler then
|
||||
* the existing JarInJarFileSystem is good enough.
|
||||
*/
|
||||
|
||||
// Lets close all unselected FileSystems
|
||||
var wasSelected = new HashSet<>(selected);
|
||||
for (var entry : selector.entries.values())
|
||||
entry.cleanup(wasSelected.contains(entry));
|
||||
|
||||
LOGGER.info(MARKER, "Found {} dependencies adding them to mods collection", selected.size());
|
||||
var ret = new ArrayList<IModFile>(selected.size());
|
||||
|
||||
// Now lets build the actual mod files
|
||||
for (var entry : selected) {
|
||||
try {
|
||||
var root = entry.getFinalPath();
|
||||
var mod = createMod(root, false, entry.type.name());
|
||||
|
||||
if (mod.ex() != null)
|
||||
throw fail("Failed to load JarInJar file " + entry, mod.ex());
|
||||
|
||||
ret.add(mod.file());
|
||||
} catch (EarlyLoadingException e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
throw fail("Failed to load JarInJar file " + entry, e);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static Boolean anyMixinsLoaded = null;
|
||||
private static boolean anyMixinsLoaded(List<IModFile> mods) {
|
||||
var ret = anyMixinsLoaded;
|
||||
if (ret != null) return ret;
|
||||
|
||||
if (!FMLEnvironment.production
|
||||
&& Launcher.INSTANCE.blackboard().get(TypesafeMap.Key.getOrCreate(Launcher.INSTANCE.blackboard(), "isMixinEnabledInDev", Boolean.class)).orElse(false)) {
|
||||
anyMixinsLoaded = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
for (var mod : mods) {
|
||||
var attributes = mod.getSecureJar().moduleDataProvider().getManifest().getMainAttributes();
|
||||
if (attributes.getValue(MIXIN_CONFIGS_ATTR) != null || attributes.getValue(MIXIN_CONNECTOR_ATTR) != null) {
|
||||
anyMixinsLoaded = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
anyMixinsLoaded = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
private record Options(List<OptionMetadata> options) {}
|
||||
private record OptionMetadata(String resource, Type layer, String id, List<ContainedJarMetadata> deps, ContainedJarMetadata meta, boolean nested) {}
|
||||
|
||||
private static void loadOptions() {
|
||||
if (optionsLoaded)
|
||||
return;
|
||||
|
||||
synchronized (OPTIONS) {
|
||||
if (optionsLoaded)
|
||||
return;
|
||||
|
||||
optionsLoaded = true;
|
||||
|
||||
var self = JarInJarDependencyLocator.class;
|
||||
|
||||
try (var stream = self.getModule().getResourceAsStream("/jarjar_options.json")) {
|
||||
if (stream == null) {
|
||||
LOGGER.error(MARKER, "Failed to find jarjar_options.json");
|
||||
return;
|
||||
}
|
||||
|
||||
var meta = MetadataIOHandler.getGson().fromJson(new InputStreamReader(stream), Options.class);
|
||||
//var meta = MetadataIOHandler.fromStream(stream).orElse(null);
|
||||
if (meta == null) {
|
||||
LOGGER.error(MARKER, "Corrupted jarjar_options.json");
|
||||
return;
|
||||
}
|
||||
|
||||
for (var jar : meta.options()) {
|
||||
Path path = ClasspathLocator.getPathFromResource(self.getClassLoader(), jar.resource());
|
||||
if (path == null) {
|
||||
LOGGER.error(MARKER, "Failed to find JarJar option for {}", jar.resource());
|
||||
continue;
|
||||
}
|
||||
|
||||
LOGGER.debug(MARKER, "Found JarJar Option: {}", path.toAbsolutePath());
|
||||
if (jar.nested())
|
||||
OPTIONS.put(path, new NestedOption(jar.id(), path, jar.layer(), jar.meta(), jar.deps()));
|
||||
else
|
||||
OPTIONS.put(path, new Option(jar.id(), path, jar.layer(), jar.meta(), jar.deps()));
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
LOGGER.error(MARKER, "Failed to read JarJar Options file", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static boolean isOption(Path path) {
|
||||
loadOptions();
|
||||
return OPTIONS.containsKey(path);
|
||||
}
|
||||
|
||||
private static EarlyLoadingException fail(String message, @Nullable Throwable cause) {
|
||||
LOGGER.error(MARKER, message);
|
||||
return new EarlyLoadingException(message, cause, Collections.emptyList());
|
||||
LOGGER.info("Found {} dependencies adding them to mods collection", dependenciesToLoad.size());
|
||||
return dependenciesToLoad;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -305,7 +76,89 @@ public class JarInJarDependencyLocator extends AbstractModProvider implements ID
|
||||
return IModFile.Type.GAMELIBRARY.name();
|
||||
}
|
||||
|
||||
protected static String identifyMod(IModFile modFile) {
|
||||
protected Optional<IModFile> loadModFileFrom(IModFile file, 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("/");
|
||||
final IModFile.Type parentType = file.getType();
|
||||
final String modType;
|
||||
if (parentType == IModFile.Type.LIBRARY || parentType == IModFile.Type.LANGPROVIDER) {
|
||||
modType = IModFile.Type.LIBRARY.name();
|
||||
} else {
|
||||
modType = IModFile.Type.GAMELIBRARY.name();
|
||||
}
|
||||
return Optional.of(createMod(pathInFS, false, modType).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(JarInJarDependencyLocator::buildExceptionData)
|
||||
.toList();
|
||||
|
||||
return new EarlyLoadingException(failedDependencies.size() + " Dependency restrictions were not met.", null, errors);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static EarlyLoadingException.ExceptionData buildExceptionData(JarSelector.ResolutionFailureInformation<IModFile> entry) {
|
||||
return new EarlyLoadingException.ExceptionData(
|
||||
getErrorTranslationKey(entry),
|
||||
entry.identifier().group() + ":" + entry.identifier().artifact(),
|
||||
entry.sources()
|
||||
.stream()
|
||||
.flatMap(JarInJarDependencyLocator::getModWithVersionRangeStream)
|
||||
.map(JarInJarDependencyLocator::formatError)
|
||||
.collect(Collectors.joining(", "))
|
||||
);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String getErrorTranslationKey(JarSelector.ResolutionFailureInformation<IModFile> entry) {
|
||||
return entry.failureReason() == JarSelector.FailureReason.VERSION_RESOLUTION_FAILED ?
|
||||
"fml.dependencyloading.conflictingdependencies" :
|
||||
"fml.dependencyloading.mismatchedcontaineddependencies";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Stream<ModWithVersionRange> getModWithVersionRangeStream(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()));
|
||||
}
|
||||
|
||||
protected Optional<InputStream> loadResourceFromModFile(IModFile modFile, Path path) {
|
||||
try {
|
||||
var pathInModFile = modFile.findResource(path.toString());
|
||||
if (!Files.exists(pathInModFile)) {
|
||||
LOGGER.debug("Failed to load resource {} from {}, it does not contain dependency information.", path, modFile.getFileName());
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(Files.newInputStream(pathInModFile));
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to load resource {} from mod {}, cause {}", path, modFile.getFileName(), e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String formatError(ModWithVersionRange modWithVersionRange){
|
||||
return YELLOW + modWithVersionRange.modInfo().getModId() + RESET + " - " +
|
||||
RED + modWithVersionRange.versionRange().toString() + RESET + " - " +
|
||||
GREEN + modWithVersionRange.artifactVersion().toString() + RESET;
|
||||
}
|
||||
|
||||
protected String identifyMod(IModFile modFile) {
|
||||
if (modFile.getModFileInfo() != null && !modFile.getModInfos().isEmpty())
|
||||
return modFile.getModInfos().stream().map(IModInfo::getModId).collect(Collectors.joining());
|
||||
|
||||
@@ -316,377 +169,5 @@ public class JarInJarDependencyLocator extends AbstractModProvider implements ID
|
||||
return modFile.getFileName();
|
||||
}
|
||||
|
||||
private static final Entry FAILED = new Entry("FAILED ENTRY", Path.of(""), Type.LIBRARY, null);
|
||||
private static sealed class Entry implements Comparable<Entry> {
|
||||
final String id;
|
||||
final Path path;
|
||||
final Type type;
|
||||
final @Nullable String coord;
|
||||
|
||||
private Entry(String id, Path path, Type type, @Nullable String coord) {
|
||||
this.id = id;
|
||||
this.path = path;
|
||||
this.type = type;
|
||||
this.coord = coord;
|
||||
}
|
||||
|
||||
Path getFinalPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
void cleanup(boolean selected) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (path.getFileName() == null)
|
||||
return path.toUri().toString();
|
||||
return path.getFileName().toString();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Path getResource(String path) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Entry o) {
|
||||
return o.toString().compareTo(toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ModEntry extends Entry {
|
||||
private final IModFile mod;
|
||||
|
||||
ModEntry(IModFile mod) {
|
||||
super(identifyMod(mod), mod.getFilePath(), mod.getType(), null);
|
||||
this.mod = mod;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
Path getResource(String path) {
|
||||
return mod.findResource(path);
|
||||
}
|
||||
}
|
||||
|
||||
private static sealed class Option extends Entry {
|
||||
final ContainedJarMetadata meta;
|
||||
final List<ContainedJarMetadata> deps;
|
||||
|
||||
private Option(String id, Path path, Type type, ContainedJarMetadata meta, List<ContainedJarMetadata> deps) {
|
||||
super(id, path, type, meta.identifier().group() + ':' + meta.identifier().artifact());
|
||||
this.meta = meta;
|
||||
this.deps = deps;
|
||||
}
|
||||
|
||||
public void addTo(Selector selector) {
|
||||
selector.option(this, meta);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class NestedOption extends Option {
|
||||
private FileSystem zip;
|
||||
private Path root;
|
||||
|
||||
private NestedOption(String id, Path path, Type type, ContainedJarMetadata meta, List<ContainedJarMetadata> deps) {
|
||||
super(id, path, type, meta, deps);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTo(Selector selector) {
|
||||
super.addTo(selector);
|
||||
selector.add(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
void cleanup(boolean selected) {
|
||||
if (this.zip != null) {
|
||||
try {
|
||||
zip.close();
|
||||
} catch (IOException e) {
|
||||
LOGGER.error(MARKER, "Failed to close unselected FileSystem {}", this, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Path getResource(String path) {
|
||||
if (zip == null) {
|
||||
try {
|
||||
this.zip = FileSystems.newFileSystem(this.path);
|
||||
this.root = zip.getRootDirectories().iterator().next();
|
||||
} catch (IOException e) {
|
||||
LOGGER.error(MARKER, "Failed to open FileSystem for option root {}", this, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
var target = root.resolve(path);
|
||||
if (Files.exists(target))
|
||||
return target;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static final class Nested extends Entry {
|
||||
private final Entry parent;
|
||||
private final String nestedPath;
|
||||
private final Path zipPath;
|
||||
|
||||
Nested(String id, Path path, Type type, String coords, Entry parent, String nestedPath, Path zipPath) {
|
||||
super(id, path, type, coords);
|
||||
this.parent = parent;
|
||||
this.nestedPath = nestedPath;
|
||||
this.zipPath = zipPath;
|
||||
}
|
||||
|
||||
/*
|
||||
* Due to a bug in Mixin, anything that is not on the DefaultFileSystem MUST be a directory.
|
||||
* Or else it can't resolve resources correctly. So for anything we put on the roimfs we need
|
||||
* to return the nested zip file system.
|
||||
*
|
||||
* This will be an annoying to backport, but we can cross that bridge when we come to it.
|
||||
*
|
||||
* See: https://github.com/SpongePowered/Mixin/blob/4053421aa10aaac6127d969028a29c94fe3054f6/src/main/java/org/spongepowered/asm/launch/platform/MainAttributes.java#L102
|
||||
*/
|
||||
@Override
|
||||
public Path getFinalPath() {
|
||||
return zipPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
void cleanup(boolean selected) {
|
||||
if (selected)
|
||||
return;
|
||||
|
||||
LOGGER.info(MARKER, "Closeing unselected FileSystem {}", this);
|
||||
try {
|
||||
zipPath.getFileSystem().close();
|
||||
path.getFileSystem().close();
|
||||
} catch (IOException e) {
|
||||
LOGGER.error(MARKER, "Failed to close unselected FileSystem {}", this, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
Path getResource(String path) {
|
||||
return zipPath.resolve(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return parent + "!/" + nestedPath;
|
||||
}
|
||||
}
|
||||
|
||||
private record Key(Entry parent, String path) {}
|
||||
|
||||
private final class Selector extends JarSelector<Entry> {
|
||||
private final HashMap<Key, Entry> entries = new HashMap<>();
|
||||
private final HashMap<Entry, HashMap<String, String>> children = new HashMap<>();
|
||||
|
||||
private Selector(Iterable<IModFile> mods) {
|
||||
for (var mod : mods)
|
||||
this.force(new ModEntry(mod));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void force(Entry entry) {
|
||||
super.force(entry);
|
||||
entries.put(new Key(entry, ""), entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void option(Entry entry, ContainedJarMetadata meta) {
|
||||
super.option(entry, meta);
|
||||
entries.put(new Key(entry, ""), entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(Entry entry) {
|
||||
super.add(entry);
|
||||
entries.put(new Key(entry, ""), entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
protected InputStream getResource(Entry source, String path) {
|
||||
try {
|
||||
Path target = source.getResource(path);
|
||||
if (target == null || !Files.exists(target)) {
|
||||
LOGGER.debug(MARKER, "Failed to load resource {} from {}, it does not contain dependency information.", path, source);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Read the metadata file and store identifiers so we can group them later.
|
||||
if (JarSelector.CONTAINED_JARS_METADATA_PATH.equals(path)) {
|
||||
try (var is = Files.newInputStream(target)) {
|
||||
var meta = MetadataIOHandler.fromStream(is).orElse(null);
|
||||
if (meta == null)
|
||||
return Files.newInputStream(target);
|
||||
var children = new HashMap<String, String>();
|
||||
for (var child : meta.jars())
|
||||
children.put(child.path().replace('\\', '/'), child.identifier().group() + ':' + child.identifier().artifact());
|
||||
this.children.put(source, children);
|
||||
}
|
||||
}
|
||||
|
||||
return Files.newInputStream(target);
|
||||
} catch (Exception e) {
|
||||
LOGGER.error(MARKER, "Failed to load resource {} from {}, cause {}", path, source, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Entry getNested(Entry source, String path) {
|
||||
var cleaned = path.replace('\\', '/');
|
||||
var key = new Key(source, cleaned);
|
||||
var entry = entries.get(key);
|
||||
if (entry == null) {
|
||||
entry = createNested(source, path, cleaned);
|
||||
if (entry == null)
|
||||
entry = FAILED;
|
||||
entries.put(key, entry);
|
||||
}
|
||||
return entry == FAILED ? null : entry;
|
||||
}
|
||||
|
||||
private Entry createNested(Entry source, String path, String cleaned) {
|
||||
var targetPath = source.getResource(path);
|
||||
|
||||
if (targetPath == null || !Files.isRegularFile(targetPath))
|
||||
return null;
|
||||
|
||||
Path roimfsPath = null;
|
||||
Path zipPath = null;
|
||||
try {
|
||||
// Lets copy the uncompressed file to memory, and then create a ReadOnlyInMemorryFileSystem for it, this allows us to reference it by URI.
|
||||
// Which is needed because a lot of things require navigation via URI.
|
||||
// And ZipFileSystem only caches the FileSystem instance when accessed via URI.
|
||||
byte[] data = Files.readAllBytes(targetPath);
|
||||
String filename = targetPath.getFileName().toString();
|
||||
URI uri = new URI(ROIMFS, "jar-jar-" + nextId() + "/" + filename, null);
|
||||
var roimfs = FileSystems.newFileSystem(uri, Collections.singletonMap("data", data));
|
||||
roimfsPath = roimfs.getPath(filename);
|
||||
var zip = FileSystems.newFileSystem(new URI("jar:" + roimfsPath.toUri()), Collections.emptyMap());
|
||||
zipPath = zip.getRootDirectories().iterator().next(); // Get first root, which on ZipFileSystem is the only root
|
||||
} catch (IOException | URISyntaxException e) {
|
||||
LOGGER.error(MARKER, "Failed to create FileSystem for nested jar {}", source, e);
|
||||
return null;
|
||||
}
|
||||
|
||||
var mf = new Manifest();
|
||||
var mfPath = zipPath.resolve(JarFile.MANIFEST_NAME);
|
||||
if (Files.exists(mfPath)) {
|
||||
try (var is = Files.newInputStream(mfPath)) {
|
||||
mf = new Manifest(is);
|
||||
} catch (IOException e) {
|
||||
LOGGER.error(MARKER, "Failed to read manifest from {}", source, e);
|
||||
}
|
||||
}
|
||||
|
||||
IModFile.Type type = null;
|
||||
String id = null;
|
||||
var tomlPath = zipPath.resolve(MODS_TOML);
|
||||
if (Files.exists(tomlPath)) {
|
||||
var cfg = FileConfig.builder(tomlPath).build();
|
||||
cfg.load();
|
||||
cfg.close();
|
||||
if (cfg.getOrElse("mods", null) instanceof Collection collection) {
|
||||
@SuppressWarnings("unchecked")
|
||||
var mods = (Collection<UnmodifiableConfig>)collection;
|
||||
if (!mods.isEmpty()) {
|
||||
var first = mods.iterator().next();
|
||||
var modId = first.getOrElse("modId", null);
|
||||
if (modId instanceof String str)
|
||||
id = str;
|
||||
}
|
||||
}
|
||||
var value = mf.getMainAttributes().getValue(ModFile.TYPE);
|
||||
if (value != null)
|
||||
type = IModFile.Type.valueOf(value);
|
||||
else
|
||||
type = IModFile.Type.MOD;
|
||||
} else {
|
||||
var value = mf.getMainAttributes().getValue(ModFile.TYPE);
|
||||
if (value != null)
|
||||
type = IModFile.Type.valueOf(value);
|
||||
else {
|
||||
// No mods.toml, no entry value, so decide based on the container's type
|
||||
if (source.type == IModFile.Type.LIBRARY || source.type == IModFile.Type.LANGPROVIDER)
|
||||
type = IModFile.Type.LIBRARY;
|
||||
else
|
||||
type = IModFile.Type.GAMELIBRARY;
|
||||
}
|
||||
}
|
||||
|
||||
if (id == null) {
|
||||
var modulePath = zipPath.resolve(MODULE_INFO);
|
||||
if (Files.exists(modulePath)) {
|
||||
try (var is = Files.newInputStream(modulePath)) {
|
||||
var module = ModuleDescriptor.read(is);
|
||||
id = module.name();
|
||||
} catch (IOException e) {
|
||||
LOGGER.error(MARKER, "Failed to read module-info.class from {}", source, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (id == null)
|
||||
id = mf.getMainAttributes().getValue("Automatic-Module-Name");
|
||||
|
||||
if (id == null)
|
||||
id = Path.of(path).getFileName().toString();
|
||||
|
||||
String coords = null;
|
||||
var child = this.children.get(source);
|
||||
if (child != null)
|
||||
coords = child.get(cleaned);
|
||||
|
||||
return new Nested(id, roimfsPath, type, coords, source, cleaned, zipPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getIdentifier(Entry entry) {
|
||||
return entry.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Throwable getFailureException(Collection<ResolutionFailureInformation<Entry>> failures) {
|
||||
var errors = new ArrayList<ExceptionData>();
|
||||
for (var failure : failures) {
|
||||
var message = failure.failureReason() == JarSelector.FailureReason.VERSION_RESOLUTION_FAILED ? "fml.dependencyloading.conflictingdependencies" : "fml.dependencyloading.mismatchedcontaineddependencies";
|
||||
var joiner = new StringJoiner(", ");
|
||||
for (var source : failure.sources()) {
|
||||
String paths = null;
|
||||
if (source.sources().size() == 0) {
|
||||
paths = "[No Sources]";
|
||||
} else if (source.sources().size() == 1) {
|
||||
var entry = source.sources().iterator().next();
|
||||
paths = entry.toString();
|
||||
} else {
|
||||
var j = new StringJoiner(", ", "[", "]");
|
||||
for (var entry : source.sources())
|
||||
j.add(entry.toString());
|
||||
paths = j.toString();
|
||||
}
|
||||
|
||||
var error = YELLOW + paths + RESET + " - " +
|
||||
RED + source.requestedVersionRange() + RESET + " - " +
|
||||
GREEN + source.includedVersion() + RESET;
|
||||
joiner.add(error);
|
||||
}
|
||||
|
||||
errors.add(new ExceptionData(message, failure.identifier().group() + ":" + failure.identifier().artifact(), joiner.toString()));
|
||||
}
|
||||
|
||||
return new EarlyLoadingException(failures.size() + " Dependency restrictions were not met.", null, errors);
|
||||
}
|
||||
}
|
||||
private record ModWithVersionRange(IModInfo modInfo, VersionRange versionRange, ArtifactVersion artifactVersion) {}
|
||||
}
|
||||
+4
-4
@@ -43,10 +43,10 @@ public final class MinecraftLocator extends AbstractModProvider implements IModL
|
||||
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", "The base Minecraft game, published by Mojang decompiled and deobfuscated by Forge");
|
||||
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));
|
||||
|
||||
var configWrapper = new NightConfigWrapper(conf);
|
||||
|
||||
+35
-27
@@ -5,6 +5,7 @@
|
||||
|
||||
package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import cpw.mods.modlauncher.Launcher;
|
||||
import cpw.mods.modlauncher.api.IModuleLayerManager;
|
||||
@@ -34,36 +35,40 @@ import java.util.ServiceLoader;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public final class ModDiscoverer {
|
||||
private ModDiscoverer() {}
|
||||
|
||||
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;
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
public static ModValidator discoverMods(Map<String, ?> arguments) {
|
||||
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();
|
||||
ServiceLoader<IModLocator> modLocators = ServiceLoader.load(moduleLayerManager.getLayer(IModuleLayerManager.Layer.SERVICE).orElseThrow(), IModLocator.class);
|
||||
ServiceLoader<IDependencyLocator> dependencyLocators = ServiceLoader.load(moduleLayerManager.getLayer(IModuleLayerManager.Layer.SERVICE).orElseThrow(), IDependencyLocator.class);
|
||||
List<IModLocator> modLocatorList = ServiceLoaderUtils.streamWithErrorHandling(modLocators, sce -> LOGGER.error("Failed to load mod locator list", sce)).toList();
|
||||
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());
|
||||
for (IModLocator iModLocator : modLocatorList) {
|
||||
iModLocator.initArguments(arguments);
|
||||
}
|
||||
List<IDependencyLocator> dependencyLocatorList = ServiceLoaderUtils.streamWithErrorHandling(dependencyLocators, sce -> LOGGER.error("Failed to load dependency locator list", sce)).toList();
|
||||
dependencyLocatorList = ServiceLoaderUtils.streamServiceLoader(()-> dependencyLocators, sce->LOGGER.error("Failed to load dependency locator list", sce)).collect(Collectors.toList());
|
||||
for (IDependencyLocator l : dependencyLocatorList) {
|
||||
l.initArguments(arguments);
|
||||
}
|
||||
if (LOGGER.isDebugEnabled(LogMarkers.CORE)) {
|
||||
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(",")));
|
||||
.map(modLocator -> "(%s:%s)".formatted(modLocator.name(),
|
||||
modLocator.getClass().getPackage().getImplementationVersion())).collect(Collectors.joining(",")));
|
||||
|
||||
LOGGER.debug(LogMarkers.CORE, "Found Dependency Locators : {}", dependencyLocatorList.stream()
|
||||
.map(dependencyLocator -> "(%s:%s)".formatted(dependencyLocator.name(),
|
||||
dependencyLocator.getClass().getPackage().getImplementationVersion())).collect(Collectors.joining(",")));
|
||||
.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<>();
|
||||
@@ -111,7 +116,7 @@ public final class ModDiscoverer {
|
||||
}
|
||||
|
||||
LOGGER.debug(LogMarkers.SCAN, "Locator {} found {} valid mod files", locator, locatedFiles.size());
|
||||
handleLocatedFiles(loadedFiles, locatedFiles, locator);
|
||||
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);
|
||||
@@ -125,7 +130,8 @@ public final class ModDiscoverer {
|
||||
//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 = new EnumMap<>(IModFile.Type.class);
|
||||
try {
|
||||
final UniqueModListBuilder.UniqueModListData uniqueModsData = UniqueModListBuilder.buildUniqueList(loadedFiles);
|
||||
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.
|
||||
@@ -145,10 +151,14 @@ public final class ModDiscoverer {
|
||||
for (IDependencyLocator locator : dependencyLocatorList) {
|
||||
try {
|
||||
LOGGER.debug(LogMarkers.SCAN,"Trying locator {}", locator);
|
||||
final List<IModFile> locatedMods = List.copyOf(loadedFiles);
|
||||
final List<IModFile> locatedMods = ImmutableList.copyOf(loadedFiles);
|
||||
|
||||
var locatedFiles = locator.scanMods(locatedMods);
|
||||
handleLocatedFiles(loadedFiles, locatedFiles, locator);
|
||||
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);
|
||||
@@ -158,7 +168,8 @@ public final class ModDiscoverer {
|
||||
|
||||
//Second processing run of the mod list. Any duplicates will cause resolution failure and only the mods list will be loaded.
|
||||
try {
|
||||
final UniqueModListBuilder.UniqueModListData uniqueModsAndDependenciesData = UniqueModListBuilder.buildUniqueList(loadedFiles);
|
||||
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()
|
||||
@@ -181,14 +192,11 @@ public final class ModDiscoverer {
|
||||
return validator;
|
||||
}
|
||||
|
||||
private static void handleLocatedFiles(final List<ModFile> loadedFiles, final List<IModFile> locatedFiles, final Object locator) {
|
||||
for (IModFile mf : locatedFiles) {
|
||||
if (mf instanceof ModFile modFile) {
|
||||
LOGGER.info(LogMarkers.SCAN, "Found mod file {} of type {} with provider {}", mf.getFileName(), mf.getType(), mf.getProvider());
|
||||
loadedFiles.add(modFile);
|
||||
} else {
|
||||
LOGGER.error(LogMarkers.SCAN, "Skipping mod file {} found by {}, as it was not a ModFile instance!", mf.getFileName(), locator);
|
||||
}
|
||||
private static 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);
|
||||
}
|
||||
}
|
||||
@@ -21,13 +21,13 @@ import org.apache.maven.artifact.versioning.ArtifactVersion;
|
||||
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
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;
|
||||
@@ -39,15 +39,17 @@ import java.util.jar.Manifest;
|
||||
public class ModFile implements IModFile {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
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 IModProvider provider;
|
||||
private final IModFileInfo modFileInfo;
|
||||
private IModFileInfo modFileInfo;
|
||||
private ModFileScanData fileModFileScanData;
|
||||
private CompletableFuture<ModFileScanData> futureScanResult;
|
||||
private List<CoreModFile> coreMods;
|
||||
private Path accessTransformer;
|
||||
private List<Path> accessTransformers = Collections.emptyList();
|
||||
|
||||
@@ -61,11 +63,12 @@ public class ModFile implements IModFile {
|
||||
public ModFile(final SecureJar jar, final IModProvider provider, final ModFileFactory.ModFileInfoParser parser, String type) {
|
||||
this.provider = provider;
|
||||
this.jar = jar;
|
||||
this.parser = parser;
|
||||
|
||||
var 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, parser);
|
||||
this.modFileInfo = ModFileParser.readModList(this, this.parser);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -92,22 +95,31 @@ public class ModFile implements IModFile {
|
||||
return modFileInfo.getMods();
|
||||
}
|
||||
|
||||
@Deprecated(forRemoval = true, since = "1.21.11")
|
||||
/** @deprecated Use {@link #getAccessTransformers()} instead*/
|
||||
@Deprecated(forRemoval=true, since="1.21.11")
|
||||
public Optional<Path> getAccessTransformer() {
|
||||
return Optional.ofNullable(accessTransformer);
|
||||
}
|
||||
|
||||
public List<Path> getAccessTransformers() {
|
||||
return this.accessTransformers;
|
||||
return accessTransformers;
|
||||
}
|
||||
|
||||
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.getFileName(), this.modFileInfo.requiredLanguageLoaders());
|
||||
var cfg = this.modFileInfo.getConfig()
|
||||
.<List<String>>getConfigElement("accessTransformers")
|
||||
.orElse(null);
|
||||
|
||||
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()));
|
||||
List<String> cfg;
|
||||
// Note: Some mods may have invalid tomls copied from other projects.
|
||||
// Unfortunately we must protect against those landmines.
|
||||
try {
|
||||
cfg = this.modFileInfo.getConfig().<List<String>>getConfigElement("accessTransformers").orElse(null);
|
||||
} catch (Exception e) {
|
||||
cfg = null;
|
||||
LOGGER.warn("{} contains an invalid 'accessTransformers' TOML entry. Should be e.g. accessTransformers = [\"META-INF/accesstransformer.cfg\", \"META-INF/extra_at.cfg\"] or accessTransformers = [] for no ATs. Falling back to default.", this.getFileName());
|
||||
}
|
||||
if (cfg == null) {
|
||||
var path = findResource("META-INF", "accesstransformer.cfg");
|
||||
if (Files.exists(path)) {
|
||||
@@ -116,14 +128,18 @@ public class ModFile implements IModFile {
|
||||
}
|
||||
} else if (!cfg.isEmpty()) {
|
||||
var paths = new ArrayList<Path>(cfg.size());
|
||||
for (var path : cfg)
|
||||
paths.add(getSecureJar().getPath(path.replace('\\', '/')));
|
||||
for (var path : cfg) {
|
||||
paths.add(getSecureJar().getPath(path.replace("\\","/")));
|
||||
}
|
||||
this.accessTransformers = List.copyOf(paths);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public List<CoreModFile> getCoreMods() {
|
||||
return coreMods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run in an executor thread to harvest the class and annotation list
|
||||
*/
|
||||
@@ -191,28 +207,12 @@ public class ModFile implements IModFile {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
var path = getFilePath();
|
||||
return "Mod File: " + path == null ? "null" : path.toUri().toString();
|
||||
return "Mod File: " + Objects.toString(this.jar.getPrimaryPath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFileName() {
|
||||
// This is only used for debugging, so lets try and make it a useful debug
|
||||
var path = getFilePath();
|
||||
var filename = path.getFileName();
|
||||
|
||||
// ZipFileSystem toString will return the outer file path
|
||||
var ret = filename == null ? path.getFileSystem().toString() : filename.toString();
|
||||
|
||||
// If it's a directory on the default file system, return the full path
|
||||
if (Files.isDirectory(path) && path.getFileSystem() == FileSystems.getDefault())
|
||||
ret = path.toAbsolutePath().toString();
|
||||
|
||||
// If we are in a FileSystem 'root' like UnionFS, return the full uri
|
||||
if (ret.isEmpty() || ret.equals("/"))
|
||||
ret = path.toUri().toString();
|
||||
|
||||
return ret;
|
||||
return getFilePath().getFileName().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+1
-1
@@ -264,7 +264,7 @@ public class ModFileInfo implements IModFileInfo, IConfigurable {
|
||||
|
||||
@Override
|
||||
public String versionString() {
|
||||
return getMods().getFirst().getVersion().toString();
|
||||
return getMods().get(0).getVersion().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+40
-4
@@ -6,16 +6,25 @@
|
||||
package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import com.electronwill.nightconfig.core.file.FileConfig;
|
||||
import com.electronwill.nightconfig.core.io.ParsingException;
|
||||
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 net.minecraftforge.forgespi.locating.ModFileLoadingException;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
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;
|
||||
|
||||
public class ModFileParser {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
@@ -24,6 +33,7 @@ public class ModFileParser {
|
||||
return parser.build(modFile);
|
||||
}
|
||||
|
||||
// Note: Although @Nullable, several other places in FML assume ModFileInfo is not null. Keep this in mind
|
||||
public static @Nullable IModFileInfo modsTomlParser(final IModFile imodFile) {
|
||||
ModFile modFile = (ModFile) imodFile;
|
||||
LOGGER.debug(LogMarkers.LOADING,"Considering mod file candidate {}", modFile.getFilePath());
|
||||
@@ -34,9 +44,35 @@ public class ModFileParser {
|
||||
}
|
||||
|
||||
final FileConfig fileConfig = FileConfig.builder(modsjson).build();
|
||||
fileConfig.load();
|
||||
fileConfig.close();
|
||||
final NightConfigWrapper configWrapper = new NightConfigWrapper(fileConfig);
|
||||
return new ModFileInfo(modFile, configWrapper, configWrapper::setFile);
|
||||
try {
|
||||
fileConfig.load();
|
||||
fileConfig.close();
|
||||
final NightConfigWrapper configWrapper = new NightConfigWrapper(fileConfig);
|
||||
return new ModFileInfo(modFile, configWrapper, configWrapper::setFile);
|
||||
} catch (ParsingException e) {
|
||||
LOGGER.error("Mod candidate {} contains a corrupt or misconfigured toml.", modFile.getFileName());
|
||||
throw new ModFileLoadingException("Mod candidate " + modFile.getFileName() + " contains a corrupt or misconfigured toml.");
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
+5
-4
@@ -34,6 +34,7 @@ public class ModValidator {
|
||||
private LoadingModList loadingModList;
|
||||
private final List<IModFile> brokenFiles = new ArrayList<>();
|
||||
private final List<EarlyLoadingException.ExceptionData> discoveryErrorData;
|
||||
private final List<ModFileLoadingException> modFileLoadingExceptions;
|
||||
|
||||
public ModValidator(Map<IModFile.Type, List<ModFile>> modFiles, List<IModFileInfo> brokenFiles, List<EarlyLoadingException.ExceptionData> discoveryErrorData, List<ModFileLoadingException> modFileLoadingExceptions) {
|
||||
this.candidateMods = lst(modFiles.get(IModFile.Type.MOD));
|
||||
@@ -43,6 +44,7 @@ public class ModValidator {
|
||||
this.candidatePlugins.addAll(lst(modFiles.get(IModFile.Type.LIBRARY)));
|
||||
this.discoveryErrorData = discoveryErrorData;
|
||||
this.brokenFiles.addAll(brokenFiles.stream().map(IModFileInfo::getFile).toList());
|
||||
this.modFileLoadingExceptions = modFileLoadingExceptions;
|
||||
}
|
||||
|
||||
public ModValidator(Map<IModFile.Type, List<ModFile>> modFiles, List<IModFileInfo> brokenFiles, List<EarlyLoadingException.ExceptionData> discoveryErrorData) {
|
||||
@@ -50,9 +52,7 @@ public class ModValidator {
|
||||
}
|
||||
|
||||
private static List<ModFile> lst(List<ModFile> files) {
|
||||
if (files == null) return new ArrayList<>();
|
||||
if (files instanceof ArrayList) return files;
|
||||
return new ArrayList<>(files);
|
||||
return files == null ? new ArrayList<>() : new ArrayList<>(files);
|
||||
}
|
||||
|
||||
public void stage1Validation() {
|
||||
@@ -60,7 +60,7 @@ public class ModValidator {
|
||||
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");
|
||||
ImmediateWindowHandler.updateProgress("Found "+candidateMods.size()+" mod candidates");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -125,6 +125,7 @@ public class ModValidator {
|
||||
allErrors.addAll(this.discoveryErrorData);
|
||||
|
||||
loadingModList = ModSorter.sort(candidateMods, allErrors);
|
||||
loadingModList.addCoreMods();
|
||||
loadingModList.addAccessTransformers();
|
||||
loadingModList.setBrokenFiles(brokenFiles);
|
||||
BackgroundScanHandler backgroundScanHandler = new BackgroundScanHandler(candidateMods);
|
||||
|
||||
+1
@@ -17,6 +17,7 @@ import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
|
||||
@@ -81,10 +81,6 @@ class ArgumentList {
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
|
||||
public boolean has(String key) {
|
||||
return this.values.containsKey(key);
|
||||
}
|
||||
|
||||
public boolean hasValue(String key) {
|
||||
return getOrDefault(key, null) != null;
|
||||
}
|
||||
|
||||
+5
-30
@@ -22,10 +22,6 @@ abstract class CommonDevLaunchHandler extends CommonLaunchHandler {
|
||||
super(type, prefix);
|
||||
}
|
||||
|
||||
protected CommonDevLaunchHandler(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
@Override public String getNaming() { return "mcp"; }
|
||||
@Override public boolean isProduction() { return false; }
|
||||
|
||||
@@ -33,35 +29,13 @@ abstract class CommonDevLaunchHandler extends CommonLaunchHandler {
|
||||
protected String[] preLaunch(String[] arguments, ModuleLayer layer) {
|
||||
super.preLaunch(arguments, layer);
|
||||
|
||||
var args = ArgumentList.from(arguments);
|
||||
|
||||
if (this.module == null) {
|
||||
var entry = args.get("launchEntry");
|
||||
if (entry == null)
|
||||
throw new IllegalArgumentException("When using " + this.name() + " launch target you must specify a --launchEntry argument");
|
||||
|
||||
args.remove("launchEntry");
|
||||
|
||||
int idx = entry.indexOf('/');
|
||||
if (idx == -1) {
|
||||
this.module = "minecraft";
|
||||
this.main = entry;
|
||||
} else {
|
||||
this.module = entry.substring(0, idx);
|
||||
this.main = entry.substring(idx + 1);
|
||||
}
|
||||
|
||||
if (args.has("launchData")) {
|
||||
args.remove("launchData");
|
||||
this.isData = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (getDist().isDedicatedServer())
|
||||
return args.getArguments();
|
||||
return arguments;
|
||||
|
||||
if (isData())
|
||||
return args.getArguments();
|
||||
return arguments;
|
||||
|
||||
var args = ArgumentList.from(arguments);
|
||||
|
||||
String username = args.get("username");
|
||||
if (username != null) { // Replace '#' placeholders with random numbers
|
||||
@@ -156,6 +130,7 @@ abstract class CommonDevLaunchHandler extends CommonLaunchHandler {
|
||||
return new String[] {
|
||||
"net/minecraftforge/",
|
||||
"META-INF/services/",
|
||||
"META-INF/coremods.json",
|
||||
"META-INF/mods.toml"
|
||||
};
|
||||
}
|
||||
|
||||
+9
-20
@@ -29,27 +29,17 @@ import java.util.List;
|
||||
public abstract class CommonLaunchHandler implements ILaunchHandlerService {
|
||||
protected static final Logger LOGGER = LogUtils.getLogger();
|
||||
|
||||
private final String name;
|
||||
protected Dist dist = Dist.CLIENT;
|
||||
protected boolean isData = false;
|
||||
protected String module;
|
||||
protected String main;
|
||||
protected final LaunchType type;
|
||||
private final String prefix;
|
||||
|
||||
protected CommonLaunchHandler(LaunchType type, String prefix) {
|
||||
this(prefix + type.name());
|
||||
this.dist = type.dist();
|
||||
this.isData = type.data();
|
||||
this.module = type.module();
|
||||
this.main = type.main();
|
||||
this.type = type;
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
protected CommonLaunchHandler(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override public String name() { return this.name; }
|
||||
public Dist getDist() { return this.dist; }
|
||||
public boolean isData() { return this.isData; }
|
||||
@Override public String name() { return this.prefix + this.type.name(); }
|
||||
public Dist getDist() { return this.type.dist(); }
|
||||
public boolean isData() { return this.type.data(); }
|
||||
public boolean isProduction() { return false; }
|
||||
public abstract String getNaming();
|
||||
|
||||
@@ -76,15 +66,14 @@ public abstract class CommonLaunchHandler implements ILaunchHandlerService {
|
||||
}
|
||||
|
||||
protected ServiceRunner makeService(final String[] arguments, final ModuleLayer gameLayer) {
|
||||
return () -> runTarget(this.module, this.main, arguments, gameLayer);
|
||||
return () -> runTarget(this.type.module(), this.type.main(), arguments, gameLayer);
|
||||
}
|
||||
|
||||
protected record LaunchType(String name, String module, String main, Dist dist, boolean data) {};
|
||||
protected static final LaunchType CLIENT = new LaunchType("client", "minecraft", "net.minecraft.client.main.Main", Dist.CLIENT, false);
|
||||
protected static final LaunchType CLIENT_DATA = new LaunchType("client_data", "minecraft", "net.minecraft.client.data.Main", Dist.CLIENT, true);
|
||||
protected static final LaunchType DATA = new LaunchType("data", "minecraft", "net.minecraft.data.Main", Dist.CLIENT, true);
|
||||
protected static final LaunchType SERVER = new LaunchType("server", "minecraft", "net.minecraft.server.Main", Dist.DEDICATED_SERVER, false);
|
||||
protected static final LaunchType SERVER_GAMETEST = new LaunchType("server_gametest", "minecraft", "net.minecraft.gametest.Main", Dist.DEDICATED_SERVER, false);
|
||||
protected static final LaunchType SERVER_GAMETEST = new LaunchType("server_gametest", "net.minecraftforge.forge", "net.minecraftforge.gametest.GameTestMain", Dist.DEDICATED_SERVER, false);
|
||||
|
||||
protected void runTarget(String module, String target, final String[] arguments, final ModuleLayer layer) throws Throwable {
|
||||
var mod = layer.findModule(module).orElse(null);
|
||||
|
||||
+4
-18
@@ -14,14 +14,12 @@ sealed abstract class ForgeDevLaunchHandler extends CommonDevLaunchHandler {
|
||||
super(type, "forge_dev_");
|
||||
}
|
||||
|
||||
private ForgeDevLaunchHandler(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Path> getMinecraftPaths() {
|
||||
// The client extra jar is on the classpath we can try locating it using the .mcassetsroot which vanilla uses for loading data
|
||||
var extra = getPathFromResource("assets/.mcassetsroot");
|
||||
|
||||
// The extra jar is on the classpath, so try and pull it out of the legacy classpath
|
||||
var legacyCP = findClassPath();
|
||||
var extra = findJarOnClasspath(legacyCP, "client-extra");
|
||||
// Minecraft is an exploded directory, so find it.
|
||||
var minecraft = getPathFromResource("net/minecraft/client/Minecraft.class");
|
||||
var forge = getPathFromResource("net/minecraftforge/common/MinecraftForge.class");
|
||||
@@ -41,12 +39,6 @@ sealed abstract class ForgeDevLaunchHandler extends CommonDevLaunchHandler {
|
||||
}
|
||||
}
|
||||
|
||||
public static final class ClientData extends ForgeDevLaunchHandler {
|
||||
public ClientData() {
|
||||
super(CLIENT_DATA);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Data extends ForgeDevLaunchHandler {
|
||||
public Data() {
|
||||
super(DATA);
|
||||
@@ -64,10 +56,4 @@ sealed abstract class ForgeDevLaunchHandler extends CommonDevLaunchHandler {
|
||||
super(SERVER_GAMETEST);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Custom extends ForgeDevLaunchHandler {
|
||||
public Custom() {
|
||||
super("forge_dev");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,14 +96,14 @@ public final class ForgeDevLocator extends AbstractModProvider implements IModLo
|
||||
|
||||
// Find resource directories for every mod in this group
|
||||
var paths = new LinkedHashSet<Path>();
|
||||
for (var modid : modids.stream().sorted().toList()) {
|
||||
for (var modid : modids) {
|
||||
var rsc = path.resolve(modid);
|
||||
if (Files.exists(rsc))
|
||||
paths.add(rsc);
|
||||
}
|
||||
|
||||
var root = memory.getPath(modids.iterator().next()); // use the first modid as our root
|
||||
buildModsToml(paths, modids, root, pkg.startsWith("net/minecraftforge/debug/client/"));
|
||||
buildModsToml(paths, modids, root);
|
||||
buildPackMeta(paths, root);
|
||||
moveModuleInfo(paths, root);
|
||||
|
||||
@@ -187,7 +187,7 @@ public final class ForgeDevLocator extends AbstractModProvider implements IModLo
|
||||
}
|
||||
|
||||
// Builds or update the mods.toml file for all @Mods in this package
|
||||
private static void buildModsToml(Set<Path> resources, Set<String> modids, Path root, boolean clientOnly) {
|
||||
private static void buildModsToml(Set<Path> resources, Set<String> modids, Path root) {
|
||||
var toml = resources.stream()
|
||||
.map(p -> p.resolve(MODS_TOML))
|
||||
.filter(Files::exists)
|
||||
@@ -205,8 +205,7 @@ public final class ForgeDevLocator extends AbstractModProvider implements IModLo
|
||||
var defaults = Map.of(
|
||||
"modLoader", "javafml",
|
||||
"loaderVersion", "[0,)",
|
||||
"license", "Doesnt fucking matter",
|
||||
"clientSideOnly", clientOnly
|
||||
"license", "Doesnt fucking matter"
|
||||
);
|
||||
|
||||
for (var key : defaults.keySet()) {
|
||||
|
||||
+4
-17
@@ -8,18 +8,17 @@ package net.minecraftforge.fml.loading.targets;
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.function.BiPredicate;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import cpw.mods.jarhandling.SecureJar;
|
||||
|
||||
@ApiStatus.Internal
|
||||
sealed abstract class ForgeUserdevLaunchHandler extends CommonDevLaunchHandler {
|
||||
private ForgeUserdevLaunchHandler(LaunchType type) {
|
||||
super(type, "forge_userdev_");
|
||||
}
|
||||
|
||||
private ForgeUserdevLaunchHandler(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Path> getMinecraftPaths() {
|
||||
var legacyCP = findClassPath();
|
||||
@@ -27,7 +26,7 @@ sealed abstract class ForgeUserdevLaunchHandler extends CommonDevLaunchHandler {
|
||||
|
||||
// Minecraft is extra jar {resources} + forge jar {patches}
|
||||
// The MC extra and forge jars are on the classpath, so try and pull them out
|
||||
var extra = findJarOnClasspath(legacyCP, "client-extra"); // This should be "client-" + vers.mcAndMCPVersion() + "-extra" but FG6 qwerks
|
||||
var extra = findJarOnClasspath(legacyCP, "client-extra");
|
||||
var forge = findJarOnClasspath(legacyCP, "forge-" + vers.mcAndForgeVersion());
|
||||
// We need to filter the forge jar to just MC code
|
||||
var minecraft = CommonDevLaunchHandler.getMinecraftOnly(extra, forge);
|
||||
@@ -40,12 +39,6 @@ sealed abstract class ForgeUserdevLaunchHandler extends CommonDevLaunchHandler {
|
||||
}
|
||||
}
|
||||
|
||||
public static final class ClientData extends ForgeUserdevLaunchHandler {
|
||||
public ClientData() {
|
||||
super(CLIENT_DATA);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Data extends ForgeUserdevLaunchHandler {
|
||||
public Data() {
|
||||
super(DATA);
|
||||
@@ -63,10 +56,4 @@ sealed abstract class ForgeUserdevLaunchHandler extends CommonDevLaunchHandler {
|
||||
super(SERVER_GAMETEST);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Custom extends ForgeUserdevLaunchHandler {
|
||||
public Custom() {
|
||||
super("forge_userdev");
|
||||
}
|
||||
}
|
||||
}
|
||||
-4
@@ -4,16 +4,12 @@ net.minecraftforge.fml.loading.targets.ForgeProdLaunchHandler$Server
|
||||
|
||||
# Mod Developers
|
||||
net.minecraftforge.fml.loading.targets.ForgeUserdevLaunchHandler$Client
|
||||
net.minecraftforge.fml.loading.targets.ForgeUserdevLaunchHandler$ClientData
|
||||
net.minecraftforge.fml.loading.targets.ForgeUserdevLaunchHandler$Custom
|
||||
net.minecraftforge.fml.loading.targets.ForgeUserdevLaunchHandler$Data
|
||||
net.minecraftforge.fml.loading.targets.ForgeUserdevLaunchHandler$Server
|
||||
net.minecraftforge.fml.loading.targets.ForgeUserdevLaunchHandler$ServerGameTest
|
||||
|
||||
#Working on Forge itself
|
||||
net.minecraftforge.fml.loading.targets.ForgeDevLaunchHandler$Client
|
||||
net.minecraftforge.fml.loading.targets.ForgeDevLaunchHandler$ClientData
|
||||
net.minecraftforge.fml.loading.targets.ForgeDevLaunchHandler$Custom
|
||||
net.minecraftforge.fml.loading.targets.ForgeDevLaunchHandler$Data
|
||||
net.minecraftforge.fml.loading.targets.ForgeDevLaunchHandler$Server
|
||||
net.minecraftforge.fml.loading.targets.ForgeDevLaunchHandler$ServerGameTest
|
||||
+1
@@ -1,4 +1,5 @@
|
||||
net.minecraftforge.fml.loading.log4j.SLF4JFixerLaunchPluginService
|
||||
net.minecraftforge.fml.loading.RuntimeDistCleaner
|
||||
net.minecraftforge.fml.common.asm.RuntimeEnumExtender
|
||||
net.minecraftforge.fml.common.asm.ObjectHolderDefinalize
|
||||
net.minecraftforge.fml.common.asm.CapabilityTokenSubclass
|
||||
@@ -1,35 +0,0 @@
|
||||
{
|
||||
"options": [
|
||||
{
|
||||
"resource": "com/llamalad7/mixinextras/platform/forge/MixinExtrasConfigPlugin.class",
|
||||
"layer": "GAMELIBRARY",
|
||||
"id": "mixinextras",
|
||||
"deps": [
|
||||
{
|
||||
"identifier": {
|
||||
"group": "com.github.LlamaLad7",
|
||||
"artifact": "MixinExtras"
|
||||
},
|
||||
"version": {
|
||||
"range": "[0.5.3,)",
|
||||
"artifactVersion": "0.5.3"
|
||||
},
|
||||
"path": "",
|
||||
"isObfuscated": false
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"identifier": {
|
||||
"group": "io.github.llamalad7",
|
||||
"artifact": "mixinextras-forge"
|
||||
},
|
||||
"version": {
|
||||
"artifactVersion": "0.5.3"
|
||||
},
|
||||
"path": "",
|
||||
"isObfuscated": false
|
||||
},
|
||||
"nested": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
SPDX-License-Identifier: LGPL-2.1-only
|
||||
-->
|
||||
|
||||
<Configuration status="warn" shutdownHook="disable">
|
||||
<Configuration status="warn" packages="net.minecrell.terminalconsole,net.minecrell.terminalconsole.util,com.mojang.util,cpw.mods.modlauncher.log,net.minecraftforge.fml.loading.log4j" shutdownHook="disable">
|
||||
<filters>
|
||||
<ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
|
||||
<MarkerFilter marker="NETWORK_PACKETS" onMatch="${sys:forge.logging.marker.networking:-DENY}" onMismatch="NEUTRAL"/>
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
import net.minecraftforge.gradleutils.PomUtils
|
||||
|
||||
plugins {
|
||||
id 'java-library'
|
||||
id 'maven-publish'
|
||||
id 'net.minecraftforge.licenser'
|
||||
id 'net.minecraftforge.gradleutils'
|
||||
alias(libs.plugins.apt)
|
||||
}
|
||||
|
||||
apply from: rootProject.file('build_shared.gradle')
|
||||
|
||||
configurations.forEach{ it.transitive = false }
|
||||
|
||||
dependencies {
|
||||
compileOnly(libs.jetbrains.annotations)
|
||||
|
||||
implementation(libs.bundles.asm) // Needed by all the black magic
|
||||
implementation(libs.log4j.api)
|
||||
implementation(libs.gson)
|
||||
implementation(libs.modlauncher)
|
||||
implementation(libs.coremods.api)
|
||||
|
||||
testCompileOnly(libs.jetbrains.annotations)
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION)
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
tasks.named('jar', Jar) {
|
||||
manifest {
|
||||
attributes([
|
||||
'Automatic-Module-Name': 'net.minecraftforge.forge.transformers',
|
||||
'Forge-Module-Layer': 'boot'
|
||||
] as LinkedHashMap)
|
||||
attributes([
|
||||
'Specification-Title': 'Forge Transformers',
|
||||
'Specification-Vendor': 'Forge Development LLC',
|
||||
'Specification-Version': '1',
|
||||
'Implementation-Title': 'Forge Transformers',
|
||||
'Implementation-Vendor': 'Forge Development LLC',
|
||||
'Implementation-Version': FORGE_VERSION
|
||||
] as LinkedHashMap, 'net/minecraftforge/forge/transformers/')
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
options.compilerArgs << '-Xlint:unchecked'
|
||||
}
|
||||
|
||||
license {
|
||||
header = rootProject.file('LICENSE-header.txt')
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications.register('mavenJava', MavenPublication) {
|
||||
from components.java
|
||||
artifactId = 'forge-transformers'
|
||||
pom {
|
||||
name = project.name
|
||||
description = 'Forge-specific transformers unrelated to the FML project.'
|
||||
url = 'https://github.com/MinecraftForge/MinecraftForge'
|
||||
PomUtils.setGitHubDetails(pom, 'MinecraftForge')
|
||||
license PomUtils.Licenses.LGPLv2_1
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven gradleutils.publishingForgeMaven
|
||||
}
|
||||
}
|
||||
-158
@@ -1,158 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.forge.transformers;
|
||||
|
||||
import cpw.mods.modlauncher.api.ITransformer;
|
||||
import cpw.mods.modlauncher.api.ITransformerVotingContext;
|
||||
import cpw.mods.modlauncher.api.TransformerVoteResult;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
import org.objectweb.asm.tree.FieldInsnNode;
|
||||
import org.objectweb.asm.tree.FieldNode;
|
||||
import org.objectweb.asm.tree.MethodInsnNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
record FieldToMethodTransformer(String className, Map<String, String> fields) implements ITransformer<ClassNode> {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
|
||||
// TODO [Forge][Transformer] Make this properly data driven or configurable.
|
||||
// It was hard-coded like this before when using JS CoreMods, though.
|
||||
static final Map<String, Map<String, String>> TARGETS = Map.of(
|
||||
"net.minecraft.world.level.biome.Biome",
|
||||
Map.of(
|
||||
"climateSettings", "getModifiedClimateSettings",
|
||||
"specialEffects", "getModifiedSpecialEffects"
|
||||
),
|
||||
|
||||
"net.minecraft.world.effect.MobEffectInstance",
|
||||
Map.of(
|
||||
"effect", "getEffect"
|
||||
),
|
||||
|
||||
"net.minecraft.world.level.block.LiquidBlock",
|
||||
Map.of(
|
||||
"fluid", "getFluid"
|
||||
),
|
||||
|
||||
"net.minecraft.world.item.BucketItem",
|
||||
Map.of(
|
||||
"content", "getFluid"
|
||||
),
|
||||
|
||||
"net.minecraft.world.level.block.FlowerPotBlock",
|
||||
Map.of(
|
||||
"potted", "getPotted"
|
||||
)
|
||||
);
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
static List<ITransformer> getAll() {
|
||||
var ret = new ArrayList<ITransformer>(TARGETS.size());
|
||||
for (var entry : TARGETS.entrySet()) {
|
||||
ret.add(new FieldToMethodTransformer(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ClassNode transform(ClassNode input, ITransformerVotingContext context) {
|
||||
for (var entry : this.fields.entrySet()) {
|
||||
redirectFieldToMethod(input, entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull TransformerVoteResult castVote(ITransformerVotingContext context) {
|
||||
return TransformerVoteResult.YES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Set<Target> targets() {
|
||||
return Set.of(Target.targetClass(this.className));
|
||||
}
|
||||
|
||||
private static final @Nullable FieldNode findField(final ClassNode classNode, final String name) {
|
||||
FieldNode ret = null;
|
||||
for (var fieldNode : classNode.fields) {
|
||||
if (name.equals(fieldNode.name)) {
|
||||
if (ret != null)
|
||||
throw new IllegalStateException("Found multiple fields with name " + name);
|
||||
ret = fieldNode;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static final @Nullable MethodNode findMethod(final ClassNode classNode, final String name, final String desc) {
|
||||
for (var methodNode : classNode.methods) {
|
||||
if (name.equals(methodNode.name) && desc.equals(methodNode.desc))
|
||||
return methodNode;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrites accesses to a specific field in the given class to a method-call.
|
||||
* <p>
|
||||
* The field specified by fieldName must be private and non-static. The method-call the field-access is redirected
|
||||
* to does not take any parameters and returns an object of the same type as the field. If no methodName is passed,
|
||||
* any method matching the described signature will be used as callable method.
|
||||
*
|
||||
* @param classNode the class to rewrite the accesses in
|
||||
* @param fieldName the field accesses should be redirected to
|
||||
* @param methodName the name of the method to redirect accesses through
|
||||
* @apiNote This method was written as a special use case for Forge. It is not recommended to use this method
|
||||
* unless you know what you are doing.
|
||||
*/
|
||||
private static void redirectFieldToMethod(final ClassNode classNode, final String fieldName, final String methodName) {
|
||||
var foundField = findField(classNode, fieldName);
|
||||
if (foundField == null)
|
||||
throw new IllegalStateException("No field with name " + fieldName + " found");
|
||||
|
||||
if (!Modifier.isPrivate(foundField.access) || Modifier.isStatic(foundField.access))
|
||||
throw new IllegalStateException("Field " + fieldName + " is not private and an instance field");
|
||||
|
||||
var methodSignature = "()" + foundField.desc;
|
||||
var foundMethod = findMethod(classNode, methodName, methodSignature);
|
||||
if (foundMethod == null)
|
||||
throw new IllegalStateException("Unable to find method " + methodName + methodSignature);
|
||||
|
||||
for (MethodNode methodNode : classNode.methods) {
|
||||
// skip the found getter method
|
||||
if (methodNode == foundMethod) continue;
|
||||
|
||||
// Why is this filtered? UI twas added in the beginning but would it matter?
|
||||
if (methodSignature.equals(foundMethod)) {
|
||||
LOGGER.debug("FieldToMethodTransformer " + classNode.name + " skipping " + methodNode.name + methodNode.hashCode());
|
||||
continue;
|
||||
}
|
||||
|
||||
for (var insn : methodNode.instructions) {
|
||||
if (insn.getOpcode() != Opcodes.GETFIELD)
|
||||
continue;
|
||||
|
||||
var fieldInsnNode = (FieldInsnNode)insn;
|
||||
if (fieldName.equals(fieldInsnNode.name)) {
|
||||
var replace = new MethodInsnNode(Opcodes.INVOKEVIRTUAL, classNode.name, foundMethod.name, foundMethod.desc, false);
|
||||
methodNode.instructions.set(insn, replace);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.forge.transformers;
|
||||
|
||||
import cpw.mods.modlauncher.api.IEnvironment;
|
||||
import cpw.mods.modlauncher.api.ITransformationService;
|
||||
import cpw.mods.modlauncher.api.ITransformer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public record ForgeModTransformers(String name) implements ITransformationService {
|
||||
public ForgeModTransformers() {
|
||||
this("forge");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(IEnvironment environment) { }
|
||||
|
||||
@Override
|
||||
public void onLoad(IEnvironment env, Set<String> otherServices) { }
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("rawtypes")
|
||||
public @NotNull List<ITransformer> transformers() {
|
||||
List<ITransformer> transformers = FieldToMethodTransformer.getAll();
|
||||
transformers.add(new MethodRedirector());
|
||||
return transformers;
|
||||
}
|
||||
}
|
||||
-152
@@ -1,152 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.forge.transformers;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import cpw.mods.modlauncher.api.ITransformer;
|
||||
import cpw.mods.modlauncher.api.ITransformerVotingContext;
|
||||
import cpw.mods.modlauncher.api.TransformerVoteResult;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
import org.objectweb.asm.tree.MethodInsnNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
// NOTE: THIS IS A SINGLETON!
|
||||
record MethodRedirector() implements ITransformer<ClassNode> {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
private static final Gson GSON = new Gson();
|
||||
|
||||
private static final Replacement[] REPLACEMENTS = {
|
||||
new Replacement(
|
||||
// finalizeSpawn redirection to ForgeEventFactory.onFinalizeSpawn
|
||||
Opcodes.INVOKEVIRTUAL,
|
||||
"finalizeSpawn",
|
||||
"(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/EntitySpawnReason;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;",
|
||||
GSON.fromJson(new InputStreamReader(sneak(() -> MethodRedirector.class.getModule().getResourceAsStream("coremods/finalize_spawn_targets.json"))), Target[].class),
|
||||
insn -> new MethodInsnNode(
|
||||
Opcodes.INVOKESTATIC,
|
||||
"net/minecraftforge/event/ForgeEventFactory",
|
||||
"onFinalizeSpawn",
|
||||
"(Lnet/minecraft/world/entity/Mob;Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/EntitySpawnReason;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;",
|
||||
false
|
||||
)
|
||||
)
|
||||
};
|
||||
|
||||
private static <T> T sneak(Callable<T> callable) {
|
||||
try {
|
||||
return callable.call();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
record Replacement(
|
||||
int opcode,
|
||||
String name,
|
||||
String desc,
|
||||
Target[] targets,
|
||||
UnaryOperator<MethodInsnNode> factory
|
||||
) { }
|
||||
|
||||
record Target(
|
||||
@SerializedName("class") String className,
|
||||
String[] methods
|
||||
) { }
|
||||
|
||||
@Override
|
||||
public @NotNull TransformerVoteResult castVote(ITransformerVotingContext context) {
|
||||
return TransformerVoteResult.YES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Set<ITransformer.Target> targets() {
|
||||
return Arrays
|
||||
.stream(REPLACEMENTS)
|
||||
.map(Replacement::targets)
|
||||
.flatMap(Arrays::stream)
|
||||
.map(Target::className)
|
||||
.map(ITransformer.Target::targetClass)
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ClassNode transform(ClassNode clazz, ITransformerVotingContext context) {
|
||||
for (var replacement : REPLACEMENTS) {
|
||||
for (var methodString : getClassTargetMethods(clazz, replacement)) {
|
||||
// cut up the string since we've put both the name and desc as the target
|
||||
var splitPos = methodString.indexOf('(');
|
||||
var methodName = methodString.substring(0, splitPos);
|
||||
var methodDesc = methodString.substring(splitPos);
|
||||
var method = findMethodNode(clazz, methodName, methodDesc);
|
||||
|
||||
if (method == null) {
|
||||
LOGGER.error("Failed to redirect method call for {}! Method {} not found in class {}! This is a Forge bug, and is likely due to a Minecraft update changing something.", replacement.name, methodString, clazz.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (var insn : method.instructions) {
|
||||
if (!shouldReplace(insn, replacement)) continue;
|
||||
|
||||
var methodInsn = (MethodInsnNode) insn;
|
||||
var redirection = replacement.factory.apply(methodInsn);
|
||||
LOGGER.debug("Redirecting method call {}{} to {}{} inside of {}.{}", methodInsn.name, methodInsn.desc, redirection.name, redirection.desc, clazz.name, method.name);
|
||||
method.instructions.set(insn, redirection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return clazz;
|
||||
}
|
||||
|
||||
private static @Nullable MethodNode findMethodNode(ClassNode clazz, String name, String desc) {
|
||||
for (var method : clazz.methods) {
|
||||
if (name.equals(method.name) && desc.equals(method.desc))
|
||||
return method;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean shouldReplace(AbstractInsnNode insn, Replacement replacement) {
|
||||
return insn.getOpcode() == replacement.opcode
|
||||
&& replacement.name.equals(((MethodInsnNode) insn).name)
|
||||
&& replacement.desc.equals(((MethodInsnNode) insn).desc);
|
||||
}
|
||||
|
||||
private static List<String> getClassTargetMethods(ClassNode clazz, Replacement replacement) {
|
||||
for (var t : replacement.targets) {
|
||||
if (!t.className.equals(clazz.name)) continue;
|
||||
|
||||
// declared methods
|
||||
var targets = new ArrayList<>(Arrays.asList(t.methods));
|
||||
|
||||
// synthetic methods
|
||||
for (var method : clazz.methods) {
|
||||
if ((method.access & Opcodes.ACC_SYNTHETIC) != 0)
|
||||
targets.add(method.name + method.desc);
|
||||
}
|
||||
|
||||
return targets;
|
||||
}
|
||||
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
-1
@@ -1 +0,0 @@
|
||||
net.minecraftforge.forge.transformers.ForgeModTransformers
|
||||
@@ -1,168 +0,0 @@
|
||||
[
|
||||
{
|
||||
"class": "net/minecraft/gametest/framework/GameTestHelper",
|
||||
"methods": [
|
||||
"spawn(Lnet/minecraft/world/entity/EntityType;Lnet/minecraft/world/phys/Vec3;Lnet/minecraft/world/entity/EntitySpawnReason;)Lnet/minecraft/world/entity/Entity;"
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "net/minecraft/server/commands/RaidCommand",
|
||||
"methods": [
|
||||
"spawnLeader(Lnet/minecraft/commands/CommandSourceStack;)I"
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "net/minecraft/server/commands/SummonCommand",
|
||||
"methods": [
|
||||
"createEntity(Lnet/minecraft/commands/CommandSourceStack;Lnet/minecraft/core/Holder$Reference;Lnet/minecraft/world/phys/Vec3;Lnet/minecraft/nbt/CompoundTag;Z)Lnet/minecraft/world/entity/Entity;"
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "net/minecraft/world/entity/EntityType",
|
||||
"methods": [
|
||||
"create(Lnet/minecraft/server/level/ServerLevel;Ljava/util/function/Consumer;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/entity/EntitySpawnReason;ZZ)Lnet/minecraft/world/entity/Entity;"
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "net/minecraft/world/entity/ai/village/VillageSiege",
|
||||
"methods": [
|
||||
"trySpawn(Lnet/minecraft/server/level/ServerLevel;)V"
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "net/minecraft/world/entity/animal/equine/SkeletonTrapGoal",
|
||||
"methods": [
|
||||
"createHorse(Lnet/minecraft/world/DifficultyInstance;)Lnet/minecraft/world/entity/animal/equine/AbstractHorse;",
|
||||
"createSkeleton(Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/animal/equine/AbstractHorse;)Lnet/minecraft/world/entity/monster/skeleton/Skeleton;"
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "net/minecraft/world/entity/animal/equine/ZombieHorse",
|
||||
"methods": [
|
||||
"finalizeSpawn(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/EntitySpawnReason;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;"
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "net/minecraft/world/entity/animal/frog/Tadpole",
|
||||
"methods": [
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "net/minecraft/world/entity/monster/Strider",
|
||||
"methods": [
|
||||
"spawnJockey(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/Mob;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;"
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "net/minecraft/world/entity/monster/illager/Evoker$EvokerSummonSpellGoal",
|
||||
"methods": [
|
||||
"performSpellCasting()V"
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "net/minecraft/world/entity/monster/spider/Spider",
|
||||
"methods": [
|
||||
"finalizeSpawn(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/EntitySpawnReason;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;"
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "net/minecraft/world/entity/monster/zombie/Drowned",
|
||||
"methods": [
|
||||
"finalizeSpawn(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/EntitySpawnReason;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;"
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "net/minecraft/world/entity/monster/zombie/Husk",
|
||||
"methods": [
|
||||
"finalizeSpawn(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/EntitySpawnReason;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;"
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "net/minecraft/world/entity/monster/zombie/Zombie",
|
||||
"methods": [
|
||||
"hurtServer(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/damagesource/DamageSource;F)Z",
|
||||
"finalizeSpawn(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/EntitySpawnReason;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;"
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "net/minecraft/world/entity/monster/zombie/ZombieVillager",
|
||||
"methods": [
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "net/minecraft/world/entity/npc/CatSpawner",
|
||||
"methods": [
|
||||
"spawnCat(Lnet/minecraft/core/BlockPos;Lnet/minecraft/server/level/ServerLevel;Z)V"
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "net/minecraft/world/entity/npc/villager/Villager",
|
||||
"methods": [
|
||||
"getBreedOffspring(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/entity/AgeableMob;)Lnet/minecraft/world/entity/npc/villager/Villager;"
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "net/minecraft/world/entity/raid/Raid",
|
||||
"methods": [
|
||||
"joinRaid(Lnet/minecraft/server/level/ServerLevel;ILnet/minecraft/world/entity/raid/Raider;Lnet/minecraft/core/BlockPos;Z)V"
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "net/minecraft/world/level/NaturalSpawner",
|
||||
"methods": [
|
||||
"spawnCategoryForPosition(Lnet/minecraft/world/entity/MobCategory;Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/level/chunk/ChunkAccess;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/NaturalSpawner$SpawnPredicate;Lnet/minecraft/world/level/NaturalSpawner$AfterSpawnCallback;)V",
|
||||
"spawnMobsForChunkGeneration(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/core/Holder;Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/util/RandomSource;)V"
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "net/minecraft/world/level/block/entity/trialspawner/TrialSpawner",
|
||||
"methods": [
|
||||
"spawnMob(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/core/BlockPos;)Ljava/util/Optional;"
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "net/minecraft/world/level/levelgen/PatrolSpawner",
|
||||
"methods": [
|
||||
"spawnPatrolMember(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/core/BlockPos;Lnet/minecraft/util/RandomSource;Z)Z"
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "net/minecraft/world/level/levelgen/PhantomSpawner",
|
||||
"methods": [
|
||||
"tick(Lnet/minecraft/server/level/ServerLevel;Z)V"
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "net/minecraft/world/level/levelgen/structure/structures/OceanMonumentPieces$OceanMonumentPiece",
|
||||
"methods": [
|
||||
"spawnElder(Lnet/minecraft/world/level/WorldGenLevel;Lnet/minecraft/world/level/levelgen/structure/BoundingBox;III)V"
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces$OceanRuinPiece",
|
||||
"methods": [
|
||||
"handleDataMarker(Ljava/lang/String;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/util/RandomSource;Lnet/minecraft/world/level/levelgen/structure/BoundingBox;)V"
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "net/minecraft/world/level/levelgen/structure/structures/SwampHutPiece",
|
||||
"methods": [
|
||||
"postProcess(Lnet/minecraft/world/level/WorldGenLevel;Lnet/minecraft/world/level/StructureManager;Lnet/minecraft/world/level/chunk/ChunkGenerator;Lnet/minecraft/util/RandomSource;Lnet/minecraft/world/level/levelgen/structure/BoundingBox;Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/core/BlockPos;)V",
|
||||
"spawnCat(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/level/levelgen/structure/BoundingBox;)V"
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "net/minecraft/world/level/levelgen/structure/structures/WoodlandMansionPieces$WoodlandMansionPiece",
|
||||
"methods": [
|
||||
"handleDataMarker(Ljava/lang/String;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/util/RandomSource;Lnet/minecraft/world/level/levelgen/structure/BoundingBox;)V"
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate",
|
||||
"methods": [
|
||||
|
||||
]
|
||||
}
|
||||
]
|
||||
+7
-7
@@ -1,17 +1,17 @@
|
||||
# Sets default memory used for gradle commands. Can be overridden by user or command line properties.
|
||||
# This is required to provide enough memory for the Minecraft decompilation process.
|
||||
org.gradle.jvmargs=-Xmx6G
|
||||
org.gradle.jvmargs=-Xmx3G
|
||||
org.gradle.daemon=false
|
||||
org.gradle.parallel=true
|
||||
|
||||
JAVA_VERSION=21
|
||||
MC_VERSION=1.21.11
|
||||
MC_VERSION=1.21.1
|
||||
MC_NEXT_VERSION=1.22
|
||||
MCP_VERSION=20251223.124241
|
||||
MCP_VERSION=20240808.132146
|
||||
MAPPING_CHANNEL=official
|
||||
MAPPING_VERSION=1.21.11
|
||||
MAPPING_VERSION=1.21.1
|
||||
|
||||
# Set to true before the first build of a new MC version, so we don't do compatibility checks
|
||||
// Set to true before the first build of a new MC version, so we don't do compatibility checks
|
||||
CHECK_COMPATIBILITY=false
|
||||
# Set to true to allow for fuzzy patching. Useful during updateing
|
||||
UPDATING=false
|
||||
// Set to true to allow for fuzzy patching. Useful during updateing
|
||||
UPDATING=false
|
||||
@@ -10,7 +10,7 @@ plugins {
|
||||
apply from: rootProject.file('build_shared.gradle')
|
||||
|
||||
dependencies {
|
||||
compileOnly(libs.jetbrains.annotations)
|
||||
compileOnly(libs.nulls)
|
||||
implementation(project(':fmlloader'))
|
||||
implementation(project(':fmlcore'))
|
||||
implementation(libs.unsafe)
|
||||
|
||||
@@ -12,10 +12,9 @@ import java.lang.annotation.Target;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.eventbus.api.bus.BusGroup;
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import net.minecraftforge.fml.Bindings;
|
||||
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* This defines a Mod to FML.
|
||||
@@ -25,11 +24,12 @@ import org.jspecify.annotations.Nullable;
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface Mod {
|
||||
public @interface Mod
|
||||
{
|
||||
/**
|
||||
* The unique mod identifier for this mod.
|
||||
* <b>Required to be lowercased in the English locale for compatibility. Will be truncated to 64 characters long.</b>
|
||||
* <br>
|
||||
* <b>Required to be lowercased in the english locale for compatibility. Will be truncated to 64 characters long.</b>
|
||||
*
|
||||
* This will be used to identify your mod for third parties (other mods), it will be used to identify your mod for registries such as block and item registries.
|
||||
* By default, you will have a resource domain that matches the modid. All these uses require that constraints are imposed on the format of the modid.
|
||||
*/
|
||||
@@ -61,40 +61,32 @@ public @interface Mod {
|
||||
String modid() default "";
|
||||
|
||||
/**
|
||||
* Specify an alternative bus to listen to.
|
||||
* <br>
|
||||
* If you know all listeners in this class are for a specific bus, you can set it here to speed up registration.
|
||||
* Specify an alternative bus to listen to
|
||||
*
|
||||
* @return the bus you wish to listen to
|
||||
*/
|
||||
Bus bus() default Bus.BOTH;
|
||||
Bus bus() default Bus.FORGE;
|
||||
|
||||
enum Bus {
|
||||
/**
|
||||
* The main BusGroup that most game events are fired on.
|
||||
* The main Forge Event Bus.
|
||||
*
|
||||
* <p>See {@code MinecraftForge#EVENT_BUS}</p>
|
||||
*/
|
||||
FORGE(Bindings.getForgeBus()),
|
||||
|
||||
/**
|
||||
* The mod-specific event BusGroup, usually for mod lifecycle events.
|
||||
* @see FMLJavaModLoadingContext#getModBusGroup()
|
||||
* The mod specific Event bus.
|
||||
* @see FMLJavaModLoadingContext#getModEventBus()
|
||||
*/
|
||||
MOD(()-> FMLJavaModLoadingContext.get().getModBusGroup()),
|
||||
MOD(()-> FMLJavaModLoadingContext.get().getModEventBus());
|
||||
|
||||
/**
|
||||
* Both the {@link #FORGE} and {@link #MOD} buses. This is slower to register events in your class but
|
||||
* allows you to listen to events from different BusGroup types without needing separate classes annotated
|
||||
* with {@link EventBusSubscriber}.
|
||||
*/
|
||||
BOTH(() -> null);
|
||||
private final Supplier<IEventBus> busSupplier;
|
||||
|
||||
private final Supplier<@Nullable BusGroup> busSupplier;
|
||||
|
||||
Bus(Supplier<@Nullable BusGroup> eventBusSupplier) {
|
||||
Bus(final Supplier<IEventBus> eventBusSupplier) {
|
||||
this.busSupplier = eventBusSupplier;
|
||||
}
|
||||
|
||||
public Supplier<@Nullable BusGroup> bus() {
|
||||
public Supplier<IEventBus> bus() {
|
||||
return busSupplier;
|
||||
}
|
||||
}
|
||||
|
||||
+10
-356
@@ -7,40 +7,22 @@ package net.minecraftforge.fml.javafmlmod;
|
||||
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.eventbus.api.bus.BusGroup;
|
||||
import net.minecraftforge.eventbus.api.bus.CancellableEventBus;
|
||||
import net.minecraftforge.eventbus.api.bus.EventBus;
|
||||
import net.minecraftforge.eventbus.api.event.characteristic.Cancellable;
|
||||
import net.minecraftforge.eventbus.api.listener.EventListener;
|
||||
import net.minecraftforge.eventbus.api.listener.ObjBooleanBiConsumer;
|
||||
import net.minecraftforge.eventbus.api.listener.Priority;
|
||||
import net.minecraftforge.eventbus.api.listener.SubscribeEvent;
|
||||
import net.minecraftforge.eventbus.internal.Event;
|
||||
import net.minecraftforge.fml.Logging;
|
||||
import net.minecraftforge.fml.ModContainer;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.common.Mod.EventBusSubscriber;
|
||||
import net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.forgespi.language.ModFileScanData;
|
||||
import net.minecraftforge.forgespi.language.ModFileScanData.AnnotationData;
|
||||
import net.minecraftforge.forgespi.language.ModFileScanData.EnumData;
|
||||
import net.minecraftforge.fml.loading.FMLEnvironment;
|
||||
import net.minecraftforge.unsafe.UnsafeHacks;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
import java.lang.invoke.LambdaMetafactory;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -48,13 +30,11 @@ import java.util.stream.Collectors;
|
||||
* annotations and passes the class instances to the {@link Bus}
|
||||
* defined by the annotation. Defaults to {@code MinecraftForge#EVENT_BUS}
|
||||
*/
|
||||
public final class AutomaticEventSubscriber {
|
||||
public class AutomaticEventSubscriber {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
private static final Type AUTO_SUBSCRIBER = Type.getType(EventBusSubscriber.class);
|
||||
private static final Type MOD_TYPE = Type.getType(Mod.class);
|
||||
private static final Type ONLY_IN_TYPE = Type.getType(OnlyIn.class);
|
||||
private static final List<EnumData> DEFAULT_SIDES = List.of(new EnumData(null, "CLIENT"), new EnumData(null, "DEDICATED_SERVER"));
|
||||
private static final EnumData DEFAULT_BUS = new EnumData(null, "BOTH");
|
||||
|
||||
public static void inject(ModContainer mod, ModFileScanData scanData, ClassLoader loader) {
|
||||
if (scanData == null) return;
|
||||
@@ -73,6 +53,9 @@ public final class AutomaticEventSubscriber {
|
||||
.filter(data -> MOD_TYPE.equals(data.annotationType()))
|
||||
.collect(Collectors.toMap(a -> a.clazz().getClassName(), a -> (String)a.annotationData().get("value")));
|
||||
|
||||
var defaultSides = List.of(new EnumData(null, "CLIENT"), new EnumData(null, "DEDICATED_SERVER"));
|
||||
var defaultBus = new EnumData(null, "FORGE");
|
||||
|
||||
for (var data : targets) {
|
||||
if (!FMLEnvironment.production && onlyIns.contains(data.clazz().getClassName())) {
|
||||
throw new RuntimeException("Found @OnlyIn on @EventBusSubscriber class " + data.clazz().getClassName() + " - this is not allowed as it causes crashes. Remove the OnlyIn and set value=Dist.CLIENT in the EventBusSubscriber annotation instead");
|
||||
@@ -81,18 +64,18 @@ public final class AutomaticEventSubscriber {
|
||||
var modId = modids.getOrDefault(data.clazz().getClassName(), mod.getModId());
|
||||
modId = value(data, "modid", modId);
|
||||
|
||||
var sidesValue = value(data, "value", DEFAULT_SIDES);
|
||||
var sidesValue = value(data, "value", defaultSides);
|
||||
var sides = sidesValue.stream()
|
||||
.map(EnumData::value)
|
||||
.map(Dist::valueOf)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
var busName = value(data, "bus", DEFAULT_BUS).value();
|
||||
var busName = value(data, "bus", defaultBus).value();
|
||||
var busTarget = Bus.valueOf(busName);
|
||||
if (Objects.equals(mod.getModId(), modId) && sides.contains(FMLEnvironment.dist)) {
|
||||
try {
|
||||
LOGGER.debug(Logging.LOADING, "Auto-subscribing {} to {}", data.clazz().getClassName(), busTarget);
|
||||
EventBusSubscriberLogic.register(busTarget.bus().get(), Class.forName(data.clazz().getClassName(), true, loader));
|
||||
busTarget.bus().get().register(Class.forName(data.clazz().getClassName(), true, loader));
|
||||
} catch (ClassNotFoundException e) {
|
||||
LOGGER.fatal(Logging.LOADING, "Failed to load mod class {} for @EventBusSubscriber annotation", data.clazz(), e);
|
||||
throw new RuntimeException(e);
|
||||
@@ -105,333 +88,4 @@ public final class AutomaticEventSubscriber {
|
||||
private static <R> R value(AnnotationData data, String key, R value) {
|
||||
return (R)data.annotationData().getOrDefault(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is mostly a carbon copy of EventBus 7's internal EventListenerFactory, with some changes to support adding
|
||||
* to different BusGroups based on the event type, among other things.
|
||||
*/
|
||||
private static final class EventBusSubscriberLogic {
|
||||
private EventBusSubscriberLogic() {}
|
||||
|
||||
private static final boolean STRICT_RUNTIME_CHECKS = Boolean.getBoolean("eventbus.api.strictRuntimeChecks");
|
||||
|
||||
/**
|
||||
* If true, performs exhaustive validation on bulk registration to aid debugging.
|
||||
*/
|
||||
private static final boolean STRICT_REGISTRATION_CHECKS = STRICT_RUNTIME_CHECKS || Boolean.getBoolean("eventbus.api.strictRegistrationChecks");
|
||||
|
||||
private static final MethodType RETURNS_CONSUMER = MethodType.methodType(Consumer.class);
|
||||
private static final MethodType RETURNS_PREDICATE = MethodType.methodType(Predicate.class);
|
||||
private static final MethodType RETURNS_MONITOR = MethodType.methodType(ObjBooleanBiConsumer.class);
|
||||
|
||||
/** The method type of the {@link Consumer} functional interface ({@code void accept(Object)}) */
|
||||
private static final MethodType CONSUMER_FI_TYPE = MethodType.methodType(void.class, Object.class);
|
||||
|
||||
/** The method type of the {@link Predicate} functional interface ({@code boolean test(Object)}) */
|
||||
private static final MethodType PREDICATE_FI_TYPE = CONSUMER_FI_TYPE.changeReturnType(boolean.class);
|
||||
|
||||
/** The method type of the {@link ObjBooleanBiConsumer} functional interface ({@code void accept(Object, boolean)}) */
|
||||
private static final MethodType MONITOR_FI_TYPE = MethodType.methodType(void.class, Object.class, boolean.class);
|
||||
|
||||
public static void register(@Nullable BusGroup busGroup, Class<?> listenerClass) {
|
||||
if (STRICT_REGISTRATION_CHECKS) registerStrict(busGroup, listenerClass);
|
||||
else registerLenient(busGroup, listenerClass);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public static void registerLenient(@Nullable BusGroup busGroup, Class<?> listenerClass) {
|
||||
Method[] declaredMethods = listenerClass.getDeclaredMethods();
|
||||
if (declaredMethods.length == 0)
|
||||
throw new IllegalArgumentException("No declared methods found in " + listenerClass);
|
||||
|
||||
Class<?> firstValidListenerEventType = null;
|
||||
|
||||
int listenersCount = 0;
|
||||
for (var method : declaredMethods) {
|
||||
if (!Modifier.isStatic(method.getModifiers()))
|
||||
continue;
|
||||
|
||||
if (method.isSynthetic())
|
||||
continue; // EventBus#89
|
||||
|
||||
int paramCount = method.getParameterCount();
|
||||
if (paramCount == 0 || paramCount > 2)
|
||||
continue;
|
||||
|
||||
Class<?> returnType = method.getReturnType();
|
||||
if (returnType != void.class && returnType != boolean.class)
|
||||
continue;
|
||||
|
||||
if (!method.isAnnotationPresent(SubscribeEvent.class))
|
||||
continue;
|
||||
|
||||
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||
if (!Event.class.isAssignableFrom(parameterTypes[0]))
|
||||
throw new IllegalArgumentException("First parameter of a @SubscribeEvent method must be an event");
|
||||
|
||||
Class<? extends Event> eventType = (Class<? extends Event>) parameterTypes[0];
|
||||
var subscribeEventAnnotation = method.getAnnotation(SubscribeEvent.class);
|
||||
|
||||
registerListener(busGroup, paramCount, returnType, eventType, subscribeEventAnnotation, method, false);
|
||||
listenersCount++;
|
||||
|
||||
if (firstValidListenerEventType == null)
|
||||
firstValidListenerEventType = eventType;
|
||||
}
|
||||
|
||||
if (listenersCount == 0)
|
||||
throw new IllegalArgumentException("No listeners found in " + listenerClass);
|
||||
else if (firstValidListenerEventType == null)
|
||||
throw new IllegalArgumentException("No valid listeners found in " + listenerClass);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public static void registerStrict(BusGroup busGroup, Class<?> listenerClass) {
|
||||
Class<? extends Event> firstValidListenerEventType = null;
|
||||
|
||||
List<Method> declaredMethods = Arrays.stream(listenerClass.getDeclaredMethods())
|
||||
.filter(Predicate.not(Method::isSynthetic)) // EventBus#89
|
||||
.toList();
|
||||
if (declaredMethods.isEmpty()) {
|
||||
var errMsg = "No declared methods found in " + listenerClass.getName();
|
||||
var superClass = listenerClass.getSuperclass();
|
||||
if (superClass != null && superClass != Record.class && superClass != Enum.class) {
|
||||
errMsg += ". Note that listener inheritance is not supported. " +
|
||||
"If you are trying to inherit listeners, please use @Override and @SubscribeEvent on the method in the subclass.";
|
||||
}
|
||||
throw fail(listenerClass, errMsg);
|
||||
}
|
||||
|
||||
int listenersCount = 0;
|
||||
for (var method : declaredMethods) {
|
||||
var hasSubscribeEvent = method.isAnnotationPresent(SubscribeEvent.class);
|
||||
int paramCount = method.getParameterCount();
|
||||
|
||||
if (hasSubscribeEvent && (paramCount == 0 || paramCount > 2))
|
||||
throw fail(method, "Invalid number of parameters: " + paramCount + " (expected 1 or 2)");
|
||||
|
||||
if (paramCount == 0)
|
||||
continue;
|
||||
|
||||
Class<?> returnType = method.getReturnType();
|
||||
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||
var firstParamExtendsEvent = Event.class.isAssignableFrom(parameterTypes[0]);
|
||||
|
||||
if (!hasSubscribeEvent && firstParamExtendsEvent)
|
||||
throw fail(method, "Missing @SubscribeEvent annotation");
|
||||
|
||||
if (hasSubscribeEvent) {
|
||||
var firstParamExtendsCancellable = Cancellable.class.isAssignableFrom(parameterTypes[0]);
|
||||
var subscribeEventAnnotation = method.getAnnotation(SubscribeEvent.class);
|
||||
var isMonitoringPriority = subscribeEventAnnotation.priority() == Priority.MONITOR;
|
||||
|
||||
if (!firstParamExtendsEvent)
|
||||
throw fail(method, "First parameter of a @SubscribeEvent method must be an event");
|
||||
|
||||
var eventType = (Class<? extends Event>) parameterTypes[0];
|
||||
|
||||
if (returnType != void.class && returnType != boolean.class)
|
||||
throw fail(method, "Invalid return type: " + returnType.getName() + " (expected void or boolean)");
|
||||
|
||||
if (!Modifier.isStatic(method.getModifiers()))
|
||||
throw fail(method, "Listener method needs to be static");
|
||||
|
||||
if (isMonitoringPriority && (returnType == boolean.class || subscribeEventAnnotation.alwaysCancelling()))
|
||||
throw fail(method, "Monitoring listeners cannot cancel events");
|
||||
|
||||
if (paramCount == 2) {
|
||||
if (!firstParamExtendsCancellable)
|
||||
throw fail(method, "Cancellation-aware monitoring listeners are only valid for cancellable events");
|
||||
|
||||
if (!boolean.class.isAssignableFrom(parameterTypes[1]))
|
||||
throw fail(method, "Second parameter of a cancellation-aware monitoring listener must be a boolean");
|
||||
|
||||
if (!isMonitoringPriority)
|
||||
throw fail(method, "Cancellation-aware monitoring listeners must have a priority of MONITOR");
|
||||
}
|
||||
|
||||
if (!firstParamExtendsCancellable) {
|
||||
if (subscribeEventAnnotation.alwaysCancelling())
|
||||
throw fail(method, "Always cancelling listeners are only valid for cancellable events");
|
||||
|
||||
if (returnType == boolean.class)
|
||||
throw fail(method, "Return type boolean is only valid for cancellable events");
|
||||
}
|
||||
|
||||
registerListener(busGroup, paramCount, returnType, eventType, subscribeEventAnnotation, method, true);
|
||||
listenersCount++;
|
||||
|
||||
if (firstValidListenerEventType == null)
|
||||
firstValidListenerEventType = eventType;
|
||||
}
|
||||
}
|
||||
|
||||
if (listenersCount == 0)
|
||||
throw fail(listenerClass, "No listeners found");
|
||||
else if (firstValidListenerEventType == null)
|
||||
throw fail(listenerClass, "No valid listeners found");
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
private static EventListener registerListener(@Nullable BusGroup busGroup,
|
||||
int paramCount, Class<?> returnType, Class<? extends Event> eventType,
|
||||
SubscribeEvent subscribeEventAnnotation, Method method, boolean strict) {
|
||||
if (busGroup == null) {
|
||||
busGroup = IModBusEvent.class.isAssignableFrom(eventType)
|
||||
? FMLJavaModLoadingContext.get().getModBusGroup()
|
||||
: BusGroup.DEFAULT;
|
||||
} else if (strict) {
|
||||
String solution = "To fix this, remove the bus param from your @EventBusSubscriber annotation or move this event listener to another class.";
|
||||
var isDefaultBusGroup = busGroup == BusGroup.DEFAULT;
|
||||
var isModBusEvent = IModBusEvent.class.isAssignableFrom(eventType);
|
||||
if (isDefaultBusGroup && isModBusEvent) { // requested forge bus and has IModBusEvent
|
||||
throw fail(method, "Event type " + eventType.getName()
|
||||
+ " is on the mod BusGroup but you are asking to register it on the default BusGroup (BusGroup.DEFAULT/EventBusSubscriber.Bus.FORGE). "
|
||||
+ solution);
|
||||
} else if (!isDefaultBusGroup && !isModBusEvent) { // requested mod bus and does not have IModBusEvent
|
||||
throw fail(method, "Event type " + eventType.getName()
|
||||
+ " is on the default BusGroup but you are asking to register it on the mod BusGroup (context.getModBusGroup()/EventBusSubscriber.Bus.MOD). "
|
||||
+ solution);
|
||||
}
|
||||
}
|
||||
|
||||
// determine the listener type from its parameters and return type
|
||||
if (paramCount == 1) {
|
||||
var priority = subscribeEventAnnotation.priority();
|
||||
if (returnType == void.class) {
|
||||
if (Cancellable.class.isAssignableFrom(eventType)) {
|
||||
// Consumer<Event & Cancellable>
|
||||
var eventBus = CancellableEventBus.create(busGroup, (Class) eventType);
|
||||
if (subscribeEventAnnotation.alwaysCancelling()) {
|
||||
return eventBus.addListener(priority, true, createConsumer(method));
|
||||
} else {
|
||||
return eventBus.addListener(priority, createConsumer(method));
|
||||
}
|
||||
} else {
|
||||
// Consumer<Event>
|
||||
return EventBus.create(busGroup, eventType)
|
||||
.addListener(priority, createConsumer(method));
|
||||
}
|
||||
} else {
|
||||
// Predicate<Event & EventCharacteristic.Cancellable>
|
||||
if (!Cancellable.class.isAssignableFrom(eventType))
|
||||
throw fail(method, "Return type boolean is only valid for cancellable events");
|
||||
|
||||
if (subscribeEventAnnotation.alwaysCancelling())
|
||||
throw new IllegalArgumentException("Always cancelling listeners must have a void return type");
|
||||
|
||||
return CancellableEventBus.create(busGroup, (Class) eventType)
|
||||
.addListener(priority, createPredicate(method));
|
||||
}
|
||||
} else {
|
||||
// ObjBooleanBiConsumer<Event & Cancellable>
|
||||
if (returnType != void.class)
|
||||
throw new IllegalArgumentException("Cancellation-aware monitoring listeners must have a void return type");
|
||||
|
||||
if (subscribeEventAnnotation.alwaysCancelling())
|
||||
throw new IllegalArgumentException("Monitoring listeners cannot cancel events");
|
||||
|
||||
return CancellableEventBus.create(busGroup, (Class) eventType).addListener(createMonitor(method));
|
||||
}
|
||||
}
|
||||
|
||||
private static IllegalArgumentException fail(Class<?> listenerClass, String reason) {
|
||||
return new IllegalArgumentException("Failed to register " + listenerClass.getName() + ": " + reason);
|
||||
}
|
||||
|
||||
private static IllegalArgumentException fail(Method mtd, String reason) {
|
||||
return new IllegalArgumentException("Failed to register " + mtd.getDeclaringClass().getName() + "." + mtd.getName() + ": " + reason);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T extends Event> Consumer<T> createConsumer(Method callback) {
|
||||
var factoryMH = getOrMakeFactory(callback, RETURNS_CONSUMER, CONSUMER_FI_TYPE, "accept");
|
||||
|
||||
try {
|
||||
return (Consumer<T>) factoryMH.invokeExact();
|
||||
} catch (Exception e) {
|
||||
throw makeRuntimeException(callback, e);
|
||||
} catch (Throwable t) {
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T extends Event> Predicate<T> createPredicate(Method callback) {
|
||||
var factoryMH = getOrMakeFactory(callback, RETURNS_PREDICATE, PREDICATE_FI_TYPE, "test");
|
||||
|
||||
try {
|
||||
return (Predicate<T>) factoryMH.invokeExact();
|
||||
} catch (Exception e) {
|
||||
throw makeRuntimeException(callback, e);
|
||||
} catch (Throwable t) {
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T extends Event> ObjBooleanBiConsumer<T> createMonitor(Method callback) {
|
||||
var factoryMH = getOrMakeFactory(callback, RETURNS_MONITOR, MONITOR_FI_TYPE, "accept");
|
||||
|
||||
try {
|
||||
return (ObjBooleanBiConsumer<T>) factoryMH.invokeExact();
|
||||
} catch (Exception e) {
|
||||
throw makeRuntimeException(callback, e);
|
||||
} catch (Throwable t) {
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
|
||||
private static MethodHandle getOrMakeFactory(Method callback, MethodType factoryReturnType,
|
||||
MethodType fiMethodType, String fiMethodName) {
|
||||
return makeFactory(callback, factoryReturnType, fiMethodType, fiMethodName);
|
||||
}
|
||||
|
||||
private static MethodHandle makeFactory(Method callback,
|
||||
MethodType factoryReturnType,
|
||||
MethodType fiMethodType, String fiMethodName) {
|
||||
final class DodgyLookup {
|
||||
private DodgyLookup() {}
|
||||
private static final MethodHandles.Lookup INSTANCE;
|
||||
static {
|
||||
try {
|
||||
var lookupField = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP");
|
||||
UnsafeHacks.setAccessible(lookupField);
|
||||
INSTANCE = (MethodHandles.Lookup) lookupField.get(null);
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
var callbackDeclaredClass = callback.getDeclaringClass();
|
||||
var lookup = DodgyLookup.INSTANCE.in(callbackDeclaredClass);
|
||||
var mh = lookup.unreflect(callback);
|
||||
|
||||
return LambdaMetafactory.metafactory(
|
||||
lookup, fiMethodName, factoryReturnType, fiMethodType, mh, mh.type()
|
||||
).getTarget();
|
||||
} catch (Exception e) {
|
||||
throw makeRuntimeException(callback, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static RuntimeException makeRuntimeException(Method callback, Exception e) {
|
||||
return switch (e) {
|
||||
case IllegalAccessException iae -> {
|
||||
var errMsg = "Failed to create listener";
|
||||
if (!Modifier.isPublic(callback.getModifiers()))
|
||||
errMsg += " - is it public?";
|
||||
|
||||
yield new RuntimeException(errMsg, iae);
|
||||
}
|
||||
case NullPointerException npe -> new RuntimeException(
|
||||
"Failed to create listener - was given a non-static method without an instance to invoke it with",
|
||||
npe
|
||||
);
|
||||
default -> new RuntimeException("Failed to create listener", e);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
-13
@@ -5,13 +5,14 @@
|
||||
|
||||
package net.minecraftforge.fml.javafmlmod;
|
||||
|
||||
import net.minecraftforge.eventbus.api.bus.BusGroup;
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import net.minecraftforge.fml.ModLoadingContext;
|
||||
|
||||
/**
|
||||
* Use the context provided by your language loader in your mod's constructor
|
||||
*/
|
||||
public class FMLJavaModLoadingContext extends ModLoadingContext {
|
||||
public class FMLJavaModLoadingContext extends ModLoadingContext
|
||||
{
|
||||
private final FMLModContainer container;
|
||||
|
||||
FMLJavaModLoadingContext(FMLModContainer container) {
|
||||
@@ -19,10 +20,11 @@ public class FMLJavaModLoadingContext extends ModLoadingContext {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The mod's event bus group, to allow subscription to Mod specific events
|
||||
* @return The mod's event bus, to allow subscription to Mod specific events
|
||||
*/
|
||||
public BusGroup getModBusGroup() {
|
||||
return container.getModBusGroup();
|
||||
public IEventBus getModEventBus()
|
||||
{
|
||||
return container.getEventBus();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,15 +39,9 @@ public class FMLJavaModLoadingContext extends ModLoadingContext {
|
||||
* Helper to get the right instance from the {@link ModLoadingContext} correctly.
|
||||
* @return The FMLJavaMod language specific extension from the ModLoadingContext
|
||||
*
|
||||
* @deprecated use {@link FMLJavaModLoadingContext} in your mod constructor. For example: {@snippet :
|
||||
* @Mod("examplemod")
|
||||
* public final class ExampleMod {
|
||||
* public ExampleMod(FMLJavaModLoadingContext context) {
|
||||
* // Use context from the param instead of FMLJavaModLoadingContext.get() or ModLoadingContext.get()
|
||||
* }
|
||||
* }}
|
||||
* @deprecated use {@link FMLJavaModLoadingContext} in your mod constructor
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "1.21.1")
|
||||
@Deprecated(forRemoval = true, since="1.21.1")
|
||||
public static FMLJavaModLoadingContext get() {
|
||||
return ModLoadingContext.get().extension();
|
||||
}
|
||||
|
||||
+19
-27
@@ -5,12 +5,14 @@
|
||||
|
||||
package net.minecraftforge.fml.javafmlmod;
|
||||
|
||||
import net.minecraftforge.eventbus.api.bus.BusGroup;
|
||||
import net.minecraftforge.eventbus.api.bus.EventBus;
|
||||
import net.minecraftforge.eventbus.EventBusErrorMessage;
|
||||
import net.minecraftforge.eventbus.api.BusBuilder;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import net.minecraftforge.eventbus.api.IEventListener;
|
||||
import net.minecraftforge.fml.ModContainer;
|
||||
import net.minecraftforge.fml.ModLoadingException;
|
||||
import net.minecraftforge.fml.ModLoadingStage;
|
||||
import net.minecraftforge.fml.config.IConfigEvent;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import net.minecraftforge.forgespi.language.ModFileScanData;
|
||||
@@ -27,13 +29,14 @@ import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.jar.Attributes;
|
||||
|
||||
public class FMLModContainer extends ModContainer {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
private static final Marker LOADING = MarkerManager.getMarker("LOADING");
|
||||
private final ModFileScanData scanResults;
|
||||
private final BusGroup eventBusGroup;
|
||||
private final IEventBus eventBus;
|
||||
private Object modInstance;
|
||||
private final Class<?> modClass;
|
||||
private final FMLJavaModLoadingContext context = new FMLJavaModLoadingContext(this);
|
||||
@@ -43,13 +46,13 @@ public class FMLModContainer extends ModContainer {
|
||||
LOGGER.debug(LOADING,"Creating FMLModContainer instance for {}", className);
|
||||
this.scanResults = modFileScanResults;
|
||||
activityMap.put(ModLoadingStage.CONSTRUCT, this::constructMod);
|
||||
this.eventBusGroup = BusGroup.create("modBusFor" + info.getModId(), IModBusEvent.class);
|
||||
this.eventBus = BusBuilder.builder().setExceptionHandler(FMLModContainer::onEventFailed).setTrackPhases(false).markerType(IModBusEvent.class).useModLauncher().build();
|
||||
this.configHandler = Optional.of(ce->this.eventBus.post(ce.self()));
|
||||
this.contextExtension = () -> context;
|
||||
try {
|
||||
var moduleName = info.getOwningFile().moduleName();
|
||||
var module = gameLayer.findModule(moduleName).orElse(null);
|
||||
if (module == null)
|
||||
throw new IllegalStateException("Failed to find " + moduleName + " in " + gameLayer);
|
||||
var module = gameLayer.findModule(moduleName)
|
||||
.orElseThrow(() -> new IllegalStateException("Failed to find " + moduleName + " in " + gameLayer));
|
||||
|
||||
openModules(gameLayer, module, info.getOwningFile().getFile().getSecureJar());
|
||||
|
||||
@@ -126,6 +129,10 @@ public class FMLModContainer extends ModContainer {
|
||||
implAddExportsOrOpens.invoke(target, pkg, reader, open, /*syncVM*/true);
|
||||
}
|
||||
|
||||
private static void onEventFailed(IEventBus iEventBus, Event event, IEventListener[] iEventListeners, int i, Throwable throwable) {
|
||||
LOGGER.error(new EventBusErrorMessage(event, i, iEventListeners, throwable));
|
||||
}
|
||||
|
||||
private void constructMod() {
|
||||
try {
|
||||
LOGGER.trace(LOADING, "Loading mod instance {} of type {}", getModId(), modClass.getName());
|
||||
@@ -167,34 +174,19 @@ public class FMLModContainer extends ModContainer {
|
||||
return modInstance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BusGroup getModBusGroup() {
|
||||
return this.eventBusGroup;
|
||||
public IEventBus getEventBus() {
|
||||
return this.eventBus;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T extends IModBusEvent> void acceptEvent(final T e) {
|
||||
protected <T extends Event & IModBusEvent> void acceptEvent(final T e) {
|
||||
try {
|
||||
LOGGER.trace(LOADING, "Firing event for modid {} : {}", this.getModId(), e);
|
||||
@SuppressWarnings("unchecked")
|
||||
var eventBus = (EventBus<T>) IModBusEvent.getBus(eventBusGroup, e.getClass());
|
||||
eventBus.post(e);
|
||||
this.eventBus.post(e);
|
||||
LOGGER.trace(LOADING, "Fired event for modid {} : {}", this.getModId(), e);
|
||||
} catch (Throwable t) {
|
||||
LOGGER.error(LOADING,"Caught exception during event {} dispatch for modid {}", e, this.getModId(), t);
|
||||
throw new ModLoadingException(modInfo, modLoadingStage, "fml.modloading.errorduringevent", t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public void dispatchConfigEvent(IConfigEvent event) {
|
||||
var eventBus = (EventBus) EventBus.create(eventBusGroup, event.self().getClass());
|
||||
eventBus.post(event.self());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FMLModContainer[" + this.getModInfo().getModId() + ", " + this.getClass().getName() + ']';
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ java {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(libs.jetbrains.annotations)
|
||||
compileOnly(libs.nulls)
|
||||
|
||||
implementation(project(':fmlloader'))
|
||||
implementation(project(':fmlcore'))
|
||||
|
||||
+15
-6
@@ -6,32 +6,41 @@
|
||||
package net.minecraftforge.fml.lowcodemod;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.IExtensionPoint;
|
||||
import net.minecraftforge.fml.ModContainer;
|
||||
import net.minecraftforge.fml.loading.LogMarkers;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import net.minecraftforge.forgespi.language.ModFileScanData;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
public class LowCodeModContainer extends ModContainer {
|
||||
import java.util.Objects;
|
||||
|
||||
import static net.minecraftforge.fml.loading.LogMarkers.LOADING;
|
||||
|
||||
public class LowCodeModContainer extends ModContainer
|
||||
{
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private final Object modInstance;
|
||||
|
||||
public LowCodeModContainer(IModInfo info, ModFileScanData modFileScanResults, ModuleLayer gameLayer) {
|
||||
public LowCodeModContainer(IModInfo info, ModFileScanData modFileScanResults, ModuleLayer gameLayer)
|
||||
{
|
||||
super(info);
|
||||
LOGGER.debug(LogMarkers.LOADING, "Creating LowCodeModContainer for {}", info.getModId());
|
||||
LOGGER.debug(LOADING, "Creating LowCodeModContainer for {}", info.getModId());
|
||||
this.modInstance = new Object();
|
||||
this.contextExtension = () -> null;
|
||||
this.extensionPoints.remove(IExtensionPoint.DisplayTest.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Object mod) {
|
||||
public boolean matches(Object mod)
|
||||
{
|
||||
return mod == modInstance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getMod() {
|
||||
public Object getMod()
|
||||
{
|
||||
return modInstance;
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ plugins {
|
||||
apply from: rootProject.file('build_shared.gradle')
|
||||
|
||||
dependencies {
|
||||
compileOnly(libs.jetbrains.annotations)
|
||||
compileOnly(libs.nulls)
|
||||
implementation(project(':fmlloader'))
|
||||
implementation(project(':fmlcore'))
|
||||
}
|
||||
|
||||
+3
-1
@@ -8,6 +8,8 @@ package net.minecraftforge.fml.mclanguageprovider;
|
||||
import net.minecraftforge.fml.ModContainer;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class MinecraftModContainer extends ModContainer {
|
||||
private static final String MCMODINSTANCE = "minecraft, the mod";
|
||||
|
||||
@@ -18,7 +20,7 @@ public class MinecraftModContainer extends ModContainer {
|
||||
|
||||
@Override
|
||||
public boolean matches(final Object mod) {
|
||||
return MCMODINSTANCE.equals(mod);
|
||||
return Objects.equals(mod, MCMODINSTANCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+3
-2
@@ -10,6 +10,7 @@ import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import net.minecraftforge.forgespi.language.IModLanguageProvider;
|
||||
import net.minecraftforge.forgespi.language.ModFileScanData;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Map;
|
||||
@@ -19,7 +20,7 @@ import java.util.function.Supplier;
|
||||
import static net.minecraftforge.fml.Logging.LOADING;
|
||||
|
||||
public class MinecraftModLanguageProvider implements IModLanguageProvider {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
@Override
|
||||
public String name() {
|
||||
return "minecraft";
|
||||
@@ -44,7 +45,7 @@ public class MinecraftModLanguageProvider implements IModLanguageProvider {
|
||||
final Class<?> mcModClass = Class.forName(getClass().getModule(), "net.minecraftforge.fml.mclanguageprovider.MinecraftModContainer");
|
||||
return (T)mcModClass.getConstructor(IModInfo.class).newInstance(info);
|
||||
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
|
||||
LogManager.getLogger().fatal(LOADING, "Unable to load MinecraftModContainer, wut?", e);
|
||||
LOGGER.fatal(LOADING,"Unable to load MinecraftModContainer, wut?", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ minecraft {
|
||||
configureEach {
|
||||
workingDir = layout.projectDirectory.dir('run')
|
||||
|
||||
systemProperty 'eventbus.api.strictRuntimeChecks', 'true'
|
||||
systemProperty 'forge.enabledGameTestNamespaces', 'examplemod'
|
||||
}
|
||||
|
||||
@@ -54,7 +53,6 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
implementation minecraft.dependency('net.minecraftforge:forge:@MC_VERSION@-@FORGE_VERSION@')
|
||||
annotationProcessor 'net.minecraftforge:eventbus-validator:@EVENTBUS_VERSION@'
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
|
||||
+11
-7
@@ -1,13 +1,14 @@
|
||||
package com.example.examplemod;
|
||||
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraftforge.common.ForgeConfigSpec;
|
||||
import net.minecraftforge.eventbus.api.listener.SubscribeEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.event.config.ModConfigEvent;
|
||||
import net.minecraftforge.registries.ForgeRegistries;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -15,7 +16,8 @@ import java.util.stream.Collectors;
|
||||
// An example config class. This is not required, but it's a good idea to have one to keep your config organized.
|
||||
// Demonstrates how to use Forge's config APIs
|
||||
@Mod.EventBusSubscriber(modid = ExampleMod.MODID, bus = Mod.EventBusSubscriber.Bus.MOD)
|
||||
public class Config {
|
||||
public class Config
|
||||
{
|
||||
private static final ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder();
|
||||
|
||||
private static final ForgeConfigSpec.BooleanValue LOG_DIRT_BLOCK = BUILDER
|
||||
@@ -42,19 +44,21 @@ public class Config {
|
||||
public static String magicNumberIntroduction;
|
||||
public static Set<Item> items;
|
||||
|
||||
private static boolean validateItemName(final Object obj) {
|
||||
return obj instanceof final String itemName && ForgeRegistries.ITEMS.containsKey(Identifier.tryParse(itemName));
|
||||
private static boolean validateItemName(final Object obj)
|
||||
{
|
||||
return obj instanceof final String itemName && ForgeRegistries.ITEMS.containsKey(ResourceLocation.tryParse(itemName));
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
static void onLoad(final ModConfigEvent event) {
|
||||
static void onLoad(final ModConfigEvent event)
|
||||
{
|
||||
logDirtBlock = LOG_DIRT_BLOCK.get();
|
||||
magicNumber = MAGIC_NUMBER.get();
|
||||
magicNumberIntroduction = MAGIC_NUMBER_INTRODUCTION.get();
|
||||
|
||||
// convert the list of strings into a set of items
|
||||
items = ITEM_STRINGS.get().stream()
|
||||
.map(itemName -> ForgeRegistries.ITEMS.getValue(Identifier.tryParse(itemName)))
|
||||
.map(itemName -> ForgeRegistries.ITEMS.getValue(ResourceLocation.tryParse(itemName)))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
+38
-34
@@ -13,8 +13,11 @@ import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||
import net.minecraft.world.level.material.MapColor;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.event.BuildCreativeModeTabContentsEvent;
|
||||
import net.minecraftforge.eventbus.api.listener.SubscribeEvent;
|
||||
import net.minecraftforge.event.server.ServerStartingEvent;
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.config.ModConfig;
|
||||
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
|
||||
@@ -27,7 +30,8 @@ import org.slf4j.Logger;
|
||||
|
||||
// The value here should match an entry in the META-INF/mods.toml file
|
||||
@Mod(ExampleMod.MODID)
|
||||
public final class ExampleMod {
|
||||
public class ExampleMod
|
||||
{
|
||||
// Define mod id in a common place for everything to reference
|
||||
public static final String MODID = "examplemod";
|
||||
// Directly reference a slf4j logger
|
||||
@@ -40,29 +44,13 @@ public final class ExampleMod {
|
||||
public static final DeferredRegister<CreativeModeTab> CREATIVE_MODE_TABS = DeferredRegister.create(Registries.CREATIVE_MODE_TAB, MODID);
|
||||
|
||||
// Creates a new Block with the id "examplemod:example_block", combining the namespace and path
|
||||
public static final RegistryObject<Block> EXAMPLE_BLOCK = BLOCKS.register("example_block",
|
||||
() -> new Block(BlockBehaviour.Properties.of()
|
||||
.setId(BLOCKS.key("example_block"))
|
||||
.mapColor(MapColor.STONE)
|
||||
)
|
||||
);
|
||||
public static final RegistryObject<Block> EXAMPLE_BLOCK = BLOCKS.register("example_block", () -> new Block(BlockBehaviour.Properties.of().mapColor(MapColor.STONE)));
|
||||
// Creates a new BlockItem with the id "examplemod:example_block", combining the namespace and path
|
||||
public static final RegistryObject<Item> EXAMPLE_BLOCK_ITEM = ITEMS.register("example_block",
|
||||
() -> new BlockItem(EXAMPLE_BLOCK.get(), new Item.Properties().setId(ITEMS.key("example_block")))
|
||||
);
|
||||
public static final RegistryObject<Item> EXAMPLE_BLOCK_ITEM = ITEMS.register("example_block", () -> new BlockItem(EXAMPLE_BLOCK.get(), new Item.Properties()));
|
||||
|
||||
// Creates a new food item with the id "examplemod:example_id", nutrition 1 and saturation 2
|
||||
public static final RegistryObject<Item> EXAMPLE_ITEM = ITEMS.register("example_item",
|
||||
() -> new Item(new Item.Properties()
|
||||
.setId(ITEMS.key("example_item"))
|
||||
.food(new FoodProperties.Builder()
|
||||
.alwaysEdible()
|
||||
.nutrition(1)
|
||||
.saturationModifier(2f)
|
||||
.build()
|
||||
)
|
||||
)
|
||||
);
|
||||
public static final RegistryObject<Item> EXAMPLE_ITEM = ITEMS.register("example_item", () -> new Item(new Item.Properties().food(new FoodProperties.Builder()
|
||||
.alwaysEdible().nutrition(1).saturationModifier(2f).build())));
|
||||
|
||||
// Creates a creative tab with the id "examplemod:example_tab" for the example item, that is placed after the combat tab
|
||||
public static final RegistryObject<CreativeModeTab> EXAMPLE_TAB = CREATIVE_MODE_TABS.register("example_tab", () -> CreativeModeTab.builder()
|
||||
@@ -72,27 +60,32 @@ public final class ExampleMod {
|
||||
output.accept(EXAMPLE_ITEM.get()); // Add the example item to the tab. For your own tabs, this method is preferred over the event
|
||||
}).build());
|
||||
|
||||
public ExampleMod(FMLJavaModLoadingContext context) {
|
||||
var modBusGroup = context.getModBusGroup();
|
||||
public ExampleMod(FMLJavaModLoadingContext context)
|
||||
{
|
||||
IEventBus modEventBus = context.getModEventBus();
|
||||
|
||||
// Register the commonSetup method for modloading
|
||||
FMLCommonSetupEvent.getBus(modBusGroup).addListener(this::commonSetup);
|
||||
modEventBus.addListener(this::commonSetup);
|
||||
|
||||
// Register the Deferred Register to the mod event bus so blocks get registered
|
||||
BLOCKS.register(modBusGroup);
|
||||
BLOCKS.register(modEventBus);
|
||||
// Register the Deferred Register to the mod event bus so items get registered
|
||||
ITEMS.register(modBusGroup);
|
||||
ITEMS.register(modEventBus);
|
||||
// Register the Deferred Register to the mod event bus so tabs get registered
|
||||
CREATIVE_MODE_TABS.register(modBusGroup);
|
||||
CREATIVE_MODE_TABS.register(modEventBus);
|
||||
|
||||
// Register ourselves for server and other game events we are interested in
|
||||
MinecraftForge.EVENT_BUS.register(this);
|
||||
|
||||
// Register the item to a creative tab
|
||||
BuildCreativeModeTabContentsEvent.BUS.addListener(ExampleMod::addCreative);
|
||||
modEventBus.addListener(this::addCreative);
|
||||
|
||||
// Register our mod's ForgeConfigSpec so that Forge can create and load the config file for us
|
||||
context.registerConfig(ModConfig.Type.COMMON, Config.SPEC);
|
||||
}
|
||||
|
||||
private void commonSetup(final FMLCommonSetupEvent event) {
|
||||
private void commonSetup(final FMLCommonSetupEvent event)
|
||||
{
|
||||
// Some common setup code
|
||||
LOGGER.info("HELLO FROM COMMON SETUP");
|
||||
|
||||
@@ -105,16 +98,27 @@ public final class ExampleMod {
|
||||
}
|
||||
|
||||
// Add the example block item to the building blocks tab
|
||||
private static void addCreative(BuildCreativeModeTabContentsEvent event) {
|
||||
private void addCreative(BuildCreativeModeTabContentsEvent event)
|
||||
{
|
||||
if (event.getTabKey() == CreativeModeTabs.BUILDING_BLOCKS)
|
||||
event.accept(EXAMPLE_BLOCK_ITEM);
|
||||
}
|
||||
|
||||
// You can use SubscribeEvent and let the Event Bus discover methods to call
|
||||
@SubscribeEvent
|
||||
public void onServerStarting(ServerStartingEvent event)
|
||||
{
|
||||
// Do something when the server starts
|
||||
LOGGER.info("HELLO from server starting");
|
||||
}
|
||||
|
||||
// You can use EventBusSubscriber to automatically register all static methods in the class annotated with @SubscribeEvent
|
||||
@Mod.EventBusSubscriber(modid = MODID, value = Dist.CLIENT)
|
||||
public static class ClientModEvents {
|
||||
@Mod.EventBusSubscriber(modid = MODID, bus = Mod.EventBusSubscriber.Bus.MOD, value = Dist.CLIENT)
|
||||
public static class ClientModEvents
|
||||
{
|
||||
@SubscribeEvent
|
||||
public static void onClientSetup(FMLClientSetupEvent event) {
|
||||
public static void onClientSetup(FMLClientSetupEvent event)
|
||||
{
|
||||
// Some client setup code
|
||||
LOGGER.info("HELLO FROM CLIENT SETUP");
|
||||
LOGGER.info("MINECRAFT NAME >> {}", Minecraft.getInstance().getUser().getName());
|
||||
@@ -19,6 +19,12 @@ displayName="Example Mod"
|
||||
authors="YourNameHere, OtherNameHere" #optional
|
||||
#displayTest="MATCH_VERSION" #optional, default=MATCH_VERSION when not clientSideOnly, else IGNORE_ALL_VERSION
|
||||
|
||||
# Access Transformers
|
||||
# Optional, if not specified Forge will attempt to find the default META-INF/accesstransformer.cfg and silently continue if not found. May default to an empty list in a future MC.
|
||||
# If you specify AT path strings in this list, Forge will attempt to load each of them and throw errors for any that aren't found.
|
||||
# Specifying an empty list when your mod has no ATs is recommended to opt-out of the default for slightly better loading performance.
|
||||
#accessTransformers = []
|
||||
|
||||
description='''Example mod description.
|
||||
Newline characters can be used like this, and rendered on the mods screen properly.'''
|
||||
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
--- a/com/mojang/blaze3d/opengl/GlCommandEncoder.java
|
||||
+++ b/com/mojang/blaze3d/opengl/GlCommandEncoder.java
|
||||
@@ -133,6 +_,9 @@
|
||||
GlStateManager._colorMask(true, true, true, true);
|
||||
GlStateManager._clear(16384);
|
||||
GlStateManager._glFramebufferTexture2D(36160, 36064, 3553, 0, 0);
|
||||
+ // Forge: if using stencil, clear it as well (3553 = GlConst.GL_TEXTURE_2D)
|
||||
+ if (p_394273_.isStencilEnabled())
|
||||
+ GlStateManager._glFramebufferTexture2D(GlConst.GL_FRAMEBUFFER, org.lwjgl.opengl.GL30.GL_STENCIL_ATTACHMENT, GlConst.GL_TEXTURE_2D, 0, 0);
|
||||
GlStateManager._glBindFramebuffer(36160, 0);
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
--- a/com/mojang/blaze3d/opengl/GlDebug.java
|
||||
+++ b/com/mojang/blaze3d/opengl/GlDebug.java
|
||||
@@ -24,6 +_,14 @@
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public class GlDebug {
|
||||
+ /**
|
||||
+ * TODO: [Forge][Rendering][VEN] Expose this in the configs
|
||||
+ * <p>
|
||||
+ * Will enable synchronous OpenGL debug logging, which means the message will be sent from the call that
|
||||
+ * would have caused the error. Additionally, enables printing a stacktrace when and where this occurs.
|
||||
+ */
|
||||
+ private static final boolean PRINT_STACKTRACE_ON_ERROR = Boolean.getBoolean("forge.printGLStackOnError");
|
||||
+
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static final int CIRCULAR_LOG_SIZE = 10;
|
||||
private final Queue<GlDebug.LogEntry> MESSAGE_BUFFER = EvictingQueue.create(10);
|
||||
@@ -105,6 +_,8 @@
|
||||
}
|
||||
|
||||
LOGGER.info("OpenGL debug message: {}", gldebug$logentry);
|
||||
+ // TODO: [VEN] Trim the stack trace
|
||||
+ if (PRINT_STACKTRACE_ON_ERROR) LOGGER.info("Trace: ", new Throwable("GlDebug"));
|
||||
}
|
||||
|
||||
public List<String> getLastOpenGlDebugMessages() {
|
||||
@@ -120,6 +_,7 @@
|
||||
}
|
||||
|
||||
public static @Nullable GlDebug enableDebugCallback(int p_394351_, boolean p_393026_, Set<String> p_393339_) {
|
||||
+ p_393026_ |= PRINT_STACKTRACE_ON_ERROR;
|
||||
if (p_394351_ <= 0) {
|
||||
return null;
|
||||
} else {
|
||||
@@ -1,74 +0,0 @@
|
||||
--- a/com/mojang/blaze3d/opengl/GlDevice.java
|
||||
+++ b/com/mojang/blaze3d/opengl/GlDevice.java
|
||||
@@ -124,8 +_,15 @@
|
||||
int p_407582_,
|
||||
int p_408928_
|
||||
) {
|
||||
+ return createTexture(p_397830_, p_394481_, p_394839_, p_391831_, p_395609_, p_407582_, p_408928_, false);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public GpuTexture createTexture(
|
||||
+ @Nullable Supplier<String> p_397830_, int p_394481_, TextureFormat p_394839_, int p_391831_, int p_395609_, int p_407582_, int p_408928_, boolean stencil
|
||||
+ ) {
|
||||
return this.createTexture(
|
||||
- this.debugLabels.exists() && p_397830_ != null ? p_397830_.get() : null, p_394481_, p_394839_, p_391831_, p_395609_, p_407582_, p_408928_
|
||||
+ this.debugLabels.exists() && p_397830_ != null ? p_397830_.get() : null, p_394481_, p_394839_, p_391831_, p_395609_, p_407582_, p_408928_, stencil
|
||||
);
|
||||
}
|
||||
|
||||
@@ -133,6 +_,15 @@
|
||||
public GpuTexture createTexture(
|
||||
@Nullable String p_394142_, @GpuTexture.Usage int p_395535_, TextureFormat p_394951_, int p_393944_, int p_392329_, int p_408015_, int p_406483_
|
||||
) {
|
||||
+ return createTexture(p_394142_, p_395535_, p_394951_, p_393944_, p_392329_, p_408015_, p_406483_, false);
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public GpuTexture createTexture(
|
||||
+ @Nullable String p_394142_, @GpuTexture.Usage int p_395535_, TextureFormat p_394951_, int p_393944_, int p_392329_, int p_408015_, int p_406483_, boolean stencil
|
||||
+ ) {
|
||||
+ // Forge: stencil rendering is only for depth
|
||||
+ stencil &= p_394951_.hasDepthAspect();
|
||||
if (p_406483_ < 1) {
|
||||
throw new IllegalArgumentException("mipLevels must be at least 1");
|
||||
} else if (p_408015_ < 1) {
|
||||
@@ -183,11 +_,14 @@
|
||||
GlStateManager._texImage2D(
|
||||
k,
|
||||
l,
|
||||
+ stencil ? org.lwjgl.opengl.GL30.GL_DEPTH32F_STENCIL8 :
|
||||
GlConst.toGlInternalId(p_394951_),
|
||||
p_393944_ >> l,
|
||||
p_392329_ >> l,
|
||||
0,
|
||||
+ stencil ? org.lwjgl.opengl.GL30.GL_DEPTH_STENCIL :
|
||||
GlConst.toGlExternalId(p_394951_),
|
||||
+ stencil ? org.lwjgl.opengl.GL30.GL_FLOAT_32_UNSIGNED_INT_24_8_REV :
|
||||
GlConst.toGlType(p_394951_),
|
||||
null
|
||||
);
|
||||
@@ -198,11 +_,14 @@
|
||||
GlStateManager._texImage2D(
|
||||
j,
|
||||
i1,
|
||||
+ stencil ? org.lwjgl.opengl.GL30.GL_DEPTH32F_STENCIL8 :
|
||||
GlConst.toGlInternalId(p_394951_),
|
||||
p_393944_ >> i1,
|
||||
p_392329_ >> i1,
|
||||
0,
|
||||
+ stencil ? org.lwjgl.opengl.GL30.GL_DEPTH_STENCIL :
|
||||
GlConst.toGlExternalId(p_394951_),
|
||||
+ stencil ? org.lwjgl.opengl.GL30.GL_FLOAT_32_UNSIGNED_INT_24_8_REV :
|
||||
GlConst.toGlType(p_394951_),
|
||||
null
|
||||
);
|
||||
@@ -215,7 +_,7 @@
|
||||
} else if (j1 != 0) {
|
||||
throw new IllegalStateException("OpenGL error " + j1);
|
||||
} else {
|
||||
- GlTexture gltexture = new GlTexture(p_395535_, p_394142_, p_394951_, p_393944_, p_392329_, p_408015_, p_406483_, i);
|
||||
+ GlTexture gltexture = new GlTexture(p_395535_, p_394142_, p_394951_, p_393944_, p_392329_, p_408015_, p_406483_, i, stencil);
|
||||
this.debugLabels.applyLabel(gltexture);
|
||||
return gltexture;
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
--- a/com/mojang/blaze3d/opengl/GlStateManager.java
|
||||
+++ b/com/mojang/blaze3d/opengl/GlStateManager.java
|
||||
@@ -86,6 +_,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
+ public static boolean _isBlendEnabled() {
|
||||
+ RenderSystem.assertOnRenderThread();
|
||||
+ return BLEND.mode.enabled;
|
||||
+ }
|
||||
+
|
||||
public static void _disableBlend() {
|
||||
RenderSystem.assertOnRenderThread();
|
||||
BLEND.mode.disable();
|
||||
@@ -371,9 +_,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
+ /* Stores the last values sent into glMultiTexCoord2f */
|
||||
+ public static float lastBrightnessX = 0.0f;
|
||||
+ public static float lastBrightnessY = 0.0f;
|
||||
+
|
||||
public static void _texParameter(int p_393876_, int p_397998_, int p_396741_) {
|
||||
RenderSystem.assertOnRenderThread();
|
||||
GL11.glTexParameteri(p_393876_, p_397998_, p_396741_);
|
||||
+ if (p_393876_ == GL13.GL_TEXTURE1) {
|
||||
+ lastBrightnessX = p_397998_;
|
||||
+ lastBrightnessY = p_396741_;
|
||||
+ }
|
||||
}
|
||||
|
||||
public static int _getTexLevelParameter(int p_393352_, int p_397204_, int p_396319_) {
|
||||
@@ -1,29 +0,0 @@
|
||||
--- a/com/mojang/blaze3d/opengl/GlTexture.java
|
||||
+++ b/com/mojang/blaze3d/opengl/GlTexture.java
|
||||
@@ -21,8 +_,13 @@
|
||||
protected GlTexture(
|
||||
@GpuTexture.Usage int p_394590_, String p_393950_, TextureFormat p_392837_, int p_391379_, int p_391947_, int p_396659_, int p_408255_, int p_408889_
|
||||
) {
|
||||
+ this(p_394590_, p_393950_, p_392837_, p_391379_, p_391947_, p_396659_, p_408255_, p_408889_, false);
|
||||
+ }
|
||||
+
|
||||
+ protected GlTexture(int p_394590_, String p_393950_, TextureFormat p_392837_, int p_391379_, int p_391947_, int p_396659_, int p_408255_, int p_408889_, boolean stencil) {
|
||||
super(p_394590_, p_393950_, p_392837_, p_391379_, p_391947_, p_396659_, p_408255_);
|
||||
this.id = p_408889_;
|
||||
+ this.stencilEnabled = stencil;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -89,5 +_,12 @@
|
||||
if (this.closed && this.views == 0) {
|
||||
this.destroyImmediately();
|
||||
}
|
||||
+ }
|
||||
+
|
||||
+ private final boolean stencilEnabled;
|
||||
+
|
||||
+ @Override
|
||||
+ public boolean isStencilEnabled() {
|
||||
+ return this.stencilEnabled;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,35 @@
|
||||
--- a/com/mojang/blaze3d/pipeline/RenderTarget.java
|
||||
+++ b/com/mojang/blaze3d/pipeline/RenderTarget.java
|
||||
@@ -80,7 +_,7 @@
|
||||
this.width = p_83951_;
|
||||
this.height = p_83952_;
|
||||
if (this.useDepth) {
|
||||
- this.depthTexture = gpudevice.createTexture(() -> this.label + " / Depth", 15, TextureFormat.DEPTH32, p_83951_, p_83952_, 1, 1);
|
||||
+ this.depthTexture = gpudevice.createTexture(() -> this.label + " / Depth", 15, TextureFormat.DEPTH32, p_83951_, p_83952_, 1, 1, this.stencilEnabled);
|
||||
this.depthTextureView = gpudevice.createTextureView(this.depthTexture);
|
||||
@@ -104,7 +_,10 @@
|
||||
GlStateManager._texParameter(3553, 34892, 0);
|
||||
GlStateManager._texParameter(3553, 10242, 33071);
|
||||
GlStateManager._texParameter(3553, 10243, 33071);
|
||||
+ if (!stencilEnabled)
|
||||
GlStateManager._texImage2D(3553, 0, 6402, this.width, this.height, 0, 6402, 5126, null);
|
||||
+ else
|
||||
+ GlStateManager._texImage2D(3553, 0, org.lwjgl.opengl.GL30.GL_DEPTH32F_STENCIL8, this.width, this.height, 0, org.lwjgl.opengl.GL30.GL_DEPTH_STENCIL, org.lwjgl.opengl.GL30.GL_FLOAT_32_UNSIGNED_INT_24_8_REV, null);
|
||||
}
|
||||
|
||||
@@ -126,5 +_,26 @@
|
||||
this.setFilterMode(9728, true);
|
||||
@@ -114,8 +_,13 @@
|
||||
GlStateManager._texImage2D(3553, 0, 32856, this.width, this.height, 0, 6408, 5121, null);
|
||||
GlStateManager._glBindFramebuffer(36160, this.frameBufferId);
|
||||
GlStateManager._glFramebufferTexture2D(36160, 36064, 3553, this.colorTextureId, 0);
|
||||
- if (this.useDepth) {
|
||||
+ if (this.useDepth && !stencilEnabled) {
|
||||
GlStateManager._glFramebufferTexture2D(36160, 36096, 3553, this.depthBufferId, 0);
|
||||
+ } else if(this.useDepth && net.minecraftforge.common.ForgeConfig.CLIENT.useCombinedDepthStencilAttachment.get()) {
|
||||
+ GlStateManager._glFramebufferTexture2D(org.lwjgl.opengl.GL30.GL_FRAMEBUFFER, org.lwjgl.opengl.GL30.GL_DEPTH_STENCIL_ATTACHMENT, 3553, this.depthBufferId, 0);
|
||||
+ } else if (this.useDepth) {
|
||||
+ GlStateManager._glFramebufferTexture2D(org.lwjgl.opengl.GL30.GL_FRAMEBUFFER, org.lwjgl.opengl.GL30.GL_DEPTH_ATTACHMENT, 3553, this.depthBufferId, 0);
|
||||
+ GlStateManager._glFramebufferTexture2D(org.lwjgl.opengl.GL30.GL_FRAMEBUFFER, org.lwjgl.opengl.GL30.GL_STENCIL_ATTACHMENT, 3553, this.depthBufferId, 0);
|
||||
}
|
||||
|
||||
public @Nullable GpuTextureView getDepthTextureView() {
|
||||
return this.depthTextureView;
|
||||
this.checkStatus();
|
||||
@@ -257,5 +_,26 @@
|
||||
|
||||
public int getDepthTextureId() {
|
||||
return this.depthBufferId;
|
||||
+ }
|
||||
+
|
||||
+ private boolean stencilEnabled = false;
|
||||
@@ -25,7 +42,7 @@
|
||||
+ public void enableStencil() {
|
||||
+ if (stencilEnabled) return;
|
||||
+ stencilEnabled = true;
|
||||
+ this.resize(width, height);
|
||||
+ this.resize(viewWidth, viewHeight, net.minecraft.client.Minecraft.ON_OSX);
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
--- a/com/mojang/blaze3d/platform/GlStateManager.java
|
||||
+++ b/com/mojang/blaze3d/platform/GlStateManager.java
|
||||
@@ -96,6 +_,11 @@
|
||||
BLEND.mode.enable();
|
||||
}
|
||||
|
||||
+ public static boolean _getBlendState() {
|
||||
+ RenderSystem.assertOnRenderThread();
|
||||
+ return BLEND.mode.enabled;
|
||||
+ }
|
||||
+
|
||||
public static void _blendFunc(int p_84329_, int p_84330_) {
|
||||
RenderSystem.assertOnRenderThread();
|
||||
if (p_84329_ != BLEND.srcRgb || p_84330_ != BLEND.dstRgb) {
|
||||
@@ -493,9 +_,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
+ /* Stores the last values sent into glMultiTexCoord2f */
|
||||
+ public static float lastBrightnessX = 0.0f;
|
||||
+ public static float lastBrightnessY = 0.0f;
|
||||
+
|
||||
public static void _texParameter(int p_84161_, int p_84162_, float p_84163_) {
|
||||
RenderSystem.assertOnRenderThreadOrInit();
|
||||
GL11.glTexParameterf(p_84161_, p_84162_, p_84163_);
|
||||
+ if (p_84161_ == GL13.GL_TEXTURE1) {
|
||||
+ lastBrightnessX = p_84162_;
|
||||
+ lastBrightnessY = p_84163_;
|
||||
+ }
|
||||
}
|
||||
|
||||
public static void _texParameter(int p_84332_, int p_84333_, int p_84334_) {
|
||||
@@ -1,25 +1,25 @@
|
||||
--- a/com/mojang/blaze3d/platform/Window.java
|
||||
+++ b/com/mojang/blaze3d/platform/Window.java
|
||||
@@ -91,7 +_,8 @@
|
||||
GLFW.glfwWindowHint(139267, 3);
|
||||
@@ -84,7 +_,8 @@
|
||||
GLFW.glfwWindowHint(139267, 2);
|
||||
GLFW.glfwWindowHint(139272, 204801);
|
||||
GLFW.glfwWindowHint(139270, 1);
|
||||
- this.handle = GLFW.glfwCreateWindow(this.width, this.height, p_85376_, this.fullscreen && monitor != null ? monitor.getMonitor() : 0L, 0L);
|
||||
+ this.handle = net.minecraftforge.fml.loading.ImmediateWindowHandler.setupMinecraftWindow(()->this.width, ()->this.height, ()->p_85376_, ()->this.fullscreen && monitor != null ? monitor.getMonitor() : 0L);
|
||||
- this.window = GLFW.glfwCreateWindow(this.width, this.height, p_85376_, this.fullscreen && monitor != null ? monitor.getMonitor() : 0L, 0L);
|
||||
+ this.window = net.minecraftforge.fml.loading.ImmediateWindowHandler.setupMinecraftWindow(()->this.width, ()->this.height, ()->p_85376_, ()->this.fullscreen && monitor != null ? monitor.getMonitor() : 0L);
|
||||
+ if (!net.minecraftforge.fml.loading.ImmediateWindowHandler.positionWindow(Optional.ofNullable(monitor), w->this.width = this.windowedWidth = w, h->this.height = this.windowedHeight = h, x->this.x = this.windowedX = x, y->this.y = this.windowedY = y)) {
|
||||
if (monitor != null) {
|
||||
VideoMode videomode = monitor.getPreferredVidMode(this.fullscreen ? this.preferredFullscreenVideoMode : Optional.empty());
|
||||
this.windowedX = this.x = monitor.getX() + videomode.getWidth() / 2 - this.width / 2;
|
||||
@@ -103,6 +_,7 @@
|
||||
@@ -96,6 +_,7 @@
|
||||
this.windowedX = this.x = aint1[0];
|
||||
this.windowedY = this.y = aint[0];
|
||||
}
|
||||
+ }
|
||||
|
||||
this.setMode();
|
||||
this.refreshFramebufferSize();
|
||||
@@ -269,6 +_,9 @@
|
||||
GLFW.glfwGetFramebufferSize(this.handle, aint, aint1);
|
||||
GLFW.glfwMakeContextCurrent(this.window);
|
||||
GL.createCapabilities();
|
||||
@@ -254,6 +_,9 @@
|
||||
GLFW.glfwGetFramebufferSize(this.window, aint, aint1);
|
||||
this.framebufferWidth = aint[0] > 0 ? aint[0] : 1;
|
||||
this.framebufferHeight = aint1[0] > 0 ? aint1[0] : 1;
|
||||
+ if (this.framebufferHeight == 0 || this.framebufferWidth == 0) {
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
--- a/com/mojang/blaze3d/systems/GpuDevice.java
|
||||
+++ b/com/mojang/blaze3d/systems/GpuDevice.java
|
||||
@@ -36,9 +_,19 @@
|
||||
int p_407070_
|
||||
);
|
||||
|
||||
+ /** Forge: same as {@link #createTexture(Supplier, int, TextureFormat, int, int, int)} but with stencil support */
|
||||
+ default GpuTexture createTexture(@Nullable Supplier<String> p_394357_, int p_395623_, TextureFormat p_395807_, int p_395802_, int p_396157_, int p_408810_, int p_407070_, boolean stencil) {
|
||||
+ return this.createTexture(p_394357_, p_395623_, p_395807_, p_395802_, p_396157_, p_408810_, p_407070_);
|
||||
+ }
|
||||
+
|
||||
GpuTexture createTexture(
|
||||
@Nullable String p_391798_, @GpuTexture.Usage int p_391800_, TextureFormat p_393333_, int p_395600_, int p_394065_, int p_408732_, int p_407125_
|
||||
);
|
||||
+
|
||||
+ /** Forge: same as {@link #createTexture(String, int, TextureFormat, int, int, int)} but with stencil support */
|
||||
+ default GpuTexture createTexture(@Nullable String p_391798_, int p_391800_, TextureFormat p_393333_, int p_395600_, int p_394065_, int p_408732_, int p_407125_, boolean stencil) {
|
||||
+ return this.createTexture(p_391798_, p_391800_, p_393333_, p_395600_, p_394065_, p_408732_, p_407125_);
|
||||
+ }
|
||||
|
||||
GpuTextureView createTextureView(GpuTexture p_409045_);
|
||||
|
||||
Loaded 100 of 1833 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user