Compare commits

..
21 Commits
Author SHA1 Message Date
LexManos 694167779c Add protection against large network channel lists 2026-10-14 19:45:46 -07:00
LexManos 3ea91e77ac Fix legacy object holders not being fired in the registry specific codepath
Better optimize the scanning for object holders by unrolling streams.
2026-10-13 10:24:28 -07:00
BonsUnleashedandLexManos fc27a35722 Store object holder handlers per registry name (#10899)
Co-authored-by: LexManos <[email protected]>
2026-10-12 16:41:51 -07:00
LexManos 12939f4cd3 Fix generic errors in new Eclipse compiler 2026-10-12 16:41:02 -07:00
Odin Okland 7a37f17c89 Use vanilla's entrypoint for GameTest servers (#10884) 2026-09-23 15:14:38 -07:00
Paint_NinjaandMartijn van den Brand 22168f6a69 Fix IForgeBlockStateModel.getModelData not being called correct for #getParticleIcon (#10818) (#10875)
Co-authored-by: Martijn van den Brand <[email protected]>
2026-08-15 13:34:02 -07:00
Meme Man d32d853c0b Update equality check for RegistryObject to include RegistryObject#key. (#10866) 2026-08-04 19:09:58 +01:00
Matt dd24e1385e Toml parsing errors are now visible in the latest log (#10840) 2026-07-09 12:45:19 -07:00
Matt 1ae17d5a04 Add support for multiple access transformer configs. 2026-03-23 12:03:29 -07:00
Jonathing deaf69e1c9 Migrate the MDK to ForgeGradle 7
Backport of d29524b to 1.21.5
2026-03-18 22:54:38 +00:00
Paint_Ninja ea7dff02a3 Skip processing vanilla classes in RuntimeDistCleaner when running on a client
Backport of #10682 to 1.21.5. Also bumped ASM to 9.9.1
2026-03-17 20:19:28 +00:00
LexManos 054c23a59a Update SecureModules to address ZipPath bug with brackets. Closes #10755 2026-01-20 18:28:21 -08:00
LexManos 220dcbe92c Fix Entity patch lines 2026-01-15 11:06:05 -08:00
MattandLexManos 6865f4e77d Optimize memory allocation for checking entity fluid collisions. Backport of #10745 (#10750)
Co-authored-by: LexManos <[email protected]>
2026-01-15 10:45:48 -08:00
LexManos 3ea77c75b4 Mark applyServerBinpatches as a dependency of BundleList. Fixes: #10729 2025-12-04 08:56:28 -08:00
LexManos d97e796c56 Migrate to Github Actions 2025-12-02 14:27:55 -08:00
Matt 6585db991d Fix crafter not inserting into iitemhandler (#10667) (#10672) 2025-09-25 13:10:57 -07:00
Paint_Ninja fe64775f09 Bump ASM to 9.8 for Java 25 runtime support
Note: Forge for 1.21.8 already has ASM 9.8
2025-09-22 11:53:38 +01:00
LexManos 9ccfaf4812 1.21.5 RB 1 2025-08-27 12:02:50 -07:00
Paint_Ninja 3ed3d2b9cf Update to EventBus 6.2.32 (#10644)
- Performance improvements, especially for posting to zero or one listeners on an event
- New Event#cancel() convenience method
- Only the monitor priority is tracked on the Forge bus now
2025-08-27 17:36:13 +01:00
Matt 3d374fe732 [1.21.5] Add ForgeLayeredDraw & ModifyOverlayLayersEvent as API to making gui layers. (#10556) 2025-07-25 10:44:49 -07:00
1236 changed files with 22037 additions and 18090 deletions

No files matched your search

+1 -1
View File
@@ -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 -->
-1
View File
@@ -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}
+1 -1
View File
@@ -2,7 +2,7 @@ name: Publish
on:
push:
branches: [ '1.21.11' ]
branches: [ '1.21.5' ]
permissions:
contents: read
+5 -4
View File
@@ -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,7 +16,8 @@ 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)
}
+3 -4
View File
@@ -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)
}
@@ -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)
}
+9 -26
View File
@@ -71,7 +71,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,7 +91,7 @@ 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.1: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.
@@ -112,7 +112,6 @@ 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
@@ -124,18 +123,17 @@ dependencies {
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 +141,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 +165,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',
@@ -306,11 +295,6 @@ tasks.userdevConfig.configure {
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 {
@@ -427,7 +411,7 @@ tasks.register('extractInheritance', ExtractInheritance) {
}
tasks.register("findFinalizeSpawnTargets", BytecodePredicateFinder) {
jar = createClientOfficial.output
output = rootProject.file('forge-transformers/src/main/resources/coremods/finalize_spawn_targets.json')
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.
@@ -971,8 +955,7 @@ 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'
@@ -1019,7 +1002,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 })
-4
View File
@@ -38,10 +38,6 @@ 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'
+2 -2
View File
@@ -2,8 +2,8 @@
MinecraftForge
=============
[![Stable Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Stable&prefix=1.21.11-&query=$.promos["1.21.11-recommended"]&color=brightgreen&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtc3RhciBmYS13LTE4IiBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1pY29uPSJzdGFyIiBkYXRhLXByZWZpeD0iZmFzIiBmb2N1c2FibGU9ImZhbHNlIiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCA1NzYgNTEyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNMjU5LjMgMTcuOEwxOTQgMTUwLjIgNDcuOSAxNzEuNWMtMjYuMiAzLjgtMzYuNyAzNi4xLTE3LjcgNTQuNmwxMDUuNyAxMDMtMjUgMTQ1LjVjLTQuNSAyNi4zIDIzLjIgNDYgNDYuNCAzMy43TDI4OCA0MzkuNmwxMzAuNyA2OC43YzIzLjIgMTIuMiA1MC45LTcuNCA0Ni40LTMzLjdsLTI1LTE0NS41IDEwNS43LTEwM2MxOS0xOC41IDguNS01MC44LTE3LjctNTQuNkwzODIgMTUwLjIgMzE2LjcgMTcuOGMtMTEuNy0yMy42LTQ1LjYtMjMuOS01Ny40IDB6IiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K)][Download]
[![Latest Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Latest&prefix=1.21.11-&query=$.promos["1.21.11-latest"]&color=blue&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtYnVnIGZhLXctMTYiIGFyaWEtaGlkZGVuPSJ0cnVlIiBkYXRhLWljb249ImJ1ZyIgZGF0YS1wcmVmaXg9ImZhcyIgZm9jdXNhYmxlPSJmYWxzZSIgcm9sZT0iaW1nIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTUxMS45ODggMjg4LjljLS40NzggMTcuNDMtMTUuMjE3IDMxLjEtMzIuNjUzIDMxLjFINDI0djE2YzAgMjEuODY0LTQuODgyIDQyLjU4NC0xMy42IDYxLjE0NWw2MC4yMjggNjAuMjI4YzEyLjQ5NiAxMi40OTcgMTIuNDk2IDMyLjc1OCAwIDQ1LjI1NS0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDBsLTU0LjczNi01NC43MzZDMzQ1Ljg4NiA0NjcuOTY1IDMxNC4zNTEgNDgwIDI4MCA0ODBWMjM2YzAtNi42MjctNS4zNzMtMTItMTItMTJoLTI0Yy02LjYyNyAwLTEyIDUuMzczLTEyIDEydjI0NGMtMzQuMzUxIDAtNjUuODg2LTEyLjAzNS05MC42MzYtMzIuMTA4bC01NC43MzYgNTQuNzM2Yy0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDAtMTIuNDk2LTEyLjQ5Ny0xMi40OTYtMzIuNzU4IDAtNDUuMjU1bDYwLjIyOC02MC4yMjhDOTIuODgyIDM3OC41ODQgODggMzU3Ljg2NCA4OCAzMzZ2LTE2SDMyLjY2NkMxNS4yMyAzMjAgLjQ5MSAzMDYuMzMuMDEzIDI4OC45LS40ODQgMjcwLjgxNiAxNC4wMjggMjU2IDMyIDI1Nmg1NnYtNTguNzQ1bC00Ni42MjgtNDYuNjI4Yy0xMi40OTYtMTIuNDk3LTEyLjQ5Ni0zMi43NTggMC00NS4yNTUgMTIuNDk4LTEyLjQ5NyAzMi43NTgtMTIuNDk3IDQ1LjI1NiAwTDE0MS4yNTUgMTYwaDIyOS40ODlsNTQuNjI3LTU0LjYyN2MxMi40OTgtMTIuNDk3IDMyLjc1OC0xMi40OTcgNDUuMjU2IDAgMTIuNDk2IDEyLjQ5NyAxMi40OTYgMzIuNzU4IDAgNDUuMjU1TDQyNCAxOTcuMjU1VjI1Nmg1NmMxNy45NzIgMCAzMi40ODQgMTQuODE2IDMxLjk4OCAzMi45ek0yNTcgMGMtNjEuODU2IDAtMTEyIDUwLjE0NC0xMTIgMTEyaDIyNEMzNjkgNTAuMTQ0IDMxOC44NTYgMCAyNTcgMHoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=
[![Stable Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Stable&prefix=1.21.5-&query=$.promos["1.21.5-recommended"]&color=brightgreen&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtc3RhciBmYS13LTE4IiBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1pY29uPSJzdGFyIiBkYXRhLXByZWZpeD0iZmFzIiBmb2N1c2FibGU9ImZhbHNlIiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCA1NzYgNTEyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNMjU5LjMgMTcuOEwxOTQgMTUwLjIgNDcuOSAxNzEuNWMtMjYuMiAzLjgtMzYuNyAzNi4xLTE3LjcgNTQuNmwxMDUuNyAxMDMtMjUgMTQ1LjVjLTQuNSAyNi4zIDIzLjIgNDYgNDYuNCAzMy43TDI4OCA0MzkuNmwxMzAuNyA2OC43YzIzLjIgMTIuMiA1MC45LTcuNCA0Ni40LTMzLjdsLTI1LTE0NS41IDEwNS43LTEwM2MxOS0xOC41IDguNS01MC44LTE3LjctNTQuNkwzODIgMTUwLjIgMzE2LjcgMTcuOGMtMTEuNy0yMy42LTQ1LjYtMjMuOS01Ny40IDB6IiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K)][Download]
[![Latest Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Latest&prefix=1.21.5-&query=$.promos["1.21.5-latest"]&color=blue&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtYnVnIGZhLXctMTYiIGFyaWEtaGlkZGVuPSJ0cnVlIiBkYXRhLWljb249ImJ1ZyIgZGF0YS1wcmVmaXg9ImZhcyIgZm9jdXNhYmxlPSJmYWxzZSIgcm9sZT0iaW1nIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTUxMS45ODggMjg4LjljLS40NzggMTcuNDMtMTUuMjE3IDMxLjEtMzIuNjUzIDMxLjFINDI0djE2YzAgMjEuODY0LTQuODgyIDQyLjU4NC0xMy42IDYxLjE0NWw2MC4yMjggNjAuMjI4YzEyLjQ5NiAxMi40OTcgMTIuNDk2IDMyLjc1OCAwIDQ1LjI1NS0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDBsLTU0LjczNi01NC43MzZDMzQ1Ljg4NiA0NjcuOTY1IDMxNC4zNTEgNDgwIDI4MCA0ODBWMjM2YzAtNi42MjctNS4zNzMtMTItMTItMTJoLTI0Yy02LjYyNyAwLTEyIDUuMzczLTEyIDEydjI0NGMtMzQuMzUxIDAtNjUuODg2LTEyLjAzNS05MC42MzYtMzIuMTA4bC01NC43MzYgNTQuNzM2Yy0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDAtMTIuNDk2LTEyLjQ5Ny0xMi40OTYtMzIuNzU4IDAtNDUuMjU1bDYwLjIyOC02MC4yMjhDOTIuODgyIDM3OC41ODQgODggMzU3Ljg2NCA4OCAzMzZ2LTE2SDMyLjY2NkMxNS4yMyAzMjAgLjQ5MSAzMDYuMzMuMDEzIDI4OC45LS40ODQgMjcwLjgxNiAxNC4wMjggMjU2IDMyIDI1Nmg1NnYtNTguNzQ1bC00Ni42MjgtNDYuNjI4Yy0xMi40OTYtMTIuNDk3LTEyLjQ5Ni0zMi43NTggMC00NS4yNTUgMTIuNDk4LTEyLjQ5NyAzMi43NTgtMTIuNDk3IDQ1LjI1NiAwTDE0MS4yNTUgMTYwaDIyOS40ODlsNTQuNjI3LTU0LjYyN2MxMi40OTgtMTIuNDk3IDMyLjc1OC0xMi40OTcgNDUuMjU2IDAgMTIuNDk2IDEyLjQ5NyAxMi40OTYgMzIuNzU4IDAgNDUuMjU1TDQyNCAxOTcuMjU1VjI1Nmg1NmMxNy45NzIgMCAzMi40ODQgMTQuODE2IDMxLjk4OCAzMi45ek0yNTcgMGMtNjEuODU2IDAtMTEyIDUwLjE0NC0xMTIgMTEyaDIyNEMzNjkgNTAuMTQ0IDMxOC44NTYgMCAyNTcgMHoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=
)][Download] [![Discord](https://img.shields.io/discord/313125603924639766.svg?color=%237289da&label=Discord&logo=discord&logoColor=%237289da)][Discord] [![Support](https://img.shields.io/badge/Patreon-Support-orange.svg?logo=Patreon)][Patreon]
Forge is a free, open-source modding API all of your favourite mods use!
+1 -2
View File
@@ -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();
}
@@ -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,13 +61,14 @@ 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(
default <T extends Event & IModBusEvent> Optional<CompletableFuture<Void>> buildTransition(
final Executor syncExecutor,
final Executor parallelExecutor,
final ProgressMeter progressBar
@@ -80,6 +83,7 @@ public interface IModLoadingState {
* 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 +91,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,6 +5,7 @@
package net.minecraftforge.fml;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
@@ -19,7 +20,7 @@ import org.jetbrains.annotations.Nullable;
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);
public static final BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> NULL_HOOK = (e, g) -> CompletableFuture.completedFuture(null);
static IModStateTransition buildNoopTransition() {
return ModStateTransitionHelper.NOOP;
@@ -50,7 +51,7 @@ public interface IModStateTransition {
}
@Nullable
default <T extends IModBusEvent> EventGenerator<T> eventFunction() {
default <T extends Event & IModBusEvent> EventGenerator<T> eventFunction() {
return null;
}
@@ -73,8 +74,8 @@ public interface IModStateTransition {
@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;
}
}
@@ -5,7 +5,7 @@
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;
@@ -158,17 +158,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) {}
}
@@ -41,14 +41,14 @@ public class ModList {
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.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 file : this.modFiles) {
for (var mod : file.getMods())
byId.put(mod.getModId(), mod.getOwningFile());
}
this.fileById = Map.copyOf(byId);
this.fileById = Collections.unmodifiableMap(byId);
CrashReportCallables.registerCrashCallable("Mod List", this::crashReport);
}
@@ -67,7 +67,7 @@ public class ModList {
}
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) {
@@ -126,9 +126,10 @@ public class ModList {
map(IModFileInfo::getFile).
distinct().
map(IModFile::getScanResult).
toList();
collect(Collectors.toList());
}
return modFileScanData;
}
public void forEachModFile(Consumer<IModFile> fileConsumer) {
@@ -6,7 +6,7 @@
package net.minecraftforge.fml;
import com.google.common.collect.ImmutableList;
import net.minecraftforge.eventbus.api.bus.BusGroup;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.loading.FMLEnvironment;
import net.minecraftforge.fml.loading.FMLLoader;
@@ -85,20 +85,20 @@ public class ModLoader {
private ModList modList;
private ModLoader() {
var loadingModList = this.loadingModList = FMLLoader.getLoadingModList();
var loadingExceptions = this.loadingExceptions = loadingModList.getErrors().stream()
this.loadingModList = FMLLoader.getLoadingModList();
this.loadingExceptions = this.loadingModList.getErrors().stream()
.flatMap(ModLoadingException::fromEarlyException)
.collect(Collectors.toList());
this.loadingWarnings = loadingModList.getBrokenFiles().stream()
.toList();
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 +113,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",""));
}
@@ -155,13 +155,11 @@ public class ModLoader {
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);
@@ -233,19 +231,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.accept(String.format("Mod loading complete - %d mods loaded", this.modList.size()));
progress.complete();
}
@@ -267,7 +253,7 @@ 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);
@@ -366,63 +352,54 @@ 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) {
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() {
@@ -439,7 +416,7 @@ public class ModLoader {
return runningDataGen;
}
private static final class ErroredModContainer extends ModContainer {
private static class ErroredModContainer extends ModContainer {
public ErroredModContainer() {
super();
}
@@ -19,13 +19,7 @@ public class ModLoadingContext {
private Object languageExtension;
/**
* @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;
@@ -35,7 +36,7 @@ public record ModLoadingState(
Optional<IModStateTransition> transition
) implements IModLoadingState {
@Override
public Optional<CompletableFuture<Void>> buildTransition(
public <T extends Event & IModBusEvent> Optional<CompletableFuture<Void>> buildTransition(
final Executor syncExecutor,
final Executor parallelExecutor,
final ProgressMeter progressBar,
@@ -60,7 +61,7 @@ public record ModLoadingState(
*/
@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 of(name, phase).after(previous).empty();
}
/**
@@ -121,22 +122,6 @@ public record ModLoadingState(
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;
@@ -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));
}
}
@@ -9,16 +9,15 @@ 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.eventbus.api.Event;
import net.minecraftforge.fml.IModStateTransition.EventGenerator;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
@@ -42,7 +41,7 @@ class ModStateTransitionHelper {
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();
final var throwables = t.stream().map(FutureResult::exception).filter(e -> e != null).toList();
CompletableFuture<Void> cf = new CompletableFuture<>();
final RuntimeException accumulator = new RuntimeException();
cf.completeExceptionally(accumulator);
@@ -71,15 +70,13 @@ class ModStateTransitionHelper {
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)));
results[i] = future.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(
private static <T extends Event & IModBusEvent> void addCompletableFutureTaskForModDispatch(
final IModStateTransition transition,
final Executor executor,
final List<CompletableFuture<Void>> completableFutures,
@@ -138,14 +135,14 @@ class ModStateTransitionHelper {
completableFutures.add(postDispatchHook);
}
private static <T extends IModBusEvent> CompletableFuture<Void> getHook(BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> hook, Executor executor, EventGenerator<T> eventGenerator) {
private static <T extends Event & 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(
static <T extends Event & IModBusEvent> CompletableFuture<Void> build(
final IModStateTransition transition,
final String name,
final Executor syncExecutor,
@@ -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() { }
}
+1 -1
View File
@@ -28,7 +28,7 @@ java {
}
dependencies {
compileOnly(libs.jetbrains.annotations)
compileOnly(libs.nulls)
implementation(project(':fmlloader'))
implementation(project(':fmlcore'))
implementation(libs.bundles.lwjgl)
+3 -23
View File
@@ -13,7 +13,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 +26,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 +36,12 @@ 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)
}
@@ -103,31 +103,11 @@ 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 {
@@ -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;
}
}
@@ -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
@@ -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;
@@ -54,7 +56,10 @@ public class FMLLoader {
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 +97,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(); ) {
@@ -143,8 +147,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 +162,10 @@ public class FMLLoader {
return List.of(modValidator.getModResources());
}
public static ICoreModProvider getCoreModProvider() {
return coreModProvider;
}
public static LanguageLoadingProvider getLanguageLoadingProvider() {
return languageLoadingProvider;
}
@@ -68,6 +68,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,12 +5,13 @@
package net.minecraftforge.fml.loading;
import net.minecraftforge.fml.loading.EarlyLoadingException.ExceptionData;
import com.mojang.logging.LogUtils;
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.nio.file.Files;
import java.nio.file.Path;
@@ -20,10 +21,6 @@ import java.util.List;
import java.util.Map;
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>
@@ -42,7 +39,9 @@ public class LoadingModList {
.map(ModFile::getModFileInfo)
.map(ModFileInfo.class::cast)
.toList();
this.sortedList = sortedList.stream().toList();
this.sortedList = sortedList.stream()
.map(ModInfo.class::cast)
.toList();
this.fileById = this.modFiles.stream()
.map(ModFileInfo::getMods)
.flatMap(Collection::stream)
@@ -62,23 +61,31 @@ public class LoadingModList {
return INSTANCE;
}
public void addAccessTransformers() {
var errors = new ArrayList<ExceptionData>();
public void addCoreMods() {
modFiles.stream()
.map(ModFileInfo::getFile)
.map(ModFile::getCoreMods)
.flatMap(List::stream)
.forEach(FMLLoader.getCoreModProvider()::addCoreMod);
}
public void addAccessTransformers() {
var errors = new ArrayList<EarlyLoadingException.ExceptionData>();
for (ModFileInfo modFile : modFiles) {
ModFile mod = modFile.getFile();
for (var at : mod.getAccessTransformers()) {
for (Path at : mod.getAccessTransformers()) {
if (!Files.exists(at)) {
var message = "Invalid mod file: " + modFile.getFile().getFileName() + ". Missing Access Transformer: " + at;
errors.add(new ExceptionData(message));
errors.add(new EarlyLoadingException.ExceptionData(message));
LOGGER.error(message);
} else
} else {
FMLLoader.addAccessTransformer(at, mod);
}
}
}
if (!errors.isEmpty())
if (!errors.isEmpty()) {
preLoadErrors.add(new EarlyLoadingException("Invalid Access Transformers", null, errors));
}
}
public void addForScanning(BackgroundScanHandler backgroundScanHandler) {
@@ -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);
}
@@ -46,7 +46,7 @@ public class ModSorter {
List<ModFile> modFiles;
try {
modFiles = UniqueModListBuilder.buildUniqueList(mods).modFiles();
modFiles = new UniqueModListBuilder(mods).buildUniqueList().modFiles();
} 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
@@ -45,7 +45,6 @@ public final class RuntimeDistCleaner implements ILaunchPluginService {
@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;
@@ -54,7 +53,7 @@ public final class RuntimeDistCleaner implements ILaunchPluginService {
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))
if (FMLEnvironment.dist.isClient() && (internalName.startsWith("net/minecraft/") || internalName.startsWith("com/mojang/")))
return NAY;
return YAY;
@@ -65,29 +64,15 @@ public final class RuntimeDistCleaner implements ILaunchPluginService {
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 +81,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 +105,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 +116,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 +142,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 +208,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() {
@@ -17,6 +17,8 @@ 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;
@@ -24,12 +26,19 @@ 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 +49,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 +66,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 +84,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 +122,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 +130,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,7 +138,7 @@ 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) {}
@@ -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();
@@ -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() + "}";
}
}
@@ -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) {}
}
@@ -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);
@@ -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,41 @@ 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(",")));
}
}
@SuppressWarnings("removal")
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 +117,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 +131,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 +152,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 +169,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 +193,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
@@ -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
@@ -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();
}
}
@@ -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);
@@ -156,6 +156,7 @@ abstract class CommonDevLaunchHandler extends CommonLaunchHandler {
return new String[] {
"net/minecraftforge/",
"META-INF/services/",
"META-INF/coremods.json",
"META-INF/mods.toml"
};
}
@@ -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()) {
@@ -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
}
]
}
-73
View File
@@ -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
}
}
@@ -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);
}
}
}
}
}
@@ -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;
}
}
@@ -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 +0,0 @@
net.minecraftforge.forge.transformers.ForgeModTransformers
+3 -3
View File
@@ -5,11 +5,11 @@ org.gradle.daemon=false
org.gradle.parallel=true
JAVA_VERSION=21
MC_VERSION=1.21.11
MC_VERSION=1.21.5
MC_NEXT_VERSION=1.22
MCP_VERSION=20251223.124241
MCP_VERSION=20250325.155543
MAPPING_CHANNEL=official
MAPPING_VERSION=1.21.11
MAPPING_VERSION=1.21.5
# Set to true before the first build of a new MC version, so we don't do compatibility checks
CHECK_COMPATIBILITY=false
+1 -1
View File
@@ -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;
}
}
@@ -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);
};
}
}
}
@@ -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();
}
@@ -5,8 +5,11 @@
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;
@@ -33,7 +36,7 @@ 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,12 @@ 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.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 +128,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,18 +173,15 @@ 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);
@@ -187,14 +190,12 @@ public class FMLModContainer extends ModContainer {
}
@Override
@SuppressWarnings({"rawtypes", "unchecked"})
public void dispatchConfigEvent(IConfigEvent event) {
var eventBus = (EventBus) EventBus.create(eventBusGroup, event.self().getClass());
eventBus.post(event.self());
this.eventBus.post(event.self());
}
@Override
public String toString() {
return "FMLModContainer[" + this.getModInfo().getModId() + ", " + this.getClass().getName() + ']';
return "FMLModContainer[" + this.getModInfo().getModId() + ", " + this.getClass().getName() + "]";
}
}
+1 -1
View File
@@ -15,7 +15,7 @@ java {
}
dependencies {
compileOnly(libs.jetbrains.annotations)
compileOnly(libs.nulls)
implementation(project(':fmlloader'))
implementation(project(':fmlcore'))
@@ -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;
}
}
+1 -1
View File
@@ -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'))
}
@@ -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
@@ -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);
}
}
-2
View File
@@ -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 {
@@ -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,6 +1,6 @@
--- a/com/mojang/blaze3d/opengl/GlCommandEncoder.java
+++ b/com/mojang/blaze3d/opengl/GlCommandEncoder.java
@@ -133,6 +_,9 @@
@@ -109,6 +_,9 @@
GlStateManager._colorMask(true, true, true, true);
GlStateManager._clear(16384);
GlStateManager._glFramebufferTexture2D(36160, 36064, 3553, 0, 0);
@@ -1,6 +1,6 @@
--- a/com/mojang/blaze3d/opengl/GlDebug.java
+++ b/com/mojang/blaze3d/opengl/GlDebug.java
@@ -24,6 +_,14 @@
@@ -23,6 +_,14 @@
@OnlyIn(Dist.CLIENT)
public class GlDebug {
@@ -24,10 +24,10 @@
}
public List<String> getLastOpenGlDebugMessages() {
@@ -120,6 +_,7 @@
}
@@ -121,6 +_,7 @@
public static @Nullable GlDebug enableDebugCallback(int p_394351_, boolean p_393026_, Set<String> p_393339_) {
@Nullable
public static GlDebug enableDebugCallback(int p_394351_, boolean p_393026_, Set<String> p_393339_) {
+ p_393026_ |= PRINT_STACKTRACE_ON_ERROR;
if (p_394351_ <= 0) {
return null;
@@ -1,74 +1,53 @@
--- 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);
@@ -80,11 +_,25 @@
@Override
public GpuTexture createTexture(@Nullable Supplier<String> p_397830_, TextureFormat p_394839_, int p_394481_, int p_391831_, int p_395609_) {
- return this.createTexture(this.debugLabels.exists() && p_397830_ != null ? p_397830_.get() : null, p_394839_, p_394481_, p_391831_, p_395609_);
+ return this.createTexture(p_397830_, p_394839_, p_394481_, p_391831_, p_395609_, false);
+ }
+
+ /** @see GpuDevice#createTexture(Supplier, TextureFormat, int, int, int, boolean) */
+ @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
);
+ public GpuTexture createTexture(@Nullable Supplier<String> p_397830_, TextureFormat p_394839_, int p_394481_, int p_391831_, int p_395609_, boolean stencil) {
+ return this.createTexture(this.debugLabels.exists() && p_397830_ != null ? p_397830_.get() : null, p_394839_, p_394481_, p_391831_, p_395609_, 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_, TextureFormat p_394951_, int p_395535_, int p_393944_, int p_392329_) {
+ return this.createTexture(p_394142_, p_394951_, p_395535_, p_393944_, p_392329_, false);
+ }
+
+ /** @see GpuDevice#createTexture(String, TextureFormat, int, int, int, boolean) */
+ @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
+ ) {
+ public GpuTexture createTexture(@Nullable String p_394142_, TextureFormat p_394951_, int p_395535_, int p_393944_, int p_392329_, boolean stencil) {
+ // Forge: stencil rendering is only for depth
+ stencil &= p_394951_.hasDepthAspect();
if (p_406483_ < 1) {
if (p_392329_ < 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 {
@@ -106,11 +_,14 @@
GlStateManager._texImage2D(
3553,
j,
+ stencil ? org.lwjgl.opengl.GL30.GL_DEPTH32F_STENCIL8 :
GlConst.toGlInternalId(p_394951_),
p_395535_ >> j,
p_393944_ >> j,
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
);
@@ -122,7 +_,7 @@
} else if (k != 0) {
throw new IllegalStateException("OpenGL error " + k);
} 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);
- GlTexture gltexture = new GlTexture(p_394142_, p_394951_, p_395535_, p_393944_, p_392329_, i);
+ GlTexture gltexture = new GlTexture(p_394142_, p_394951_, p_395535_, p_393944_, p_392329_, i, stencil);
this.debugLabels.applyLabel(gltexture);
return gltexture;
}
@@ -1,6 +1,6 @@
--- a/com/mojang/blaze3d/opengl/GlStateManager.java
+++ b/com/mojang/blaze3d/opengl/GlStateManager.java
@@ -86,6 +_,11 @@
@@ -90,6 +_,11 @@
}
}
@@ -12,7 +12,7 @@
public static void _disableBlend() {
RenderSystem.assertOnRenderThread();
BLEND.mode.disable();
@@ -371,9 +_,17 @@
@@ -411,9 +_,17 @@
}
}
@@ -1,25 +1,28 @@
--- 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);
@@ -18,8 +_,13 @@
protected boolean modesDirty = true;
protected GlTexture(String p_393950_, TextureFormat p_392837_, int p_394590_, int p_391379_, int p_391947_, int p_396659_) {
+ this(p_393950_, p_392837_, p_394590_, p_391379_, p_391947_, p_396659_, 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_;
+ protected GlTexture(String p_393950_, TextureFormat p_392837_, int p_394590_, int p_391379_, int p_391947_, int p_396659_, boolean stencil) {
super(p_393950_, p_392837_, p_394590_, p_391379_, p_391947_);
this.id = p_396659_;
+ this.stencilEnabled = stencil;
}
@Override
@@ -89,5 +_,12 @@
if (this.closed && this.views == 0) {
this.destroyImmediately();
}
@@ -86,5 +_,15 @@
public void setTextureFilter(FilterMode p_394977_, FilterMode p_397175_, boolean p_393558_) {
super.setTextureFilter(p_394977_, p_397175_, p_393558_);
this.modesDirty = true;
+ }
+
+
+ /* FORGE START */
+
+ private final boolean stencilEnabled;
+
+ @Override
@@ -1,18 +1,18 @@
--- a/com/mojang/blaze3d/pipeline/RenderTarget.java
+++ b/com/mojang/blaze3d/pipeline/RenderTarget.java
@@ -80,7 +_,7 @@
@@ -75,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);
- this.depthTexture = RenderSystem.getDevice().createTexture(() -> this.label + " / Depth", TextureFormat.DEPTH32, p_83951_, p_83952_, 1);
+ this.depthTexture = RenderSystem.getDevice().createTexture(() -> this.label + " / Depth", TextureFormat.DEPTH32, p_83951_, p_83952_, 1, this.stencilEnabled);
this.depthTexture.setTextureFilter(FilterMode.NEAREST, false);
this.depthTexture.setAddressMode(AddressMode.CLAMP_TO_EDGE);
}
@@ -126,5 +_,26 @@
public @Nullable GpuTextureView getDepthTextureView() {
return this.depthTextureView;
@@ -134,5 +_,26 @@
@Nullable
public GpuTexture getDepthTexture() {
return this.depthTexture;
+ }
+
+ private boolean stencilEnabled = false;
@@ -25,7 +25,7 @@
+ public void enableStencil() {
+ if (stencilEnabled) return;
+ stencilEnabled = true;
+ this.resize(width, height);
+ this.resize(viewWidth, viewHeight);
+ }
+
+ /**
@@ -1,16 +1,16 @@
--- a/com/mojang/blaze3d/platform/Window.java
+++ b/com/mojang/blaze3d/platform/Window.java
@@ -91,7 +_,8 @@
GLFW.glfwWindowHint(139267, 3);
@@ -88,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 @@
@@ -100,6 +_,7 @@
this.windowedX = this.x = aint1[0];
this.windowedY = this.y = aint[0];
}
@@ -18,8 +18,8 @@
this.setMode();
this.refreshFramebufferSize();
@@ -269,6 +_,9 @@
GLFW.glfwGetFramebufferSize(this.handle, aint, aint1);
@@ -266,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 +1,20 @@
--- a/com/mojang/blaze3d/systems/GpuDevice.java
+++ b/com/mojang/blaze3d/systems/GpuDevice.java
@@ -36,9 +_,19 @@
int p_407070_
);
@@ -25,7 +_,17 @@
+ /** 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 Supplier<String> p_394357_, TextureFormat p_395807_, int p_395623_, int p_395802_, int p_396157_);
+ /** Forge: same as {@link #createTexture(Supplier, TextureFormat, int, int, int)} but with stencil support */
+ default GpuTexture createTexture(@Nullable Supplier<String> p_394357_, TextureFormat p_395807_, int p_395623_, int p_395802_, int p_396157_, boolean stencil) {
+ return this.createTexture(p_394357_, p_395807_, p_395623_, p_395802_, p_396157_);
+ }
+
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_
);
GpuTexture createTexture(@Nullable String p_391798_, TextureFormat p_393333_, int p_391800_, int p_395600_, int p_394065_);
+
+ /** 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_);
+ /** Forge: same as {@link #createTexture(String, TextureFormat, int, int, int)} but with stencil support */
+ default GpuTexture createTexture(@Nullable String p_391798_, TextureFormat p_393333_, int p_391800_, int p_395600_, int p_394065_, boolean stencil) {
+ return this.createTexture(p_391798_, p_393333_, p_391800_, p_395600_, p_394065_);
+ }
GpuTextureView createTextureView(GpuTexture p_409045_);
GpuBuffer createBuffer(@Nullable Supplier<String> p_395215_, BufferType p_392847_, BufferUsage p_391478_, int p_397054_);
@@ -1,11 +1,11 @@
--- a/com/mojang/blaze3d/textures/GpuTexture.java
+++ b/com/mojang/blaze3d/textures/GpuTexture.java
@@ -10,7 +_,7 @@
@@ -6,7 +_,7 @@
@OnlyIn(Dist.CLIENT)
@DontObfuscate
-public abstract class GpuTexture implements AutoCloseable {
+public abstract class GpuTexture implements AutoCloseable, net.minecraftforge.client.extensions.IForgeGpuTexture {
public static final int USAGE_COPY_DST = 1;
public static final int USAGE_COPY_SRC = 2;
public static final int USAGE_TEXTURE_BINDING = 4;
private final TextureFormat format;
private final int width;
private final int height;
@@ -1,7 +1,7 @@
--- a/com/mojang/blaze3d/vertex/VertexConsumer.java
+++ b/com/mojang/blaze3d/vertex/VertexConsumer.java
@@ -14,7 +_,7 @@
import org.joml.Vector3fc;
@@ -13,7 +_,7 @@
import org.lwjgl.system.MemoryStack;
@OnlyIn(Dist.CLIENT)
-public interface VertexConsumer {
@@ -9,3 +9,15 @@
VertexConsumer addVertex(float p_344294_, float p_342213_, float p_344859_);
VertexConsumer setColor(int p_342749_, int p_344324_, int p_343336_, int p_342831_);
@@ -133,9 +_,11 @@
int j1 = ARGB.color(k, (int)f3, (int)f4, (int)f5);
int k1 = LightTexture.lightCoordsWithEmission(p_331444_[i1], l);
+ k1 = applyBakedLighting(k1, bytebuffer);
float f10 = bytebuffer.getFloat(16);
float f9 = bytebuffer.getFloat(20);
Vector3f vector3f1 = matrix4f.transformPosition(f, f1, f2, new Vector3f());
+ applyBakedNormals(vector3f, bytebuffer, p_85988_.normal());
this.addVertex(vector3f1.x(), vector3f1.y(), vector3f1.z(), j1, f10, f9, p_85993_, k1, vector3f.x(), vector3f.y(), vector3f.z());
}
}
@@ -9,15 +9,15 @@
public static final int UNKNOWN_ELEMENT = -1;
private final List<VertexFormatElement> elements;
private final List<String> names;
@@ -29,6 +_,7 @@
private final int[] offsetsByElement = new int[32];
private @Nullable GpuBuffer immediateDrawVertexBuffer;
private @Nullable GpuBuffer immediateDrawIndexBuffer;
@@ -31,6 +_,7 @@
private GpuBuffer immediateDrawVertexBuffer;
@Nullable
private GpuBuffer immediateDrawIndexBuffer;
+ private final com.google.common.collect.ImmutableMap<String, VertexFormatElement> elementMapping;
VertexFormat(List<VertexFormatElement> p_343616_, List<String> p_345241_, IntList p_345522_, int p_344162_) {
this.elements = p_343616_;
@@ -41,6 +_,11 @@
@@ -43,6 +_,11 @@
int j = vertexformatelement != null ? p_343616_.indexOf(vertexformatelement) : -1;
this.offsetsByElement[i] = j != -1 ? p_345522_.getInt(j) : -1;
}
@@ -29,8 +29,8 @@
}
public static VertexFormat.Builder builder() {
@@ -139,6 +_,9 @@
this.immediateDrawIndexBuffer = uploadToBuffer(this.immediateDrawIndexBuffer, p_391835_, 72, () -> "Immediate index buffer for " + this);
@@ -146,6 +_,9 @@
return this.immediateDrawIndexBuffer;
}
+
@@ -1,18 +1,18 @@
--- a/com/mojang/math/Transformation.java
+++ b/com/mojang/math/Transformation.java
@@ -16,7 +_,7 @@
import org.joml.Vector3fc;
import org.jspecify.annotations.Nullable;
@@ -14,7 +_,7 @@
import org.joml.Quaternionf;
import org.joml.Vector3f;
-public final class Transformation {
+public final class Transformation implements net.minecraftforge.common.extensions.IForgeTransformation {
private final Matrix4fc matrix;
public static final Codec<Transformation> CODEC = RecordCodecBuilder.create(
p_269604_ -> p_269604_.group(
@@ -168,5 +_,19 @@
this.getScale().lerp(p_175938_.getScale(), p_175939_, new Vector3f()),
this.getRightRotation().slerp(p_175938_.getRightRotation(), p_175939_, new Quaternionf())
);
@@ -174,5 +_,19 @@
vector3f1.lerp(p_175938_.getScale(), p_175939_);
quaternionf1.slerp(p_175938_.getRightRotation(), p_175939_);
return new Transformation(vector3f, quaternionf, vector3f1, quaternionf1);
+ }
+
+ private Matrix3f normalTransform = null;
@@ -1,18 +1,18 @@
--- a/com/mojang/realmsclient/gui/screens/RealmsGenericErrorScreen.java
+++ b/com/mojang/realmsclient/gui/screens/RealmsGenericErrorScreen.java
@@ -62,6 +_,15 @@
@@ -71,6 +_,15 @@
}
@Override
+ public boolean keyPressed(net.minecraft.client.input.KeyEvent event) {
+ if (event.key() == org.lwjgl.glfw.GLFW.GLFW_KEY_ESCAPE) {
+ public boolean keyPressed(int key, int scanCode, int modifiers) {
+ if (key == org.lwjgl.glfw.GLFW.GLFW_KEY_ESCAPE) {
+ minecraft.setScreen(this.nextScreen);
+ return true;
+ }
+ return super.keyPressed(event);
+ return super.keyPressed(key, scanCode, modifiers);
+ }
+
+ @Override
public void render(GuiGraphics p_283497_, int p_88680_, int p_88681_, float p_88682_) {
super.render(p_283497_, p_88680_, p_88681_, p_88682_);
p_283497_.drawCenteredString(this.font, this.title, this.width / 2, 80, -1);
p_283497_.drawCenteredString(this.font, this.lines.title, this.width / 2, 80, -1);
@@ -1,11 +0,0 @@
--- a/com/mojang/realmsclient/gui/screens/RealmsResetWorldScreen.java
+++ b/com/mojang/realmsclient/gui/screens/RealmsResetWorldScreen.java
@@ -263,7 +_,7 @@
int k = this.getY();
p_282595_.blit(RenderPipelines.GUI_TEXTURED, this.image, j + 2, k + 2, 0.0F, 0.0F, 56, 56, 56, 56, 56, 56, i);
p_282595_.blitSprite(RenderPipelines.GUI_TEXTURED, SLOT_FRAME_SPRITE, j, k, 60, 60, i);
- int l = flag ? -6250336 : -1;
+ int l = getFGColor();
p_282595_.drawCenteredString(RealmsResetWorldScreen.this.font, this.getMessage(), j + 28, k - 14, l);
}
}
@@ -1,6 +1,6 @@
--- a/net/minecraft/CrashReportCategory.java
+++ b/net/minecraft/CrashReportCategory.java
@@ -116,8 +_,10 @@
@@ -112,8 +_,10 @@
if (astacktraceelement.length <= 0) {
return 0;
} else {
@@ -13,7 +13,7 @@
return this.stackTrace.length;
}
}
@@ -164,16 +_,16 @@
@@ -160,16 +_,16 @@
if (this.stackTrace != null && this.stackTrace.length > 0) {
p_128169_.append("\nStacktrace:");
@@ -1,10 +1,10 @@
--- a/net/minecraft/SharedConstants.java
+++ b/net/minecraft/SharedConstants.java
@@ -216,6 +_,7 @@
@@ -171,6 +_,7 @@
}
static {
+ if (System.getProperty("io.netty.leakDetection.level") == null) // Forge: allow level to be manually specified
ResourceLeakDetector.setLevel(NETTY_LEAK_DETECTION);
CommandSyntaxException.ENABLE_COMMAND_STACK_TRACES = COMMAND_STACK_TRACES;
CommandSyntaxException.ENABLE_COMMAND_STACK_TRACES = false;
CommandSyntaxException.BUILT_IN_EXCEPTIONS = new BrigadierExceptions();
@@ -0,0 +1,11 @@
--- a/net/minecraft/Util.java
+++ b/net/minecraft/Util.java
@@ -268,7 +_,7 @@
.getSchema(DataFixUtils.makeKey(SharedConstants.getCurrentVersion().getDataVersion().getVersion()))
.getChoiceType(p_137552_, p_137553_);
} catch (IllegalArgumentException illegalargumentexception) {
- LOGGER.error("No data fixer registered for {}", p_137553_);
+ LOGGER.debug("No data fixer registered for {}", p_137553_);
if (SharedConstants.IS_RUNNING_IN_IDE) {
throw illegalargumentexception;
}
@@ -1,14 +1,14 @@
--- a/net/minecraft/advancements/Advancement.java
+++ b/net/minecraft/advancements/Advancement.java
@@ -231,7 +_,11 @@
@@ -226,7 +_,11 @@
}
public AdvancementHolder save(Consumer<AdvancementHolder> p_138390_, String p_138391_) {
- AdvancementHolder advancementholder = this.build(Identifier.parse(p_138391_));
+ return save(p_138390_, Identifier.parse(p_138391_));
- AdvancementHolder advancementholder = this.build(ResourceLocation.parse(p_138391_));
+ return save(p_138390_, ResourceLocation.parse(p_138391_));
+ }
+
+ public AdvancementHolder save(Consumer<AdvancementHolder> p_138390_, Identifier id) {
+ public AdvancementHolder save(Consumer<AdvancementHolder> p_138390_, ResourceLocation id) {
+ AdvancementHolder advancementholder = this.build(id);
p_138390_.accept(advancementholder);
return advancementholder;
@@ -1,7 +1,7 @@
--- a/net/minecraft/advancements/AdvancementRewards.java
+++ b/net/minecraft/advancements/AdvancementRewards.java
@@ -44,6 +_,7 @@
LootParams lootparams = new LootParams.Builder(serverlevel)
@@ -40,6 +_,7 @@
LootParams lootparams = new LootParams.Builder(p_9990_.serverLevel())
.withParameter(LootContextParams.THIS_ENTITY, p_9990_)
.withParameter(LootContextParams.ORIGIN, p_9990_.position())
+ .withLuck(p_9990_.getLuck())
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/Camera.java
+++ b/net/minecraft/client/Camera.java
@@ -136,9 +_,13 @@
@@ -118,9 +_,13 @@
}
protected void setRotation(float p_90573_, float p_90574_) {
@@ -15,10 +15,10 @@
FORWARDS.rotate(this.rotation, this.forwards);
UP.rotate(this.rotation, this.up);
LEFT.rotate(this.rotation, this.left);
@@ -168,6 +_,13 @@
@@ -236,6 +_,13 @@
public float yRot() {
return this.yRot;
public float getPartialTickTime() {
return this.partialTickTime;
+ }
+
+ public net.minecraft.world.level.block.state.BlockState getBlockAtCamera() {
@@ -28,4 +28,4 @@
+ return this.level.getBlockState(this.blockPosition).getStateAtViewpoint(this.level, this.blockPosition, this.position);
}
@Override
@OnlyIn(Dist.CLIENT)
@@ -1,27 +1,35 @@
--- a/net/minecraft/client/KeyMapping.java
+++ b/net/minecraft/client/KeyMapping.java
@@ -19,9 +_,9 @@
import org.jspecify.annotations.Nullable;
@@ -15,9 +_,9 @@
import net.minecraftforge.api.distmarker.OnlyIn;
@OnlyIn(Dist.CLIENT)
-public class KeyMapping implements Comparable<KeyMapping> {
+public class KeyMapping implements Comparable<KeyMapping>, net.minecraftforge.client.extensions.IForgeKeyMapping {
private static final Map<String, KeyMapping> ALL = Maps.newHashMap();
- private static final Map<InputConstants.Key, List<KeyMapping>> MAP = Maps.newHashMap();
- private static final Map<InputConstants.Key, KeyMapping> MAP = Maps.newHashMap();
+ private static final net.minecraftforge.client.settings.KeyMappingLookup MAP = new net.minecraftforge.client.settings.KeyMappingLookup();
private final String name;
private final InputConstants.Key defaultKey;
private final KeyMapping.Category category;
@@ -39,7 +_,7 @@
private static final Set<String> CATEGORIES = Sets.newHashSet();
public static final String CATEGORY_MOVEMENT = "key.categories.movement";
public static final String CATEGORY_MISC = "key.categories.misc";
@@ -43,14 +_,14 @@
private int clickCount;
public static void click(InputConstants.Key p_90836_) {
- KeyMapping keymapping = MAP.get(p_90836_);
+ for (KeyMapping keymapping : MAP.getAll(p_90836_))
if (keymapping != null) {
keymapping.clickCount++;
}
}
private static void forAllKeyMappings(InputConstants.Key p_424096_, Consumer<KeyMapping> p_427756_) {
- List<KeyMapping> list = MAP.get(p_424096_);
+ List<KeyMapping> list = MAP.getAll(p_424096_);
if (list != null && !list.isEmpty()) {
for (KeyMapping keymapping : list) {
p_427756_.accept(keymapping);
@@ -106,7 +_,7 @@
public static void set(InputConstants.Key p_90838_, boolean p_90839_) {
- KeyMapping keymapping = MAP.get(p_90838_);
+ for (KeyMapping keymapping : MAP.getAll(p_90838_))
if (keymapping != null) {
keymapping.setDown(p_90839_);
}
@@ -101,7 +_,7 @@
}
public boolean isDown() {
@@ -29,27 +37,26 @@
+ return this.isDown && isConflictContextAndModifierActive();
}
public KeyMapping.Category getCategory() {
@@ -149,8 +_,17 @@
? I18n.get(this.name).compareTo(I18n.get(p_90841_.name))
: Integer.compare(this.order, p_90841_.order);
} else {
- return Integer.compare(KeyMapping.Category.SORT_ORDER.indexOf(this.category), KeyMapping.Category.SORT_ORDER.indexOf(p_90841_.category));
+ return compareSort(this.category, p_90841_.category);
}
public String getCategory() {
@@ -137,7 +_,16 @@
public int compareTo(KeyMapping p_90841_) {
return this.category.equals(p_90841_.category)
? I18n.get(this.name).compareTo(I18n.get(p_90841_.name))
- : CATEGORY_SORT_ORDER.get(this.category).compareTo(CATEGORY_SORT_ORDER.get(p_90841_.category));
+ : compareSort(this.category, p_90841_.category);
+ }
+
+ private static int compareSort(Category c1, Category c2) {
+ int o1 = KeyMapping.Category.SORT_ORDER.indexOf(c1);
+ int o2 = KeyMapping.Category.SORT_ORDER.indexOf(c2);
+ if (o1 == -1 && o2 != -1) return 1;
+ if (o1 != -1 && o2 == -1) return -1;
+ if (o1 == -1 && o2 == -1) return I18n.get(c1.id().toLanguageKey("key.category")).compareTo(I18n.get(c1.id().toLanguageKey("key.category")));
+ return o1 - o2;
+ private static int compareSort(String c1, String c2) {
+ Integer o1 = CATEGORY_SORT_ORDER.get(c1);
+ Integer o2 = CATEGORY_SORT_ORDER.get(c2);
+ if (o1 == null && o2 != null) return 1;
+ if (o1 != null && o2 == null) return -1;
+ if (o1 == null && o2 == null) return I18n.get(c1).compareTo(I18n.get(c2));
+ return o1.compareTo(o2);
}
public static Supplier<Component> createNameSupplier(String p_90843_) {
@@ -159,6 +_,20 @@
@@ -146,6 +_,20 @@
}
public boolean same(KeyMapping p_90851_) {
@@ -70,7 +77,7 @@
return this.key.equals(p_90851_.key);
}
@@ -177,11 +_,13 @@
@@ -164,11 +_,13 @@
}
public Component getTranslatedKeyMessage() {
@@ -85,15 +92,9 @@
}
public String saveString() {
@@ -193,11 +_,94 @@
}
private void registerMapping(InputConstants.Key p_423386_) {
- MAP.computeIfAbsent(p_423386_, p_420623_ -> new ArrayList<>()).add(this);
+ MAP.put(p_423386_, this);
}
public static @Nullable KeyMapping get(String p_378660_) {
@@ -182,5 +_,88 @@
@Nullable
public static KeyMapping get(String p_378660_) {
return ALL.get(p_378660_);
+ }
+
@@ -104,28 +105,28 @@
+ /**
+ * Convenience constructor for creating KeyBindings with keyConflictContext set.
+ */
+ public KeyMapping(String description, net.minecraftforge.client.settings.IKeyConflictContext keyConflictContext, final InputConstants.Type inputType, final int keyCode, Category category, int order) {
+ this(description, keyConflictContext, inputType.getOrCreate(keyCode), category, order);
+ public KeyMapping(String description, net.minecraftforge.client.settings.IKeyConflictContext keyConflictContext, final InputConstants.Type inputType, final int keyCode, String category) {
+ this(description, keyConflictContext, inputType.getOrCreate(keyCode), category);
+ }
+
+ /**
+ * Convenience constructor for creating KeyBindings with keyConflictContext set.
+ */
+ public KeyMapping(String description, net.minecraftforge.client.settings.IKeyConflictContext keyConflictContext, InputConstants.Key keyCode, Category category, int order) {
+ this(description, keyConflictContext, net.minecraftforge.client.settings.KeyModifier.NONE, keyCode, category, order);
+ public KeyMapping(String description, net.minecraftforge.client.settings.IKeyConflictContext keyConflictContext, InputConstants.Key keyCode, String category) {
+ this(description, keyConflictContext, net.minecraftforge.client.settings.KeyModifier.NONE, keyCode, category);
+ }
+
+ /**
+ * Convenience constructor for creating KeyBindings with keyConflictContext and keyModifier set.
+ */
+ public KeyMapping(String description, net.minecraftforge.client.settings.IKeyConflictContext keyConflictContext, net.minecraftforge.client.settings.KeyModifier keyModifier, final InputConstants.Type inputType, final int keyCode, Category category, int order) {
+ this(description, keyConflictContext, keyModifier, inputType.getOrCreate(keyCode), category, order);
+ public KeyMapping(String description, net.minecraftforge.client.settings.IKeyConflictContext keyConflictContext, net.minecraftforge.client.settings.KeyModifier keyModifier, final InputConstants.Type inputType, final int keyCode, String category) {
+ this(description, keyConflictContext, keyModifier, inputType.getOrCreate(keyCode), category);
+ }
+
+ /**
+ * Convenience constructor for creating KeyBindings with keyConflictContext and keyModifier set.
+ */
+ public KeyMapping(String description, net.minecraftforge.client.settings.IKeyConflictContext keyConflictContext, net.minecraftforge.client.settings.KeyModifier keyModifier, InputConstants.Key keyCode, Category category, int order) {
+ public KeyMapping(String description, net.minecraftforge.client.settings.IKeyConflictContext keyConflictContext, net.minecraftforge.client.settings.KeyModifier keyModifier, InputConstants.Key keyCode, String category) {
+ this.name = description;
+ this.key = keyCode;
+ this.defaultKey = keyCode;
@@ -133,11 +134,11 @@
+ this.keyConflictContext = keyConflictContext;
+ this.keyModifier = keyModifier;
+ this.keyModifierDefault = keyModifier;
+ this.order = order;
+ if (this.keyModifier.matches(keyCode))
+ this.keyModifier = net.minecraftforge.client.settings.KeyModifier.NONE;
+ ALL.put(description, this);
+ MAP.put(keyCode, this);
+ CATEGORIES.add(category);
+ }
+
+ @Override
@@ -179,5 +180,4 @@
+
+ MAP.put(keyCode, this);
}
@OnlyIn(Dist.CLIENT)
}
@@ -1,37 +1,39 @@
--- a/net/minecraft/client/KeyboardHandler.java
+++ b/net/minecraft/client/KeyboardHandler.java
@@ -497,7 +_,7 @@
@@ -431,12 +_,12 @@
if (screen != null) {
try {
if (p_90895_ != 1 && p_90895_ != 2) {
- if (p_90895_ == 0 && screen.keyReleased(p_423534_)) {
+ if (p_90895_ == 0 && net.minecraftforge.client.ForgeHooksClient.onScreenKeyReleased(screen, p_423534_)) {
if (options.keyDebugModifier.matches(p_423534_)) {
this.usedDebugKeyAsModifier = false;
}
@@ -506,7 +_,7 @@
if (p_90897_ != 1 && p_90897_ != 2) {
- if (p_90897_ == 0 && screen.keyReleased(p_90895_, p_90896_, p_90898_)) {
+ if (p_90897_ == 0 && net.minecraftforge.client.ForgeHooksClient.onScreenKeyReleased(screen, p_90895_, p_90896_, p_90898_)) {
return;
}
} else {
screen.afterKeyboardAction();
- if (screen.keyPressed(p_423534_)) {
+ if (net.minecraftforge.client.ForgeHooksClient.onScreenKeyPressed(screen, p_423534_)) {
if (this.minecraft.screen == null) {
InputConstants.Key inputconstants$key = InputConstants.getKey(p_423534_);
KeyMapping.set(inputconstants$key, false);
@@ -582,6 +_,7 @@
- if (screen.keyPressed(p_90895_, p_90896_, p_90898_)) {
+ if (net.minecraftforge.client.ForgeHooksClient.onScreenKeyPressed(screen, p_90895_, p_90896_, p_90898_)) {
return;
}
}
@@ -495,6 +_,7 @@
}
}
}
+ net.minecraftforge.client.ForgeHooksClient.onKeyInput(p_423534_, p_90895_);
+ net.minecraftforge.client.ForgeHooksClient.onKeyInput(p_90895_, p_90896_, p_90897_, p_90898_);
}
}
@@ -590,7 +_,7 @@
Screen screen = this.minecraft.screen;
@@ -504,10 +_,10 @@
if (screen != null && this.minecraft.getOverlay() == null) {
try {
- screen.charTyped(p_427531_);
+ net.minecraftforge.client.ForgeHooksClient.onScreenCharTyped(screen, p_427531_);
if (Character.isBmpCodePoint(p_90891_)) {
- screen.charTyped((char)p_90891_, p_90892_);
+ net.minecraftforge.client.ForgeHooksClient.onScreenCharTyped(screen, (char)p_90891_, p_90892_);
} else if (Character.isValidCodePoint(p_90891_)) {
- screen.charTyped(Character.highSurrogate(p_90891_), p_90892_);
- screen.charTyped(Character.lowSurrogate(p_90891_), p_90892_);
+ net.minecraftforge.client.ForgeHooksClient.onScreenCharTyped(screen, Character.highSurrogate(p_90891_), p_90892_);
+ net.minecraftforge.client.ForgeHooksClient.onScreenCharTyped(screen, Character.lowSurrogate(p_90891_), p_90892_);
}
} catch (Throwable throwable) {
CrashReport crashreport = CrashReport.forThrowable(throwable, "charTyped event handler");
screen.fillCrashDetails(crashreport);
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/Minecraft.java
+++ b/net/minecraft/client/Minecraft.java
@@ -265,7 +_,7 @@
@@ -248,7 +_,7 @@
import org.slf4j.Logger;
@OnlyIn(Dist.CLIENT)
@@ -8,8 +8,8 @@
+public class Minecraft extends ReentrantBlockableEventLoop<Runnable> implements WindowEventHandler, net.minecraftforge.client.extensions.IForgeMinecraft {
static Minecraft instance;
private static final Logger LOGGER = LogUtils.getLogger();
private static final int MAX_TICKS_PER_UPDATE = 10;
@@ -431,7 +_,6 @@
public static final boolean ON_OSX = Util.getPlatform() == Util.OS.OSX;
@@ -426,7 +_,6 @@
}
}, Util.nonCriticalIoPool());
LOGGER.info("Setting user: {}", this.user.getName());
@@ -17,19 +17,19 @@
this.demo = p_91084_.game.demo;
this.allowsMultiplayer = !p_91084_.game.disableMultiplayer;
this.allowsChat = !p_91084_.game.disableChat;
@@ -483,10 +_,9 @@
@@ -478,10 +_,9 @@
LOGGER.error("Couldn't set icon", (Throwable)ioexception);
}
+ // FORGE: Move mouse and keyboard handler setup further below
this.mouseHandler = new MouseHandler(this);
- this.mouseHandler.setup(this.window);
- this.mouseHandler.setup(this.window.getWindow());
this.keyboardHandler = new KeyboardHandler(this);
- this.keyboardHandler.setup(this.window);
- this.keyboardHandler.setup(this.window.getWindow());
RenderSystem.initRenderer(
this.window.handle(),
this.window.getWindow(),
this.options.glDebugVerbosity,
@@ -498,6 +_,7 @@
@@ -492,6 +_,7 @@
LOGGER.info("Using optional rendering extensions: {}", String.join(", ", RenderSystem.getDevice().getEnabledExtensions()));
this.mainRenderTarget = new MainTarget(this.window.getWidth(), this.window.getHeight());
this.resourceManager = new ReloadableResourceManager(PackType.CLIENT_RESOURCES);
@@ -37,40 +37,40 @@
this.resourcePackRepository.reload();
this.options.loadSelectedResourcePacks(this.resourcePackRepository);
this.languageManager = new LanguageManager(this.options.languageCode, p_340763_ -> {
@@ -585,6 +_,7 @@
this.particleResources = new ParticleResources();
this.resourceManager.registerReloadListener(this.particleResources);
this.particleEngine = new ParticleEngine(this.level, this.particleResources);
+ net.minecraftforge.client.ForgeHooksClient.onRegisterParticleProviders(this.particleResources);
this.particleResources.onReload(this.particleEngine::clearParticles);
this.waypointStyles = new WaypointStyleManager();
this.resourceManager.registerReloadListener(this.waypointStyles);
@@ -596,6 +_,9 @@
@@ -570,6 +_,7 @@
this.blockEntityRenderDispatcher = new BlockEntityRenderDispatcher(this.font, this.modelManager.entityModels(), this.blockRenderer, this.itemModelResolver, this.itemRenderer, this.entityRenderDispatcher);
this.resourceManager.registerReloadListener(this.blockEntityRenderDispatcher);
this.particleEngine = new ParticleEngine(this.level, this.textureManager);
+ net.minecraftforge.client.ForgeHooksClient.onRegisterParticleProviders(this.particleEngine);
this.resourceManager.registerReloadListener(this.particleEngine);
this.paintingTextures = new PaintingTextureManager(this.textureManager);
this.resourceManager.registerReloadListener(this.paintingTextures);
@@ -585,6 +_,9 @@
this.resourceManager.registerReloadListener(this.gpuWarnlistManager);
this.resourceManager.registerReloadListener(this.regionalCompliancies);
this.gui = new Gui(this);
+ // FORGE: Moved keyboard and mouse handler setup below ingame gui creation to prevent NPEs in them.
+ this.mouseHandler.setup(this.window);
+ this.keyboardHandler.setup(this.window);
+ this.mouseHandler.setup(this.window.getWindow());
+ this.keyboardHandler.setup(this.window.getWindow());
this.debugRenderer = new DebugRenderer(this);
RealmsClient realmsclient = RealmsClient.getOrCreate(this);
this.realmsDataFetcher = new RealmsDataFetcher(realmsclient);
RenderSystem.setErrorCallback(this::onFullscreenError);
@@ -628,6 +_,7 @@
@@ -618,6 +_,7 @@
}
}
+ net.minecraftforge.client.ForgeHooksClient.initClientHooks(this, this.resourceManager);
this.window.updateVsync(this.options.enableVsync().get());
this.window.updateRawMouseInput(this.options.rawMouseInput().get());
this.window.setAllowCursorChanges(this.options.allowCursorChanges().get());
@@ -651,16 +_,17 @@
this.window.setDefaultErrorCallback();
@@ -639,16 +_,17 @@
GameLoadTimesEvent.INSTANCE.beginStep(TelemetryProperty.LOAD_TIME_LOADING_OVERLAY_MS);
Minecraft.GameLoadCookie minecraft$gameloadcookie = new Minecraft.GameLoadCookie(realmsclient, p_91084_.quickPlay);
this.setOverlay(
- new LoadingOverlay(
- this, reloadinstance, p_447796_ -> Util.ifElse(p_447796_, p_296162_ -> this.rollbackResourcePacks(p_296162_, minecraft$gameloadcookie), () -> {
- this, reloadinstance, p_296164_ -> Util.ifElse(p_296164_, p_296162_ -> this.rollbackResourcePacks(p_296162_, minecraft$gameloadcookie), () -> {
+ net.minecraftforge.fml.loading.ImmediateWindowHandler.<LoadingOverlay>loadingOverlay(
+ () -> this, () -> reloadinstance, p_296164_ -> Util.ifElse(p_296164_, p_447796_ -> this.rollbackResourcePacks(p_447796_, minecraft$gameloadcookie), () -> {
+ () -> this, () -> reloadinstance, p_296164_ -> Util.ifElse(p_296164_, p_296162_ -> this.rollbackResourcePacks(p_296162_, minecraft$gameloadcookie), () -> {
if (SharedConstants.IS_RUNNING_IN_IDE) {
this.selfTest();
}
@@ -82,9 +82,9 @@
- )
+ ).get()
);
this.quickPlayLog = QuickPlayLog.of(p_91084_.quickPlay.logPath());
this.quickPlayLog = QuickPlayLog.of(p_91084_.quickPlay.path());
this.framerateLimitTracker = new FramerateLimitTracker(this.options, this);
@@ -779,6 +_,7 @@
@@ -746,6 +_,7 @@
StringBuilder stringbuilder = new StringBuilder("Minecraft");
if (checkModStatus().shouldReportAsModified()) {
stringbuilder.append("*");
@@ -92,25 +92,25 @@
}
stringbuilder.append(" ");
@@ -802,6 +_,8 @@
@@ -769,6 +_,8 @@
}
private UserApiService createUserApiService(YggdrasilAuthenticationService p_193586_, GameConfig p_193587_) {
+ if ("0".equals(p_193587_.user.user.getAccessToken())) // Forge: We use "0" in dev. Short circuit to stop exception spam.
+ return UserApiService.OFFLINE;
return p_193587_.game.offlineDeveloperMode ? UserApiService.OFFLINE : p_193586_.createUserApiService(p_193587_.user.user.getAccessToken());
return p_193587_.user.user.getType() != User.Type.MSA
? UserApiService.OFFLINE
: p_193586_.createUserApiService(p_193587_.user.user.getAccessToken());
@@ -779,7 +_,7 @@
}
@@ -814,7 +_,7 @@
}
private void rollbackResourcePacks(Throwable p_91240_, Minecraft.@Nullable GameLoadCookie p_299515_) {
private void rollbackResourcePacks(Throwable p_91240_, @Nullable Minecraft.GameLoadCookie p_299515_) {
- if (this.resourcePackRepository.getSelectedIds().size() > 1) {
+ if (this.resourcePackRepository.getSelectedPacks().stream().anyMatch(e -> !e.isRequired())) { //Forge: This caused infinite loop if any resource packs are forced. Such as mod resources. So check if we can disable any.
this.clearResourcePacksOnError(p_91240_, null, p_299515_);
} else {
Util.throwAsRuntime(p_91240_);
@@ -1100,12 +_,6 @@
@@ -1065,12 +_,6 @@
LOGGER.error("setScreen called from non-game thread");
}
@@ -120,55 +120,57 @@
- this.setLastInputType(InputType.NONE);
- }
-
if (p_91153_ == null) {
if (this.clientLevelTeardownInProgress) {
throw new IllegalStateException("Trying to return to in-game GUI during disconnection");
@@ -1124,6 +_,23 @@
if (p_91153_ == null && this.clientLevelTeardownInProgress) {
throw new IllegalStateException("Trying to return to in-game GUI during disconnection");
} else {
@@ -1084,6 +_,23 @@
}
}
}
+ net.minecraftforge.client.ForgeHooksClient.clearGuiLayers(this);
+ Screen old = this.screen;
+ if (p_91153_ != null) {
+ var event = net.minecraftforge.client.event.ForgeEventFactoryClient.onScreenOpening(old, p_91153_);
+ if (event == null) return;
+ p_91153_ = event;
+ }
+
+ if (p_91153_ != old) {
+ if (old != null) {
+ net.minecraftforge.client.event.ForgeEventFactoryClient.onScreenClose(old);
+ old.removed();
+ } else {
+ this.setLastInputType(InputType.NONE);
+ net.minecraftforge.client.ForgeHooksClient.clearGuiLayers(this);
+ Screen old = this.screen;
+ if (p_91153_ != null) {
+ var event = net.minecraftforge.client.event.ForgeEventFactoryClient.onScreenOpening(old, p_91153_);
+ if (event == null) return;
+ p_91153_ = event;
+ }
+ }
+
this.screen = p_91153_;
if (this.screen != null) {
this.screen.added();
@@ -1207,6 +_,7 @@
+ if (p_91153_ != old) {
+ if (old != null) {
+ net.minecraftforge.client.event.ForgeEventFactoryClient.onScreenClose(old);
+ old.removed();
+ } else {
+ this.setLastInputType(InputType.NONE);
+ }
+ }
+
this.screen = p_91153_;
if (this.screen != null) {
this.screen.added();
@@ -1168,6 +_,7 @@
FreeTypeUtil.destroy();
Util.shutdownExecutors();
RenderSystem.getSamplerCache().close();
RenderSystem.getDevice().close();
+ net.minecraftforge.fml.config.ConfigTracker.INSTANCE.forceUnload();
} catch (Throwable throwable) {
LOGGER.error("Shutdown failure!", throwable);
throw throwable;
@@ -1289,7 +_,9 @@
RenderSystem.getDevice().createCommandEncoder().clearColorAndDepthTextures(rendertarget.getColorTexture(), 0, rendertarget.getDepthTexture(), 1.0);
profilerfiller.push("gameRenderer");
if (!this.noRender) {
+ net.minecraftforge.client.event.ForgeEventFactoryClient.onRenderTickStart(this.deltaTracker);
this.gameRenderer.render(this.deltaTracker, p_91384_);
+ net.minecraftforge.client.event.ForgeEventFactoryClient.onRenderTickEnd(this.deltaTracker);
}
@@ -1237,9 +_,11 @@
this.mouseHandler.handleAccumulatedMovement();
profilerfiller.pop();
if (!this.noRender) {
+ net.minecraftforge.client.event.ForgeEventFactoryClient.onRenderTickStart(this.deltaTracker);
profilerfiller.popPush("gameRenderer");
this.gameRenderer.render(this.deltaTracker, p_91384_);
profilerfiller.pop();
+ net.minecraftforge.client.event.ForgeEventFactoryClient.onRenderTickEnd(this.deltaTracker);
}
profilerfiller.popPush("blit");
@@ -1401,10 +_,12 @@
profilerfiller.push("blit");
@@ -1363,10 +_,12 @@
this.window.setGuiScale(i);
if (this.screen != null) {
this.screen.resize(this.window.getGuiScaledWidth(), this.window.getGuiScaledHeight());
this.screen.resize(this, this.window.getGuiScaledWidth(), this.window.getGuiScaledHeight());
+ net.minecraftforge.client.ForgeHooksClient.resizeGuiLayers(this, this.window.getGuiScaledWidth(), this.window.getGuiScaledHeight());
}
@@ -178,7 +180,7 @@
this.gameRenderer.resize(this.window.getWidth(), this.window.getHeight());
this.mouseHandler.setIgnoreFirstMove();
}
@@ -1542,6 +_,7 @@
@@ -1504,6 +_,7 @@
}
public void stop() {
@@ -186,64 +188,63 @@
this.running = false;
}
@@ -1571,10 +_,18 @@
if (p_91387_ && this.hitResult != null && this.hitResult.getType() == HitResult.Type.BLOCK) {
BlockHitResult blockhitresult = (BlockHitResult)this.hitResult;
BlockPos blockpos = blockhitresult.getBlockPos();
- if (!this.level.getBlockState(blockpos).isAir()) {
+ if (!this.level.isEmptyBlock(blockpos)) {
+ var inputEvent = new net.minecraftforge.client.event.InputEvent.InteractionKeyMappingTriggered(0, this.options.keyAttack, InteractionHand.MAIN_HAND);
+ if (net.minecraftforge.client.event.InputEvent.InteractionKeyMappingTriggered.BUS.post(inputEvent)) {
+ if (inputEvent.shouldSwingHand()) {
+ this.level.addBreakingBlockEffect(blockpos, blockhitresult);
+ this.player.swing(InteractionHand.MAIN_HAND);
+ }
+ return;
+ }
Direction direction = blockhitresult.getDirection();
- if (this.gameMode.continueDestroyBlock(blockpos, direction)) {
- this.level.addBreakingBlockEffect(blockpos, direction);
+ if (this.gameMode.continueDestroyBlock(blockpos, direction) && inputEvent.shouldSwingHand()) {
+ this.level.addBreakingBlockEffect(blockpos, blockhitresult);
this.player.swing(InteractionHand.MAIN_HAND);
}
}
@@ -1611,6 +_,8 @@
this.player.swing(InteractionHand.MAIN_HAND);
return true;
} else {
+ var inputEvent = new net.minecraftforge.client.event.InputEvent.InteractionKeyMappingTriggered(0, this.options.keyAttack, InteractionHand.MAIN_HAND);
+ if (!net.minecraftforge.client.event.InputEvent.InteractionKeyMappingTriggered.BUS.post(inputEvent))
switch (this.hitResult.getType()) {
case ENTITY:
AttackRange attackrange = itemstack.get(DataComponents.ATTACK_RANGE);
@@ -1621,7 +_,7 @@
case BLOCK:
BlockHitResult blockhitresult = (BlockHitResult)this.hitResult;
BlockPos blockpos = blockhitresult.getBlockPos();
- if (!this.level.getBlockState(blockpos).isAir()) {
+ if (!this.level.isEmptyBlock(blockpos)) {
this.gameMode.startDestroyBlock(blockpos, blockhitresult.getDirection());
if (this.level.getBlockState(blockpos).isAir()) {
flag = true;
@@ -1634,9 +_,10 @@
}
this.player.resetAttackStrengthTicker();
+ net.minecraftforge.event.ForgeEventFactory.onLeftClickEmpty(this.player);
}
- if (!this.player.isSpectator()) {
+ if (!this.player.isSpectator() && inputEvent.shouldSwingHand()) {
@@ -1532,10 +_,18 @@
if (p_91387_ && this.hitResult != null && this.hitResult.getType() == HitResult.Type.BLOCK) {
BlockHitResult blockhitresult = (BlockHitResult)this.hitResult;
BlockPos blockpos = blockhitresult.getBlockPos();
- if (!this.level.getBlockState(blockpos).isAir()) {
+ if (!this.level.isEmptyBlock(blockpos)) {
+ var inputEvent = net.minecraftforge.client.event.ForgeEventFactoryClient.onClickInput(0, this.options.keyAttack, InteractionHand.MAIN_HAND);
+ if (inputEvent.isCanceled()) {
+ if (inputEvent.shouldSwingHand()) {
+ this.particleEngine.addBlockHitEffects(blockpos, blockhitresult);
+ this.player.swing(InteractionHand.MAIN_HAND);
+ }
+ return;
+ }
Direction direction = blockhitresult.getDirection();
- if (this.gameMode.continueDestroyBlock(blockpos, direction)) {
- this.particleEngine.crack(blockpos, direction);
+ if (this.gameMode.continueDestroyBlock(blockpos, direction) && inputEvent.shouldSwingHand()) {
+ this.particleEngine.addBlockHitEffects(blockpos, blockhitresult);
this.player.swing(InteractionHand.MAIN_HAND);
}
}
@@ -1563,6 +_,8 @@
return false;
} else {
boolean flag = false;
+ var inputEvent = net.minecraftforge.client.event.ForgeEventFactoryClient.onClickInput(0, this.options.keyAttack, InteractionHand.MAIN_HAND);
+ if (!inputEvent.isCanceled())
switch (this.hitResult.getType()) {
case ENTITY:
this.gameMode.attack(this.player, ((EntityHitResult)this.hitResult).getEntity());
@@ -1570,7 +_,7 @@
case BLOCK:
BlockHitResult blockhitresult = (BlockHitResult)this.hitResult;
BlockPos blockpos = blockhitresult.getBlockPos();
- if (!this.level.getBlockState(blockpos).isAir()) {
+ if (!this.level.isEmptyBlock(blockpos)) {
this.gameMode.startDestroyBlock(blockpos, blockhitresult.getDirection());
if (this.level.getBlockState(blockpos).isAir()) {
flag = true;
@@ -1583,8 +_,10 @@
}
@@ -1655,6 +_,12 @@
this.player.resetAttackStrengthTicker();
+ net.minecraftforge.event.ForgeEventFactory.onLeftClickEmpty(this.player);
}
+ if (inputEvent.shouldSwingHand())
this.player.swing(InteractionHand.MAIN_HAND);
return flag;
}
@@ -1600,6 +_,12 @@
}
for (InteractionHand interactionhand : InteractionHand.values()) {
+ var inputEvent = new net.minecraftforge.client.event.InputEvent.InteractionKeyMappingTriggered(1, this.options.keyUse, interactionhand);
+ if (net.minecraftforge.client.event.InputEvent.InteractionKeyMappingTriggered.BUS.post(inputEvent)) {
+ var inputEvent = net.minecraftforge.client.event.ForgeEventFactoryClient.onClickInput(1, this.options.keyUse, interactionhand);
+ if (inputEvent.isCanceled()) {
+ if (inputEvent.shouldSwingHand()) this.player.swing(interactionhand);
+ return;
+ }
@@ -251,16 +252,16 @@
ItemStack itemstack = this.player.getItemInHand(interactionhand);
if (!itemstack.isItemEnabled(this.level.enabledFeatures())) {
return;
@@ -1676,7 +_,7 @@
@@ -1620,7 +_,7 @@
}
if (interactionresult instanceof InteractionResult.Success interactionresult$success2) {
- if (interactionresult$success2.swingSource() == InteractionResult.SwingSource.CLIENT) {
+ if (interactionresult$success2.swingSource() == InteractionResult.SwingSource.CLIENT && inputEvent.shouldSwingHand()) {
this.player.swing(interactionhand);
}
if (interactionresult instanceof InteractionResult.Success interactionresult$success2) {
- if (interactionresult$success2.swingSource() == InteractionResult.SwingSource.CLIENT) {
+ if (interactionresult$success2.swingSource() == InteractionResult.SwingSource.CLIENT && inputEvent.shouldSwingHand()) {
this.player.swing(interactionhand);
}
@@ -1689,7 +_,7 @@
@@ -1632,7 +_,7 @@
int i = itemstack.getCount();
InteractionResult interactionresult1 = this.gameMode.useItemOn(this.player, interactionhand, blockhitresult);
if (interactionresult1 instanceof InteractionResult.Success interactionresult$success) {
@@ -269,7 +270,7 @@
this.player.swing(interactionhand);
if (!itemstack.isEmpty() && (itemstack.getCount() != i || this.player.hasInfiniteMaterials())) {
this.gameRenderer.itemInHandRenderer.itemUsed(interactionhand);
@@ -1705,6 +_,9 @@
@@ -1648,6 +_,9 @@
}
}
@@ -279,7 +280,7 @@
if (!itemstack.isEmpty()
&& this.gameMode.useItem(this.player, interactionhand) instanceof InteractionResult.Success interactionresult$success1) {
if (interactionresult$success1.swingSource() == InteractionResult.SwingSource.CLIENT) {
@@ -1734,6 +_,8 @@
@@ -1677,6 +_,8 @@
}
ProfilerFiller profilerfiller = Profiler.get();
@@ -288,7 +289,7 @@
profilerfiller.push("gui");
this.chatListener.tick();
this.gui.tick(this.pause);
@@ -1815,6 +_,7 @@
@@ -1766,6 +_,7 @@
this.tutorial.tick();
@@ -296,7 +297,7 @@
try {
this.level.tick(() -> true);
} catch (Throwable throwable1) {
@@ -1828,6 +_,7 @@
@@ -1779,6 +_,7 @@
throw new ReportedException(crashreport1);
}
@@ -304,7 +305,7 @@
}
profilerfiller.popPush("animateTick");
@@ -1852,6 +_,7 @@
@@ -1803,6 +_,7 @@
profilerfiller.popPush("keyboard");
this.keyboardHandler.tick();
profilerfiller.pop();
@@ -312,15 +313,15 @@
}
private boolean isLevelRunningNormally() {
@@ -2070,6 +_,7 @@
@@ -2003,6 +_,7 @@
}
public void setLevel(ClientLevel p_91157_) {
public void setLevel(ClientLevel p_91157_, ReceivingLevelScreen.Reason p_335893_) {
+ if (this.level != null) net.minecraftforge.event.ForgeEventFactory.onLevelUnload(this.level);
this.updateScreenAndTick(new ReceivingLevelScreen(() -> false, p_335893_));
this.level = p_91157_;
this.updateLevelInEngines(p_91157_);
}
@@ -2131,6 +_,7 @@
@@ -2040,6 +_,7 @@
IntegratedServer integratedserver = this.singleplayerServer;
this.singleplayerServer = null;
this.gameRenderer.resetData();
@@ -328,27 +329,27 @@
this.gameMode = null;
this.narrator.clear();
this.clientLevelTeardownInProgress = true;
@@ -2138,6 +_,7 @@
@@ -2047,6 +_,7 @@
try {
this.updateScreenAndTick(p_335030_);
if (this.level != null) {
this.gui.onDisconnected();
+ net.minecraftforge.event.ForgeEventFactory.onLevelUnload(this.level);
}
if (integratedserver != null) {
ProfilerFiller profilerfiller = Profiler.get();
profilerfiller.push("waitForServer");
@@ -2060,6 +_,7 @@
if (integratedserver != null) {
@@ -2150,6 +_,7 @@
}
profilerfiller.pop();
this.gui.onDisconnected();
this.isLocalServer = false;
+ net.minecraftforge.client.ForgeHooksClient.handleClientLevelClosing(this.level);
}
this.setScreenAndShow(p_457642_);
@@ -2292,6 +_,7 @@
this.level = null;
@@ -2207,6 +_,7 @@
private void pickBlock() {
if (this.hitResult != null && this.hitResult.getType() != HitResult.Type.MISS) {
+ if (net.minecraftforge.client.event.ForgeEventFactoryClient.onClickInputPickBlock(this.options.keyPickItem)) return;
boolean flag = this.hasControlDown();
+ if (net.minecraftforge.client.event.ForgeEventFactoryClient.onClickInput(2, this.options.keyPickItem, InteractionHand.MAIN_HAND).isCanceled()) return;
boolean flag = Screen.hasControlDown();
switch (this.hitResult) {
case BlockHitResult blockhitresult:
@@ -1,40 +1,40 @@
--- a/net/minecraft/client/MouseHandler.java
+++ b/net/minecraft/client/MouseHandler.java
@@ -87,6 +_,7 @@
this.activeButton = null;
@@ -90,6 +_,7 @@
this.activeButton = -1;
}
+ if (net.minecraftforge.client.event.ForgeEventFactoryClient.onMouseButtonPre(mousebuttoninfo, p_423948_)) return;
+ if (net.minecraftforge.client.event.ForgeEventFactoryClient.onMouseButtonPre(p_91532_, p_91533_, p_91534_)) return;
if (this.minecraft.getOverlay() == null) {
if (this.minecraft.screen == null) {
if (!this.mouseGrabbed && flag) {
@@ -106,7 +_,7 @@
&& i - this.lastClick.time() < 250L
&& this.lastClick.screen() == screen
&& this.lastClickButton == mousebuttonevent.button();
- if (screen.mouseClicked(mousebuttonevent, flag1)) {
+ if (net.minecraftforge.client.event.ForgeEventFactoryClient.onScreenMouseClicked(screen, d0, d1, mousebuttonevent, flag1)) {
this.lastClick = new MouseHandler.LastClick(i, screen);
this.lastClickButton = mousebuttoninfo.button();
@@ -103,7 +_,7 @@
screen.afterMouseAction();
try {
- if (screen.mouseClicked(d0, d1, i)) {
+ if (net.minecraftforge.client.event.ForgeEventFactoryClient.onScreenMouseClicked(screen, d0, d1, i)) {
return;
@@ -121,7 +_,7 @@
}
} catch (Throwable throwable1) {
@@ -116,7 +_,7 @@
}
} else {
try {
- if (screen.mouseReleased(mousebuttonevent)) {
+ if (net.minecraftforge.client.event.ForgeEventFactoryClient.onScreenMouseReleased(screen, d0, d1, mousebuttonevent)) {
- if (screen.mouseReleased(d0, d1, i)) {
+ if (net.minecraftforge.client.event.ForgeEventFactoryClient.onScreenMouseReleased(screen, d0, d1, i)) {
return;
}
} catch (Throwable throwable) {
@@ -151,6 +_,7 @@
KeyMapping.click(inputconstants$key);
@@ -150,6 +_,7 @@
}
}
+ net.minecraftforge.client.event.ForgeEventFactoryClient.onMouseButtonPost(mousebuttoninfo, p_423948_);
}
+ net.minecraftforge.client.event.ForgeEventFactoryClient.onMouseButtonPost(p_91532_, p_91533_, p_91534_);
}
@@ -207,7 +_,9 @@
public void fillMousePositionDetails(CrashReportCategory p_398230_, Window p_398216_) {
@@ -189,7 +_,9 @@
if (this.minecraft.screen != null) {
double d3 = this.getScaledXPos(this.minecraft.getWindow());
double d4 = this.getScaledYPos(this.minecraft.getWindow());
@@ -45,7 +45,7 @@
this.minecraft.screen.afterMouseAction();
} else if (this.minecraft.player != null) {
Vector2i vector2i = this.scrollWheelHandler.onMouseScroll(d1, d2);
@@ -216,6 +_,7 @@
@@ -198,6 +_,7 @@
}
int i = vector2i.y == 0 ? -vector2i.x : vector2i.y;
@@ -53,16 +53,16 @@
if (this.minecraft.player.isSpectator()) {
if (this.minecraft.gui.getSpectatorGui().isMenuActive()) {
this.minecraft.gui.getSpectatorGui().onMouseScrolled(-i);
@@ -324,7 +_,7 @@
@@ -303,7 +_,7 @@
double d5 = getScaledYPos(window, this.accumulatedDY);
try {
- screen.mouseDragged(new MouseButtonEvent(d2, d3, this.activeButton), d4, d5);
+ net.minecraftforge.client.ForgeHooksClient.onScreenMouseDrag(screen, new MouseButtonEvent(d2, d3, this.activeButton), d4, d5);
- screen.mouseDragged(d2, d3, this.activeButton, d4, d5);
+ net.minecraftforge.client.ForgeHooksClient.onScreenMouseDrag(screen, d2, d3, this.activeButton, d4, d5);
} catch (Throwable throwable) {
CrashReport crashreport1 = CrashReport.forThrowable(throwable, "mouseDragged event handler");
screen.fillCrashDetails(crashreport1);
@@ -411,6 +_,14 @@
@@ -393,6 +_,14 @@
public double ypos() {
return this.ypos;
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/Options.java
+++ b/net/minecraft/client/Options.java
@@ -816,7 +_,8 @@
@@ -668,7 +_,8 @@
},
new OptionInstance.LazyEnum<>(
() -> Stream.concat(Stream.of(""), Minecraft.getInstance().getSoundManager().getAvailableSoundDevices().stream()).toList(),
@@ -10,15 +10,15 @@
? Optional.empty()
: Optional.of(p_232011_),
Codec.STRING
@@ -848,6 +_,7 @@
);
@@ -683,6 +_,7 @@
public boolean onboardAccessibility = true;
public boolean syncWrites;
public boolean startedCleanly = true;
+ private final Map<String, String> unknownKeys = new java.util.HashMap<>();
private static void operateOnLevelRenderer(Consumer<LevelRenderer> p_457625_) {
LevelRenderer levelrenderer = Minecraft.getInstance().levelRenderer;
@@ -1313,6 +_,7 @@
public OptionInstance<Boolean> darkMojangStudiosBackground() {
return this.darkMojangStudiosBackground;
@@ -1050,6 +_,7 @@
}
public Options(Minecraft p_92138_, File p_92139_) {
@@ -26,10 +26,10 @@
this.minecraft = p_92138_;
this.optionsFile = new File(p_92139_, "options.txt");
boolean flag = Runtime.getRuntime().maxMemory() >= 1000000000L;
@@ -1463,15 +_,28 @@
@@ -1181,15 +_,28 @@
this.onboardAccessibility = p_168428_.process("onboardAccessibility", this.onboardAccessibility);
p_168428_.process("menuBackgroundBlurriness", this.menuBackgroundBlurriness);
this.startedCleanly = p_168428_.process("startedCleanly", this.startedCleanly);
p_168428_.process("musicToast", this.musicToast);
p_168428_.process("musicFrequency", this.musicFrequency);
+ processOptionsKeysOnly(p_168428_);
+ processOptionsEnd(p_168428_);
+ }
@@ -56,7 +56,7 @@
for (SoundSource soundsource : SoundSource.values()) {
p_168428_.process("soundCategory_" + soundsource.getName(), this.soundSourceVolumes.get(soundsource));
}
@@ -1486,6 +_,10 @@
@@ -1204,6 +_,10 @@
}
public void load() {
@@ -67,17 +67,17 @@
try {
if (!this.optionsFile.exists()) {
return;
@@ -1505,7 +_,8 @@
@@ -1228,7 +_,8 @@
this.graphicsMode.set(isTrue(optional.get()) ? GraphicsStatus.FANCY : GraphicsStatus.FAST);
}
final CompoundTag compoundtag1 = this.dataFix(compoundtag);
- this.processOptions(
+ java.util.function.Consumer<FieldAccess> processor = limited ? this::processOptionsKeysOnly : this::processOptions;
+ processor.accept(
new Options.FieldAccess() {
private @Nullable String getValue(String p_394210_) {
Tag tag = compoundtag1.get(p_394210_);
@@ -1585,6 +_,17 @@
@Nullable
private String getValue(String p_394210_) {
@@ -1310,6 +_,17 @@
);
compoundtag1.getString("fullscreenResolution").ifPresent(p_389133_ -> this.fullscreenVideoModeString = p_389133_);
KeyMapping.resetMapping();
@@ -95,10 +95,10 @@
} catch (Exception exception) {
LOGGER.error("Failed to load options", (Throwable)exception);
}
@@ -1612,9 +_,11 @@
@@ -1337,9 +_,11 @@
public void save() {
try (final PrintWriter printwriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(this.optionsFile), StandardCharsets.UTF_8))) {
printwriter.println("version:" + SharedConstants.getCurrentVersion().dataVersion().version());
printwriter.println("version:" + SharedConstants.getCurrentVersion().getDataVersion().getVersion());
+ var seen = new java.util.HashSet<String>();
this.processOptions(
new Options.FieldAccess() {
@@ -107,7 +107,7 @@
printwriter.print(p_168491_);
printwriter.print(':');
}
@@ -1670,6 +_,12 @@
@@ -1395,6 +_,12 @@
if (s != null) {
printwriter.println("fullscreenResolution:" + s);
}
@@ -120,7 +120,7 @@
} catch (Exception exception) {
LOGGER.error("Failed to save options", (Throwable)exception);
}
@@ -1707,6 +_,7 @@
@@ -1433,6 +_,7 @@
}
public void broadcastOptions() {
@@ -128,7 +128,7 @@
if (this.minecraft.player != null) {
this.minecraft.player.connection.broadcastClientInformation(this.buildPlayerInformation());
}
@@ -1820,6 +_,23 @@
@@ -1546,6 +_,23 @@
public static Component genericValueLabel(Component p_231901_, int p_231902_) {
return genericValueLabel(p_231901_, Component.literal(Integer.toString(p_231902_)));
@@ -4,8 +4,8 @@
file2 = new File(file1, p_92297_);
}
+ var event = new net.minecraftforge.client.event.ScreenshotEvent(p_447863_, file2);
+ if (net.minecraftforge.client.event.ScreenshotEvent.BUS.post(event)) {
+ var event = net.minecraftforge.client.event.ForgeEventFactoryClient.onScreenshot(p_389141_, file2);
+ if (event.isCanceled()) {
+ p_92301_.accept(event.getCancelMessage());
+ return;
+ }
@@ -15,12 +15,12 @@
.execute(
() -> {
@@ -50,10 +_,13 @@
NativeImage $$4x = p_447863_;
NativeImage $$4x = p_389141_;
try {
- p_447863_.writeToFile(file2);
- p_389141_.writeToFile(file2);
- Component component = Component.literal(file2.getName())
+ p_447863_.writeToFile(target);
+ p_389141_.writeToFile(target);
+ Component component = Component.literal(target.getName())
.withStyle(ChatFormatting.UNDERLINE)
.withStyle(p_389149_ -> p_389149_.withClickEvent(new ClickEvent.OpenFile(file2.getAbsoluteFile())));
@@ -29,4 +29,4 @@
+ else
p_92301_.accept(Component.translatable("screenshot.success", component));
} catch (Throwable throwable1) {
if (p_447863_ != null) {
if (p_389141_ != null) {
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/ToggleKeyMapping.java
+++ b/net/minecraft/client/ToggleKeyMapping.java
@@ -31,7 +_,7 @@
@@ -17,7 +_,7 @@
@Override
public void setDown(boolean p_92534_) {
if (this.needsToggle.getAsBoolean()) {
@@ -9,7 +9,7 @@
super.setDown(!this.isDown());
}
} else {
@@ -56,5 +_,9 @@
@@ -27,5 +_,9 @@
protected void reset() {
super.setDown(false);
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/data/models/ItemModelGenerators.java
+++ b/net/minecraft/client/data/models/ItemModelGenerators.java
@@ -912,6 +_,9 @@
@@ -852,6 +_,9 @@
this.declareCustomModelItem(Items.COD);
this.declareCustomModelItem(Items.FEATHER);
this.declareCustomModelItem(Items.LEAD);
@@ -21,11 +21,11 @@
}
+ protected Stream<Block> getKnownBlocks() {
+ return BuiltInRegistries.BLOCK.stream().filter(block -> "minecraft".equals(block.builtInRegistryHolder().key().identifier().getNamespace()));
+ return BuiltInRegistries.BLOCK.stream().filter(block -> "minecraft".equals(block.builtInRegistryHolder().key().location().getNamespace()));
+ }
+
+ protected Stream<Item> getKnownItems() {
+ return BuiltInRegistries.ITEM.stream().filter(item -> "minecraft".equals(item.builtInRegistryHolder().key().identifier().getNamespace()));
+ return BuiltInRegistries.ITEM.stream().filter(item -> "minecraft".equals(item.builtInRegistryHolder().key().location().getNamespace()));
+ }
+
+ protected BlockModelGenerators getBlockModelGenerators(BlockStateGeneratorCollector blocks, ItemInfoCollector items, SimpleModelCollector models) {
@@ -46,7 +46,7 @@
+ private final Supplier<Stream<Block>> known;
+
+ public BlockStateGeneratorCollector() {
+ this(() -> BuiltInRegistries.BLOCK.stream().filter(block -> "minecraft".equals(block.builtInRegistryHolder().key().identifier().getNamespace())));
+ this(() -> BuiltInRegistries.BLOCK.stream().filter(block -> "minecraft".equals(block.builtInRegistryHolder().key().location().getNamespace())));
+ }
+
+ public BlockStateGeneratorCollector(Supplier<Stream<Block>> known) {
@@ -61,8 +61,8 @@
public void validate() {
- Stream<Holder.Reference<Block>> stream = BuiltInRegistries.BLOCK.listElements().filter(p_376480_ -> true);
+ Stream<Holder.Reference<Block>> stream = known.get().map(Block::builtInRegistryHolder);
List<Identifier> list = stream.filter(p_378423_ -> !this.generators.containsKey(p_378423_.value()))
.map(p_447940_ -> p_447940_.key().identifier())
List<ResourceLocation> list = stream.filter(p_378423_ -> !this.generators.containsKey(p_378423_.value()))
.map(p_378200_ -> p_378200_.key().location())
.toList();
@@ -97,6 +_,15 @@
public static class ItemInfoCollector implements ItemModelOutput {
@@ -71,7 +71,7 @@
+ private final Supplier<Stream<Item>> known;
+
+ public ItemInfoCollector() {
+ this(() -> BuiltInRegistries.ITEM.stream().filter(item -> "minecraft".equals(item.builtInRegistryHolder().key().identifier().getNamespace())));
+ this(() -> BuiltInRegistries.ITEM.stream().filter(item -> "minecraft".equals(item.builtInRegistryHolder().key().location().getNamespace())));
+ }
+
+ public ItemInfoCollector(Supplier<Stream<Item>> known) {
@@ -79,16 +79,16 @@
+ }
@Override
public void accept(Item p_376450_, ItemModel.Unbaked p_378513_, ClientItem.Properties p_455809_) {
public void accept(Item p_376450_, ItemModel.Unbaked p_378513_) {
@@ -115,7 +_,7 @@
this.copies.put(p_376965_, p_377438_);
}
- public void finalizeAndValidate() {
+ public void generateDefaultBlockModels() {
BuiltInRegistries.ITEM.forEach(p_447944_ -> {
if (!this.copies.containsKey(p_447944_)) {
if (p_447944_ instanceof BlockItem blockitem && !this.itemInfos.containsKey(blockitem)) {
BuiltInRegistries.ITEM.forEach(p_378629_ -> {
if (!this.copies.containsKey(p_378629_)) {
if (p_378629_ instanceof BlockItem blockitem && !this.itemInfos.containsKey(blockitem)) {
@@ -124,6 +_,8 @@
}
}
@@ -102,10 +102,10 @@
this.register(p_376289_, clientitem);
}
});
- List<Identifier> list = BuiltInRegistries.ITEM
- List<ResourceLocation> list = BuiltInRegistries.ITEM
- .listElements()
+ List<Identifier> list = known.get()
+ List<ResourceLocation> list = known.get()
+ .map(item -> item.builtInRegistryHolder())
.filter(p_377225_ -> !this.itemInfos.containsKey(p_377225_.value()))
.map(p_447943_ -> p_447943_.key().identifier())
.map(p_378591_ -> p_378591_.key().location())
.toList();
Loaded 100 of 1236 files, more files were not shown because too many files have changed in this diff. Show more