Compare commits

..
36 Commits
Author SHA1 Message Date
Jonathing 0bca349d64 1.21.1 RB 1
https://forums.minecraftforge.net/topic/154391-forge-521-minecraft-1211/
2025-03-04 15:49:44 -05:00
Jonathing 7a44626d5e Add #forge:chorus_additionally_grows_on tag for similiar mechanics to #minecraft:azalea_grows_on but for chorus (#10461) 2025-03-04 12:39:44 -08:00
JonathingandLexManos 30ec79b19e Speed up mod annotation scanning by ~30% (#10467)
Co-authored-by: LexManos <[email protected]>
2025-03-04 12:27:17 -08:00
Jonathing 0794e51f62 Add missed license headers (#10476) 2025-03-04 12:26:57 -08:00
JonathingandLexManos c86172db31 Redstone updates and gametests structure fixes (#10422)
Fix game test structure rotations being inconsistent (#10391)
Fix redstone update order not matching vanilla. (#10392)
Fix redstone updates for comparators going up/down incorrectly. Related: #9973
Make block pos assertions in GameTestHelper behave like in 1.21.3, thus fixing rotated tests functionality.
This can be disabled in the Forge config if you have old tests that this breaks. Tho it is recommended you update your tests.

---------
Co-authored-by: LexManos <[email protected]>
2025-03-04 12:26:42 -08:00
Jonathing ee14c0c911 Bump dependencies (#10386)
- CoreMods 5.2.4 -> 5.2.6
- EventBus 6.2.15 -> 6.2.27
    - Reduced memory usage
    - Further performance improvements
    - Fixed EventBus#39, unregistered listeners are now immediately reclaimable by the GC
- Apache MavenArtifact 3.8.5 -> 3.8.8
- NightConfig 3.7.3 -> 3.7.4
2025-02-17 18:46:48 -08:00
b3a4ac864f Re-introduce ItemStack Capabilities (#10363)
Co-authored-by: RealMangoRage <[email protected]>
Co-authored-by: LexManos <[email protected]>
2025-02-17 18:40:35 -08:00
Jonathing f9139a3002 Re-introduce IForgeItem.damageItem when an item takes damage, Fixes #10344 (#10373) 2025-02-17 18:35:26 -08:00
Jonathing 784693b7e5 Add updated tag context to AddReloadListenerEvent (#10421)
Fixes using custom tags in GlobalLootModifiers.
2025-02-17 18:28:07 -08:00
Jonathing 774048154f Add fast graphics render type to block model jsons (#10396)
Make modded leaves behave like vanilla leaves by default (Fixes #10389)
2025-02-17 18:22:26 -08:00
Jonathing 490e35ba8e Fix corrupted and invalidly symlinked worlds crashing on level select (#10420) 2025-02-17 18:18:10 -08:00
Jonathing 667a164430 Honor attacker shield disabling status (#10318) 2025-01-27 15:33:47 -08:00
Joseph T. McQuigg 0434d67016 Fix issues in VillagerTrades.EmeraldsForVillagerTypeItem related to custom Villager Types (#10300)
Co-authored-by: Joseph T. McQuigg <[email protected]>
2025-01-27 15:33:29 -08:00
JonathingandLexManos 02229557ed GameTestHelper additions (#10340)
Co-authored-by: LexManos <[email protected]>
2025-01-27 09:58:00 -08:00
Jonathing 79224a70e6 Update Gradle to 8.12.1 (#10354)
Automate IntelliJ download sources in MDK
2025-01-27 09:57:46 -08:00
Jonathing 5c9c8ce539 Fix JOpt Simple by bumping ModLauncher (#10349) 2025-01-27 09:57:09 -08:00
Jonathing c186c38113 Fix JOpt Simple needing a strict version requirement declaration (#10313) 2025-01-25 18:39:10 +00:00
Jonathing 1507717fae Fix incorrect method reference in TntBlock.explode() (#10328) 2025-01-25 18:38:43 +00:00
Jonathing 12c4233fac Bump dependencies (#10276)
- EventBus 6.2.8 -> 6.2.15
- Bootstrap 2.1.3 -> 2.1.8
- SecureModules 2.2.20 -> 2.2.21
- AccessTransformers 8.2.0 -> 8.2.1
- ModLauncher 10.2.2 -> 10.2.3
These bumps include further cleanup and optimisations.
2025-01-04 16:42:08 +00:00
Jonathing f331376d9d Simplify Title Screen Brandings (#10289) 2025-01-02 21:57:02 -08:00
Gabriel Harris-Rouquette b5b5064a4f Update jline for better darwin/arm64 support 2024-12-30 09:47:02 -08:00
JonathingandPaint_Ninja f4801960e7 Skip Vanilla classes for the CapabilityTokenSubclass transformer (#10224)
Co-authored-by: Paint_Ninja <[email protected]>
2024-12-23 23:10:40 -08:00
Jonathing 68c60c17e5 Bump CoreMods to 5.2.4 (#10266) 2024-12-23 15:47:22 -08:00
JonathingandPaint_Ninja 4a5f977691 Skip processing Forge classes in RuntimeDistCleaner (#10211)
Co-authored-by: Paint_Ninja <[email protected]>
2024-12-23 14:03:50 -08:00
JonathingandPaint_Ninja 896cfb38a3 Skip Forge classes in the RuntimeEnumExtender transformer (#10197) (#10219)
Mod classes are still transformed as usual

Co-authored-by: Paint_Ninja <[email protected]>
2024-12-23 13:50:21 -08:00
JonathingandPaint_Ninja 185273022c Deprecate @ObjectHolder, add a couple of fast-paths (#10195) (#10231)
Co-authored-by: Paint_Ninja <[email protected]>
2024-12-23 13:40:29 -08:00
Jonathing eb65e1db51 Allow mipmap lowering to be disabled (#10255) 2024-12-23 13:30:30 -08:00
JonathingandPaint_Ninja 6a5c413a13 Simplify memory usage display on loading screen (#10236)
Co-authored-by: Paint_Ninja <[email protected]>
2024-12-23 13:30:01 -08:00
Jonathing 1b7b2b6ff1 Fix NightConfig FileWatcher causing dedicated server to hang when exiting (#10215) 2024-12-23 13:03:27 -08:00
Martijn van den Brand cb290f23f5 Pass player argument when firing OnDatapackSyncEvent (#10171) 2024-11-16 21:34:03 -08:00
Jonathing 6e8eaeeec9 Add optional fix of use item duration, disabled by default (#10151) 2024-11-10 13:17:08 -08:00
Jonathing 56c41f3ca8 Bump CoreMods to 5.2 (#10160)
Full Changelog: https://gist.github.com/Jonathing/c3ad28b2a048ac839a7baba5417ee870

The key features are:
- ES6 language support
- Thoroughly updated ASMAPI, with full documentation
- Bug fixes (some optional for backwards-compatibility)
- Partial internal code cleanup
- Request CoreMods to not apply fix for ASMAPI.findFirstInstructionBefore by default
2024-11-08 12:41:02 -08:00
LexManos 0084b8cf48 Add proper support for JPMS in normal mods.toml mods (#10125)
Make FMLModContainer read and apply Add-Opens and Add-Exports manifest entries from mod files.
Forge is now the `net.minecraftforge.forge` module instead of `forge`

https://forums.minecraftforge.net/topic/153333-proper-java-module-support-in-forge-mods/
2024-11-04 17:11:56 -08:00
Paint_Ninja eb6c564ce4 Add more c fluid tags (#10137)
Replaces the existing `forge`-namespaced tags, which are now deprecated.
Adds `c:experience`
2024-11-04 18:21:54 +00:00
Paint_Ninja 618da273dc Add c:stripped_logs and c:stripped_woods tags (#10136) 2024-10-26 19:47:54 +01:00
Jonathing 074c0cdfd6 [1.21.1] Bump CoreMods and ASM (#10127)
CoreMods 5.1.13 changelog: https://gist.github.com/Jonathing/905d142447ecef670526ae27243adbba
2024-10-26 11:45:37 +01:00
1832 changed files with 38056 additions and 31733 deletions

No files matched your search

-1
View File
@@ -3,7 +3,6 @@
*.patch text eol=lf
*.java text eol=lf
*.gradle text eol=crlf
**/MANIFEST.MF text eol=crlf
*.png binary
*.gif binary
*.exe binary
-28
View File
@@ -1,28 +0,0 @@
name: Publish
on:
push:
branches: [ '1.21.11' ]
permissions:
contents: read
jobs:
build:
uses: MinecraftForge/SharedActions/.github/workflows/gradle.yml@main
with:
java: 21
gradle_setup: './gradlew setup'
gradle_tasks: 'build publish'
artifact_name: 'forge'
secrets:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
PROMOTE_ARTIFACT_WEBHOOK: ${{ secrets.PROMOTE_ARTIFACT_WEBHOOK }}
PROMOTE_ARTIFACT_USERNAME: ${{ secrets.PROMOTE_ARTIFACT_USERNAME }}
PROMOTE_ARTIFACT_PASSWORD: ${{ secrets.PROMOTE_ARTIFACT_PASSWORD }}
MAVEN_USER: ${{ secrets.MAVEN_USER }}
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
SIGN_KEY_ALIAS: ${{ secrets.SIGN_KEY_ALIAS }}
SIGN_KEY_PASSWORD: ${{ secrets.SIGN_KEY_PASSWORD }}
SIGN_KEYSTORE_PASSWORD: ${{ secrets.SIGN_KEYSTORE_PASSWORD }}
SIGN_KEYSTORE_DATA: ${{ secrets.SIGN_KEYSTORE_DATA }}
+12 -6
View File
@@ -26,6 +26,7 @@
/projects/mcp/
/projects/clean/
/projects/forge/
#/patches/*
#occupational hazards
/projects/**/build/
@@ -34,15 +35,20 @@
/projects/**/*.launch
/repo/
src/*/generated/**/.cache/
# Generated by gradle every import
**/src/main/resources/META-INF/MANIFEST.MF
/fmlloader/src/main/resources/forge_version.json
#Patch rejects
#/patches-/
#*.patch.rej
#/projects/*/rejects/
# FML changelog
changelog.txt
*.py
*.7z
/logs/
/ForgeRoot.ipr
/ForgeRoot.iws
# APT stuff
/*/.gitignore
/*/.factorypath
/*/.apt_generated/
+104
View File
@@ -0,0 +1,104 @@
<?xml version="1.0"?>
<project>
<modelVersion>4.0.0</modelVersion>
<name>MinecraftForge_Forge Config DSL Script</name>
<groupId>MinecraftForge_Forge</groupId>
<artifactId>MinecraftForge_Forge_dsl</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.jetbrains.teamcity</groupId>
<artifactId>configs-dsl-kotlin-parent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<repositories>
<repository>
<id>jetbrains-all</id>
<url>https://download.jetbrains.com/teamcity-repository</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>teamcity-server</id>
<url>https://teamcity.minecraftforge.net/app/dsl-plugins-repository</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>JetBrains</id>
<url>https://download.jetbrains.com/teamcity-repository</url>
</pluginRepository>
</pluginRepositories>
<build>
<sourceDirectory>${basedir}</sourceDirectory>
<plugins>
<plugin>
<artifactId>kotlin-maven-plugin</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
<version>${kotlin.version}</version>
<configuration/>
<executions>
<execution>
<id>compile</id>
<phase>process-sources</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>process-test-sources</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jetbrains.teamcity</groupId>
<artifactId>teamcity-configs-maven-plugin</artifactId>
<version>${teamcity.dsl.version}</version>
<configuration>
<format>kotlin</format>
<dstDir>target/generated-configs</dstDir>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.jetbrains.teamcity</groupId>
<artifactId>configs-dsl-kotlin</artifactId>
<version>${teamcity.dsl.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.teamcity</groupId>
<artifactId>configs-dsl-kotlin-plugins</artifactId>
<version>1.0-SNAPSHOT</version>
<type>pom</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>${kotlin.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-script-runtime</artifactId>
<version>${kotlin.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
+193
View File
@@ -0,0 +1,193 @@
import jetbrains.buildServer.configs.kotlin.v2019_2.*
import jetbrains.buildServer.configs.kotlin.v2019_2.projectFeatures.githubIssues
import jetbrains.buildServer.configs.kotlin.*
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.gradle
/*
The settings script is an entry point for defining a TeamCity
project hierarchy. The script should contain a single call to the
project() function with a Project instance or an init function as
an argument.
VcsRoots, BuildTypes, Templates, and subprojects can be
registered inside the project using the vcsRoot(), buildType(),
template(), and subProject() methods respectively.
To debug settings scripts in command-line, run the
mvnDebug org.jetbrains.teamcity:teamcity-configs-maven-plugin:generate
command and attach your debugger to the port 8000.
To debug in IntelliJ Idea, open the 'Maven Projects' tool window (View
-> Tool Windows -> Maven Projects), find the generate task node
(Plugins -> teamcity-configs -> teamcity-configs:generate), the
'Debug' option is available in the context menu for the task.
*/
version = "2021.2"
project {
buildType(Build)
buildType(BuildSecondaryBranches)
buildType(PullRequests)
buildType(PullRequestChecks)
buildType(PullRequestCompatibility)
params {
text("docker_jdk_version", "21", label = "Gradle version", description = "The version of the JDK to use during execution of tasks in a JDK.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
text("docker_gradle_version", "8.7", label = "Gradle version", description = "The version of Gradle to use during execution of Gradle tasks.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
text("git_main_branch", "1.21.x", label = "Git Main Branch", description = "The git main or default branch to use in VCS operations.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
text("git_branch_spec", """
+:refs/heads/(main*)
+:refs/heads/(master*)
+:refs/heads/(develop|release|staging|main|master)
+:refs/heads/(1.*)
""".trimIndent(), label = "The branch specification of the repository", description = "By default all main branches are build by the configuration. Modify this value to adapt the branches build.", display = ParameterDisplay.HIDDEN, allowEmpty = true)
text("github_repository_name", "MinecraftForge", label = "The github repository name. Used to connect to it in VCS Roots.", description = "This is the repository slug on github. So for example `MinecraftForge` or `MinecraftForge`. It is interpolated into the global VCS Roots.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
text("env.PUBLISHED_JAVA_ARTIFACT_ID", "forge", label = "Published artifact id", description = "The maven coordinate artifact id that has been published by this build. Can not be empty.", allowEmpty = false)
text("env.PUBLISHED_JAVA_GROUP", "net.minecraftforge", label = "Published group", description = "The maven coordinate group that has been published by this build. Can not be empty.", allowEmpty = false)
//These are references and not actually keys
password("env.CROWDIN_KEY", "credentialsJSON:a3102dbe-805d-4177-9f54-3d2c2eb08fd5", display = ParameterDisplay.HIDDEN)
password("env.KEYSTORE_URL", "credentialsJSON:a7ae1c82-8058-4061-8d12-7f6bc2618d2e", display = ParameterDisplay.HIDDEN)
password("env.KEYSTORE_PASSWORD", "credentialsJSON:d7b964e3-a1fd-47a8-b892-6f601fe47479", display = ParameterDisplay.HIDDEN)
text("additional_publishing_gradle_parameters", "-PcrowdinKey=%env.CROWDIN_KEY% -PkeystoreKeyPass=%env.KEYSTORE_PASSWORD% -PkeystoreStorePass=%env.KEYSTORE_PASSWORD% -Pkeystore=%env.KEYSTORE_URL%", label = "Additional gradle parameters for publish", description = "Contains the additional gradle parameters used during publishing.", display = ParameterDisplay.HIDDEN, allowEmpty = true)
checkbox("should_execute_build", "false", label = "Should build", description = "Indicates if the build task should be executed.", display = ParameterDisplay.HIDDEN,
checked = "true", unchecked = "false")
checkbox("should_execute_test", "false", label = "Should build", description = "Indicates if the build task should be executed.", display = ParameterDisplay.HIDDEN,
checked = "true", unchecked = "false")
}
features {
githubIssues {
id = "MinecraftForge_MinecraftForge__IssueTracker"
displayName = "MinecraftForge/MinecraftForge"
repositoryURL = "https://github.com/MinecraftForge/MinecraftForge"
}
}
}
object Build : BuildType({
templates(AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_BuildMainBranches"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"), AbsoluteId("MinecraftForge_BuildUsingGradle"), AbsoluteId("MinecraftForge_PublishProjectUsingGradle"), AbsoluteId("MinecraftForge_TriggersStaticFilesWebpageGenerator"))
id("MinecraftForge_MinecraftForge__Build")
name = "Build"
description = "Builds and Publishes the main branches of the project."
})
object BuildSecondaryBranches : BuildType({
templates(AbsoluteId("MinecraftForge_ExcludesBuildingDefaultBranch"), AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_BuildMainBranches"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"), AbsoluteId("MinecraftForge_BuildUsingGradle"))
id("MinecraftForge_MinecraftForge__BuildSecondaryBranches")
name = "Build - Secondary Branches"
description = "Builds and Publishes the secondary branches of the project."
params {
text("git_branch_spec", """
+:refs/heads/(*)
-:refs/heads/noci*
""".trimIndent(), label = "The branch specification of the repository", description = "By default all main branches are build by the configuration. Modify this value to adapt the branches build.", display = ParameterDisplay.HIDDEN, allowEmpty = true)
}
vcs {
branchFilter = """
+:*
-:1.*
-:<default>
""".trimIndent()
}
})
object PullRequests : BuildType({
templates(AbsoluteId("MinecraftForge_BuildPullRequests"), AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"), AbsoluteId("MinecraftForge_BuildUsingGradle"))
id("MinecraftForge_MinecraftForge__PullRequests")
name = "Pull Requests"
description = "Builds pull requests for the project"
params {
checkbox("should_execute_build", "true", label = "Should build", description = "Indicates if the build task should be executed.", display = ParameterDisplay.HIDDEN,
checked = "true", unchecked = "false")
text(
"gradle_build_task",
"assemble",
label = "Gradle build task to execute during build",
description = "Determines the build task that is executed to build the project.",
display = ParameterDisplay.HIDDEN,
allowEmpty = false
)
}
vcs {
branchFilter = """
+:*
-:1.*
-:<default>
""".trimIndent()
}
})
object PullRequestChecks : BuildType({
templates(AbsoluteId("MinecraftForge_BuildPullRequests"), AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"))
id("MinecraftForge_MinecraftForge__PullRequestChecks")
name = "Pull Requests (Checks)"
description = "Checks pull requests for the project"
steps {
gradle {
name = "Check"
id = "RUNNER_10_Check"
tasks = "checkAll"
gradleParams = "--continue %gradle_custom_args%"
enableStacktrace = true
dockerImage = "%docker_gradle_image%"
dockerRunParameters = """
-v "/opt/cache/agent/gradle:/home/gradle/.gradle"
-v "/opt/cache/shared/gradle:/home/gradle/rocache:ro"
--network=host
-u 1000:1000
%docker_additional_args%
""".trimIndent()
}
}
vcs {
branchFilter = """
+:*
-:1.*
-:<default>
""".trimIndent()
}
})
object PullRequestCompatibility : BuildType({
templates(AbsoluteId("MinecraftForge_BuildPullRequests"), AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"))
id("MinecraftForge_MinecraftForge__PullRequestCompatibility")
name = "Pull Requests (Compatibility)"
description = "Validates binary compatibility for pull requests made to the project"
steps {
gradle {
name = "Validate"
id = "RUNNER_10_Compatibility"
tasks = "checkJarCompatibility"
gradleParams = "--continue %gradle_custom_args%"
enableStacktrace = true
dockerImage = "%docker_gradle_image%"
dockerRunParameters = """
-v "/opt/cache/agent/gradle:/home/gradle/.gradle"
-v "/opt/cache/shared/gradle:/home/gradle/rocache:ro"
--network=host
-u 1000:1000
%docker_additional_args%
""".trimIndent()
}
}
vcs {
branchFilter = """
+:*
-:1.*
-:<default>
""".trimIndent()
}
})
+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)
}
+2 -3
View File
@@ -4,10 +4,9 @@ repositories {
}
dependencies {
implementation 'org.ow2.asm:asm:9.8'
implementation 'org.ow2.asm:asm-tree:9.8'
implementation 'org.ow2.asm:asm:9.7'
implementation 'org.ow2.asm:asm-tree:9.7'
implementation 'net.minecraftforge:srgutils:0.5.10'
implementation 'net.minecraftforge:JarJarMetadata:0.3.27'
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'
@@ -21,8 +21,6 @@ abstract class BundleList extends DefaultTask {
configure {
dependsOn(project.tasks.universalJar)
inputs.file(project.tasks.universalJar.archiveFile)
dependsOn(project.tasks.applyServerBinPatches)
inputs.file(project.tasks.applyServerBinPatches.output)
}
}
@@ -3,7 +3,6 @@ package net.minecraftforge.forge.tasks
import groovy.transform.CompileStatic
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Internal
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.MethodNode
@@ -13,19 +12,13 @@ abstract class BytecodePredicateFinder extends BytecodeFinder {
@Internal
abstract Property<Closure<Boolean>> getPredicate()
private final Map<String, List<String>> matches = new TreeMap<>(Comparator.naturalOrder())
private final Set<String> matches = new HashSet<>()
@Override
protected process(ClassNode parent, MethodNode node) {
for (final current : node.instructions) {
if (predicate.get().call(parent, node, current)) {
var methods = matches.compute(parent.name, { k, v -> v ?: new ArrayList<>() })
// only add non-synthetic methods
if ((node.access & Opcodes.ACC_SYNTHETIC) == 0) {
methods.add(node.name + node.desc)
}
matches.add(parent.name)
return
}
}
@@ -34,15 +27,6 @@ abstract class BytecodePredicateFinder extends BytecodeFinder {
@Internal
@Override
protected Object getData() {
var array = new ArrayList<Object>()
matches.forEach { c, m ->
{
array.add(
'class': c,
'methods': m
)
}
}
return array ?: { throw new RuntimeException('Failed to find any targets, please ensure that method names and descriptors are correct.') }()
return matches.toSorted(Comparator.<String>naturalOrder()) ?: {throw new RuntimeException('Failed to find any targets, please ensure that method names and descriptors are correct.')}()
}
}
@@ -1,415 +0,0 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.forge.tasks;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import net.minecraftforge.jarjar.metadata.ContainedJarIdentifier;
import net.minecraftforge.jarjar.metadata.ContainedJarMetadata;
import net.minecraftforge.jarjar.metadata.ContainedVersion;
import net.minecraftforge.jarjar.metadata.Metadata;
import net.minecraftforge.jarjar.metadata.MetadataIOHandler;
import net.minecraftforge.jarjar.metadata.json.ArtifactVersionSerializer;
import net.minecraftforge.jarjar.metadata.json.ContainedJarIdentifierSerializer;
import net.minecraftforge.jarjar.metadata.json.ContainedJarMetadataSerializer;
import net.minecraftforge.jarjar.metadata.json.ContainedVersionSerializer;
import net.minecraftforge.jarjar.metadata.json.MetadataSerializer;
import net.minecraftforge.jarjar.metadata.json.VersionRangeSerializer;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.artifact.versioning.VersionRange;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.gradle.api.Action;
import org.gradle.api.DefaultTask;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.ExternalModuleDependency;
import org.gradle.api.artifacts.FileCollectionDependency;
import org.gradle.api.artifacts.MinimalExternalModuleDependency;
import org.gradle.api.artifacts.ModuleDependency;
import org.gradle.api.artifacts.ModuleIdentifier;
import org.gradle.api.artifacts.ModuleVersionIdentifier;
import org.gradle.api.artifacts.ResolvedArtifact;
import org.gradle.api.file.ProjectLayout;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Provider;
import org.gradle.api.provider.SetProperty;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;
import javax.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.io.Serial;
import java.io.Serializable;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.zip.ZipFile;
// TODO SUPER SUPER SUPER UGLY, CLEAN UP IN FORGEDEV 7
@Deprecated(forRemoval = true) // Will be moved to JarJar plugin in ForgeDev 7
public abstract class JarJarMetadataOptions extends DefaultTask {
private static final Gson GSON = new GsonBuilder()
.registerTypeAdapter(VersionRange.class, new VersionRangeSerializer())
.registerTypeAdapter(ArtifactVersion.class, new ArtifactVersionSerializer())
.registerTypeAdapter(DefaultArtifactVersion.class, new ArtifactVersionSerializer())
.registerTypeAdapter(ContainedJarIdentifier.class, new ContainedJarIdentifierSerializer())
.registerTypeAdapter(ContainedJarMetadata.class, new ContainedJarMetadataSerializer())
.registerTypeAdapter(ContainedVersion.class, new ContainedVersionSerializer())
.registerTypeAdapter(Metadata.class, new MetadataSerializer())
.setPrettyPrinting()
.create();
protected abstract @Input SetProperty<ResolvedDependencyInfoImpl> getResolvedDependencies();
protected abstract @OutputFile RegularFileProperty getMetadataFile();
protected abstract @Inject ProjectLayout getLayout();
// NOTE: I'm not adding a non-provider version. please just use the version catalog entries for now.
public void add(Provider<MinimalExternalModuleDependency> dependency, Action<? super ResolvedDependencyInfo> action) {
this.getResolvedDependencies().add(dependency.map(d -> {
var ret = ResolvedDependencyInfoImpl.from(this.getProject().getConfigurations(), d);
action.execute(ret);
return ret;
}));
}
@Inject
public JarJarMetadataOptions() {
this.getMetadataFile().convention(this.getLayout().getBuildDirectory().file(this.getName() + "/options.json"));
}
@TaskAction
protected void exec() {
record ForgeLocaterOptions(String resource, String layer, String id, List<ContainedJarMetadata> deps, ContainedJarMetadata meta, boolean nested) { }
var resolved = this.getResolvedDependencies().get();
var jars = new ArrayList<ForgeLocaterOptions>(resolved.size());
for (var dependency : resolved) {
var deps = new ArrayList<ContainedJarMetadata>();
try (var zip = new ZipFile(dependency.artifact)) {
var entry = zip.getEntry("META-INF/jarjar/metadata.json");
if (entry != null) {
try (var stream = zip.getInputStream(entry)) {
var meta = MetadataIOHandler.fromStream(stream).orElse(null);
if (meta == null)
throw new IllegalStateException("Corrupt metadata.json in " + dependency.artifact.getAbsolutePath());
for (var dep : meta.jars())
deps.add(new ContainedJarMetadata(dep.identifier(), dep.version(), "", dep.isObfuscated()));
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
jars.add(new ForgeLocaterOptions(
dependency.resource,
dependency.layer,
dependency.identifier,
deps,
new ContainedJarMetadata(
new ContainedJarIdentifier(validateGroup(dependency), dependency.module.getName()),
new ContainedVersion(null, parseVersion(dependency)),
Objects.requireNonNull(dependency.path, "Dependency path is unspecified: " + dependency.asString),
false
),
dependency.nested
));
}
try {
record Meta(List<ForgeLocaterOptions> options){}
Files.writeString(
this.getMetadataFile().getAsFile().get().toPath(),
GSON.toJson(new Meta(jars), Meta.class)
);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private String validateGroup(ResolvedDependencyInfoImpl dependency) {
try {
return Objects.requireNonNull(dependency.module.getGroup());
} catch (NullPointerException e) {
throw new IllegalArgumentException("Module dependency has no group: " + dependency.asString, e);
}
}
private ArtifactVersion parseVersion(ResolvedDependencyInfoImpl resolved) {
try {
return VersionRange.createFromVersionSpec(Objects.requireNonNull(resolved.version)).getRecommendedVersion();
} catch (InvalidVersionSpecificationException e) {
throw new IllegalArgumentException("Version is invalid for: " + resolved.asString, e);
} catch (NullPointerException e) {
throw new IllegalArgumentException("Version is unspecified for: " + resolved.asString, e);
}
}
private VersionRange parseVersionRange(ResolvedDependencyInfoImpl dependency) {
if (dependency.hasManuallySpecifiedRange) {
try {
return VersionRange.createFromVersionSpec(dependency.versionRange);
} catch (InvalidVersionSpecificationException e) {
throw new IllegalArgumentException("Version is invalid for: " + dependency.asString, e);
}
} else {
try {
return VersionRange.createFromVersionSpec("[%s,)".formatted(Objects.requireNonNull(dependency.versionRange)));
} catch (InvalidVersionSpecificationException e) {
throw new IllegalArgumentException("Version range is invalid for: " + dependency.asString, e);
} catch (NullPointerException e) {
throw new IllegalArgumentException("Version is unspecified for: " + dependency.asString, e);
}
}
}
public interface ResolvedDependencyInfo {
void containedJarMetadata(Action<? super ContainedJarMetadataInfo> action);
void setResource(String resource);
void setLayer(String layer);
void setId(String identifier);
void setNested(boolean nested);
interface ContainedJarMetadataInfo {
void setGroup(String group);
void setName(String name);
void setPath(String version);
}
}
static final class ResolvedDependencyInfoImpl implements ResolvedDependencyInfo, ResolvedDependencyInfo.ContainedJarMetadataInfo, Serializable {
private static final @Serial long serialVersionUID = -7577318115877822993L;
final MinimalModuleVersionIdentifier module;
final String version;
String versionRange;
boolean hasManuallySpecifiedRange;
boolean nested;
final File artifact;
String path;
String resource;
String layer;
String identifier;
final String asString;
public ResolvedDependencyInfoImpl(MinimalModuleVersionIdentifier module, String version, File artifact, String asString) {
this.module = module;
this.version = version;
this.artifact = artifact;
this.asString = asString;
}
@Override
public void containedJarMetadata(Action<? super ContainedJarMetadataInfo> action) {
action.execute(this);
}
@Override
public void setGroup(String group) {
this.module.group = group;
}
@Override
public void setName(String name) {
this.module.name = name;
}
@Override
public void setPath(String path) {
this.path = path;
}
@Override
public void setResource(String resource) {
this.resource = resource;
}
@Override
public void setLayer(String layer) {
this.layer = layer;
}
@Override
public void setId(String identifier) {
this.identifier = identifier;
}
@Override
public void setNested(boolean nested) {
this.nested = nested;
}
static Set<File> getFiles(Set<ResolvedDependencyInfoImpl> resolvedDependencies) {
var ret = new HashSet<File>(resolvedDependencies.size());
for (var dependency : resolvedDependencies) {
ret.add(dependency.artifact);
}
return ret;
}
static ResolvedDependencyInfoImpl from(ConfigurationContainer configurations, Dependency dependency) {
var group = dependency.getGroup();
var name = dependency.getName();
var version = dependency.getVersion();
if (dependency instanceof FileCollectionDependency filesDependency) {
File artifact;
try {
artifact = filesDependency.getFiles().getSingleFile();
} catch (IllegalStateException e) {
// TODO fileCollectionDependencyIsNotSingleFile
throw e;
}
return new ResolvedDependencyInfoImpl(
new MinimalModuleVersionIdentifier(group, name, version),
version,
artifact,
filesDependency.toString()
);
} else if (dependency instanceof ModuleDependency moduleDependency) {
moduleDependency = moduleDependency.copy();
if (moduleDependency instanceof ExternalModuleDependency externalModuleDependency) {
externalModuleDependency.version(v -> v.strictly(version.toString()));
}
var detachedConfiguration = configurations.detachedConfiguration(moduleDependency);
detachedConfiguration.setTransitive(false);
ResolvedDependencyInfoImpl ret = null;
for (var artifact : detachedConfiguration.getResolvedConfiguration().getFirstLevelModuleDependencies().iterator().next().getModuleArtifacts()) {
var fileName = getFileName(artifact);
if (!fileName.endsWith(".jar"))
continue;
if (ret != null)
throw new IllegalArgumentException("Module dependency has too many Jar artifacts: " + moduleDependency);
ret = new ResolvedDependencyInfoImpl(
new MinimalModuleVersionIdentifier(group, name, artifact.getModuleVersion().getId().getVersion()),
version,
artifact.getFile(),
moduleDependency.toString()
);
}
if (ret == null)
throw new IllegalArgumentException("Module dependency has no Jar artifacts: " + moduleDependency);
return ret;
} else {
throw new IllegalArgumentException("Unsupported dependency type: " + dependency.getClass().getName() + " -- " + dependency);
}
}
private static String getFileName(ResolvedArtifact artifact) {
try {
return InvokerHelper.getProperty(artifact.getId(), "fileName").toString();
} catch (Throwable e) {
// NOTE: Why not just use this to begin with?
// ComponentArtifactIdentifier can have a getFileName() method, which doesn't necessarily resolve the file itself.
// This allows us to get the name of the file to be used without asking Gradle to download the file.
// So, if a file is not a JAR file, we can check the name without actually downloading it.
return artifact.getFile().getName();
}
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj == null || obj.getClass() != this.getClass()) return false;
var that = (ResolvedDependencyInfoImpl) obj;
return Objects.equals(this.module, that.module) &&
Objects.equals(this.version, that.version) &&
Objects.equals(this.versionRange, that.versionRange) &&
Objects.equals(this.artifact, that.artifact);
}
@Override
public int hashCode() {
return Objects.hash(module, version, versionRange, artifact);
}
@Override
public String toString() {
return "ResolvedDependencyInfo[" +
"module=" + module + ", " +
"fixedVersion=" + version + ", " +
"versionRange=" + versionRange + ", " +
"artifact=" + artifact + ']';
}
static final class MinimalModuleVersionIdentifier implements ModuleIdentifier, ModuleVersionIdentifier {
private static final @Serial long serialVersionUID = -955346236759069739L;
private String group;
private String name;
private final String version;
@Inject
public MinimalModuleVersionIdentifier(String group, String name, String version) {
this.group = group;
this.name = name;
this.version = version;
}
@Override
public ModuleIdentifier getModule() {
return this;
}
@Override
public String getGroup() {
return this.group;
}
@Override
public String getName() {
return this.name;
}
@Override
public String getVersion() {
return this.version;
}
@Override
public boolean equals(Object obj) {
return this == obj || obj instanceof MinimalModuleVersionIdentifier o
&& Objects.equals(this.group, o.group)
&& Objects.equals(this.name, o.name)
&& Objects.equals(this.version, o.version);
}
@Override
public int hashCode() {
return Objects.hash(group, name, version);
}
@Override
public String toString() {
return "MinimalModuleVersionIdentifier[" +
"group=" + group + ", " +
"name=" + name + ", " +
"version=" + version + ']';
}
}
}
}
+23 -68
View File
@@ -47,7 +47,6 @@ jarSigner.autoDetect('forge')
applyPatches {
level 'WARNING'
failOnError = UPDATING != 'true'
rejects = rootProject.layout.projectDirectory.dir('rejects').asFile
}
sourceSets {
@@ -71,7 +70,7 @@ sourceSets {
}
}
final String SPEC_VERSION = gitversion.info.tag
final String SPEC_VERSION = gradleutils.gitInfo.tag
// The new versioning sceme is <MCVersion>-<ForgeMC>.<RB>.<CommitsSinceRB>
// ForgeMC is a unique identifier for every MC version we have supported.
@@ -92,18 +91,18 @@ final String MAVEN_PATH = ext.MAVEN_PATH
final String BINPATCH_TOOL = 'net.minecraftforge:binarypatcher:1.2.0:fatjar'
final String INSTALLER_TOOLS = 'net.minecraftforge:installertools:1.4.3'
final String FART = 'net.minecraftforge:ForgeAutoRenamingTool:1.0.6'
final String S2S_TOOL = 'net.minecraftforge:Srg2Source:8.1.1:fatjar'
final String S2S_TOOL = 'net.minecraftforge:Srg2Source:8.1.0:fatjar'
configurations {
// Don't pull all libraries, if we're missing something, add it to the installer list so the installer knows to download it.
bootstrap { transitive = false }
installer {
installer {
extendsFrom(bootstrap)
transitive = false
transitive = false
}
installerextra {
installerextra {
extendsFrom(bootstrap)
transitive = false
transitive = false
}
api.extendsFrom(installer)
}
@@ -113,19 +112,18 @@ dependencies {
// And this is only needed because custom file systems are REQUIRED to be on the boot classloader.
// This has ASM/BootStrap/Unsafe all because I haven't gotten around to moving UnionFileSystem out to its own project.
bootstrap(libs.jarjar.fs) // JarInJar file system
bootstrap(libs.roimfs) // JarInJar File System - FileSystems need to be on boot loader.
bootstrap(libs.bundles.jimfs) // In memory file system used for ForgeDev launches
bootstrap(libs.securemodules) // Has Union file system in it
bootstrap(libs.unsafe) // Needed by securemodules
bootstrap(libs.bundles.asm) // Needed by securemodules
implementation(libs.jopt.simple)
installer(libs.bootstrap)
installer(libs.bootstrap.api) // Needed by securemodules
installer(libs.accesstransformers)
installer(libs.eventbus)
installer(libs.jspecify) // Dep of EventBus
installer(libs.typetools) // Dep of EventBus
installer(libs.forgespi)
installer(libs.coremods)
installer(libs.nashorn) // Dep of Coremods
@@ -135,9 +133,7 @@ dependencies {
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'))
@@ -145,14 +141,11 @@ 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 {
@@ -172,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',
@@ -215,6 +208,7 @@ patcher {
'--all',
'--validate',
'--output', rootProject.file('src/main/generated/'),
'--validate',
'--existing', sourceSets.main.resources.srcDirs[0],
'--assetsDir', downloadAssets.output,
'--assetIndex', "{asset_index}"
@@ -232,30 +226,6 @@ patcher {
'--assetsDir', downloadAssets.output,
'--assetIndex', "{asset_index}"
}
forge_client_data {
args '--launchTarget', 'forge_dev_client_data',
'--mod', 'forge',
'--all',
'--output', rootProject.file('src/main/generated/'),
'--existing', sourceSets.main.resources.srcDirs[0],
'--assetsDir', downloadAssets.output,
'--assetIndex', "{asset_index}"
}
forge_client_data_test {
source sourceSets.test
args '--launchTarget', 'forge_dev',
'--launchEntry', 'minecraft/net.minecraft.client.data.Main',
'--launchData',
'--mod', '.+',
'--all',
'--output', rootProject.file('src/test/generated/'),
'--existing', sourceSets.main.resources.srcDirs[0],
'--existing', sourceSets.test.resources.srcDirs[0],
'--assetsDir', downloadAssets.output,
'--assetIndex', "{asset_index}"
}
}
}
@@ -297,16 +267,11 @@ tasks.register('downloadCrowdin', Download) {
tasks.userdevConfig.configure {
configurations.installer.allDependencies.forEach {
def dep = it.toString()
if (it instanceof ProjectDependency)
if (it instanceof ProjectDependency)
dep = "net.minecraftforge:$it.dependencyProject.name:$it.dependencyProject.version"
libraries.add(dep)
}
// TODO [ForgeDev][UserDev] Split API/Runtime elements in userdev config
// so we don't gotta keep making exceptions for MixinExtras
addCompileDependency(libs.mixinextras.common)
addAnnotationProcessorDependency(libs.mixinextras.common)
inject = '' // We don't have a userdev sourceset anymore. Empty as a gradle workaround...
runs {
client {
@@ -318,13 +283,6 @@ tasks.userdevConfig.configure {
args '--assetsDir', '{assets_root}'
}
clientData {
environment 'MCP_MAPPINGS', '{mcp_mappings}'
args '--launchTarget', "forge_userdev_client_data"
args '--assetIndex', '{asset_index}'
args '--assetsDir', '{assets_root}'
}
server {
environment 'MCP_MAPPINGS', '{mcp_mappings}'
property 'forge.enableGameTest', 'true'
@@ -422,14 +380,14 @@ tasks.register('extractInheritance', ExtractInheritance) {
}
}
tasks.register("findFinalizeSpawnTargets", BytecodePredicateFinder) {
jar = createClientOfficial.output
output = rootProject.file('forge-transformers/src/main/resources/coremods/finalize_spawn_targets.json')
jar = createJoinedSRG.output
output = rootProject.file('src/main/resources/coremods/finalize_spawn_targets.json')
predicate = {
parent, node, insn ->
return 'net/minecraft/world/level/BaseSpawner' != parent.name // Ignore this class as we special case it.
&& insn.getOpcode().equals(Opcodes.INVOKEVIRTUAL)
&& insn.name == 'finalizeSpawn'
&& insn.desc == '(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/EntitySpawnReason;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;';
&& insn.name == 'm_6518_'
&& insn.desc == '(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/MobSpawnType;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;';
}
}
tasks.register('validateDeprecations', ValidateDeprecations) {
@@ -534,7 +492,7 @@ tasks.register('serverShimJar', Jar) {
from {
zipTree(downloadServerShim.output).find { it.name == 'MANIFEST.MF' }
}
attributes('Class-Path': configurations.bootstrap.resolvedConfiguration.resolvedArtifacts.collect { "libraries/${Util.getMavenInfoFromDep(it).path}" }.join(' '))
}
archiveClassifier = 'shim'
@@ -627,7 +585,7 @@ tasks.register('checkJarCompatibility', CheckJarCompatibility) {
baseLibraries.from(project.tasks.named('createJoinedSRG').flatMap { it.output })
inputJar = project.tasks.named('reobfJar').flatMap { it.output }
commonLibraries.from(project.configurations.minecraftImplementation)
commonLibraries.from(project.configurations.installer)
}
@@ -648,11 +606,11 @@ tasks.register('launcherJson', LauncherJson).configure {
tasks.register('installerJson', InstallerJson) {
icon = rootProject.file('icon.ico')
// Json to install into the client's launcher
dependsOn(launcherJson)
input.from(launcherJson.output)
// Download Official Mappings
dependsOn(downloadClientMappings, downloadServerMappings)
input.from(downloadClientMappings.output, downloadServerMappings.output)
@@ -964,9 +922,6 @@ tasks.register('mdkZip', Zip) {
rename 'gitignore\\.txt', '.gitignore'
rename 'gitattributes\\.txt', '.gitattributes'
}
from(rootProject.file('src/test/java/com/example/examplemod/')) {
into('src/main/java/com/example/examplemod/')
}
from(rootProject.file('src/test/generated/mdk_datagen/')) {
into('src/main/resources/')
exclude '**/.cache/'
@@ -1006,7 +961,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 })
@@ -1100,7 +1055,7 @@ publishing {
license gradleutils.pom.Licenses.LGPLv2_1
}
}
repositories {
maven gradleutils.publishingForgeMaven
}
+1 -5
View File
@@ -38,17 +38,13 @@ tasks.register('generateResources') {
dependsOn('writeManifest')
}
tasks.named('processResources') {
dependsOn(generateResources)
}
// Make sure out manifests get written before compiling the code, IDEA calls this task if you tell it to use the gradle build.
tasks.withType(JavaCompile).configureEach {
dependsOn 'generateResources'
dependsOn 'processResources' // Needed because we merge the output of this with the output of the compile task. And gradle detects downstream tasks using the output without a hard dep
options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation
options.warnings = false // Shutup deprecated for removal warnings
options.forkOptions.jvmArgs += '-Xmx6G' // Needed to make compiling faster, and not run out of heap space in some cases.
options.forkOptions.jvmArgs += '-Xmx3G' // Needed to make compiling faster, and not run out of heap space in some cases.
}
// Merge the resources and classes into the same directory. We'll need to split them at runtime because
+2 -8
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.10-&query=$.promos["1.21.10-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.10-&query=$.promos["1.21.10-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.1-&query=$.promos["1.21.1-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.1-&query=$.promos["1.21.1-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!
@@ -70,11 +70,6 @@ Please ensure you have a valid email address associated with your GitHub account
to use and modify. However, it costs money to run such a large project as this, so please consider
[becoming a patron][Patreon].*
### Thanks
[![YourKit](https://www.yourkit.com/images/yklogo.png)](https://www.yourkit.com/)
YourKit for providing us accesss to their [YourKit Java Profiler] which helps identify bottlenecks and places for improvment.
[Contributing]: ./CONTRIBUTING.md
[CLA]: https://cla-assistant.io/MinecraftForge/MinecraftForge
@@ -98,4 +93,3 @@ YourKit for providing us accesss to their [YourKit Java Profiler] which helps id
[Pull-Requests]: https://docs.minecraftforge.net/en/latest/forgedev/#making-changes-and-pull-requests
[Patreon]: https://www.patreon.com/LexManos
[YourKit Java Profiler]: https://www.yourkit.com/java/profiler/
+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();
}
@@ -25,18 +25,7 @@ import java.util.function.Supplier;
* <li>When you want to call one thing on one side, another thing on the other and return a result {@link #safeRunForDist(Supplier, Supplier)}</li>
* <li>When you want to run something on one side {@link #safeRunWhenOn(Dist, Supplier)}</li>
* </ul>
*
* @deprecated Historically, this allowed for safe execution of code on the correct side by exploiting a low-level
* detail in Java 8, but in newer versions a simple if check is enough.
* <br>
* For example:
* <pre>{@code
* if (FMLEnvironment.dist.isClient()) {
* // client code here
* }
* }</pre>
*/
@Deprecated(since = "1.21.5", forRemoval = true)
public final class DistExecutor
{
private static final Logger LOGGER = LogManager.getLogger();
@@ -5,13 +5,13 @@
package net.minecraftforge.fml;
import net.minecraftforge.eventbus.api.bus.BusGroup;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.fml.config.IConfigEvent;
import java.util.function.Supplier;
public interface IBindingsProvider {
Supplier<BusGroup> getForgeBusSupplier();
Supplier<IEventBus> getForgeBusSupplier();
Supplier<I18NParser> getMessageParser();
Supplier<IConfigEvent.ConfigConfig> getConfigConfiguration();
}
@@ -5,6 +5,8 @@
package net.minecraftforge.fml;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import java.util.Optional;
@@ -59,27 +61,27 @@ public interface IModLoadingState {
/**
* Builds the transition task for this state with a blank pre-sync and post-sync task.
*
* @param <T> a type of event fired on the mod-specific event bus
* @param syncExecutor a synchronous executor
* @param parallelExecutor a parallel executor
* @param progressBar a progress meter for tracking progress
* @return a transition task for this state
* @see #buildTransition(Executor, Executor, ProgressMeter, Function, Function)
*/
default Optional<CompletableFuture<Void>> buildTransition(
final Executor syncExecutor,
final Executor parallelExecutor,
final ProgressMeter progressBar
) {
default <T extends Event & IModBusEvent>
Optional<CompletableFuture<Void>> buildTransition(final Executor syncExecutor,
final Executor parallelExecutor,
final ProgressMeter progressBar) {
return buildTransition(syncExecutor, parallelExecutor, progressBar,
e -> CompletableFuture.completedFuture(null),
e -> CompletableFuture.completedFuture(null)
);
e -> CompletableFuture.runAsync(() -> {}, e),
e -> CompletableFuture.runAsync(() -> {}, e));
}
/**
* Builds the transition task for this state. The pre-sync and post-sync task functions allow the transition builder
* to run these tasks on the same executor as the actual event dispatch and pre/post hooks.
*
* @param <T> a type of event fired on the mod-specific event bus
* @param syncExecutor a synchronous executor
* @param parallelExecutor a parallel executor
* @param progressBar a progress meter for tracking progress
@@ -87,6 +89,7 @@ public interface IModLoadingState {
* @param postSyncTask a function which returns a task to run after event post-dispatch hook
* @return a transition task for this state
*/
<T extends Event & IModBusEvent>
Optional<CompletableFuture<Void>> buildTransition(final Executor syncExecutor,
final Executor parallelExecutor,
final ProgressMeter progressBar,
@@ -5,9 +5,12 @@
package net.minecraftforge.fml;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.BiFunction;
@@ -15,67 +18,103 @@ import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable;
@SuppressWarnings("unchecked")
public interface IModStateTransition {
/** Magic value to allow me to optimize the futures list by ignoring the default value without making old methods nullable. */
BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> NULL_HOOK = (e, g) -> CompletableFuture.completedFuture(null);
static IModStateTransition buildNoopTransition() {
return ModStateTransitionHelper.NOOP;
return new NoopTransition();
}
default CompletableFuture<Void> build(
final String name,
final Executor syncExecutor,
final Executor parallelExecutor,
final ProgressMeter progressBar,
final Function<Executor, CompletableFuture<Void>> preSyncTask,
final Function<Executor, CompletableFuture<Void>> postSyncTask
) {
return ModStateTransitionHelper.build(this, name, syncExecutor, parallelExecutor, progressBar, preSyncTask, postSyncTask);
default <T extends Event & IModBusEvent>
CompletableFuture<Void> build(final String name,
final Executor syncExecutor,
final Executor parallelExecutor,
final ProgressMeter progressBar,
final Function<Executor, CompletableFuture<Void>> preSyncTask,
final Function<Executor, CompletableFuture<Void>> postSyncTask) {
List<CompletableFuture<Void>> futures = new ArrayList<>();
this.eventFunctionStream().get()
.map(f -> (EventGenerator<T>) f)
.reduce((head, tail) -> addCompletableFutureTaskForModDispatch(syncExecutor, parallelExecutor, futures, progressBar, head, ModLoadingStage::currentState, tail))
.ifPresent(last -> addCompletableFutureTaskForModDispatch(syncExecutor, parallelExecutor, futures, progressBar, last, nextModLoadingStage(), null));
final CompletableFuture<Void> preSyncTaskCF = preSyncTask.apply(syncExecutor);
final CompletableFuture<Void> eventDispatchCF = ModList.gather(futures).thenCompose(ModList::completableFutureFromExceptionList);
final CompletableFuture<Void> postEventDispatchCF = preSyncTaskCF
.thenApplyAsync(n -> {
progressBar.label(progressBar.name() + ": dispatching "+name);
return null;
}, parallelExecutor)
.thenComposeAsync(n -> eventDispatchCF, parallelExecutor)
.thenApply(r -> {
postSyncTask.apply(syncExecutor);
return null;
});
return this.finalActivityGenerator().apply(syncExecutor, postEventDispatchCF);
}
default BiFunction<ModLoadingStage, Throwable, ModLoadingStage> nextModLoadingStage() {
return ModLoadingStage::nextState;
}
/**
* This used to allow you to fire multiple events during the transition. However, in doing so it would cause issues with the default
* ModContainer's event handlers causing issues such as mod classes being constructed multiple times.
*/
@Deprecated(since = "1.21.3", forRemoval = true)
default Supplier<Stream<EventGenerator<?>>> eventFunctionStream() {
return () -> Stream.ofNullable(eventFunction());
}
@Nullable
default <T extends IModBusEvent> EventGenerator<T> eventFunction() {
return null;
private <T extends Event & IModBusEvent>
EventGenerator<T> addCompletableFutureTaskForModDispatch(final Executor syncExecutor,
final Executor parallelExecutor,
final List<CompletableFuture<Void>> completableFutures,
final ProgressMeter progressBar,
final EventGenerator<T> eventGenerator,
final BiFunction<ModLoadingStage, Throwable, ModLoadingStage> nextState,
final EventGenerator<T> nextGenerator) {
final Executor selectedExecutor = threadSelector().apply(syncExecutor, parallelExecutor);
var preDispatchHook = (BiFunction<Executor, EventGenerator<T>, CompletableFuture<Void>>) preDispatchHook();
completableFutures.add(preDispatchHook.apply(selectedExecutor, eventGenerator));
completableFutures.add(ModList.get().futureVisitor(eventGenerator, progressBar, nextState).apply(threadSelector().apply(syncExecutor, parallelExecutor)));
var postDispatchHook = (BiFunction<Executor, EventGenerator<T>, CompletableFuture<Void>>) postDispatchHook();
completableFutures.add(postDispatchHook.apply(selectedExecutor, eventGenerator));
return nextGenerator;
}
Supplier<Stream<EventGenerator<?>>> eventFunctionStream();
ThreadSelector threadSelector();
BiFunction<Executor, CompletableFuture<Void>, CompletableFuture<Void>> finalActivityGenerator();
BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> preDispatchHook();
BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> postDispatchHook();
/**
* I think this was meant as a way to do some things for each mod container beforge/after the transition had been sent to the container.
* However, the Future returned by this was never linked to the main transition future in any way. Which means that it was run
* in parallel and couldn't guarantee the state of the ModContainer.
* <p>
* Plus all existing code that I could find returned a completedFuture, so I don't think anyone ever used this.
* <p>
* If I were to add this back it would be a CompletableFuture wrap(ModContainer, CompletableFuture)
* <p>
* Added magic value NULL_HOOK to allow me to optimize the futures list by ignoring the default value without making this method nullable.
*/
@Deprecated(since = "1.21.3", forRemoval = true)
default BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> preDispatchHook() { return NULL_HOOK; }
@Deprecated(since = "1.21.3", forRemoval = true)
default BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> postDispatchHook() { return NULL_HOOK; }
interface EventGenerator<T extends IModBusEvent> extends Function<ModContainer, T> {
static <FN extends IModBusEvent> EventGenerator<FN> fromFunction(Function<ModContainer, FN> fn) {
interface EventGenerator<T extends Event & IModBusEvent> extends Function<ModContainer, T> {
static <FN extends Event & IModBusEvent> EventGenerator<FN> fromFunction(Function<ModContainer, FN> fn) {
return fn::apply;
}
}
}
record NoopTransition() implements IModStateTransition {
@Override
public Supplier<Stream<EventGenerator<?>>> eventFunctionStream() {
return Stream::of;
}
@Override
public ThreadSelector threadSelector() {
return ThreadSelector.SYNC;
}
@Override
public BiFunction<Executor, CompletableFuture<Void>, CompletableFuture<Void>> finalActivityGenerator() {
return (e, t) -> t.thenApplyAsync(Function.identity(), e);
}
@Override
public BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> preDispatchHook() {
return (t, f)-> CompletableFuture.completedFuture(null);
}
@Override
public BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> postDispatchHook() {
return (t, f)-> CompletableFuture.completedFuture(null);
}
}
@@ -5,19 +5,22 @@
package net.minecraftforge.fml;
import net.minecraftforge.eventbus.api.bus.BusGroup;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.config.IConfigEvent;
import net.minecraftforge.fml.config.ModConfig;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import net.minecraftforge.forgespi.language.IModInfo;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -30,8 +33,13 @@ import java.util.function.Supplier;
* a mechanism by which we can wrap actual mod code so that the loader and other
* facilities can treat mods at arms length.
* </p>
*
* @author cpw
*
*/
public abstract class ModContainer {
public abstract class ModContainer
{
protected final String modId;
protected final String namespace;
protected final IModInfo modInfo;
@@ -40,14 +48,11 @@ public abstract class ModContainer {
protected final Map<ModLoadingStage, Runnable> activityMap = new EnumMap<>(ModLoadingStage.class);
protected final Map<Class<? extends IExtensionPoint<?>>, Supplier<?>> extensionPoints = new IdentityHashMap<>();
protected final EnumMap<ModConfig.Type, ModConfig> configs = new EnumMap<>(ModConfig.Type.class);
final Set<ModContainer> dependencies = new HashSet<>();
/**
* If you want to handle the event, override {@link #dispatchConfigEvent(IConfigEvent)}
*/
@Deprecated(since = "1.21.3", forRemoval = true)
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
protected Optional<Consumer<IConfigEvent>> configHandler = Optional.empty();
public ModContainer(IModInfo info) {
public ModContainer(IModInfo info)
{
this.modId = info.getModId();
// TODO: Currently not reading namespace from configuration..
this.namespace = this.modId;
@@ -77,35 +82,58 @@ public abstract class ModContainer {
/**
* Errored container state, used for filtering. Does nothing.
*/
ModContainer() {
ModContainer()
{
this.modLoadingStage = ModLoadingStage.ERROR;
modId = "BROKEN";
namespace = "BROKEN";
modInfo = null;
}
/**
* @return the modid for this mod
*/
public final String getModId() {
public final String getModId()
{
return modId;
}
/**
* @return the resource prefix for the mod
*/
public final String getNamespace() {
public final String getNamespace()
{
return namespace;
}
/**
* @return The current loading stage for this mod
*/
public ModLoadingStage getCurrentState() {
public ModLoadingStage getCurrentState()
{
return modLoadingStage;
}
public IModInfo getModInfo() {
public static <T extends Event & IModBusEvent> CompletableFuture<Void> buildTransitionHandler(
final ModContainer target,
final IModStateTransition.EventGenerator<T> eventGenerator,
final ProgressMeter progressBar,
final BiFunction<ModLoadingStage, Throwable, ModLoadingStage> stateChangeHandler,
final Executor executor) {
return CompletableFuture
.runAsync(() -> {
ModLoadingContext.get().setActiveContainer(target);
target.activityMap.getOrDefault(target.modLoadingStage, ()->{}).run();
target.acceptEvent(eventGenerator.apply(target));
}, executor)
.whenComplete((mc, exception) -> {
target.modLoadingStage = stateChangeHandler.apply(target.modLoadingStage, exception);
progressBar.increment();
ModLoadingContext.get().setActiveContainer(null);
});
}
public IModInfo getModInfo()
{
return modInfo;
}
@@ -114,7 +142,8 @@ public abstract class ModContainer {
return Optional.ofNullable((T)extensionPoints.getOrDefault(point,()-> null).get());
}
public <T extends Record & IExtensionPoint<T>> void registerExtensionPoint(Class<? extends IExtensionPoint<T>> point, Supplier<T> extension) {
public <T extends Record & IExtensionPoint<T>> void registerExtensionPoint(Class<? extends IExtensionPoint<T>> point, Supplier<T> extension)
{
extensionPoints.put(point, extension);
}
@@ -158,17 +187,9 @@ public abstract class ModContainer {
*/
public abstract Object getMod();
/**
* @return the mod bus group for this specific mod, or null if there is none.
* Leave null if you use {@link BusGroup#DEFAULT} for your mod lifecycle events.
*/
public BusGroup getModBusGroup() {
return null;
}
/**
* Accept an arbitrary event for processing by the mod. Probably posted to an event bus in the lower level container.
* @param e Event to accept
*/
protected <T extends IModBusEvent> void acceptEvent(T e) {}
protected <T extends Event & IModBusEvent> void acceptEvent(T e) {}
}
@@ -5,6 +5,9 @@
package net.minecraftforge.fml;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import net.minecraftforge.forgespi.language.IModFileInfo;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.forgespi.language.ModFileScanData;
@@ -12,15 +15,27 @@ import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
import net.minecraftforge.forgespi.locating.IModFile;
import java.util.Collections;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.Executor;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinWorkerThread;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -30,25 +45,27 @@ import java.util.stream.Stream;
* Master list of all mods - game-side version. This is classloaded in the game scope and
* can dispatch game level events as a result.
*/
public class ModList {
public class ModList
{
//private static final Logger LOGGER = LogManager.getLogger();
private static ModList INSTANCE;
private final List<IModFileInfo> modFiles;
private final List<IModInfo> sortedList;
private final Map<String, IModFileInfo> fileById;
private final Map<String, ModFileInfo> fileById;
private List<ModContainer> mods;
private Map<String, ModContainer> indexedMods;
private List<ModFileScanData> modFileScanData;
private List<ModContainer> sortedContainers;
private ModList(final List<ModFile> modFiles, final List<ModInfo> sortedList) {
var modFileInfos = this.modFiles = modFiles.stream().map(ModFile::getModFileInfo).toList();
this.sortedList = sortedList.stream().map(IModInfo.class::cast).toList();
var byId = new HashMap<String, IModFileInfo>();
for (var file : modFileInfos) {
for (var mod : file.getMods())
byId.put(mod.getModId(), mod.getOwningFile());
}
this.fileById = Map.copyOf(byId);
private ModList(final List<ModFile> modFiles, final List<ModInfo> sortedList)
{
this.modFiles = modFiles.stream().map(ModFile::getModFileInfo).map(ModFileInfo.class::cast).collect(Collectors.toList());
this.sortedList = sortedList.stream().
map(ModInfo.class::cast).
collect(Collectors.toList());
this.fileById = this.modFiles.stream().map(IModFileInfo::getMods).flatMap(Collection::stream).
map(ModInfo.class::cast).
collect(Collectors.toMap(ModInfo::getModId, ModInfo::getOwningFile));
CrashReportCallables.registerCrashCallable("Mod List", this::crashReport);
}
@@ -65,12 +82,12 @@ public class ModList {
getModContainerState(mainMod.getModId()),
((ModFileInfo)mf.getModFileInfo()).getCodeSigningFingerprint().orElse("NOSIGNATURE"));
}
private String crashReport() {
return "\n" + applyForEachModFile(this::fileToLine).collect(Collectors.joining("\n\t\t", "\t\t", ""));
return "\n"+applyForEachModFile(this::fileToLine).collect(Collectors.joining("\n\t\t", "\t\t", ""));
}
public static ModList of(List<ModFile> modFiles, List<ModInfo> sortedList) {
public static ModList of(List<ModFile> modFiles, List<ModInfo> sortedList)
{
INSTANCE = new ModList(modFiles, sortedList);
return INSTANCE;
}
@@ -79,59 +96,117 @@ public class ModList {
return INSTANCE;
}
public List<IModFileInfo> getModFiles() {
public List<IModFileInfo> getModFiles()
{
return modFiles;
}
public IModFileInfo getModFileById(String modid) {
public IModFileInfo getModFileById(String modid)
{
return this.fileById.get(modid);
}
void setLoadedMods(final List<ModContainer> modContainers) {
<T extends Event & IModBusEvent> Function<Executor, CompletableFuture<Void>> futureVisitor(
final IModStateTransition.EventGenerator<T> eventGenerator,
final ProgressMeter progressBar,
final BiFunction<ModLoadingStage, Throwable, ModLoadingStage> stateChange) {
return executor -> gather(
this.mods.stream()
.map(mod -> ModContainer.buildTransitionHandler(mod, eventGenerator, progressBar, stateChange, executor))
.toList()
).thenComposeAsync(ModList::completableFutureFromExceptionList, executor);
}
static CompletionStage<Void> completableFutureFromExceptionList(List<? extends Map.Entry<?, Throwable>> t) {
if (t.stream().noneMatch(e->e.getValue()!=null)) {
return CompletableFuture.completedFuture(null);
} else {
final List<Throwable> throwables = t.stream().filter(e -> e.getValue() != null).map(Map.Entry::getValue).toList();
CompletableFuture<Void> cf = new CompletableFuture<>();
final RuntimeException accumulator = new RuntimeException();
cf.completeExceptionally(accumulator);
for (Throwable exception : throwables) {
if (exception instanceof CompletionException) {
exception = exception.getCause();
}
if (exception.getSuppressed().length != 0) {
for (Throwable throwable : exception.getSuppressed()) {
accumulator.addSuppressed(throwable);
}
} else {
accumulator.addSuppressed(exception);
}
}
return cf;
}
}
static <V> CompletableFuture<List<Map.Entry<V, Throwable>>> gather(List<? extends CompletableFuture<? extends V>> futures) {
List<Map.Entry<V, Throwable>> list = new ArrayList<>(futures.size());
CompletableFuture<?>[] results = new CompletableFuture[futures.size()];
for (var future : futures) {
int i = list.size();
list.add(null);
results[i] = future.whenComplete((result, exception) -> list.set(i, new AbstractMap.SimpleImmutableEntry<>(result, exception)));
}
return CompletableFuture.allOf(results).handle((r, th)->null).thenApply(res -> list);
}
void setLoadedMods(final List<ModContainer> modContainers)
{
this.mods = modContainers;
this.sortedContainers = modContainers.stream().sorted(Comparator.comparingInt(c -> sortedList.indexOf(c.getModInfo()))).toList();
this.sortedContainers = modContainers.stream().sorted(Comparator.comparingInt(c->sortedList.indexOf(c.getModInfo()))).toList();
this.indexedMods = modContainers.stream().collect(Collectors.toMap(ModContainer::getModId, Function.identity()));
}
@SuppressWarnings("unchecked")
public <T> Optional<T> getModObjectById(String modId) {
public <T> Optional<T> getModObjectById(String modId)
{
return getModContainerById(modId).map(ModContainer::getMod).map(o -> (T) o);
}
public Optional<? extends ModContainer> getModContainerById(String modId) {
public Optional<? extends ModContainer> getModContainerById(String modId)
{
return Optional.ofNullable(this.indexedMods.get(modId));
}
public Optional<? extends ModContainer> getModContainerByObject(Object obj) {
public Optional<? extends ModContainer> getModContainerByObject(Object obj)
{
return mods.stream().filter(mc -> mc.getMod() == obj).findFirst();
}
public List<IModInfo> getMods() {
public List<IModInfo> getMods()
{
return this.sortedList;
}
public boolean isLoaded(String modTarget) {
public boolean isLoaded(String modTarget)
{
return this.indexedMods.containsKey(modTarget);
}
public int size() {
public int size()
{
return mods.size();
}
public List<ModFileScanData> getAllScanData() {
if (modFileScanData == null) {
public List<ModFileScanData> getAllScanData()
{
if (modFileScanData == null)
{
modFileScanData = this.sortedList.stream().
map(IModInfo::getOwningFile).
filter(Objects::nonNull).
map(IModFileInfo::getFile).
distinct().
map(IModFile::getScanResult).
toList();
collect(Collectors.toList());
}
return modFileScanData;
}
public void forEachModFile(Consumer<IModFile> fileConsumer) {
public void forEachModFile(Consumer<IModFile> fileConsumer)
{
modFiles.stream().map(IModFileInfo::getFile).forEach(fileConsumer);
}
@@ -147,11 +222,13 @@ public class ModList {
this.sortedContainers.forEach(containerConsumer);
}
public List<ModContainer> getLoadedMods() {
return this.sortedContainers;
}
public <T> Stream<T> applyForEachModContainer(Function<ModContainer, T> function) {
return indexedMods.values().stream().map(function);
}
private static class UncaughtModLoadingException extends ModLoadingException {
public UncaughtModLoadingException(ModLoadingStage stage, Throwable originalException) {
super(null, stage, "fml.modloading.uncaughterror", originalException);
}
}
}
@@ -6,7 +6,8 @@
package net.minecraftforge.fml;
import com.google.common.collect.ImmutableList;
import net.minecraftforge.eventbus.api.bus.BusGroup;
import com.google.common.collect.Streams;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.loading.FMLEnvironment;
import net.minecraftforge.fml.loading.FMLLoader;
@@ -32,6 +33,8 @@ import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static net.minecraftforge.fml.Logging.CORE;
import static net.minecraftforge.fml.Logging.LOADING;
@@ -71,7 +74,8 @@ import static net.minecraftforge.fml.Logging.LOADING;
* and completes the mod loading sequence.</dd>
* </dl>
*/
public class ModLoader {
public class ModLoader
{
private static final Logger LOGGER = LogManager.getLogger();
private final LoadingModList loadingModList;
@@ -80,25 +84,27 @@ public class ModLoader {
private final List<ModLoadingWarning> loadingWarnings;
private final ModStateManager stateManager;
private static boolean loadingStateValid;
private final Consumer<String> statusConsumer = StartupNotificationManager.modLoaderConsumer().orElse(msg -> {});
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private final Optional<Consumer<String>> statusConsumer = StartupNotificationManager.modLoaderConsumer();
private final Set<IModLoadingState> completedStates = new HashSet<>();
private ModList modList;
private ModLoader() {
var loadingModList = this.loadingModList = FMLLoader.getLoadingModList();
var loadingExceptions = this.loadingExceptions = loadingModList.getErrors().stream()
private ModLoader()
{
this.loadingModList = FMLLoader.getLoadingModList();
this.loadingExceptions = this.loadingModList.getErrors().stream()
.flatMap(ModLoadingException::fromEarlyException)
.collect(Collectors.toList());
this.loadingWarnings = loadingModList.getBrokenFiles().stream()
this.loadingWarnings = this.loadingModList.getBrokenFiles().stream()
.map(file -> new ModLoadingWarning(null, ModLoadingStage.VALIDATE, InvalidModIdentifier.identifyJarProblem(file.getFilePath()).orElse("fml.modloading.brokenfile"), file.getFileName()))
.collect(Collectors.toList());
if (loadingExceptions.isEmpty()) {
if (this.loadingExceptions.isEmpty()) {
this.erroredModInfos = Collections.emptySet();
} else {
this.erroredModInfos = Collections.newSetFromMap(new IdentityHashMap<>());
this.erroredModInfos.addAll(loadingExceptions.stream().map(ModLoadingException::getModInfo).toList());
this.erroredModInfos.addAll(this.loadingExceptions.stream().map(ModLoadingException::getModInfo).toList());
}
loadingModList.getModFiles().stream()
this.loadingModList.getModFiles().stream()
.filter(ModFileInfo::missingLicense)
.filter(modFileInfo -> modFileInfo.getMods().stream().noneMatch(this.erroredModInfos::contains)) //Ignore files where any other mod already encountered an error
.map(modFileInfo -> new ModLoadingException(null, ModLoadingStage.VALIDATE, "fml.modloading.missinglicense", null, modFileInfo.getFile()))
@@ -113,16 +119,16 @@ public class ModLoader {
private static String computeLanguageList() {
return "\n" + FMLLoader.getLanguageLoadingProvider()
.applyForEach(lp -> lp.name() + '@' + lp.getClass().getPackage().getImplementationVersion())
.applyForEach(lp -> lp.name() + "@" + lp.getClass().getPackage().getImplementationVersion())
.collect(Collectors.joining("\n\t\t", "\t\t", ""));
}
private static String computeModLauncherServiceList() {
final List<Map<String, String>> mods = FMLLoader.modLauncherModList();
return "\n" + mods.stream().map(mod -> mod.getOrDefault("file","nofile") +
' ' + mod.getOrDefault("name", "missing") +
' ' + mod.getOrDefault("type","NOTYPE") +
' ' + mod.getOrDefault("description", "")).
return "\n"+mods.stream().map(mod->mod.getOrDefault("file","nofile")+
" "+mod.getOrDefault("name", "missing")+
" "+mod.getOrDefault("type","NOTYPE")+
" "+mod.getOrDefault("description", "")).
collect(Collectors.joining("\n\t\t","\t\t",""));
}
@@ -150,22 +156,20 @@ public class ModLoader {
loadingModList.getMods());
if (!this.loadingExceptions.isEmpty()) {
LOGGER.fatal(CORE, "Error during pre-loading phase", loadingExceptions.getFirst());
statusConsumer.accept("ERROR DURING MOD LOADING");
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
modList.setLoadedMods(Collections.emptyList());
loadingStateValid = false;
throw new LoadingFailedException(loadingExceptions);
}
var failedBounds = new ArrayList<ForgeFeature.Bound>();
for (var mod : loadingModList.getMods()) {
for (var feature : mod.getForgeFeatures()) {
if (!ForgeFeature.testFeature(FMLEnvironment.dist, feature))
failedBounds.add(feature);
}
}
List<? extends ForgeFeature.Bound> failedBounds = loadingModList.getMods().stream()
.map(ModInfo::getForgeFeatures)
.flatMap(List::stream)
.filter(bound -> !ForgeFeature.testFeature(FMLEnvironment.dist, bound))
.toList();
if (!failedBounds.isEmpty()) {
LOGGER.fatal(CORE, "Failed to validate feature bounds for mods: {}", failedBounds);
statusConsumer.accept("ERROR DURING MOD LOADING");
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
modList.setLoadedMods(Collections.emptyList());
loadingStateValid = false;
throw new LoadingFailedException(failedBounds.stream()
@@ -173,42 +177,19 @@ public class ModLoader {
.toList());
}
final var modContainers = new HashMap<String, ModContainer>();
for (var file : loadingModList.getModFiles()) {
var containers = this.buildMods(file.getFile());
for (var container : containers)
modContainers.put(container.getModId(), container);
}
final List<ModContainer> modContainers = loadingModList.getModFiles().stream()
.map(ModFileInfo::getFile)
.map(this::buildMods)
.flatMap(List::stream)
.toList();
if (!loadingExceptions.isEmpty()) {
LOGGER.fatal(CORE, "Failed to initialize mod containers", loadingExceptions.getFirst());
statusConsumer.accept("ERROR DURING MOD LOADING");
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
modList.setLoadedMods(Collections.emptyList());
loadingStateValid = false;
throw new LoadingFailedException(loadingExceptions);
}
// Gather all dependencies, so we can make sure they have finished their transition events before we fire ours
for (var mod : modContainers.values()) {
for (var dep : mod.getModInfo().getDependencies()) {
var target = modContainers.get(dep.getModId());
if (target == null)
continue;
switch (dep.getOrdering()) {
case AFTER:
mod.dependencies.add(target);
break;
case BEFORE:
target.dependencies.add(mod);
break;
default:
break;
}
}
}
modList.setLoadedMods(modContainers.values().stream().toList());
modList.setLoadedMods(modContainers);
this.modList = modList;
var stateList = stateManager.getStates(ModLoadingPhase.GATHER);
var progress = StartupMessageManager.addProgressBar("Mod Gather", stateList.stream().mapToInt(mls -> mls.size().applyAsInt(this.modList)).sum());
@@ -233,19 +214,7 @@ public class ModLoader {
for (IModLoadingState mls : stateList) {
dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress);
}
var modBusGroups = Set.of(
modList.getLoadedMods().stream()
.map(ModContainer::getModBusGroup)
.filter(Objects::nonNull)
.toArray(BusGroup[]::new)
);
for (var modBusGroup : modBusGroups) {
parallelExecutor.execute(modBusGroup::trim);
}
parallelExecutor.execute(BusGroup.DEFAULT::trim);
statusConsumer.accept("Mod loading complete - %d mods loaded".formatted(this.modList.size()));
statusConsumer.ifPresent(c->c.accept(String.format("Mod loading complete - %d mods loaded", this.modList.size())));
progress.complete();
}
@@ -267,19 +236,17 @@ public class ModLoader {
}
private void handleInlineTransition(final Consumer<ModList> transition, final IModLoadingState state, final ModWorkManager.DrivenExecutor syncExecutor, final Runnable ticker) {
var pb = StartupMessageManager.addProgressBar("State transition " + state.name() + " running", 0);
var pb = StartupMessageManager.addProgressBar("State transition " +state.name()+" running", 0);
syncExecutor.drive(ticker);
transition.accept(this.modList);
syncExecutor.drive(ticker);
pb.complete();
syncExecutor.drive(ticker);
}
private void waitForTransition(final IModLoadingState state, final ModWorkManager.DrivenExecutor syncExecutor, final Runnable ticker, final CompletableFuture<Void> transition) {
while (!transition.isDone()) {
syncExecutor.drive(ticker);
}
try {
transition.join();
} catch (CompletionException e) {
@@ -289,38 +256,36 @@ public class ModLoader {
.anyMatch(obj -> !(obj instanceof ModLoadingException));
if (hasNotModLoadingEx) {
LOGGER.fatal("Encountered non-modloading exceptions!", e);
statusConsumer.accept("ERROR DURING MOD LOADING");
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
throw e;
}
final List<ModLoadingException> modLoadingExceptions = Arrays.stream(t.getSuppressed())
.filter(ModLoadingException.class::isInstance)
.map(ModLoadingException.class::cast)
.toList();
.collect(Collectors.toList());
LOGGER.fatal(LOADING,"Failed to complete lifecycle event {}, {} errors found", state.name(), modLoadingExceptions.size());
statusConsumer.accept("ERROR DURING MOD LOADING");
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
throw new LoadingFailedException(modLoadingExceptions);
}
}
private List<ModContainer> buildMods(final IModFile modFile) {
final Map<String, IModInfo> modInfoMap = new HashMap<>();
for (var mod : modFile.getModFileInfo().getMods())
modInfoMap.put(mod.getModId(), mod);
private List<ModContainer> buildMods(final IModFile modFile)
{
final Map<String, IModInfo> modInfoMap = modFile.getModFileInfo().getMods().stream().collect(Collectors.toMap(IModInfo::getModId, Function.identity()));
LOGGER.trace(LOADING, "ModContainer is {}", ModContainer.class.getClassLoader());
final List<ModContainer> containers = new ArrayList<>();
for (var entry : modFile.getScanResult().getTargets().entrySet()) {
var container = buildModContainerFromTOML(modFile, modInfoMap, entry.getKey(), entry.getValue());
if (container != null)
containers.add(container);
}
final List<ModContainer> containers = modFile.getScanResult().getTargets()
.entrySet()
.stream()
.map(e -> buildModContainerFromTOML(modFile, modInfoMap, e))
.filter(Objects::nonNull)
.toList();
if (containers.size() != modInfoMap.size()) {
var modIds = modInfoMap.values().stream().map(IModInfo::getModId).sorted().toList();
var containerIds = containers.stream().map(c -> c != null ? c.getModId() : "(null)").sorted().toList();
LOGGER.fatal(LOADING, "File {} constructed {} mods: {}, but had {} mods specified: {}",
LOGGER.fatal(LOADING,"File {} constructed {} mods: {}, but had {} mods specified: {}",
modFile.getFilePath(),
containers.size(), containerIds,
modInfoMap.size(), modIds);
@@ -335,15 +300,15 @@ public class ModLoader {
loadingExceptions.add(new ModLoadingException(null, ModLoadingStage.CONSTRUCT, "fml.modloading.missingclasses", null, modFile.getFilePath()));
}
// remove errored mod containers
return containers.stream().filter(mc -> mc.modLoadingStage != ModLoadingStage.ERROR).toList();
}
private ModContainer buildModContainerFromTOML(final IModFile modFile, final Map<String, IModInfo> modInfoMap,
final String modId, final IModLanguageProvider.IModLanguageLoader languageLoader
) {
private ModContainer buildModContainerFromTOML(final IModFile modFile, final Map<String, IModInfo> modInfoMap, final Map.Entry<String, ? extends IModLanguageProvider.IModLanguageLoader> idToProviderEntry) {
try {
final String modId = idToProviderEntry.getKey();
final IModLanguageProvider.IModLanguageLoader languageLoader = idToProviderEntry.getValue();
IModInfo info = modInfoMap.get(modId);
// throw a missing metadata error if there is no matching modid in the modInfoMap from the mods.toml file
if (info == null)
@@ -366,70 +331,56 @@ public class ModLoader {
return loadingStateValid;
}
/** @deprecated Use {@link #hasCompletedState(IModLoadingState)} instead */
@Deprecated(forRemoval = true)
public boolean hasCompletedState(final String stateName) {
IModLoadingState state = stateManager.findState(stateName);
return completedStates.contains(state);
}
public boolean hasCompletedState(IModLoadingState state) {
return completedStates.contains(state);
}
public static <T extends IModBusEvent> void runEventGenerator(Function<ModContainer, T> generator) {
public <T extends Event & IModBusEvent> void runEventGenerator(Function<ModContainer, T> generator) {
if (!loadingStateValid) {
LOGGER.error("Cowardly refusing to send event generator to a broken mod state");
return;
}
for (var mod : ModList.get().getLoadedMods())
mod.acceptEvent(generator.apply(mod));
ModList.get().forEachModInOrder(mc -> mc.acceptEvent(generator.apply(mc)));
}
public static <T extends IModBusEvent> void postEvent(T e) {
public <T extends Event & IModBusEvent> void postEvent(T e) {
if (!loadingStateValid) {
LOGGER.error("Cowardly refusing to send event {} to a broken mod state", e.getClass().getName());
return;
}
for (var mod : ModList.get().getLoadedMods())
mod.acceptEvent(e);
ModList.get().forEachModInOrder(mc -> mc.acceptEvent(e));
}
public static <T extends IModBusEvent> T postEventWithReturn(T e) {
public <T extends Event & IModBusEvent> T postEventWithReturn(T e) {
if (!loadingStateValid) {
LOGGER.error("Cowardly refusing to send event {} to a broken mod state", e.getClass().getName());
return e;
}
for (var mod : ModList.get().getLoadedMods())
mod.acceptEvent(e);
ModList.get().forEachModInOrder(mc -> mc.acceptEvent(e));
return e;
}
@SuppressWarnings("removal")
public static <T extends IModBusEvent> void postEventWrapContainerInModOrder(T event) {
postEventWithWrapInModOrder(event,
(mc, e) -> ModLoadingContext.get().setActiveContainer(mc),
(mc, e) -> ModLoadingContext.get().setActiveContainer(null)
);
public <T extends Event & IModBusEvent> void postEventWrapContainerInModOrder(T event) {
postEventWithWrapInModOrder(event, (mc, e) -> ModLoadingContext.get().setActiveContainer(mc), (mc, e) -> ModLoadingContext.get().setActiveContainer(null));
}
public static <T extends IModBusEvent> void postEventWithWrapInModOrder(T e, BiConsumer<ModContainer, T> pre, BiConsumer<ModContainer, T> post) {
public <T extends Event & IModBusEvent> void postEventWithWrapInModOrder(T e, BiConsumer<ModContainer, T> pre, BiConsumer<ModContainer, T> post) {
if (!loadingStateValid) {
LOGGER.error("Cowardly refusing to send event {} to a broken mod state", e.getClass().getName());
return;
}
for (var mod : ModList.get().getLoadedMods()) {
pre.accept(mod, e);
mod.acceptEvent(e);
post.accept(mod, e);
}
ModList.get().forEachModInOrder(mc -> {
pre.accept(mc, e);
mc.acceptEvent(e);
post.accept(mc, e);
});
}
public List<ModLoadingWarning> getWarnings() {
public List<ModLoadingWarning> getWarnings()
{
return ImmutableList.copyOf(this.loadingWarnings);
}
public void addWarning(ModLoadingWarning warning) {
public void addWarning(ModLoadingWarning warning)
{
this.loadingWarnings.add(warning);
}
@@ -439,7 +390,7 @@ public class ModLoader {
return runningDataGen;
}
private static final class ErroredModContainer extends ModContainer {
private static class ErroredModContainer extends ModContainer {
public ErroredModContainer() {
super();
}
@@ -8,24 +8,23 @@ package net.minecraftforge.fml;
import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.config.IConfigSpec;
import net.minecraftforge.fml.config.ModConfig;
import net.minecraftforge.fml.loading.FMLLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.function.BiPredicate;
import java.util.function.Supplier;
public class ModLoadingContext {
public class ModLoadingContext
{
private static final Logger LOGGER = LogUtils.getLogger();
private static final ThreadLocal<ModLoadingContext> context = ThreadLocal.withInitial(ModLoadingContext::new);
private ModContainer activeContainer;
private Object languageExtension;
private ModLoadingStage stage;
/**
* @deprecated Use the context provided by your language loader in your mod's constructor. Example for Java mods: {@snippet :
* @Mod("examplemod")
* public final class ExampleMod {
* public ExampleMod(FMLJavaModLoadingContext context) {
* // Use context from the param instead of FMLJavaModLoadingContext.get() or ModLoadingContext.get()
* }
* }}
* @deprecated Use the context provided by your language loader in your mod's constructor
*/
@Deprecated(forRemoval = true, since="1.21.1")
public static ModLoadingContext get() {
@@ -5,9 +5,10 @@
package net.minecraftforge.fml;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
@@ -25,23 +26,19 @@ import java.util.function.ToIntFunction;
* @param inlineRunnable an optional runnable, which runs before starting the transition from this state to the next
* @param transition optional state transition information
*/
public record ModLoadingState(
String name,
String previous,
Function<ModList, String> message,
ToIntFunction<ModList> size,
ModLoadingPhase phase,
Optional<Consumer<ModList>> inlineRunnable,
Optional<IModStateTransition> transition
) implements IModLoadingState {
public record ModLoadingState(String name, String previous,
Function<ModList, String> message,
ToIntFunction<ModList> size,
ModLoadingPhase phase,
Optional<Consumer<ModList>> inlineRunnable,
Optional<IModStateTransition> transition) implements IModLoadingState {
@Override
public Optional<CompletableFuture<Void>> buildTransition(
final Executor syncExecutor,
final Executor parallelExecutor,
final ProgressMeter progressBar,
final Function<Executor, CompletableFuture<Void>> preSyncTask,
final Function<Executor, CompletableFuture<Void>> postSyncTask
) {
public <T extends Event & IModBusEvent>
Optional<CompletableFuture<Void>> buildTransition(final Executor syncExecutor,
final Executor parallelExecutor,
final ProgressMeter progressBar,
final Function<Executor, CompletableFuture<Void>> preSyncTask,
final Function<Executor, CompletableFuture<Void>> postSyncTask) {
var transition = this.transition.orElse(null);
return transition == null
? Optional.empty()
@@ -55,12 +52,9 @@ public record ModLoadingState(
* @param name the name of the state
* @param previous the name of the immediately previous state to this state
* @param phase the mod loading phase the state belongs to
*
* @deprecated Use the builder
*/
@Deprecated(since = "1.21.3", forRemoval = true)
public static ModLoadingState empty(final String name, final String previous, final ModLoadingPhase phase) {
return new ModLoadingState(name, previous, ml -> "", ml -> 0, phase, Optional.empty(), Optional.empty());
return new ModLoadingState(name, previous, ml -> "", f->0, phase, Optional.empty(), Optional.empty());
}
/**
@@ -71,13 +65,11 @@ public record ModLoadingState(
* @param previous the name of the immediately previous state to this state
* @param phase the mod loading phase the state belongs to
* @param transition the state transition information
*
* @deprecated Use the builder
* @return a mod loading state with state transition information and a default message
*/
@Deprecated(since = "1.21.3", forRemoval = true)
public static ModLoadingState withTransition(final String name, final String previous, final ModLoadingPhase phase,
final IModStateTransition transition) {
return of(name, phase).after(previous).withTransition(transition);
return new ModLoadingState(name, previous, ml -> "Processing transition " + name, ModList::size, phase, Optional.empty(), Optional.of(transition));
}
/**
@@ -89,14 +81,11 @@ public record ModLoadingState(
* @param phase the mod loading phase the state belongs to
* @param transition the state transition information
* @return a mod loading state with state transition information and a custom message
*
* @deprecated Use the builder
*/
@Deprecated(since = "1.21.3", forRemoval = true)
public static ModLoadingState withTransition(final String name, final String previous,
final Function<ModList, String> message, final ModLoadingPhase phase,
final IModStateTransition transition) {
return of(name, phase).after(previous).message(message).withTransition(transition);
return new ModLoadingState(name, previous, message, ModList::size, phase, Optional.empty(), Optional.of(transition));
}
/**
@@ -108,75 +97,9 @@ public record ModLoadingState(
* @param phase the mod loading phase the state belongs to
* @param inline an optional runnable, which runs before starting the transition from this state to the next
* @return a mod loading state with an inline runnable and default message
*
* @deprecated Use the builder
*/
@Deprecated(since = "1.21.3", forRemoval = true)
public static ModLoadingState withInline(final String name, final String previous, final ModLoadingPhase phase,
final Consumer<ModList> inline) {
return of(name, phase).after(previous).withInline(inline);
}
public static Builder of(final String name, final ModLoadingPhase phase) {
return new Builder(name, phase);
}
@Override
public boolean equals(Object o) {
return o instanceof ModLoadingState that
&& this.phase == that.phase
&& this.name.equals(that.name)
&& Objects.equals(this.previous, that.previous);
}
@Override
public int hashCode() {
int result = phase.hashCode();
result = 31 * result + name.hashCode();
result = 31 * result + Objects.hashCode(previous);
return result;
}
public static class Builder {
private final String name;
private final ModLoadingPhase phase;
private String after;
private Function<ModList, String> message = null;
private ToIntFunction<ModList> size = null;
private Builder(final String name, final ModLoadingPhase phase) {
this.name = name;
this.phase = phase;
}
public Builder after(final ModLoadingState value) { return after(value.name()); }
public Builder after(final String value) {
this.after = value;
return this;
}
public Builder message(final String value) { return message(ml -> value); }
public Builder message(final Function<ModList, String> value) {
this.message = value;
return this;
}
public Builder size(final int value) { return size(ml -> value); }
public Builder size(final ToIntFunction<ModList> value) {
this.size = value;
return this;
}
public ModLoadingState empty() {
return new ModLoadingState(name, after, message != null ? message : ml -> "", size != null ? size : ml -> 0, phase, Optional.empty(), Optional.empty());
}
public ModLoadingState withTransition(final IModStateTransition transition) {
return new ModLoadingState(name, after, message != null ? message : ml -> "Processing transition " + name, size != null ? size : ModList::size, phase, Optional.empty(), Optional.of(transition));
}
public ModLoadingState withInline(final Consumer<ModList> inline) {
return new ModLoadingState(name, after, message != null ? message : ml -> "Processing work " + name, size != null ? size : ml -> 0, phase, Optional.of(inline), Optional.empty());
}
return new ModLoadingState(name, previous, ml -> "Processing work " + name, ml->0, phase, Optional.of(inline), Optional.empty());
}
}
@@ -41,13 +41,11 @@ public class ModStateManager {
}
public IModLoadingState findState(final String stateName) {
var result = stateMap.values()
return stateMap.values()
.stream()
.flatMap(List::stream)
.filter(mls -> mls.name().equals(stateName))
.findFirst()
.orElse(null);
if (result != null) return result;
else throw new IllegalArgumentException("Unknown IModLoadingState: " + stateName);
.orElseThrow(() -> new IllegalArgumentException("Unknown IModLoadingState: " + stateName));
}
}
@@ -1,184 +0,0 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.Executor;
import java.util.function.BiFunction;
import java.util.function.Function;
import org.jetbrains.annotations.ApiStatus;
import net.minecraftforge.fml.IModStateTransition.EventGenerator;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
@ApiStatus.Internal
class ModStateTransitionHelper {
static final IModStateTransition NOOP = new NoopTransition();
record NoopTransition() implements IModStateTransition {
@Override
public ThreadSelector threadSelector() {
return ThreadSelector.SYNC;
}
@Override
public BiFunction<Executor, CompletableFuture<Void>, CompletableFuture<Void>> finalActivityGenerator() {
return (e, t) -> t.thenApplyAsync(Function.identity(), e);
}
}
static <V> CompletionStage<Void> completableFutureFromExceptionList(List<FutureResult<V>> t) {
if (t.stream().noneMatch(e -> e.exception() != null)) {
return CompletableFuture.completedFuture(null);
} else {
final var throwables = t.stream().map(FutureResult::exception).filter(Objects::nonNull).toList();
CompletableFuture<Void> cf = new CompletableFuture<>();
final RuntimeException accumulator = new RuntimeException();
cf.completeExceptionally(accumulator);
for (Throwable exception : throwables) {
if (exception instanceof CompletionException) {
exception = exception.getCause();
}
if (exception.getSuppressed().length != 0) {
for (Throwable throwable : exception.getSuppressed()) {
accumulator.addSuppressed(throwable);
}
} else {
accumulator.addSuppressed(exception);
}
}
return cf;
}
}
record FutureResult<V>(V value, Throwable exception){}
static <V> CompletableFuture<List<FutureResult<V>>> gather(Collection<? extends CompletableFuture<? extends V>> futures) {
var list = new ArrayList<FutureResult<V>>(futures.size());
var results = new CompletableFuture[futures.size()];
for (var future : futures) {
int i = list.size();
list.add(null);
@SuppressWarnings("unchecked") // Bypass a weird javac error with generic by forcing it to be the exact type we want.
var raw = ((CompletableFuture<V>)future);
results[i] = raw.whenComplete((result, exception) -> list.set(i, new FutureResult<>(result, exception)));
}
return CompletableFuture.allOf(results).handle((r, th)->null).thenApply(res -> list);
}
private static <T extends IModBusEvent> void addCompletableFutureTaskForModDispatch(
final IModStateTransition transition,
final Executor executor,
final List<CompletableFuture<Void>> completableFutures,
final ProgressMeter progressBar,
final EventGenerator<T> eventGenerator,
final BiFunction<ModLoadingStage, Throwable, ModLoadingStage> nextState
) {
@SuppressWarnings("removal")
var preDispatchHook = getHook(transition.preDispatchHook(), executor, eventGenerator);
if (preDispatchHook != null)
completableFutures.add(preDispatchHook);
var modFutures = new LinkedHashMap<String, CompletableFuture<Void>>();
for (var mod : ModList.get().getLoadedMods()) {
CompletableFuture<Void> parent = null;
if (mod.dependencies.isEmpty()) {
parent = CompletableFuture.allOf();
} else {
var deps = new CompletableFuture[mod.dependencies.size()];
int idx = 0;
for (var depContainer : mod.dependencies) {
var future = modFutures.get(depContainer.getModId());
if (future == null)
throw new IllegalStateException("Could not find dependency future " + depContainer.getModId() + " for " + mod.getModId());
deps[idx++] = future;
}
parent = CompletableFuture.allOf(deps);
}
@SuppressWarnings("removal")
var dispatch = parent
.thenRunAsync(() -> {
ModLoadingContext.get().setActiveContainer(mod);
var handler = mod.activityMap.get(mod.modLoadingStage);
if (handler != null)
handler.run();
mod.acceptEvent(eventGenerator.apply(mod));
}, executor)
.whenComplete((mc, exception) -> {
mod.modLoadingStage = nextState.apply(mod.modLoadingStage, exception);
progressBar.increment();
ModLoadingContext.get().setActiveContainer(null);
});
modFutures.put(mod.getModId(), dispatch);
}
var dispatch = gather(modFutures.values()).thenComposeAsync(ModStateTransitionHelper::completableFutureFromExceptionList, executor);
completableFutures.add(dispatch);
@SuppressWarnings("removal")
var postDispatchHook = getHook(transition.preDispatchHook(), executor, eventGenerator);
if (postDispatchHook != null)
completableFutures.add(postDispatchHook);
}
private static <T extends IModBusEvent> CompletableFuture<Void> getHook(BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> hook, Executor executor, EventGenerator<T> eventGenerator) {
if (hook == null || hook == IModStateTransition.NULL_HOOK) return null;
@SuppressWarnings("unchecked")
var hookTyped = (BiFunction<Executor, EventGenerator<T>, CompletableFuture<Void>>)hook;
return hookTyped.apply(executor, eventGenerator);
}
static <T extends IModBusEvent> CompletableFuture<Void> build(
final IModStateTransition transition,
final String name,
final Executor syncExecutor,
final Executor parallelExecutor,
final ProgressMeter progressBar,
final Function<Executor, CompletableFuture<Void>> preSyncTask,
final Function<Executor, CompletableFuture<Void>> postSyncTask
) {
List<CompletableFuture<Void>> futures = new ArrayList<>();
final var executor = transition.threadSelector().apply(syncExecutor, parallelExecutor);
@SuppressWarnings({ "removal", "unchecked" })
var events = transition.eventFunctionStream().get().map(f -> (EventGenerator<T>)f).toList();
for (int x = 0; x < events.size(); x++) {
var gen = events.get(x);
BiFunction<ModLoadingStage, Throwable, ModLoadingStage> state = x == events.size() - 1
? transition.nextModLoadingStage()
: ModLoadingStage::currentState;
addCompletableFutureTaskForModDispatch(transition, executor, futures, progressBar, gen, state);
}
final CompletableFuture<Void> preSyncTaskCF = preSyncTask.apply(syncExecutor);
final CompletableFuture<Void> eventDispatchCF = gather(futures).thenCompose(ModStateTransitionHelper::completableFutureFromExceptionList);
final CompletableFuture<Void> postEventDispatchCF = preSyncTaskCF
.thenApplyAsync(n -> {
progressBar.label(progressBar.name() + ": dispatching " + name);
return null;
}, parallelExecutor)
.thenComposeAsync(n -> eventDispatchCF, parallelExecutor)
.thenApply(r -> {
postSyncTask.apply(syncExecutor);
return null;
});
return transition.finalActivityGenerator().apply(syncExecutor, postEventDispatchCF);
}
}
@@ -5,7 +5,7 @@
package net.minecraftforge.fml.config;
import net.minecraftforge.eventbus.api.event.InheritableEvent;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.Bindings;
import java.util.function.Function;
@@ -29,7 +29,7 @@ public interface IConfigEvent {
ModConfig getConfig();
@SuppressWarnings("unchecked")
default <T extends InheritableEvent & IConfigEvent> T self() {
default <T extends Event & IConfigEvent> T self() {
return (T) this;
}
}
@@ -5,33 +5,8 @@
package net.minecraftforge.fml.event;
import net.minecraftforge.eventbus.api.bus.BusGroup;
import net.minecraftforge.eventbus.api.bus.EventBus;
import net.minecraftforge.eventbus.api.event.InheritableEvent;
import org.jspecify.annotations.NonNull;
/**
* Marker interface for mod lifecycle events dispatched on the
* {@linkplain net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext#getModBusGroup() mod BusGroup} instead
* of the {@linkplain BusGroup#DEFAULT default BusGroup}.
*
* @apiNote Each mod gets its own unique mod BusGroup instance, obtained from the mod loading context. Most events
* should go on the default BusGroup.
* Marker interface for events dispatched on the ModLifecycle event bus instead of the primary event bus
*/
public interface IModBusEvent extends InheritableEvent {
/**
* @param modEventBusGroup Obtained from {@link net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext#getModBusGroup()} or your language provider's equivalent.
* @param eventClass The event type you want to get the associated bus for.
*/
static <T extends IModBusEvent> @NonNull EventBus<@NonNull T> getBus(@NonNull BusGroup modEventBusGroup, @NonNull Class<T> eventClass) {
// Do not copy! Temporary solution until the FML rewrite is complete. May throw in future
// without prior notice.
//
// Mods creating their own events should directly assign the result of the create method to a
// static final field in their event class, without wrapping or calling multiple times.
//
// Mods using existing events should instead use the existing static final BUS field in the
// event class (for most events) or getBus(BusGroup) method (for mod bus events).
return EventBus.create(modEventBusGroup, eventClass);
}
public interface IModBusEvent {
}
@@ -7,11 +7,8 @@ package net.minecraftforge.fml.util.thread;
import net.minecraftforge.fml.LogicalSide;
/**
* Attempts to determine the effect side of the current thread, based on know threads in networking.
* This is not guaranteed to be correct, and modders should use other methods to determine the logical side when possible.
*/
public class EffectiveSide {
public class EffectiveSide
{
public static LogicalSide get() {
final ThreadGroup group = Thread.currentThread().getThreadGroup();
return group instanceof SidedThreadGroup ? ((SidedThreadGroup) group).getSide() : LogicalSide.CLIENT;
@@ -7,13 +7,11 @@ package net.minecraftforge.fml.util.thread;
import net.minecraftforge.fml.LogicalSide;
public final class SidedThreadGroups {
public final class SidedThreadGroups
{
public static final SidedThreadGroup CLIENT = new SidedThreadGroup(LogicalSide.CLIENT);
public static final SidedThreadGroup SERVER = new SidedThreadGroup(LogicalSide.SERVER);
public static SidedThreadGroup get(boolean client) {
return client ? CLIENT : SERVER;
private SidedThreadGroups() {
}
private SidedThreadGroups() { }
}
+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)
@@ -85,17 +85,11 @@ public class DisplayWindow implements ImmediateWindowProvider {
private ScheduledFuture<?> initializationFuture;
private PerformanceInfo performanceInfo;
@SuppressWarnings("unused")
private ScheduledFuture<?> performanceTick;
// The GL ID of the window. Used for all operations
private long window;
// The thread that contains and ticks the window while Forge is loading mods
private static final ScheduledExecutorService renderScheduler = Executors.newSingleThreadScheduledExecutor(r -> {
final var thread = Executors.defaultThreadFactory().newThread(r);
thread.setName("EarlyDisplay");
thread.setDaemon(true);
return thread;
});
private ScheduledExecutorService renderScheduler;
private int fbWidth;
private int fbHeight;
private int fbScale;
@@ -154,7 +148,7 @@ public class DisplayWindow implements ImmediateWindowProvider {
}
this.maximized = parsed.has(maximizedopt) || FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_MAXIMIZED);
StartupNotificationManager.modLoaderConsumer().ifPresent(c->c.accept("Forge loading " + FMLLoader.versionInfo().forgeVersion()));
StartupNotificationManager.modLoaderConsumer().ifPresent(c->c.accept("Forge loading " + forgeVersion));
performanceInfo = new PerformanceInfo();
return start(mcVersion, forgeVersion);
}
@@ -243,7 +237,7 @@ public class DisplayWindow implements ImmediateWindowProvider {
var date = Calendar.getInstance();
if (FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_SQUIR) || (date.get(Calendar.MONTH) == Calendar.APRIL && date.get(Calendar.DAY_OF_MONTH) == 1))
this.elements.addFirst(RenderElement.squir());
this.elements.add(0, RenderElement.squir());
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
@@ -263,11 +257,7 @@ public class DisplayWindow implements ImmediateWindowProvider {
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
for (var itr = this.elements.iterator(); itr.hasNext(); ) {
var element = itr.next();
if (!element.render(context, framecount))
itr.remove();
}
this.elements.removeIf(element -> !element.render(context, framecount));
if (animationTimerTrigger.compareAndSet(true, false)) // we only increment the framecount on a periodic basis
framecount++;
}
@@ -292,6 +282,12 @@ public class DisplayWindow implements ImmediateWindowProvider {
* Start the window and Render Thread; we're ready to go.
*/
public Runnable start(@Nullable String mcVersion, final String forgeVersion) {
renderScheduler = Executors.newSingleThreadScheduledExecutor(r -> {
final var thread = Executors.defaultThreadFactory().newThread(r);
thread.setName("EarlyDisplay");
thread.setDaemon(true);
return thread;
});
initWindow(mcVersion);
this.initializationFuture = renderScheduler.schedule(() -> initRender(mcVersion, forgeVersion), 1, TimeUnit.MILLISECONDS);
return this::periodicTick;
@@ -303,7 +299,7 @@ public class DisplayWindow implements ImmediateWindowProvider {
return this.glVersion;
}
private static void crashElegantly(String errorDetails) {
private void crashElegantly(String errorDetails) {
StringBuilder msgBuilder = new StringBuilder(2000);
msgBuilder.append("Failed to initialize graphics window with current settings.\n");
msgBuilder.append("\n\n");
@@ -492,6 +488,10 @@ public class DisplayWindow implements ImmediateWindowProvider {
glfwPollEvents();
}
private void badWindowHandler(final int code, final long desc) {
LOGGER.error("Got error from GLFW window init: "+code+ " "+MemoryUtil.memUTF8(desc));
}
private void winResize(long window, int width, int height) {
if (window == this.window && width != 0 && height != 0) {
this.winWidth = width;
@@ -511,7 +511,7 @@ public class DisplayWindow implements ImmediateWindowProvider {
this.winY = y;
}
}
private static void handleLastGLFWError(BiConsumer<Integer, String> handler) {
private void handleLastGLFWError(BiConsumer<Integer, String> handler) {
try (MemoryStack memorystack = MemoryStack.stackPush()) {
PointerBuffer pointerbuffer = memorystack.mallocPointer(1);
int error = glfwGetError(pointerbuffer);
@@ -639,7 +639,8 @@ public class DisplayWindow implements ImmediateWindowProvider {
}
public void addMojangTexture(final int textureId) {
this.elements.addFirst(RenderElement.mojang(textureId, framecount));
this.elements.add(0, RenderElement.mojang(textureId, framecount));
// this.elements.get(0).retire(framecount + 1);
}
public void close() {
@@ -22,8 +22,8 @@ public class RenderElement {
static int globalAlpha = 255;
private int retireCount;
@FunctionalInterface
interface Renderer {
void accept(SimpleBufferBuilder bb, DisplayContext context, int frame);
default Renderer then(Renderer r) {
@@ -38,9 +38,6 @@ public class RenderElement {
interface TextureRenderer {
void accept(SimpleBufferBuilder bb, DisplayContext context, int[] size, int frame);
}
/** @deprecated Use {@link Renderer} directly. This was always eagerly resolved, making it an unnecessary wrapper. */
@Deprecated(since = "1.21.5", forRemoval = true)
@FunctionalInterface
interface Initializer extends Supplier<Renderer> {}
@@ -59,18 +56,11 @@ public class RenderElement {
}
}
/** @deprecated Use {@link RenderElement#RenderElement(Renderer)} instead */
@Deprecated(since = "1.21.5", forRemoval = true)
public RenderElement(final Initializer rendererInitializer) {
this.bb = new SimpleBufferBuilder(1);
this.renderer = rendererInitializer.get();
}
public RenderElement(Renderer renderer) {
this.bb = new SimpleBufferBuilder(1);
this.renderer = renderer;
}
public boolean render(DisplayContext ctx, int count) {
this.renderer.accept(bb, ctx, count);
return this.retireCount == 0 || this.retireCount < count;
@@ -85,7 +75,7 @@ public class RenderElement {
List<SimpleFont.DisplayText> texts = new ArrayList<>();
for (int i = messages.size() - 1; i >= 0; i--) {
final StartupNotificationManager.AgeMessage pair = messages.get(i);
final float fade = Math.clamp((4000.0f - (float) pair.age() - ( i - 4 ) * 1000.0f) / 5000.0f, 0.0f, 1.0f);
final float fade = clamp((4000.0f - (float) pair.age() - ( i - 4 ) * 1000.0f) / 5000.0f, 0.0f, 1.0f);
if (fade <0.01f) continue;
Message msg = pair.message();
int colour = Math.min((int)(fade * 255f), globalAlpha) << 24 | 0xFFFFFF;
@@ -94,9 +84,19 @@ public class RenderElement {
font.generateVerticesForTexts(10, context.scaledHeight() - texts.size() * font.lineSpacing() + font.descent() - 10, bb, texts.toArray(SimpleFont.DisplayText[]::new));
}
public static RenderElement monag() {
return new RenderElement(RenderElement.initializeTexture("monagstudios.png", 45000, 4, (bb, ctx, sz, frame) -> {
var size = 256;
var x0 = (ctx.width() - 2 * size) / 2;
var y0 = 64;
QuadHelper.loadQuad(bb, x0, x0+size, y0, y0+size/2f, 0f, 1f, 0f, 0.5f, 0xFFFFFFFF);
QuadHelper.loadQuad(bb, x0+size, x0+2*size, y0, y0+size/2f, 0f, 1f, 0.5f, 1f, 0xFFFFFFFF);
}));
}
public static RenderElement mojang(final int textureId, final int frameStart) {
return new RenderElement((bb, ctx, frame) -> {
return new RenderElement(()->(bb, ctx, frame) -> {
var size = 256 * ctx.scale();
var x0 = (ctx.scaledWidth() - 2 * size) / 2;
var y0 = 64 * ctx.scale() + 32;
@@ -148,11 +148,11 @@ public class RenderElement {
}));
}
public static RenderElement progressBars(SimpleFont font) {
return new RenderElement((bb, ctx, frame) -> RenderElement.startupProgressBars(font, bb, ctx, frame));
return new RenderElement(() -> (bb, ctx, frame) -> RenderElement.startupProgressBars(font, bb, ctx, frame));
}
public static RenderElement performanceBar(SimpleFont font) {
return new RenderElement((bb, ctx, frame) -> RenderElement.memoryInfo(font, bb, ctx, frame));
return new RenderElement(() -> (bb, ctx, frame) -> RenderElement.memoryInfo(font, bb, ctx, frame));
}
public static void startupProgressBars(SimpleFont font, final SimpleBufferBuilder buffer, final DisplayContext context, final int frameNumber) {
@@ -187,10 +187,10 @@ public class RenderElement {
}
private static float[] indeterminateBar(int frame, boolean isActive) {
if (RenderElement.globalAlpha != 0xFF || !isActive) {
return new float[] { 0f, 1f };
return new float[] {0f,1f};
} else {
int progress = frame % 100;
return new float[] { Math.clamp((progress - 2) / 100f, 0f, 1f), Math.clamp((progress + 2) / 100f, 0f, 1f) };
var progress = frame % 100;
return new float[]{clamp((progress - 2) / 100f, 0f, 1f), clamp((progress + 2) / 100f, 0f, 1f)};
}
}
@@ -249,8 +249,8 @@ public class RenderElement {
};
}
private static Renderer initializeText(SimpleFont font, TextGenerator textGenerator) {
return (bb, context, frame) -> renderText(font, textGenerator, bb, context);
private static Initializer initializeText(SimpleFont font, TextGenerator textGenerator) {
return () -> (bb, context, frame) -> renderText(font, textGenerator, bb, context);
}
private static void renderText(final SimpleFont font, final TextGenerator textGenerator, final SimpleBufferBuilder bb, final DisplayContext context) {
@@ -264,13 +264,14 @@ public class RenderElement {
private static TextGenerator text(int x, int y, String text, int colour) {
return (bb, font, context) -> font.generateVerticesForTexts(x, y, bb, new SimpleFont.DisplayText(text, colour));
}
private static Renderer initializeTexture(final String textureFileName, int size, int textureNumber, TextureRenderer positionAndColour) {
int[] imgSize = STBHelper.loadTextureFromClasspath(textureFileName, size, GL_TEXTURE0 + textureNumber + INDEX_TEXTURE_OFFSET);
return (bb, ctx, frame) -> {
ctx.elementShader().updateTextureUniform(textureNumber + INDEX_TEXTURE_OFFSET);
ctx.elementShader().updateRenderTypeUniform(ElementShader.RenderType.TEXTURE);
renderTexture(bb, ctx, frame, imgSize, positionAndColour);
private static Initializer initializeTexture(final String textureFileName, int size, int textureNumber, TextureRenderer positionAndColour) {
return ()->{
int[] imgSize = STBHelper.loadTextureFromClasspath(textureFileName, size, GL_TEXTURE0 + textureNumber + INDEX_TEXTURE_OFFSET);
return (bb, ctx, frame) -> {
ctx.elementShader().updateTextureUniform(textureNumber + INDEX_TEXTURE_OFFSET);
ctx.elementShader().updateRenderTypeUniform(ElementShader.RenderType.TEXTURE);
renderTexture(bb, ctx, frame, imgSize, positionAndColour);
};
};
}
@@ -280,13 +281,21 @@ public class RenderElement {
bb.draw();
}
@Deprecated(since = "1.21.5", forRemoval = true)
public static float clamp(float num, float min, float max) {
return Math.clamp(num, min, max);
if (num < min) {
return min;
} else {
return Math.min(num, max);
}
}
public static int clamp(int num, int min, int max) {
return Math.min(max, Math.max(num, min));
if (num < min) {
return min;
} else {
return Math.min(num, max);
}
}
public static int hsvToRGB(float hue, float saturation, float value) {
@@ -11,6 +11,8 @@ import org.lwjgl.stb.STBTTFontinfo;
import org.lwjgl.stb.STBTTPackContext;
import org.lwjgl.stb.STBTTPackRange;
import org.lwjgl.stb.STBTTPackedchar;
import org.lwjgl.system.MemoryStack;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
@@ -22,7 +24,7 @@ public class SimpleFont {
private final int textureNumber;
private final int lineSpacing;
private final int descent;
private static final int GLYPH_COUNT = 127 - 32;
private final int GLYPH_COUNT = 127-32;
private final Glyph[] glyphs;
private record Glyph(char c, int charwidth, int[] pos, float[] uv) {
@@ -120,7 +122,7 @@ public class SimpleFont {
case '\n', '\t' -> 0;
case ' ' -> glyphs[0].charwidth();
default -> {
if (c - 32 < GLYPH_COUNT && c > 32) {
if (c - 32 < this.GLYPH_COUNT && c > 32) {
yield this.glyphs[c - 32].charwidth();
} else {
yield 0;
@@ -151,7 +153,7 @@ public class SimpleFont {
case '\t' -> new Pos(pos.x()+font.glyphs[0].charwidth() * 4, pos.y(), pos.minx());
case ' ' -> new Pos(pos.x()+font.glyphs[0].charwidth(), pos.y(), pos.minx());
default -> {
if (c-32 < GLYPH_COUNT && c > 32) {
if (c-32 < font.GLYPH_COUNT && c > 32) {
pos = font.glyphs[c - 32].loadQuad(pos, colour(), bb);
}
yield pos;
+3 -44
View File
@@ -5,7 +5,6 @@ plugins {
id 'maven-publish'
id 'net.minecraftforge.licenser'
id 'net.minecraftforge.gradleutils'
alias(libs.plugins.apt)
}
apply from: rootProject.file('build_shared.gradle')
@@ -13,7 +12,7 @@ apply from: rootProject.file('build_shared.gradle')
configurations.forEach{ it.transitive = false }
dependencies {
compileOnly(libs.jetbrains.annotations)
compileOnly(libs.nulls)
api(libs.bundles.asm) // Needed by all the black magic
api(libs.forgespi)
@@ -36,13 +35,11 @@ dependencies {
implementation(libs.accesstransformers)
implementation(libs.terminalconsoleappender)
implementation(libs.jimfs)
implementation(libs.roimfs)
// Needed because we have a custom log4j plugin, and they removed package scanning and require a data file to be generated
implementation(libs.log4j.core)
annotationProcessor(libs.bundles.log4j)
testCompileOnly(libs.jetbrains.annotations)
testCompileOnly(libs.nulls)
testRuntimeOnly(libs.bootstrap)
}
@@ -88,7 +85,7 @@ publishing {
license PomUtils.Licenses.LGPLv2_1
}
}
repositories {
maven gradleutils.publishingForgeMaven
}
@@ -104,44 +101,6 @@ tasks.register('writeForgeVersionJson') {
}
}
tasks.register('jarJarOptionsJson', net.minecraftforge.forge.tasks.JarJarMetadataOptions) {
metadataFile = project.file('src/main/resources/jarjar_options.json')
// This is resolved too early by cpw.mods.modlauncher.TransformationServicesHandler.discoverServices(DiscoveryData) But eventually...
//add(libs.mixin, 'org/spongepowered/asm/mixin/Mixin.class')
add(libs.mixinextras.forge) {
containedJarMetadata {
path = ''
}
resource = 'com/llamalad7/mixinextras/platform/forge/MixinExtrasConfigPlugin.class'
layer = 'GAMELIBRARY'
id = 'mixinextras'
nested = true
}
}
tasks.named('generateResources').configure {
dependsOn('eclipseJdt')
dependsOn('eclipseJdtApt')
dependsOn('eclipseFactorypath')
dependsOn('writeForgeVersionJson')
dependsOn('jarJarOptionsJson')
}
tasks.named('sourcesJar') {
dependsOn('jarJarOptionsJson')
}
eclipse {
classpath {
// We need to set the default output directory for the log4j annotation processor
// Ideally we'd just set the value, but the eclipse plugin deduplicates the output directories
// So we have to do a hacky whenMerged
//defaultOutputDir = file('bin/main')
file.whenMerged {
entries.each { entry ->
if (entry.kind == 'output' && entry.hasProperty('path'))
entry.path = 'bin/main'
}
}
}
}
@@ -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
@@ -6,13 +6,14 @@
package net.minecraftforge.fml.loading;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class BackgroundWaiter {
private static final ExecutorService runner = Executors.newSingleThreadExecutor();
public static void runAndTick(Runnable r, Runnable tick) {
var runner = Executors.newSingleThreadExecutor();
ImmediateWindowHandler.updateProgress("Loading bootstrap resources");
final Future<?> work = runner.submit(r);
do {
@@ -54,8 +54,12 @@ public class FMLLoader {
LOGGER.debug(CORE, "Detected version data : {}", versionInfo);
LOGGER.debug(CORE, "FML {} loading", LauncherVersion.getVersion());
// Allows us to communicate properties with other services through ModLauncher
setupBlackboardKeys();
checkPackage(ITransformationService.class, "4.0", "ModLauncher");
accessTransformer = getPlugin(env, "accesstransformer", "1.0", "AccessTransformer");
/*eventBus =*/ getPlugin(env, "eventbus", "1.0", "EventBus");
runtimeDistCleaner = getPlugin(env, "runtimedistcleaner", "1.0", "RuntimeDistCleaner");
coreModProvider = getSingleService(ICoreModProvider.class, "CoreMod");
LOGGER.debug(CORE, "FML found CoreMod version : {}", JarVersionLookupHandler.getInfo(coreModProvider.getClass()).impl().version().orElse("MISSING"));
@@ -117,6 +121,12 @@ public class FMLLoader {
return providers.get(0);
}
private static void setupBlackboardKeys() {
LOGGER.debug(CORE, "Requesting CoreMods to not apply the fix for ASMAPI.findFirstInstructionBefore by default");
var blackboardKey = TypesafeMap.Key.getOrCreate(Launcher.INSTANCE.blackboard(), "coremods.use_old_findFirstInstructionBefore", Boolean.class);
Launcher.INSTANCE.blackboard().<Boolean>computeIfAbsent(blackboardKey, k -> true);
}
static void setupLaunchHandler(final IEnvironment environment, final Map<String, Object> arguments) {
final String launchTarget = environment.getProperty(IEnvironment.Keys.LAUNCHTARGET.get()).orElse("MISSING");
arguments.put("launchTarget", launchTarget);
@@ -146,8 +156,9 @@ public class FMLLoader {
}
public static List<ITransformationService.Resource> beginModScan(final Map<String,?> arguments) {
LOGGER.debug(SCAN, "Scanning for Mod Locators");
modValidator = ModDiscoverer.discoverMods(arguments);
LOGGER.debug(SCAN,"Scanning for Mod Locators");
var modDiscoverer = new ModDiscoverer(arguments);
modValidator = modDiscoverer.discoverMods();
var pluginResources = modValidator.getPluginResources();
return List.of(pluginResources);
}
@@ -7,6 +7,7 @@ package net.minecraftforge.fml.loading;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.api.*;
import joptsimple.OptionSpecBuilder;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import net.minecraftforge.forgespi.Environment;
import org.jetbrains.annotations.NotNull;
@@ -18,6 +19,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
public class FMLServiceProvider implements ITransformationService {
@@ -5,25 +5,31 @@
package net.minecraftforge.fml.loading;
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
import net.minecraftforge.fml.loading.moddiscovery.BackgroundScanHandler;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
import net.minecraftforge.forgespi.locating.IModFile;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.stream.Collectors;
/**
* Master list of all mods <em>in the loading context. This class cannot refer outside the
* loading package</em>
*/
public class LoadingModList {
public class LoadingModList
{
private static LoadingModList INSTANCE;
private final List<ModFileInfo> modFiles;
private final List<ModInfo> sortedList;
@@ -31,12 +37,15 @@ public class LoadingModList {
private final List<EarlyLoadingException> preLoadErrors;
private List<IModFile> brokenFiles;
private LoadingModList(final List<ModFile> modFiles, final List<ModInfo> sortedList) {
private LoadingModList(final List<ModFile> modFiles, final List<ModInfo> sortedList)
{
this.modFiles = modFiles.stream()
.map(ModFile::getModFileInfo)
.map(ModFileInfo.class::cast)
.toList();
this.sortedList = sortedList.stream().toList();
.collect(Collectors.toList());
this.sortedList = sortedList.stream()
.map(ModInfo.class::cast)
.collect(Collectors.toList());
this.fileById = this.modFiles.stream()
.map(ModFileInfo::getMods)
.flatMap(Collection::stream)
@@ -45,18 +54,21 @@ public class LoadingModList {
this.preLoadErrors = new ArrayList<>();
}
public static LoadingModList of(List<ModFile> modFiles, List<ModInfo> sortedList, final EarlyLoadingException earlyLoadingException) {
public static LoadingModList of(List<ModFile> modFiles, List<ModInfo> sortedList, final EarlyLoadingException earlyLoadingException)
{
INSTANCE = new LoadingModList(modFiles, sortedList);
if (earlyLoadingException != null)
{
INSTANCE.preLoadErrors.add(earlyLoadingException);
}
return INSTANCE;
}
public static LoadingModList get() {
return INSTANCE;
}
public void addCoreMods() {
public void addCoreMods()
{
modFiles.stream()
.map(ModFileInfo::getFile)
.map(ModFile::getCoreMods)
@@ -64,7 +76,8 @@ public class LoadingModList {
.forEach(FMLLoader.getCoreModProvider()::addCoreMod);
}
public void addAccessTransformers() {
public void addAccessTransformers()
{
for (ModFileInfo modFile : modFiles) {
ModFile mod = modFile.getFile();
var at = mod.getAccessTransformer().orElse(null);
@@ -74,18 +87,21 @@ public class LoadingModList {
}
}
public void addForScanning(BackgroundScanHandler backgroundScanHandler) {
public void addForScanning(BackgroundScanHandler backgroundScanHandler)
{
backgroundScanHandler.setLoadingModList(this);
modFiles.stream()
.map(ModFileInfo::getFile)
.forEach(backgroundScanHandler::submitForScanning);
}
public List<ModFileInfo> getModFiles() {
public List<ModFileInfo> getModFiles()
{
return modFiles;
}
public Path findResource(final String className) {
public Path findResource(final String className)
{
for (ModFileInfo mf : modFiles) {
final Path resource = mf.getFile().findResource(className);
if (Files.exists(resource)) return resource;
@@ -93,11 +109,55 @@ public class LoadingModList {
return null;
}
public ModFileInfo getModFileById(String modid) {
public Enumeration<URL> findAllURLsForResource(final String resName) {
final String resourceName;
// strip a leading slash
if (resName.startsWith("/")) {
resourceName = resName.substring(1);
} else {
resourceName = resName;
}
return new Enumeration<URL>() {
private final Iterator<ModFileInfo> modFileIterator = modFiles.iterator();
private URL next;
@Override
public boolean hasMoreElements() {
if (next!=null) return true;
next = findNextURL();
return next != null;
}
@Override
public URL nextElement() {
if (next == null) {
next = findNextURL();
if (next == null) throw new NoSuchElementException();
}
URL result = next;
next = null;
return result;
}
private URL findNextURL() {
while (modFileIterator.hasNext()) {
final ModFileInfo next = modFileIterator.next();
final Path resource = next.getFile().findResource(resourceName);
if (Files.exists(resource)) {
return LamdbaExceptionUtils.uncheck(()->new URL("modjar://" + next.getMods().get(0).getModId() + "/" + resourceName));
}
}
return null;
}
};
}
public ModFileInfo getModFileById(String modid)
{
return this.fileById.get(modid);
}
public List<ModInfo> getMods() {
public List<ModInfo> getMods()
{
return this.sortedList;
}
@@ -7,10 +7,8 @@ package net.minecraftforge.fml.loading;
import com.mojang.logging.LogUtils;
import cpw.mods.jarhandling.SecureJar;
import cpw.mods.modlauncher.Launcher;
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
import cpw.mods.modlauncher.api.NamedPath;
import cpw.mods.modlauncher.api.TypesafeMap;
import cpw.mods.modlauncher.serviceapi.ITransformerDiscoveryService;
import org.jetbrains.annotations.ApiStatus;
@@ -42,17 +40,6 @@ public class ModDirTransformerDiscoverer implements ITransformerDiscoveryService
@Override
public void earlyInitialization(final String launchTarget, final String[] arguments) {
boolean isMixinEnabledInDev = false;
for (String arg : arguments) {
if (arg.startsWith("-mixin") || arg.startsWith("--mixin")) {
isMixinEnabledInDev = true;
break;
}
}
Launcher.INSTANCE.blackboard().putIfAbsent(
TypesafeMap.Key.getOrCreate(Launcher.INSTANCE.blackboard(), "isMixinEnabledInDev", Boolean.class),
isMixinEnabledInDev
);
ImmediateWindowHandler.load(launchTarget, arguments);
}
@@ -8,7 +8,7 @@ package net.minecraftforge.fml.loading;
import com.google.common.graph.GraphBuilder;
import com.google.common.graph.MutableGraph;
import com.mojang.logging.LogUtils;
import net.minecraftforge.forgespi.language.IModInfo.ModVersion;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.fml.loading.EarlyLoadingException.ExceptionData;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
@@ -20,250 +20,235 @@ import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.slf4j.Logger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.stream.Collectors.*;
import static net.minecraftforge.fml.loading.LogMarkers.LOADING;
public class ModSorter {
public class ModSorter
{
private static final Logger LOGGER = LogUtils.getLogger();
private final UniqueModListBuilder uniqueModListBuilder;
private List<ModFile> modFiles;
private List<ModInfo> sortedList;
private Map<String, IModInfo> modIdNameLookup;
private List<ModFile> systemMods;
private record State(List<ModFile> files, List<ModInfo> mods) {}
private ModSorter() {}
@SuppressWarnings("removal")
public static LoadingModList sort(List<ModFile> mods, final List<ExceptionData> errors) {
State systemMods = detectSystemMods(mods);
List<ModFile> modFiles;
private ModSorter(final List<ModFile> modFiles)
{
this.uniqueModListBuilder = new UniqueModListBuilder(modFiles);
}
public static LoadingModList sort(List<ModFile> mods, final List<ExceptionData> errors)
{
final ModSorter ms = new ModSorter(mods);
try {
modFiles = UniqueModListBuilder.buildUniqueList(mods).modFiles();
ms.buildUniqueList();
} catch (EarlyLoadingException e) {
// We cannot build any list with duped mods. We have to abort immediately and report it
// Note this will never actually throw an error because the duplicate checks are done in ModDiscovererer before we get to this phase
// So all this is really doing is wasting time.
// But i'm leaving it here until I rewrite all of cpw's mod loading code because its such a clusterfuck.
return LoadingModList.of(systemMods.files(), systemMods.mods(), e);
return LoadingModList.of(ms.systemMods, ms.systemMods.stream().map(mf->(ModInfo)mf.getModInfos().get(0)).toList(), e);
}
var named = new HashMap<String, ModInfo>();
for (var file : modFiles) {
for (var info : file.getModInfos())
named.put(info.getModId(), (ModInfo)info);
}
// try and validate dependencies
final List<ExceptionData> failedList = Stream.concat(verifyDependencyVersions(modFiles).stream(), errors.stream()).toList();
final List<ExceptionData> failedList = Stream.concat(ms.verifyDependencyVersions().stream(), errors.stream()).toList();
// if we miss one or the other, we abort now
if (!failedList.isEmpty()) {
return LoadingModList.of(systemMods.files(), systemMods.mods(), new EarlyLoadingException("failure to validate mod list", null, failedList));
return LoadingModList.of(ms.systemMods, ms.systemMods.stream().map(mf->(ModInfo)mf.getModInfos().get(0)).toList(), new EarlyLoadingException("failure to validate mod list", null, failedList));
} else {
// Otherwise, lets try and sort the modlist and proceed
EarlyLoadingException earlyLoadingException = null;
try {
var sorted = sort(modFiles, named);
return LoadingModList.of(sorted.files(), sorted.mods(), null);
ms.sort();
} catch (EarlyLoadingException e) {
// The only exception that can happen here is a cyclic exception, but fall back to system mods so we can display the nice screen.
return LoadingModList.of(systemMods.files(), systemMods.mods(), e);
earlyLoadingException = e;
}
return LoadingModList.of(ms.modFiles, ms.sortedList, earlyLoadingException);
}
}
private static State sort(final List<ModFile> modFiles, final Map<String, ModInfo> named) {
final MutableGraph<ModInfo> graph = GraphBuilder.directed().build();
private void sort()
{
// lambdas are identity based, so sorting them is impossible unless you hold reference to them
final MutableGraph<ModFileInfo> graph = GraphBuilder.directed().build();
AtomicInteger counter = new AtomicInteger();
Map<ModFileInfo, Integer> infos = modFiles.stream()
.map(ModFile::getModFileInfo)
.filter(ModFileInfo.class::isInstance)
.map(ModFileInfo.class::cast)
.collect(toMap(Function.identity(), e -> counter.incrementAndGet()));
infos.keySet().forEach(graph::addNode);
modFiles.stream()
.map(ModFile::getModInfos)
.flatMap(List::stream)
.map(IModInfo::getDependencies)
.<IModInfo.ModVersion>flatMap(List::stream)
.forEach(dep -> addDependency(graph, dep));
int counter = 0;
var infos = new HashMap<ModInfo, Integer>();
for (var file : modFiles) {
if (file.getModFileInfo() instanceof ModFileInfo info) {
for (var imod : info.getMods()) {
var mod = (ModInfo)imod;
infos.put(mod, counter++);
graph.addNode(mod);
}
}
}
for (var file : modFiles) {
for (var info : file.getModInfos()) {
for (var dep : info.getDependencies()) {
// Ordering isn't effected by sides, should it be?
//if (!dep.getSide().isCorrectSide())
// continue;
var target = named.get(dep.getModId());
// soft dep that doesn't exist. No edge required.
if (target == null)
continue;
var self = (ModInfo)dep.getOwner();
switch (dep.getOrdering()) {
case BEFORE -> graph.putEdge(self, target);
case AFTER -> graph.putEdge(target, self);
default -> {}
}
}
}
}
final List<ModInfo> sorted;
try {
final List<ModFileInfo> sorted;
try
{
sorted = TopologicalSort.topologicalSort(graph, Comparator.comparing(infos::get));
} catch (CyclePresentException e) {
Set<Set<ModInfo>> cycles = e.getCycles();
var buf = new StringBuilder();
buf.append("Mod Sorting failed - Detected Cycles: \n");
var dataList = new ArrayList<ExceptionData>();
for (var cycle : cycles) {
buf.append("\tCycle:\n");
for (var mod : cycle) {
var modDeps = new StringBuilder()
.append(mod.getModId())
.append(' ')
.append(mod.getDependencies().stream()
.filter(v -> cycle.stream().anyMatch(m -> m.getModId().equals(v.getModId())))
.map(dep -> dep.getOrdering().name() + " " + dep.getModId())
.collect(Collectors.joining(", "))
);
dataList.add(new ExceptionData("fml.modloading.cycle", modDeps.toString()));
buf.append("\t\tMod: ").append(modDeps.toString()).append('\n');
}
}
catch (CyclePresentException e)
{
Set<Set<ModFileInfo>> cycles = e.getCycles();
if (LOGGER.isErrorEnabled(LOADING))
{
LOGGER.error(LOADING, "Mod Sorting failed.\nDetected Cycles: {}\n", cycles);
}
LOGGER.error(LOADING, buf.toString());
var dataList = cycles.stream()
.flatMap(Set::stream)
.<IModInfo>mapMulti((mf,c)->mf.getMods().forEach(c))
.map(IModInfo::getModId)
.map(list -> new ExceptionData("fml.modloading.cycle", list))
.toList();
throw new EarlyLoadingException("Sorting error", e, dataList);
}
var files = new LinkedHashSet<ModFile>();
var list = new ArrayList<ModInfo>();
for (var mod : sorted) {
files.add(mod.getOwningFile().getFile());
list.add(mod);
}
return new State(files.stream().toList(), list);
this.sortedList = sorted.stream()
.map(ModFileInfo::getMods)
.flatMap(List::stream)
.map(ModInfo.class::cast)
.collect(toList());
this.modFiles = sorted.stream()
.map(ModFileInfo::getFile)
.collect(toList());
}
private static State detectSystemMods(final List<ModFile> modFiles) {
private void addDependency(MutableGraph<ModFileInfo> topoGraph, IModInfo.ModVersion dep)
{
final IModInfo targetModInfo = modIdNameLookup.get(dep.getModId());
// soft dep that doesn't exist. Just return. No edge required.
if (targetModInfo == null || !(targetModInfo.getOwningFile() instanceof final ModFileInfo target)) return;
final ModFileInfo self = (ModFileInfo)dep.getOwner().getOwningFile();
if (self == target)
return; // in case a jar has two mods that have dependencies between
switch (dep.getOrdering()) {
case BEFORE -> topoGraph.putEdge(self, target);
case AFTER -> topoGraph.putEdge(target, self);
default -> {}
}
}
private void buildUniqueList()
{
final UniqueModListBuilder.UniqueModListData uniqueModListData = uniqueModListBuilder.buildUniqueList();
this.modFiles = uniqueModListData.modFiles();
detectSystemMods(uniqueModListData.modFilesByFirstId());
modIdNameLookup = uniqueModListData.modFilesByFirstId().entrySet().stream()
.filter(e -> !e.getValue().get(0).getModInfos().isEmpty())
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> e.getValue().get(0).getModInfos().get(0)
));
}
private void detectSystemMods(final Map<String, List<ModFile>> modFilesByFirstId) {
// Capture system mods (ex. MC, Forge) here, so we can keep them for later
var systemMods = List.of("minecraft", "forge");
LOGGER.debug("Configured system mods: {}", systemMods);
var mods = new ArrayList<ModInfo>();
var files = new ArrayList<ModFile>();
this.systemMods = new ArrayList<>();
for (var systemMod : systemMods) {
var mod = findMod(modFiles, systemMod);
if (mod == null)
var container = modFilesByFirstId.get(systemMod);
if (container != null && !container.isEmpty()) {
LOGGER.debug("Found system mod: {}", systemMod);
this.systemMods.add(container.getFirst());
} else {
throw new IllegalStateException("Failed to find system mod: " + systemMod);
LOGGER.debug("Found system mod: {}", systemMod);
mods.add(mod.info());
files.add(mod.file());
}
return new State(files, mods);
}
private record ModPair(ModFile file, ModInfo info) {}
private static ModPair findMod(final List<ModFile> modFiles, String name) {
for (var file : modFiles) {
for (var mod : file.getModFileInfo().getMods()) {
if (name.equals(mod.getModId()))
return new ModPair(file, (ModInfo)mod);
}
}
return null;
}
private static List<ExceptionData> verifyDependencyVersions(final List<ModFile> files) {
final var modVersions = new HashMap<String, ArtifactVersion>();
final var modRequirements = new HashSet<ModVersion>();
int mandatoryRequired = 0;
private List<EarlyLoadingException.ExceptionData> verifyDependencyVersions()
{
final var modVersions = modFiles.stream()
.map(ModFile::getModInfos)
.flatMap(List::stream)
.collect(toMap(IModInfo::getModId, IModInfo::getVersion));
for (var file : files) {
for (var info : file.getModInfos()) {
modVersions.put(info.getModId(), info.getVersion());
for (var dep : info.getDependencies()) {
if (dep.getSide().isCorrectSide()) {
if (modRequirements.add(dep) && dep.isMandatory())
mandatoryRequired++;
}
}
}
}
final var modVersionDependencies = modFiles.stream()
.map(ModFile::getModInfos)
.flatMap(List::stream)
.collect(groupingBy(Function.identity(), flatMapping(e -> e.getDependencies().stream(), toList())));
final var modRequirements = modVersionDependencies.values().stream()
.<IModInfo.ModVersion>flatMap(List::stream)
.filter(mv -> mv.getSide().isCorrectSide())
.collect(toSet());
final long mandatoryRequired = modRequirements.stream().filter(IModInfo.ModVersion::isMandatory).count();
LOGGER.debug(LOADING, "Found {} mod requirements ({} mandatory, {} optional)", modRequirements.size(), mandatoryRequired, modRequirements.size() - mandatoryRequired);
final var missingVersions = modRequirements.stream()
.filter(mv -> (mv.isMandatory() || modVersions.containsKey(mv.getModId())) && !modVersionContained(mv, modVersions))
.collect(toSet());
final long mandatoryMissing = missingVersions.stream().filter(IModInfo.ModVersion::isMandatory).count();
LOGGER.debug(LOADING, "Found {} mod requirements missing ({} mandatory, {} optional)", missingVersions.size(), mandatoryMissing, missingVersions.size() - mandatoryMissing);
final var missingMandatory = new HashSet<ModVersion>();
final var missingOptional = new HashSet<ModVersion>();
if (!missingVersions.isEmpty()) {
if (mandatoryMissing > 0) {
LOGGER.error(
LOADING,
"Missing or unsupported mandatory dependencies:\n{}",
missingVersions.stream()
.filter(IModInfo.ModVersion::isMandatory)
.map(ver -> formatDependencyError(ver, modVersions))
.collect(Collectors.joining("\n"))
);
}
if (missingVersions.size() - mandatoryMissing > 0) {
LOGGER.error(
LOADING,
"Unsupported installed optional dependencies:\n{}",
missingVersions.stream()
.filter(ver -> !ver.isMandatory())
.map(ver -> formatDependencyError(ver, modVersions))
.collect(Collectors.joining("\n"))
);
}
for (var dep : modRequirements) {
var modId = dep.getModId();
var existing = modVersions.get(modId);
if (!dep.isMandatory() && existing == null)
continue;
var range = dep.getVersionRange();
if (existing != null && (range.containsVersion(existing) || "0.0NONE".equals(existing.toString())))
continue;
if (!VersionSupportMatrix.testVersionSupportMatrix(range, modId, "mod"))
(dep.isMandatory() ? missingMandatory : missingOptional).add(dep);
return missingVersions.stream()
.map(mv -> new ExceptionData(mv.isMandatory() ? "fml.modloading.missingdependency" : "fml.modloading.missingdependency.optional",
mv.getOwner(), mv.getModId(), mv.getOwner().getModId(), mv.getVersionRange(),
modVersions.getOrDefault(mv.getModId(), new DefaultArtifactVersion("null"))))
.toList();
}
LOGGER.debug(LOADING, "Found {} mod requirements missing ({} mandatory, {} optional)", missingMandatory.size() + missingOptional.size(), missingMandatory.size(), missingOptional.size());
var ret = new ArrayList<ExceptionData>();
if (!missingMandatory.isEmpty()) {
LOGGER.error(LOADING, "Missing or unsupported mandatory dependencies:\n{}", formatDependencyError(missingMandatory, modVersions));
for (var mv : missingMandatory)
ret.add(data(mv, modVersions, "fml.modloading.missingdependency"));
}
if (!missingOptional.isEmpty()) {
LOGGER.error(LOADING, "Unsupported installed optional dependencies:\n{}", formatDependencyError(missingMandatory, modVersions));
for (var mv : missingMandatory)
ret.add(data(mv, modVersions, "fml.modloading.missingdependency.optional"));
}
return ret;
return Collections.emptyList();
}
private static String formatDependencyError(Collection<ModVersion> missing, Map<String, ArtifactVersion> modVersions) {
var ret = new ArrayList<String>();
for (var dep : missing) {
var installed = modVersions.get(dep.getModId());
ret.add(String.format(
private static String formatDependencyError(IModInfo.ModVersion dependency, Map<String, ArtifactVersion> modVersions)
{
ArtifactVersion installed = modVersions.get(dependency.getModId());
return String.format(
"\tMod ID: '%s', Requested by: '%s', Expected range: '%s', Actual version: '%s'",
dep.getModId(),
dep.getOwner().getModId(),
dep.getVersionRange(),
dependency.getModId(),
dependency.getOwner().getModId(),
dependency.getVersionRange(),
installed != null ? installed.toString() : "[MISSING]"
));
}
return String.join("\n", ret);
);
}
private static final ArtifactVersion NULL_VERSION = new DefaultArtifactVersion("null");
private static ExceptionData data(ModVersion mv, Map<String, ArtifactVersion> modVersions, String key) {
return new ExceptionData(key, mv.getOwner(), mv.getModId(), mv.getOwner().getModId(), mv.getVersionRange(), modVersions.getOrDefault(mv.getModId(), NULL_VERSION));
private static boolean modVersionContained(IModInfo.ModVersion mv, Map<String, ArtifactVersion> modVersions) {
var modId = mv.getModId();
var range = mv.getVersionRange();
if (modVersions.containsKey(modId)
&& (range.containsVersion(modVersions.get(modId)) || modVersions.get(modId).toString().equals("0.0NONE")))
return true;
return VersionSupportMatrix.testVersionSupportMatrix(mv.getVersionRange(), mv.getModId(), "mod");
}
}
@@ -27,7 +27,7 @@ import org.slf4j.Logger;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
public final class RuntimeDistCleaner implements ILaunchPluginService {
public class RuntimeDistCleaner implements ILaunchPluginService {
private static final Logger LOGGER = LogUtils.getLogger();
private static final Marker DISTXFORM = MarkerFactory.getMarker("DISTXFORM");
private static String DIST;
@@ -40,25 +40,6 @@ public final class RuntimeDistCleaner implements ILaunchPluginService {
return "runtimedistcleaner";
}
private static final EnumSet<Phase> YAY = EnumSet.of(Phase.AFTER);
private static final EnumSet<Phase> NAY = EnumSet.noneOf(Phase.class);
@Override
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty) {
if (isEmpty)
return NAY;
String internalName = classType.getInternalName();
if (internalName.startsWith("net/minecraftforge/"))
return NAY;
// No Vanilla classes use @OnlyIn(Dist.SERVER), so we can skip them entirely when we're running on a client
if (FMLEnvironment.dist.isClient() && (internalName.startsWith("net/minecraft/") || internalName.startsWith("com/mojang/")))
return NAY;
return YAY;
}
@Override
public int processClassWithFlags(final Phase phase, final ClassNode classNode, final Type classType, final String reason) {
var changed = false;
@@ -217,6 +198,21 @@ public final class RuntimeDistCleaner implements ILaunchPluginService {
};
}
private static final EnumSet<Phase> YAY = EnumSet.of(Phase.AFTER);
private static final EnumSet<Phase> NAY = EnumSet.noneOf(Phase.class);
@Override
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty) {
if (isEmpty)
return NAY;
String internalName = classType.getInternalName();
if (internalName.startsWith("net/minecraftforge/"))
return NAY;
return YAY;
}
private static class LambdaGatherer extends MethodVisitor {
private static final Handle META_FACTORY = new Handle(Opcodes.H_INVOKESTATIC,
"java/lang/invoke/LambdaMetafactory", "metafactory",
@@ -9,7 +9,6 @@ import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import net.minecraftforge.forgespi.language.IModInfo;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.jetbrains.annotations.ApiStatus;
import org.slf4j.Logger;
import java.util.ArrayList;
@@ -17,19 +16,26 @@ import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.joining;
import static net.minecraftforge.fml.loading.LogMarkers.LOADING;
@ApiStatus.Internal
@Deprecated(since = "1.21.3", forRemoval = true) // TODO: [FML][Loading] Convert to package private in 1.22
public final class UniqueModListBuilder {
private UniqueModListBuilder() {}
public class UniqueModListBuilder
{
private final static Logger LOGGER = LogUtils.getLogger();
private static final Logger LOGGER = LogUtils.getLogger();
private final List<ModFile> modFiles;
public UniqueModListBuilder(final List<ModFile> modFiles) {this.modFiles = modFiles;}
public UniqueModListData buildUniqueList()
{
List<ModFile> uniqueModList;
List<ModFile> uniqueLibListWithVersion;
public static UniqueModListData buildUniqueList(List<ModFile> modFiles) {
// Collect mod files by module name. This will be used for deduping purposes
final Map<String, List<ModFile>> modFilesByFirstId = modFiles.stream()
.filter(mf -> mf.getModFileInfo() != null)
@@ -40,12 +46,12 @@ public final class UniqueModListBuilder {
.collect(groupingBy(UniqueModListBuilder::getModId));
// Select the newest by artifact version sorting of non-unique files thus identified
final List<ModFile> uniqueModList = modFilesByFirstId.entrySet().stream()
uniqueModList = modFilesByFirstId.entrySet().stream()
.map(UniqueModListBuilder::selectNewestModInfo)
.toList();
// Select the newest by artifact version sorting of non-unique files thus identified
final List<ModFile> uniqueLibListWithVersion = libFilesWithVersionByModuleName.entrySet().stream()
uniqueLibListWithVersion = libFilesWithVersionByModuleName.entrySet().stream()
.map(UniqueModListBuilder::selectNewestModInfo)
.toList();
@@ -57,10 +63,12 @@ public final class UniqueModListBuilder {
.collect(groupingBy(IModInfo::getModId));
// Transform to the full lib id list
final List<List<ModFile>> versionedLibs = uniqueLibListWithVersion.stream()
final Map<String, List<ModFile>> versionedLibIds = uniqueLibListWithVersion.stream()
.map(UniqueModListBuilder::getModId)
.map(libFilesWithVersionByModuleName::get)
.toList();
.collect(Collectors.toMap(
Function.identity(),
libFilesWithVersionByModuleName::get
));
// Its theoretically possible that some mod has somehow moved an id to a secondary place, thus causing a dupe.
// We can't handle this
@@ -73,24 +81,24 @@ public final class UniqueModListBuilder {
)).toList();
if (!dupedModErrors.isEmpty()) {
LOGGER.error(LOADING, "Found duplicate mods:\n{}", String.join("\n", dupedModErrors));
LOGGER.error(LOADING, "Found duplicate mods:\n{}", dupedModErrors.stream().collect(joining("\n")));
throw new EarlyLoadingException("Duplicate mods found", null, dupedModErrors.stream()
.map(EarlyLoadingException.ExceptionData::new)
.map(s -> new EarlyLoadingException.ExceptionData(s))
.toList());
}
final List<String> dupedLibErrors = versionedLibs.stream()
.filter(libModFiles -> libModFiles.size() > 1)
final List<String> dupedLibErrors = versionedLibIds.values().stream()
.filter(modFiles -> modFiles.size() > 1)
.map(mods -> String.format("\tLibrary: '%s' from files: %s",
getModId(mods.getFirst()),
mods.stream().map(ModFile::getFileName).collect(joining(", "))
)).toList();
if (!dupedLibErrors.isEmpty()) {
LOGGER.error(LOADING, "Found duplicate plugins or libraries:\n{}", String.join("\n", dupedLibErrors));
LOGGER.error(LOADING, "Found duplicate plugins or libraries:\n{}", dupedLibErrors.stream().collect(joining("\n")));
throw new EarlyLoadingException("Duplicate plugins or libraries found", null, dupedLibErrors.stream()
.map(EarlyLoadingException.ExceptionData::new)
.map(s -> new EarlyLoadingException.ExceptionData(s))
.toList());
}
@@ -111,7 +119,7 @@ public final class UniqueModListBuilder {
modInfoList.sort(Comparator.comparing(UniqueModListBuilder::getVersion).reversed());
LOGGER.debug("Selected file {} for modid {} with version {}", modInfoList.getFirst().getFileName(), fullList.getKey(), getVersion(modInfoList.getFirst()));
}
return modInfoList.getFirst();
return modInfoList.get(0);
}
private static ArtifactVersion getVersion(final ModFile mf) {
@@ -119,7 +127,7 @@ public final class UniqueModListBuilder {
return mf.getJarVersion();
}
return mf.getModInfos().getFirst().getVersion();
return mf.getModInfos().get(0).getVersion();
}
private static String getModId(ModFile modFile) {
@@ -127,8 +135,9 @@ public final class UniqueModListBuilder {
return modFile.getSecureJar().name();
}
return modFile.getModFileInfo().getMods().getFirst().getModId();
return modFile.getModFileInfo().getMods().get(0).getModId();
}
public record UniqueModListData(List<ModFile> modFiles, Map<String, List<ModFile>> modFilesByFirstId) {}
}
@@ -18,7 +18,9 @@ import org.apache.logging.log4j.util.PerformanceSensitive;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* A wrapper for {@link HighlightConverter} that auto-disables ANSI when the terminal doesn't support it.
@@ -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;
@@ -9,6 +9,7 @@ import cpw.mods.modlauncher.api.LamdbaExceptionUtils.Supplier_WithExceptions;
import net.minecraftforge.fml.loading.StringUtils;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Optional;
import java.util.function.BiPredicate;
import java.util.stream.Stream;
@@ -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,40 @@ import java.util.ServiceLoader;
import java.util.stream.Collectors;
@ApiStatus.Internal
public final class ModDiscoverer {
private ModDiscoverer() {}
public class ModDiscoverer {
private static final Logger LOGGER = LogUtils.getLogger();
private final ServiceLoader<IModLocator> modLocators;
private final ServiceLoader<IDependencyLocator> dependencyLocators;
private final List<IModLocator> modLocatorList;
private final List<IDependencyLocator> dependencyLocatorList;
@SuppressWarnings("removal")
public static ModValidator discoverMods(Map<String, ?> arguments) {
public ModDiscoverer(Map<String, ?> arguments) {
Launcher.INSTANCE.environment().computePropertyIfAbsent(Environment.Keys.MODDIRECTORYFACTORY.get(), v->ModsFolderLocator::new);
Launcher.INSTANCE.environment().computePropertyIfAbsent(Environment.Keys.PROGRESSMESSAGE.get(), v-> StartupNotificationManager.locatorConsumer().orElseGet(()-> s->{}));
final var moduleLayerManager = Launcher.INSTANCE.environment().findModuleLayerManager().orElseThrow();
ServiceLoader<IModLocator> modLocators = ServiceLoader.load(moduleLayerManager.getLayer(IModuleLayerManager.Layer.SERVICE).orElseThrow(), IModLocator.class);
ServiceLoader<IDependencyLocator> dependencyLocators = ServiceLoader.load(moduleLayerManager.getLayer(IModuleLayerManager.Layer.SERVICE).orElseThrow(), IDependencyLocator.class);
List<IModLocator> modLocatorList = ServiceLoaderUtils.streamWithErrorHandling(modLocators, sce -> LOGGER.error("Failed to load mod locator list", sce)).toList();
modLocators = ServiceLoader.load(moduleLayerManager.getLayer(IModuleLayerManager.Layer.SERVICE).orElseThrow(), IModLocator.class);
dependencyLocators = ServiceLoader.load(moduleLayerManager.getLayer(IModuleLayerManager.Layer.SERVICE).orElseThrow(), IDependencyLocator.class);
modLocatorList = ServiceLoaderUtils.streamServiceLoader(()-> modLocators, sce->LOGGER.error("Failed to load mod locator list", sce)).collect(Collectors.toList());
for (IModLocator iModLocator : modLocatorList) {
iModLocator.initArguments(arguments);
}
List<IDependencyLocator> dependencyLocatorList = ServiceLoaderUtils.streamWithErrorHandling(dependencyLocators, sce -> LOGGER.error("Failed to load dependency locator list", sce)).toList();
dependencyLocatorList = ServiceLoaderUtils.streamServiceLoader(()-> dependencyLocators, sce->LOGGER.error("Failed to load dependency locator list", sce)).collect(Collectors.toList());
for (IDependencyLocator l : dependencyLocatorList) {
l.initArguments(arguments);
}
if (LOGGER.isDebugEnabled(LogMarkers.CORE)) {
if (LOGGER.isDebugEnabled(LogMarkers.CORE))
{
LOGGER.debug(LogMarkers.CORE, "Found Mod Locators : {}", modLocatorList.stream()
.map(modLocator -> "(%s:%s)".formatted(modLocator.name(),
modLocator.getClass().getPackage().getImplementationVersion())).collect(Collectors.joining(",")));
.map(modLocator -> "(%s:%s)".formatted(modLocator.name(),
modLocator.getClass().getPackage().getImplementationVersion())).collect(Collectors.joining(",")));
LOGGER.debug(LogMarkers.CORE, "Found Dependency Locators : {}", dependencyLocatorList.stream()
.map(dependencyLocator -> "(%s:%s)".formatted(dependencyLocator.name(),
dependencyLocator.getClass().getPackage().getImplementationVersion())).collect(Collectors.joining(",")));
.map(dependencyLocator -> "(%s:%s)".formatted(dependencyLocator.name(),
dependencyLocator.getClass().getPackage().getImplementationVersion())).collect(Collectors.joining(",")));
}
}
public ModValidator discoverMods() {
LOGGER.debug(LogMarkers.SCAN,"Scanning for mods and other resources to load. We know {} ways to find mods", modLocatorList.size());
List<ModFile> loadedFiles = new ArrayList<>();
List<EarlyLoadingException.ExceptionData> discoveryErrorData = new ArrayList<>();
@@ -111,7 +116,7 @@ public final class ModDiscoverer {
}
LOGGER.debug(LogMarkers.SCAN, "Locator {} found {} valid mod files", locator, locatedFiles.size());
handleLocatedFiles(loadedFiles, locatedFiles, locator);
handleLocatedFiles(loadedFiles, locatedFiles);
} catch (InvalidModFileException imfe) {
// We don't generally expect this exception, since it should come from the candidates stream above and be handled in the Locator, but just in case.
LOGGER.error(LogMarkers.SCAN, "Locator {} found an invalid mod file {}", locator, imfe.getBrokenFile(), imfe);
@@ -125,7 +130,8 @@ public final class ModDiscoverer {
//First processing run of the mod list. Any duplicates will cause resolution failure and dependency loading will be skipped.
Map<IModFile.Type, List<ModFile>> modFilesMap = new EnumMap<>(IModFile.Type.class);
try {
final UniqueModListBuilder.UniqueModListData uniqueModsData = UniqueModListBuilder.buildUniqueList(loadedFiles);
final UniqueModListBuilder modsUniqueListBuilder = new UniqueModListBuilder(loadedFiles);
final UniqueModListBuilder.UniqueModListData uniqueModsData = modsUniqueListBuilder.buildUniqueList();
//Grab the temporary results.
//This allows loading to continue to a base state, in case dependency loading fails.
@@ -145,10 +151,14 @@ public final class ModDiscoverer {
for (IDependencyLocator locator : dependencyLocatorList) {
try {
LOGGER.debug(LogMarkers.SCAN,"Trying locator {}", locator);
final List<IModFile> locatedMods = List.copyOf(loadedFiles);
final List<IModFile> locatedMods = ImmutableList.copyOf(loadedFiles);
var locatedFiles = locator.scanMods(locatedMods);
handleLocatedFiles(loadedFiles, locatedFiles, locator);
if (locatedFiles.stream().anyMatch(file -> !(file instanceof ModFile))) {
LOGGER.error(LogMarkers.SCAN, "A dependency locator returned a file which is not a ModFile instance!. They will be skipped!");
}
handleLocatedFiles(loadedFiles, locatedFiles);
}
catch (EarlyLoadingException exception) {
LOGGER.error(LogMarkers.SCAN, "Failed to load dependencies with locator {}", locator, exception);
@@ -158,7 +168,8 @@ public final class ModDiscoverer {
//Second processing run of the mod list. Any duplicates will cause resolution failure and only the mods list will be loaded.
try {
final UniqueModListBuilder.UniqueModListData uniqueModsAndDependenciesData = UniqueModListBuilder.buildUniqueList(loadedFiles);
final UniqueModListBuilder modsAndDependenciesUniqueListBuilder = new UniqueModListBuilder(loadedFiles);
final UniqueModListBuilder.UniqueModListData uniqueModsAndDependenciesData = modsAndDependenciesUniqueListBuilder.buildUniqueList();
//We now only need the mod files map, not the list.
modFilesMap = uniqueModsAndDependenciesData.modFiles().stream()
@@ -181,14 +192,11 @@ public final class ModDiscoverer {
return validator;
}
private static void handleLocatedFiles(final List<ModFile> loadedFiles, final List<IModFile> locatedFiles, final Object locator) {
for (IModFile mf : locatedFiles) {
if (mf instanceof ModFile modFile) {
LOGGER.info(LogMarkers.SCAN, "Found mod file {} of type {} with provider {}", mf.getFileName(), mf.getType(), mf.getProvider());
loadedFiles.add(modFile);
} else {
LOGGER.error(LogMarkers.SCAN, "Skipping mod file {} found by {}, as it was not a ModFile instance!", mf.getFileName(), locator);
}
private static void handleLocatedFiles(final List<ModFile> loadedFiles, final List<IModFile> locatedFiles) {
var locatedModFiles = locatedFiles.stream().filter(ModFile.class::isInstance).map(ModFile.class::cast).toList();
for (IModFile mf : locatedModFiles) {
LOGGER.info(LogMarkers.SCAN, "Found mod file {} of type {} with provider {}", mf.getFileName(), mf.getType(), mf.getProvider());
}
loadedFiles.addAll(locatedModFiles);
}
}
@@ -21,13 +21,13 @@ import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.slf4j.Logger;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
@@ -39,13 +39,14 @@ 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;
@@ -61,11 +62,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
@@ -97,13 +99,11 @@ public class ModFile implements IModFile {
}
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());
LOGGER.debug(LogMarkers.LOADING,"Loading mod file {} with languages {}", this.getFilePath(), this.modFileInfo.requiredLanguageLoaders());
this.coreMods = ModFileParser.getCoreMods(this);
if (!this.coreMods.isEmpty())
LOGGER.warn("Mod file {} contains javascript coremods! JS CoreMods are deprecated and will be removed in a future release. Consider using Mixin or ModLauncher transformers.", this.getFileName());
for (var mi : this.coreMods)
LOGGER.debug(LogMarkers.LOADING, "Found coremod {}", mi.getPath());
this.coreMods.forEach(mi-> LOGGER.debug(LogMarkers.LOADING,"Found coremod {}", mi.getPath()));
this.accessTransformer = findResource("META-INF", "accesstransformer.cfg");
return true;
}
@@ -179,28 +179,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
@@ -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
@@ -17,6 +17,7 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
@@ -81,10 +81,6 @@ class ArgumentList {
.toArray(String[]::new);
}
public boolean has(String key) {
return this.values.containsKey(key);
}
public boolean hasValue(String key) {
return getOrDefault(key, null) != null;
}
@@ -22,10 +22,6 @@ abstract class CommonDevLaunchHandler extends CommonLaunchHandler {
super(type, prefix);
}
protected CommonDevLaunchHandler(String name) {
super(name);
}
@Override public String getNaming() { return "mcp"; }
@Override public boolean isProduction() { return false; }
@@ -33,35 +29,13 @@ abstract class CommonDevLaunchHandler extends CommonLaunchHandler {
protected String[] preLaunch(String[] arguments, ModuleLayer layer) {
super.preLaunch(arguments, layer);
var args = ArgumentList.from(arguments);
if (this.module == null) {
var entry = args.get("launchEntry");
if (entry == null)
throw new IllegalArgumentException("When using " + this.name() + " launch target you must specify a --launchEntry argument");
args.remove("launchEntry");
int idx = entry.indexOf('/');
if (idx == -1) {
this.module = "minecraft";
this.main = entry;
} else {
this.module = entry.substring(0, idx);
this.main = entry.substring(idx + 1);
}
if (args.has("launchData")) {
args.remove("launchData");
this.isData = true;
}
}
if (getDist().isDedicatedServer())
return args.getArguments();
return arguments;
if (isData())
return args.getArguments();
return arguments;
var args = ArgumentList.from(arguments);
String username = args.get("username");
if (username != null) { // Replace '#' placeholders with random numbers
@@ -29,27 +29,17 @@ import java.util.List;
public abstract class CommonLaunchHandler implements ILaunchHandlerService {
protected static final Logger LOGGER = LogUtils.getLogger();
private final String name;
protected Dist dist = Dist.CLIENT;
protected boolean isData = false;
protected String module;
protected String main;
protected final LaunchType type;
private final String prefix;
protected CommonLaunchHandler(LaunchType type, String prefix) {
this(prefix + type.name());
this.dist = type.dist();
this.isData = type.data();
this.module = type.module();
this.main = type.main();
this.type = type;
this.prefix = prefix;
}
protected CommonLaunchHandler(String name) {
this.name = name;
}
@Override public String name() { return this.name; }
public Dist getDist() { return this.dist; }
public boolean isData() { return this.isData; }
@Override public String name() { return this.prefix + this.type.name(); }
public Dist getDist() { return this.type.dist(); }
public boolean isData() { return this.type.data(); }
public boolean isProduction() { return false; }
public abstract String getNaming();
@@ -76,15 +66,14 @@ public abstract class CommonLaunchHandler implements ILaunchHandlerService {
}
protected ServiceRunner makeService(final String[] arguments, final ModuleLayer gameLayer) {
return () -> runTarget(this.module, this.main, arguments, gameLayer);
return () -> runTarget(this.type.module(), this.type.main(), arguments, gameLayer);
}
protected record LaunchType(String name, String module, String main, Dist dist, boolean data) {};
protected static final LaunchType CLIENT = new LaunchType("client", "minecraft", "net.minecraft.client.main.Main", Dist.CLIENT, false);
protected static final LaunchType CLIENT_DATA = new LaunchType("client_data", "minecraft", "net.minecraft.client.data.Main", Dist.CLIENT, true);
protected static final LaunchType DATA = new LaunchType("data", "minecraft", "net.minecraft.data.Main", Dist.CLIENT, true);
protected static final LaunchType SERVER = new LaunchType("server", "minecraft", "net.minecraft.server.Main", Dist.DEDICATED_SERVER, false);
protected static final LaunchType SERVER_GAMETEST = new LaunchType("server_gametest", "minecraft", "net.minecraft.gametest.Main", Dist.DEDICATED_SERVER, false);
protected static final LaunchType SERVER_GAMETEST = new LaunchType("server_gametest", "net.minecraftforge.forge", "net.minecraftforge.gametest.GameTestMain", Dist.DEDICATED_SERVER, false);
protected void runTarget(String module, String target, final String[] arguments, final ModuleLayer layer) throws Throwable {
var mod = layer.findModule(module).orElse(null);
@@ -14,14 +14,12 @@ sealed abstract class ForgeDevLaunchHandler extends CommonDevLaunchHandler {
super(type, "forge_dev_");
}
private ForgeDevLaunchHandler(String name) {
super(name);
}
@Override
public List<Path> getMinecraftPaths() {
// The client extra jar is on the classpath we can try locating it using the .mcassetsroot which vanilla uses for loading data
var extra = getPathFromResource("assets/.mcassetsroot");
// The extra jar is on the classpath, so try and pull it out of the legacy classpath
var legacyCP = findClassPath();
var extra = findJarOnClasspath(legacyCP, "client-extra");
// Minecraft is an exploded directory, so find it.
var minecraft = getPathFromResource("net/minecraft/client/Minecraft.class");
var forge = getPathFromResource("net/minecraftforge/common/MinecraftForge.class");
@@ -41,12 +39,6 @@ sealed abstract class ForgeDevLaunchHandler extends CommonDevLaunchHandler {
}
}
public static final class ClientData extends ForgeDevLaunchHandler {
public ClientData() {
super(CLIENT_DATA);
}
}
public static final class Data extends ForgeDevLaunchHandler {
public Data() {
super(DATA);
@@ -64,10 +56,4 @@ sealed abstract class ForgeDevLaunchHandler extends CommonDevLaunchHandler {
super(SERVER_GAMETEST);
}
}
public static final class Custom extends ForgeDevLaunchHandler {
public Custom() {
super("forge_dev");
}
}
}
@@ -96,14 +96,14 @@ public final class ForgeDevLocator extends AbstractModProvider implements IModLo
// Find resource directories for every mod in this group
var paths = new LinkedHashSet<Path>();
for (var modid : modids.stream().sorted().toList()) {
for (var modid : modids) {
var rsc = path.resolve(modid);
if (Files.exists(rsc))
paths.add(rsc);
}
var root = memory.getPath(modids.iterator().next()); // use the first modid as our root
buildModsToml(paths, modids, root, pkg.startsWith("net/minecraftforge/debug/client/"));
buildModsToml(paths, modids, root);
buildPackMeta(paths, root);
moveModuleInfo(paths, root);
@@ -187,7 +187,7 @@ public final class ForgeDevLocator extends AbstractModProvider implements IModLo
}
// Builds or update the mods.toml file for all @Mods in this package
private static void buildModsToml(Set<Path> resources, Set<String> modids, Path root, boolean clientOnly) {
private static void buildModsToml(Set<Path> resources, Set<String> modids, Path root) {
var toml = resources.stream()
.map(p -> p.resolve(MODS_TOML))
.filter(Files::exists)
@@ -205,8 +205,7 @@ public final class ForgeDevLocator extends AbstractModProvider implements IModLo
var defaults = Map.of(
"modLoader", "javafml",
"loaderVersion", "[0,)",
"license", "Doesnt fucking matter",
"clientSideOnly", clientOnly
"license", "Doesnt fucking matter"
);
for (var key : defaults.keySet()) {
@@ -8,18 +8,17 @@ package net.minecraftforge.fml.loading.targets;
import net.minecraftforge.fml.loading.FMLLoader;
import java.nio.file.Path;
import java.util.List;
import java.util.function.BiPredicate;
import org.jetbrains.annotations.ApiStatus;
import cpw.mods.jarhandling.SecureJar;
@ApiStatus.Internal
sealed abstract class ForgeUserdevLaunchHandler extends CommonDevLaunchHandler {
private ForgeUserdevLaunchHandler(LaunchType type) {
super(type, "forge_userdev_");
}
private ForgeUserdevLaunchHandler(String name) {
super(name);
}
@Override
public List<Path> getMinecraftPaths() {
var legacyCP = findClassPath();
@@ -27,7 +26,7 @@ sealed abstract class ForgeUserdevLaunchHandler extends CommonDevLaunchHandler {
// Minecraft is extra jar {resources} + forge jar {patches}
// The MC extra and forge jars are on the classpath, so try and pull them out
var extra = findJarOnClasspath(legacyCP, "client-extra"); // This should be "client-" + vers.mcAndMCPVersion() + "-extra" but FG6 qwerks
var extra = findJarOnClasspath(legacyCP, "client-extra");
var forge = findJarOnClasspath(legacyCP, "forge-" + vers.mcAndForgeVersion());
// We need to filter the forge jar to just MC code
var minecraft = CommonDevLaunchHandler.getMinecraftOnly(extra, forge);
@@ -40,12 +39,6 @@ sealed abstract class ForgeUserdevLaunchHandler extends CommonDevLaunchHandler {
}
}
public static final class ClientData extends ForgeUserdevLaunchHandler {
public ClientData() {
super(CLIENT_DATA);
}
}
public static final class Data extends ForgeUserdevLaunchHandler {
public Data() {
super(DATA);
@@ -63,10 +56,4 @@ sealed abstract class ForgeUserdevLaunchHandler extends CommonDevLaunchHandler {
super(SERVER_GAMETEST);
}
}
public static final class Custom extends ForgeUserdevLaunchHandler {
public Custom() {
super("forge_userdev");
}
}
}
@@ -4,16 +4,12 @@ net.minecraftforge.fml.loading.targets.ForgeProdLaunchHandler$Server
# Mod Developers
net.minecraftforge.fml.loading.targets.ForgeUserdevLaunchHandler$Client
net.minecraftforge.fml.loading.targets.ForgeUserdevLaunchHandler$ClientData
net.minecraftforge.fml.loading.targets.ForgeUserdevLaunchHandler$Custom
net.minecraftforge.fml.loading.targets.ForgeUserdevLaunchHandler$Data
net.minecraftforge.fml.loading.targets.ForgeUserdevLaunchHandler$Server
net.minecraftforge.fml.loading.targets.ForgeUserdevLaunchHandler$ServerGameTest
#Working on Forge itself
net.minecraftforge.fml.loading.targets.ForgeDevLaunchHandler$Client
net.minecraftforge.fml.loading.targets.ForgeDevLaunchHandler$ClientData
net.minecraftforge.fml.loading.targets.ForgeDevLaunchHandler$Custom
net.minecraftforge.fml.loading.targets.ForgeDevLaunchHandler$Data
net.minecraftforge.fml.loading.targets.ForgeDevLaunchHandler$Server
net.minecraftforge.fml.loading.targets.ForgeDevLaunchHandler$ServerGameTest
@@ -1,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.0,)",
"artifactVersion": "0.5.0"
},
"path": "",
"isObfuscated": false
}
],
"meta": {
"identifier": {
"group": "io.github.llamalad7",
"artifact": "mixinextras-forge"
},
"version": {
"artifactVersion": "0.5.0"
},
"path": "",
"isObfuscated": false
},
"nested": true
}
]
}
+1 -1
View File
@@ -4,7 +4,7 @@
SPDX-License-Identifier: LGPL-2.1-only
-->
<Configuration status="warn" shutdownHook="disable">
<Configuration status="warn" packages="net.minecrell.terminalconsole,net.minecrell.terminalconsole.util,com.mojang.util,cpw.mods.modlauncher.log,net.minecraftforge.fml.loading.log4j" shutdownHook="disable">
<filters>
<ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
<MarkerFilter marker="NETWORK_PACKETS" onMatch="${sys:forge.logging.marker.networking:-DENY}" onMismatch="NEUTRAL"/>
-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) // Used for ASMAPI (for now)
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,78 +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 net.minecraftforge.coremod.api.ASMAPI;
import org.jetbrains.annotations.NotNull;
import org.objectweb.asm.tree.ClassNode;
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> {
// 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()) {
ASMAPI.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));
}
}
@@ -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,142 +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 net.minecraftforge.coremod.api.ASMAPI;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodInsnNode;
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
ASMAPI.MethodType.VIRTUAL,
"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 -> ASMAPI.buildMethodCall(
ASMAPI.MethodType.STATIC,
"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;"
)
)
};
private static <T> T sneak(Callable<T> callable) {
try {
return callable.call();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
record Replacement(
ASMAPI.MethodType type,
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 = ASMAPI.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);
ASMAPI.insertInsn(method, methodInsn, redirection, ASMAPI.InsertMode.REMOVE_ORIGINAL);
}
}
}
return clazz;
}
private static boolean shouldReplace(AbstractInsnNode insn, Replacement replacement) {
return insn.getOpcode() == replacement.type.toOpcode()
&& 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
@@ -1,168 +0,0 @@
[
{
"class": "net/minecraft/gametest/framework/GameTestHelper",
"methods": [
"spawn(Lnet/minecraft/world/entity/EntityType;Lnet/minecraft/world/phys/Vec3;Lnet/minecraft/world/entity/EntitySpawnReason;)Lnet/minecraft/world/entity/Entity;"
]
},
{
"class": "net/minecraft/server/commands/RaidCommand",
"methods": [
"spawnLeader(Lnet/minecraft/commands/CommandSourceStack;)I"
]
},
{
"class": "net/minecraft/server/commands/SummonCommand",
"methods": [
"createEntity(Lnet/minecraft/commands/CommandSourceStack;Lnet/minecraft/core/Holder$Reference;Lnet/minecraft/world/phys/Vec3;Lnet/minecraft/nbt/CompoundTag;Z)Lnet/minecraft/world/entity/Entity;"
]
},
{
"class": "net/minecraft/world/entity/EntityType",
"methods": [
"create(Lnet/minecraft/server/level/ServerLevel;Ljava/util/function/Consumer;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/entity/EntitySpawnReason;ZZ)Lnet/minecraft/world/entity/Entity;"
]
},
{
"class": "net/minecraft/world/entity/ai/village/VillageSiege",
"methods": [
"trySpawn(Lnet/minecraft/server/level/ServerLevel;)V"
]
},
{
"class": "net/minecraft/world/entity/animal/equine/SkeletonTrapGoal",
"methods": [
"createHorse(Lnet/minecraft/world/DifficultyInstance;)Lnet/minecraft/world/entity/animal/equine/AbstractHorse;",
"createSkeleton(Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/animal/equine/AbstractHorse;)Lnet/minecraft/world/entity/monster/skeleton/Skeleton;"
]
},
{
"class": "net/minecraft/world/entity/animal/equine/ZombieHorse",
"methods": [
"finalizeSpawn(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/EntitySpawnReason;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;"
]
},
{
"class": "net/minecraft/world/entity/animal/frog/Tadpole",
"methods": [
]
},
{
"class": "net/minecraft/world/entity/monster/Strider",
"methods": [
"spawnJockey(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/Mob;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;"
]
},
{
"class": "net/minecraft/world/entity/monster/illager/Evoker$EvokerSummonSpellGoal",
"methods": [
"performSpellCasting()V"
]
},
{
"class": "net/minecraft/world/entity/monster/spider/Spider",
"methods": [
"finalizeSpawn(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/EntitySpawnReason;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;"
]
},
{
"class": "net/minecraft/world/entity/monster/zombie/Drowned",
"methods": [
"finalizeSpawn(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/EntitySpawnReason;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;"
]
},
{
"class": "net/minecraft/world/entity/monster/zombie/Husk",
"methods": [
"finalizeSpawn(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/EntitySpawnReason;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;"
]
},
{
"class": "net/minecraft/world/entity/monster/zombie/Zombie",
"methods": [
"hurtServer(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/damagesource/DamageSource;F)Z",
"finalizeSpawn(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/EntitySpawnReason;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;"
]
},
{
"class": "net/minecraft/world/entity/monster/zombie/ZombieVillager",
"methods": [
]
},
{
"class": "net/minecraft/world/entity/npc/CatSpawner",
"methods": [
"spawnCat(Lnet/minecraft/core/BlockPos;Lnet/minecraft/server/level/ServerLevel;Z)V"
]
},
{
"class": "net/minecraft/world/entity/npc/villager/Villager",
"methods": [
"getBreedOffspring(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/entity/AgeableMob;)Lnet/minecraft/world/entity/npc/villager/Villager;"
]
},
{
"class": "net/minecraft/world/entity/raid/Raid",
"methods": [
"joinRaid(Lnet/minecraft/server/level/ServerLevel;ILnet/minecraft/world/entity/raid/Raider;Lnet/minecraft/core/BlockPos;Z)V"
]
},
{
"class": "net/minecraft/world/level/NaturalSpawner",
"methods": [
"spawnCategoryForPosition(Lnet/minecraft/world/entity/MobCategory;Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/level/chunk/ChunkAccess;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/NaturalSpawner$SpawnPredicate;Lnet/minecraft/world/level/NaturalSpawner$AfterSpawnCallback;)V",
"spawnMobsForChunkGeneration(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/core/Holder;Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/util/RandomSource;)V"
]
},
{
"class": "net/minecraft/world/level/block/entity/trialspawner/TrialSpawner",
"methods": [
"spawnMob(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/core/BlockPos;)Ljava/util/Optional;"
]
},
{
"class": "net/minecraft/world/level/levelgen/PatrolSpawner",
"methods": [
"spawnPatrolMember(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/core/BlockPos;Lnet/minecraft/util/RandomSource;Z)Z"
]
},
{
"class": "net/minecraft/world/level/levelgen/PhantomSpawner",
"methods": [
"tick(Lnet/minecraft/server/level/ServerLevel;Z)V"
]
},
{
"class": "net/minecraft/world/level/levelgen/structure/structures/OceanMonumentPieces$OceanMonumentPiece",
"methods": [
"spawnElder(Lnet/minecraft/world/level/WorldGenLevel;Lnet/minecraft/world/level/levelgen/structure/BoundingBox;III)V"
]
},
{
"class": "net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces$OceanRuinPiece",
"methods": [
"handleDataMarker(Ljava/lang/String;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/util/RandomSource;Lnet/minecraft/world/level/levelgen/structure/BoundingBox;)V"
]
},
{
"class": "net/minecraft/world/level/levelgen/structure/structures/SwampHutPiece",
"methods": [
"postProcess(Lnet/minecraft/world/level/WorldGenLevel;Lnet/minecraft/world/level/StructureManager;Lnet/minecraft/world/level/chunk/ChunkGenerator;Lnet/minecraft/util/RandomSource;Lnet/minecraft/world/level/levelgen/structure/BoundingBox;Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/core/BlockPos;)V",
"spawnCat(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/level/levelgen/structure/BoundingBox;)V"
]
},
{
"class": "net/minecraft/world/level/levelgen/structure/structures/WoodlandMansionPieces$WoodlandMansionPiece",
"methods": [
"handleDataMarker(Ljava/lang/String;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/util/RandomSource;Lnet/minecraft/world/level/levelgen/structure/BoundingBox;)V"
]
},
{
"class": "net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate",
"methods": [
]
}
]
+7 -7
View File
@@ -1,17 +1,17 @@
# Sets default memory used for gradle commands. Can be overridden by user or command line properties.
# This is required to provide enough memory for the Minecraft decompilation process.
org.gradle.jvmargs=-Xmx6G
org.gradle.jvmargs=-Xmx3G
org.gradle.daemon=false
org.gradle.parallel=true
JAVA_VERSION=21
MC_VERSION=1.21.11
MC_VERSION=1.21.1
MC_NEXT_VERSION=1.22
MCP_VERSION=20251209.095502
MCP_VERSION=20240808.132146
MAPPING_CHANNEL=official
MAPPING_VERSION=1.21.11
MAPPING_VERSION=1.21.1
# Set to true before the first build of a new MC version, so we don't do compatibility checks
// Set to true before the first build of a new MC version, so we don't do compatibility checks
CHECK_COMPATIBILITY=false
# Set to true to allow for fuzzy patching. Useful during updateing
UPDATING=false
// Set to true to allow for fuzzy patching. Useful during updateing
UPDATING=false
+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,12 +5,14 @@
package net.minecraftforge.fml.javafmlmod;
import net.minecraftforge.eventbus.api.bus.BusGroup;
import net.minecraftforge.eventbus.api.bus.EventBus;
import net.minecraftforge.eventbus.EventBusErrorMessage;
import net.minecraftforge.eventbus.api.BusBuilder;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.eventbus.api.IEventListener;
import net.minecraftforge.fml.ModContainer;
import net.minecraftforge.fml.ModLoadingException;
import net.minecraftforge.fml.ModLoadingStage;
import net.minecraftforge.fml.config.IConfigEvent;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.forgespi.language.ModFileScanData;
@@ -27,13 +29,14 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.Optional;
import java.util.jar.Attributes;
public class FMLModContainer extends ModContainer {
private static final Logger LOGGER = LogManager.getLogger();
private static final Marker LOADING = MarkerManager.getMarker("LOADING");
private final ModFileScanData scanResults;
private final BusGroup eventBusGroup;
private final IEventBus eventBus;
private Object modInstance;
private final Class<?> modClass;
private final FMLJavaModLoadingContext context = new FMLJavaModLoadingContext(this);
@@ -43,13 +46,13 @@ public class FMLModContainer extends ModContainer {
LOGGER.debug(LOADING,"Creating FMLModContainer instance for {}", className);
this.scanResults = modFileScanResults;
activityMap.put(ModLoadingStage.CONSTRUCT, this::constructMod);
this.eventBusGroup = BusGroup.create("modBusFor" + info.getModId(), IModBusEvent.class);
this.eventBus = BusBuilder.builder().setExceptionHandler(FMLModContainer::onEventFailed).setTrackPhases(false).markerType(IModBusEvent.class).useModLauncher().build();
this.configHandler = Optional.of(ce->this.eventBus.post(ce.self()));
this.contextExtension = () -> context;
try {
var moduleName = info.getOwningFile().moduleName();
var module = gameLayer.findModule(moduleName).orElse(null);
if (module == null)
throw new IllegalStateException("Failed to find " + moduleName + " in " + gameLayer);
var module = gameLayer.findModule(moduleName)
.orElseThrow(() -> new IllegalStateException("Failed to find " + moduleName + " in " + gameLayer));
openModules(gameLayer, module, info.getOwningFile().getFile().getSecureJar());
@@ -126,6 +129,10 @@ public class FMLModContainer extends ModContainer {
implAddExportsOrOpens.invoke(target, pkg, reader, open, /*syncVM*/true);
}
private static void onEventFailed(IEventBus iEventBus, Event event, IEventListener[] iEventListeners, int i, Throwable throwable) {
LOGGER.error(new EventBusErrorMessage(event, i, iEventListeners, throwable));
}
private void constructMod() {
try {
LOGGER.trace(LOADING, "Loading mod instance {} of type {}", getModId(), modClass.getName());
@@ -167,34 +174,19 @@ public class FMLModContainer extends ModContainer {
return modInstance;
}
@Override
public BusGroup getModBusGroup() {
return this.eventBusGroup;
public IEventBus getEventBus() {
return this.eventBus;
}
@Override
protected <T extends IModBusEvent> void acceptEvent(final T e) {
protected <T extends Event & IModBusEvent> void acceptEvent(final T e) {
try {
LOGGER.trace(LOADING, "Firing event for modid {} : {}", this.getModId(), e);
@SuppressWarnings("unchecked")
var eventBus = (EventBus<T>) IModBusEvent.getBus(eventBusGroup, e.getClass());
eventBus.post(e);
this.eventBus.post(e);
LOGGER.trace(LOADING, "Fired event for modid {} : {}", this.getModId(), e);
} catch (Throwable t) {
LOGGER.error(LOADING,"Caught exception during event {} dispatch for modid {}", e, this.getModId(), t);
throw new ModLoadingException(modInfo, modLoadingStage, "fml.modloading.errorduringevent", t);
}
}
@Override
@SuppressWarnings({"rawtypes", "unchecked"})
public void dispatchConfigEvent(IConfigEvent event) {
var eventBus = (EventBus) EventBus.create(eventBusGroup, event.self().getClass());
eventBus.post(event.self());
}
@Override
public String toString() {
return "FMLModContainer[" + this.getModInfo().getModId() + ", " + this.getClass().getName() + ']';
}
}
+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);
}
}
+22 -41
View File
@@ -2,7 +2,7 @@ plugins {
id 'eclipse'
id 'idea'
id 'maven-publish'
id 'net.minecraftforge.gradle' version '[6.0.46,6.2)'
id 'net.minecraftforge.gradle' version '[6.0.24,6.2)'
}
version = mod_version
@@ -22,13 +22,17 @@ minecraft {
// official MCVersion Official field/method names from Mojang mapping files
// parchment YYYY.MM.DD-MCVersion Open community-sourced parameter names and javadocs layered on top of official
//
// You must be aware of the Mojang license when using the 'official' or 'parchment' mappings.
// See more information here: https://github.com/MinecraftForge/MCPConfig/blob/master/Mojang.md
//
// Parchment is an unofficial project maintained by ParchmentMC, separate from MinecraftForge
// Additional setup is needed to use their mappings: https://parchmentmc.org/docs/getting-started
//
// Use non-default mappings at your own risk. They may not always work.
// Simply re-run your setup task after changing the mappings to update your workspace.
mappings channel: mapping_channel, version: mapping_version
// Forge 1.20.6 and newer use official mappings at runtime, so we shouldn't reobf from official to SRG
// Tell FG to not automtically create the reobf tasks, as we now use Official mappings at runtime, If you don't use them at dev time then you'll have to fix your reobf yourself.
reobf = false
// When true, this property will have all Eclipse/IntelliJ IDEA run configurations run the "prepareX" task for the given run configuration before launching the game.
@@ -46,7 +50,8 @@ minecraft {
// By default, the folder name of a run configuration is the name of the Gradle project containing it.
// generateRunFolders = true
// This property enables access transformers for use in development, applied to the Minecraft artifact.
// This property enables access transformers for use in development.
// They will be applied to the Minecraft artifact.
// The access transformer file can be anywhere in the project.
// However, it must be at "META-INF/accesstransformer.cfg" in the final mod jar to be loaded by Forge.
// This default location is a best practice to automatically put the file in the right place in the final jar.
@@ -60,18 +65,17 @@ minecraft {
configureEach {
workingDirectory project.file('run')
// Optional additional logging. The markers can be added/remove as needed, separated by commas.
// Recommended logging data for a userdev environment
// The markers can be added/remove as needed separated by commas.
// "SCAN": For mods scan.
// "REGISTRIES": For firing of registry events.
// "REGISTRYDUMP": For getting the contents of all registries.
// property 'forge.logging.markers', 'REGISTRIES'
property 'forge.logging.markers', 'REGISTRIES'
// Recommended logging level for the console
// You can set various levels here.
// Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels
property 'forge.logging.console.level', 'debug'
// Recommended for development - enables more descriptive errors at the cost of slower startup and registration.
property 'eventbus.api.strictRuntimeChecks', 'true'
// arg "-mixin.config=${mod_id}.mixins.json"
}
client {
@@ -106,26 +110,7 @@ sourceSets.main.resources { srcDir 'src/generated/resources' }
repositories {
// Put repositories for dependencies here
mavenCentral()
maven {
name = 'Forge'
url = 'https://maven.minecraftforge.net'
}
maven {
name = 'Minecraft libraries'
url = 'https://libraries.minecraft.net'
}
exclusiveContent {
forRepository {
maven {
name = 'Sponge'
url = 'https://repo.spongepowered.org/repository/maven-public'
}
}
filter {
includeGroupAndSubgroups('org.spongepowered')
}
}
// ForgeGradle automatically adds the Forge maven and Maven Central for you
// If you have mod jar dependencies in ./libs, you can declare them as a repository like so.
// See https://docs.gradle.org/current/userguide/declaring_repositories.html#sub:flat_dir_resolver
@@ -142,11 +127,6 @@ dependencies {
// then special handling is done to allow a setup of a vanilla dependency without the use of an external repository.
minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}"
// Forge 1.21.6+ uses EventBus 7, which shifts most of its runtime validation to compile-time via an annotation processor
// to improve performance in production environments. This line is required to enable said compile-time validation
// in your development environment, helping you catch issues early.
annotationProcessor 'net.minecraftforge:eventbus-validator:7.0-beta.12'
// Example mod dependency with JEI
// The JEI API is declared for compile time use, while the full JEI artifact is used at runtime
// compileOnly "mezz.jei:jei-${mc_version}-common-api:${jei_version}"
@@ -167,7 +147,7 @@ dependencies {
// A missing property will result in an error. Properties are expanded using ${} Groovy notation.
// When "copyIdeResources" is enabled, this will also run before the game launches in IDE environments.
// See https://docs.gradle.org/current/dsl/org.gradle.language.jvm.tasks.ProcessResources.html
tasks.named('processResources', ProcessResources) {
tasks.named('processResources', ProcessResources).configure {
var replaceProperties = [
minecraft_version: minecraft_version, minecraft_version_range: minecraft_version_range,
forge_version: forge_version, forge_version_range: forge_version_range,
@@ -183,7 +163,7 @@ tasks.named('processResources', ProcessResources) {
}
// Example for how to get properties into the manifest for reading at runtime.
tasks.named('jar', Jar) {
tasks.named('jar', Jar).configure {
manifest {
attributes([
'Specification-Title' : mod_id,
@@ -193,7 +173,6 @@ tasks.named('jar', Jar) {
'Implementation-Version' : project.jar.archiveVersion,
'Implementation-Vendor' : mod_authors
])
// attributes['MixinConfigs'] = "${mod_id}.mixins.json"
}
}
@@ -215,7 +194,8 @@ tasks.withType(JavaCompile).configureEach {
options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation
}
// IntelliJ no longer downloads javadocs and sources by default, this tells Gradle to force IntelliJ to do it.
// IntelliJ no longer downloads javadocs and sources by default.
// This tells Gradle to force IDEA to do it.
idea.module { downloadJavadoc = downloadSources = true }
eclipse {
@@ -225,7 +205,8 @@ eclipse {
synchronizationTasks 'genEclipseRuns'
}
// Merge the resources and classes into the same directory, because Java expects modules to be in a single directory.
// Merge the resources and classes into the same directory.
// This is done because java expects modules to be in a single directory.
// And if we have it in multiple we have to do performance intensive hacks like having the UnionFileSystem
// This will eventually be migrated to ForgeGradle so modders don't need to manually do it. But that is later.
sourceSets.each {
+2 -9
View File
@@ -1,15 +1,8 @@
# Sets default memory used for Gradle commands. Can be overridden by user or command line properties.
# Sets default memory used for gradle commands. Can be overridden by user or command line properties.
# This is required to provide enough memory for the Minecraft decompilation process.
org.gradle.jvmargs=-Xmx5G
org.gradle.jvmargs=-Xmx3G
org.gradle.daemon=false
# In the case that Gradle needs to fork to recompile, this will set the memory for that process.
systemProp.net.minecraftforge.gradle.repo.recompile.fork=true
systemProp.net.minecraftforge.gradle.repo.recompile.fork.args=-Xmx5G
# Opts-out of ForgeGradle automatically adding mavenCentral(), Forge's maven and MC libs maven to the repositories block
systemProp.net.minecraftforge.gradle.repo.attach=false
## Environment Properties
+2 -2
View File
@@ -9,5 +9,5 @@ pluginManagement {
}
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.10.0'
}
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0'
}
@@ -1,13 +1,14 @@
package com.example.examplemod;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import net.minecraftforge.common.ForgeConfigSpec;
import net.minecraftforge.eventbus.api.listener.SubscribeEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.config.ModConfigEvent;
import net.minecraftforge.registries.ForgeRegistries;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@@ -15,7 +16,8 @@ import java.util.stream.Collectors;
// An example config class. This is not required, but it's a good idea to have one to keep your config organized.
// Demonstrates how to use Forge's config APIs
@Mod.EventBusSubscriber(modid = ExampleMod.MODID, bus = Mod.EventBusSubscriber.Bus.MOD)
public class Config {
public class Config
{
private static final ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder();
private static final ForgeConfigSpec.BooleanValue LOG_DIRT_BLOCK = BUILDER
@@ -42,19 +44,21 @@ public class Config {
public static String magicNumberIntroduction;
public static Set<Item> items;
private static boolean validateItemName(final Object obj) {
return obj instanceof final String itemName && ForgeRegistries.ITEMS.containsKey(Identifier.tryParse(itemName));
private static boolean validateItemName(final Object obj)
{
return obj instanceof final String itemName && ForgeRegistries.ITEMS.containsKey(ResourceLocation.tryParse(itemName));
}
@SubscribeEvent
static void onLoad(final ModConfigEvent event) {
static void onLoad(final ModConfigEvent event)
{
logDirtBlock = LOG_DIRT_BLOCK.get();
magicNumber = MAGIC_NUMBER.get();
magicNumberIntroduction = MAGIC_NUMBER_INTRODUCTION.get();
// convert the list of strings into a set of items
items = ITEM_STRINGS.get().stream()
.map(itemName -> ForgeRegistries.ITEMS.getValue(Identifier.tryParse(itemName)))
.map(itemName -> ForgeRegistries.ITEMS.getValue(ResourceLocation.tryParse(itemName)))
.collect(Collectors.toSet());
}
}
@@ -13,8 +13,11 @@ import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.material.MapColor;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.BuildCreativeModeTabContentsEvent;
import net.minecraftforge.eventbus.api.listener.SubscribeEvent;
import net.minecraftforge.event.server.ServerStartingEvent;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.config.ModConfig;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
@@ -27,7 +30,8 @@ import org.slf4j.Logger;
// The value here should match an entry in the META-INF/mods.toml file
@Mod(ExampleMod.MODID)
public final class ExampleMod {
public class ExampleMod
{
// Define mod id in a common place for everything to reference
public static final String MODID = "examplemod";
// Directly reference a slf4j logger
@@ -40,29 +44,13 @@ public final class ExampleMod {
public static final DeferredRegister<CreativeModeTab> CREATIVE_MODE_TABS = DeferredRegister.create(Registries.CREATIVE_MODE_TAB, MODID);
// Creates a new Block with the id "examplemod:example_block", combining the namespace and path
public static final RegistryObject<Block> EXAMPLE_BLOCK = BLOCKS.register("example_block",
() -> new Block(BlockBehaviour.Properties.of()
.setId(BLOCKS.key("example_block"))
.mapColor(MapColor.STONE)
)
);
public static final RegistryObject<Block> EXAMPLE_BLOCK = BLOCKS.register("example_block", () -> new Block(BlockBehaviour.Properties.of().mapColor(MapColor.STONE)));
// Creates a new BlockItem with the id "examplemod:example_block", combining the namespace and path
public static final RegistryObject<Item> EXAMPLE_BLOCK_ITEM = ITEMS.register("example_block",
() -> new BlockItem(EXAMPLE_BLOCK.get(), new Item.Properties().setId(ITEMS.key("example_block")))
);
public static final RegistryObject<Item> EXAMPLE_BLOCK_ITEM = ITEMS.register("example_block", () -> new BlockItem(EXAMPLE_BLOCK.get(), new Item.Properties()));
// Creates a new food item with the id "examplemod:example_id", nutrition 1 and saturation 2
public static final RegistryObject<Item> EXAMPLE_ITEM = ITEMS.register("example_item",
() -> new Item(new Item.Properties()
.setId(ITEMS.key("example_item"))
.food(new FoodProperties.Builder()
.alwaysEdible()
.nutrition(1)
.saturationModifier(2f)
.build()
)
)
);
public static final RegistryObject<Item> EXAMPLE_ITEM = ITEMS.register("example_item", () -> new Item(new Item.Properties().food(new FoodProperties.Builder()
.alwaysEdible().nutrition(1).saturationModifier(2f).build())));
// Creates a creative tab with the id "examplemod:example_tab" for the example item, that is placed after the combat tab
public static final RegistryObject<CreativeModeTab> EXAMPLE_TAB = CREATIVE_MODE_TABS.register("example_tab", () -> CreativeModeTab.builder()
@@ -72,27 +60,32 @@ public final class ExampleMod {
output.accept(EXAMPLE_ITEM.get()); // Add the example item to the tab. For your own tabs, this method is preferred over the event
}).build());
public ExampleMod(FMLJavaModLoadingContext context) {
var modBusGroup = context.getModBusGroup();
public ExampleMod(FMLJavaModLoadingContext context)
{
IEventBus modEventBus = context.getModEventBus();
// Register the commonSetup method for modloading
FMLCommonSetupEvent.getBus(modBusGroup).addListener(this::commonSetup);
modEventBus.addListener(this::commonSetup);
// Register the Deferred Register to the mod event bus so blocks get registered
BLOCKS.register(modBusGroup);
BLOCKS.register(modEventBus);
// Register the Deferred Register to the mod event bus so items get registered
ITEMS.register(modBusGroup);
ITEMS.register(modEventBus);
// Register the Deferred Register to the mod event bus so tabs get registered
CREATIVE_MODE_TABS.register(modBusGroup);
CREATIVE_MODE_TABS.register(modEventBus);
// Register ourselves for server and other game events we are interested in
MinecraftForge.EVENT_BUS.register(this);
// Register the item to a creative tab
BuildCreativeModeTabContentsEvent.BUS.addListener(ExampleMod::addCreative);
modEventBus.addListener(this::addCreative);
// Register our mod's ForgeConfigSpec so that Forge can create and load the config file for us
context.registerConfig(ModConfig.Type.COMMON, Config.SPEC);
}
private void commonSetup(final FMLCommonSetupEvent event) {
private void commonSetup(final FMLCommonSetupEvent event)
{
// Some common setup code
LOGGER.info("HELLO FROM COMMON SETUP");
@@ -105,16 +98,27 @@ public final class ExampleMod {
}
// Add the example block item to the building blocks tab
private static void addCreative(BuildCreativeModeTabContentsEvent event) {
private void addCreative(BuildCreativeModeTabContentsEvent event)
{
if (event.getTabKey() == CreativeModeTabs.BUILDING_BLOCKS)
event.accept(EXAMPLE_BLOCK_ITEM);
}
// You can use SubscribeEvent and let the Event Bus discover methods to call
@SubscribeEvent
public void onServerStarting(ServerStartingEvent event)
{
// Do something when the server starts
LOGGER.info("HELLO from server starting");
}
// You can use EventBusSubscriber to automatically register all static methods in the class annotated with @SubscribeEvent
@Mod.EventBusSubscriber(modid = MODID, value = Dist.CLIENT)
public static class ClientModEvents {
@Mod.EventBusSubscriber(modid = MODID, bus = Mod.EventBusSubscriber.Bus.MOD, value = Dist.CLIENT)
public static class ClientModEvents
{
@SubscribeEvent
public static void onClientSetup(FMLClientSetupEvent event) {
public static void onClientSetup(FMLClientSetupEvent event)
{
// Some client setup code
LOGGER.info("HELLO FROM CLIENT SETUP");
LOGGER.info("MINECRAFT NAME >> {}", Minecraft.getInstance().getUser().getName());
@@ -1,12 +0,0 @@
--- a/com/mojang/blaze3d/opengl/GlCommandEncoder.java
+++ b/com/mojang/blaze3d/opengl/GlCommandEncoder.java
@@ -133,6 +_,9 @@
GlStateManager._colorMask(true, true, true, true);
GlStateManager._clear(16384);
GlStateManager._glFramebufferTexture2D(36160, 36064, 3553, 0, 0);
+ // Forge: if using stencil, clear it as well (3553 = GlConst.GL_TEXTURE_2D)
+ if (p_394273_.isStencilEnabled())
+ GlStateManager._glFramebufferTexture2D(GlConst.GL_FRAMEBUFFER, org.lwjgl.opengl.GL30.GL_STENCIL_ATTACHMENT, GlConst.GL_TEXTURE_2D, 0, 0);
GlStateManager._glBindFramebuffer(36160, 0);
}
}
@@ -1,34 +0,0 @@
--- a/com/mojang/blaze3d/opengl/GlDebug.java
+++ b/com/mojang/blaze3d/opengl/GlDebug.java
@@ -24,6 +_,14 @@
@OnlyIn(Dist.CLIENT)
public class GlDebug {
+ /**
+ * TODO: [Forge][Rendering][VEN] Expose this in the configs
+ * <p>
+ * Will enable synchronous OpenGL debug logging, which means the message will be sent from the call that
+ * would have caused the error. Additionally, enables printing a stacktrace when and where this occurs.
+ */
+ private static final boolean PRINT_STACKTRACE_ON_ERROR = Boolean.getBoolean("forge.printGLStackOnError");
+
private static final Logger LOGGER = LogUtils.getLogger();
private static final int CIRCULAR_LOG_SIZE = 10;
private final Queue<GlDebug.LogEntry> MESSAGE_BUFFER = EvictingQueue.create(10);
@@ -105,6 +_,8 @@
}
LOGGER.info("OpenGL debug message: {}", gldebug$logentry);
+ // TODO: [VEN] Trim the stack trace
+ if (PRINT_STACKTRACE_ON_ERROR) LOGGER.info("Trace: ", new Throwable("GlDebug"));
}
public List<String> getLastOpenGlDebugMessages() {
@@ -120,6 +_,7 @@
}
public static @Nullable GlDebug enableDebugCallback(int p_394351_, boolean p_393026_, Set<String> p_393339_) {
+ p_393026_ |= PRINT_STACKTRACE_ON_ERROR;
if (p_394351_ <= 0) {
return null;
} else {
@@ -1,74 +0,0 @@
--- a/com/mojang/blaze3d/opengl/GlDevice.java
+++ b/com/mojang/blaze3d/opengl/GlDevice.java
@@ -124,8 +_,15 @@
int p_407582_,
int p_408928_
) {
+ return createTexture(p_397830_, p_394481_, p_394839_, p_391831_, p_395609_, p_407582_, p_408928_, false);
+ }
+
+ @Override
+ public GpuTexture createTexture(
+ @Nullable Supplier<String> p_397830_, int p_394481_, TextureFormat p_394839_, int p_391831_, int p_395609_, int p_407582_, int p_408928_, boolean stencil
+ ) {
return this.createTexture(
- this.debugLabels.exists() && p_397830_ != null ? p_397830_.get() : null, p_394481_, p_394839_, p_391831_, p_395609_, p_407582_, p_408928_
+ this.debugLabels.exists() && p_397830_ != null ? p_397830_.get() : null, p_394481_, p_394839_, p_391831_, p_395609_, p_407582_, p_408928_, stencil
);
}
@@ -133,6 +_,15 @@
public GpuTexture createTexture(
@Nullable String p_394142_, @GpuTexture.Usage int p_395535_, TextureFormat p_394951_, int p_393944_, int p_392329_, int p_408015_, int p_406483_
) {
+ return createTexture(p_394142_, p_395535_, p_394951_, p_393944_, p_392329_, p_408015_, p_406483_, false);
+ }
+
+ @Override
+ public GpuTexture createTexture(
+ @Nullable String p_394142_, @GpuTexture.Usage int p_395535_, TextureFormat p_394951_, int p_393944_, int p_392329_, int p_408015_, int p_406483_, boolean stencil
+ ) {
+ // Forge: stencil rendering is only for depth
+ stencil &= p_394951_.hasDepthAspect();
if (p_406483_ < 1) {
throw new IllegalArgumentException("mipLevels must be at least 1");
} else if (p_408015_ < 1) {
@@ -183,11 +_,14 @@
GlStateManager._texImage2D(
k,
l,
+ stencil ? org.lwjgl.opengl.GL30.GL_DEPTH32F_STENCIL8 :
GlConst.toGlInternalId(p_394951_),
p_393944_ >> l,
p_392329_ >> l,
0,
+ stencil ? org.lwjgl.opengl.GL30.GL_DEPTH_STENCIL :
GlConst.toGlExternalId(p_394951_),
+ stencil ? org.lwjgl.opengl.GL30.GL_FLOAT_32_UNSIGNED_INT_24_8_REV :
GlConst.toGlType(p_394951_),
null
);
@@ -198,11 +_,14 @@
GlStateManager._texImage2D(
j,
i1,
+ stencil ? org.lwjgl.opengl.GL30.GL_DEPTH32F_STENCIL8 :
GlConst.toGlInternalId(p_394951_),
p_393944_ >> i1,
p_392329_ >> i1,
0,
+ stencil ? org.lwjgl.opengl.GL30.GL_DEPTH_STENCIL :
GlConst.toGlExternalId(p_394951_),
+ stencil ? org.lwjgl.opengl.GL30.GL_FLOAT_32_UNSIGNED_INT_24_8_REV :
GlConst.toGlType(p_394951_),
null
);
@@ -215,7 +_,7 @@
} else if (j1 != 0) {
throw new IllegalStateException("OpenGL error " + j1);
} else {
- GlTexture gltexture = new GlTexture(p_395535_, p_394142_, p_394951_, p_393944_, p_392329_, p_408015_, p_406483_, i);
+ GlTexture gltexture = new GlTexture(p_395535_, p_394142_, p_394951_, p_393944_, p_392329_, p_408015_, p_406483_, i, stencil);
this.debugLabels.applyLabel(gltexture);
return gltexture;
}
@@ -1,32 +0,0 @@
--- a/com/mojang/blaze3d/opengl/GlStateManager.java
+++ b/com/mojang/blaze3d/opengl/GlStateManager.java
@@ -86,6 +_,11 @@
}
}
+ public static boolean _isBlendEnabled() {
+ RenderSystem.assertOnRenderThread();
+ return BLEND.mode.enabled;
+ }
+
public static void _disableBlend() {
RenderSystem.assertOnRenderThread();
BLEND.mode.disable();
@@ -371,9 +_,17 @@
}
}
+ /* Stores the last values sent into glMultiTexCoord2f */
+ public static float lastBrightnessX = 0.0f;
+ public static float lastBrightnessY = 0.0f;
+
public static void _texParameter(int p_393876_, int p_397998_, int p_396741_) {
RenderSystem.assertOnRenderThread();
GL11.glTexParameteri(p_393876_, p_397998_, p_396741_);
+ if (p_393876_ == GL13.GL_TEXTURE1) {
+ lastBrightnessX = p_397998_;
+ lastBrightnessY = p_396741_;
+ }
}
public static int _getTexLevelParameter(int p_393352_, int p_397204_, int p_396319_) {
@@ -1,29 +0,0 @@
--- a/com/mojang/blaze3d/opengl/GlTexture.java
+++ b/com/mojang/blaze3d/opengl/GlTexture.java
@@ -21,8 +_,13 @@
protected GlTexture(
@GpuTexture.Usage int p_394590_, String p_393950_, TextureFormat p_392837_, int p_391379_, int p_391947_, int p_396659_, int p_408255_, int p_408889_
) {
+ this(p_394590_, p_393950_, p_392837_, p_391379_, p_391947_, p_396659_, p_408255_, p_408889_, false);
+ }
+
+ protected GlTexture(int p_394590_, String p_393950_, TextureFormat p_392837_, int p_391379_, int p_391947_, int p_396659_, int p_408255_, int p_408889_, boolean stencil) {
super(p_394590_, p_393950_, p_392837_, p_391379_, p_391947_, p_396659_, p_408255_);
this.id = p_408889_;
+ this.stencilEnabled = stencil;
}
@Override
@@ -89,5 +_,12 @@
if (this.closed && this.views == 0) {
this.destroyImmediately();
}
+ }
+
+ private final boolean stencilEnabled;
+
+ @Override
+ public boolean isStencilEnabled() {
+ return this.stencilEnabled;
}
}
@@ -1,18 +1,35 @@
--- a/com/mojang/blaze3d/pipeline/RenderTarget.java
+++ b/com/mojang/blaze3d/pipeline/RenderTarget.java
@@ -80,7 +_,7 @@
this.width = p_83951_;
this.height = p_83952_;
if (this.useDepth) {
- this.depthTexture = gpudevice.createTexture(() -> this.label + " / Depth", 15, TextureFormat.DEPTH32, p_83951_, p_83952_, 1, 1);
+ this.depthTexture = gpudevice.createTexture(() -> this.label + " / Depth", 15, TextureFormat.DEPTH32, p_83951_, p_83952_, 1, 1, this.stencilEnabled);
this.depthTextureView = gpudevice.createTextureView(this.depthTexture);
@@ -104,7 +_,10 @@
GlStateManager._texParameter(3553, 34892, 0);
GlStateManager._texParameter(3553, 10242, 33071);
GlStateManager._texParameter(3553, 10243, 33071);
+ if (!stencilEnabled)
GlStateManager._texImage2D(3553, 0, 6402, this.width, this.height, 0, 6402, 5126, null);
+ else
+ GlStateManager._texImage2D(3553, 0, org.lwjgl.opengl.GL30.GL_DEPTH32F_STENCIL8, this.width, this.height, 0, org.lwjgl.opengl.GL30.GL_DEPTH_STENCIL, org.lwjgl.opengl.GL30.GL_FLOAT_32_UNSIGNED_INT_24_8_REV, null);
}
@@ -126,5 +_,26 @@
this.setFilterMode(9728, true);
@@ -114,8 +_,13 @@
GlStateManager._texImage2D(3553, 0, 32856, this.width, this.height, 0, 6408, 5121, null);
GlStateManager._glBindFramebuffer(36160, this.frameBufferId);
GlStateManager._glFramebufferTexture2D(36160, 36064, 3553, this.colorTextureId, 0);
- if (this.useDepth) {
+ if (this.useDepth && !stencilEnabled) {
GlStateManager._glFramebufferTexture2D(36160, 36096, 3553, this.depthBufferId, 0);
+ } else if(this.useDepth && net.minecraftforge.common.ForgeConfig.CLIENT.useCombinedDepthStencilAttachment.get()) {
+ GlStateManager._glFramebufferTexture2D(org.lwjgl.opengl.GL30.GL_FRAMEBUFFER, org.lwjgl.opengl.GL30.GL_DEPTH_STENCIL_ATTACHMENT, 3553, this.depthBufferId, 0);
+ } else if (this.useDepth) {
+ GlStateManager._glFramebufferTexture2D(org.lwjgl.opengl.GL30.GL_FRAMEBUFFER, org.lwjgl.opengl.GL30.GL_DEPTH_ATTACHMENT, 3553, this.depthBufferId, 0);
+ GlStateManager._glFramebufferTexture2D(org.lwjgl.opengl.GL30.GL_FRAMEBUFFER, org.lwjgl.opengl.GL30.GL_STENCIL_ATTACHMENT, 3553, this.depthBufferId, 0);
}
public @Nullable GpuTextureView getDepthTextureView() {
return this.depthTextureView;
this.checkStatus();
@@ -257,5 +_,26 @@
public int getDepthTextureId() {
return this.depthBufferId;
+ }
+
+ private boolean stencilEnabled = false;
@@ -25,7 +42,7 @@
+ public void enableStencil() {
+ if (stencilEnabled) return;
+ stencilEnabled = true;
+ this.resize(width, height);
+ this.resize(viewWidth, viewHeight, net.minecraft.client.Minecraft.ON_OSX);
+ }
+
+ /**
@@ -0,0 +1,20 @@
--- a/com/mojang/blaze3d/platform/GlStateManager.java
+++ b/com/mojang/blaze3d/platform/GlStateManager.java
@@ -493,9 +_,17 @@
}
}
+ /* Stores the last values sent into glMultiTexCoord2f */
+ public static float lastBrightnessX = 0.0f;
+ public static float lastBrightnessY = 0.0f;
+
public static void _texParameter(int p_84161_, int p_84162_, float p_84163_) {
RenderSystem.assertOnRenderThreadOrInit();
GL11.glTexParameterf(p_84161_, p_84162_, p_84163_);
+ if (p_84161_ == GL13.GL_TEXTURE1) {
+ lastBrightnessX = p_84162_;
+ lastBrightnessY = p_84163_;
+ }
}
public static void _texParameter(int p_84332_, int p_84333_, int p_84334_) {
@@ -1,25 +1,25 @@
--- a/com/mojang/blaze3d/platform/Window.java
+++ b/com/mojang/blaze3d/platform/Window.java
@@ -91,7 +_,8 @@
GLFW.glfwWindowHint(139267, 3);
@@ -84,7 +_,8 @@
GLFW.glfwWindowHint(139267, 2);
GLFW.glfwWindowHint(139272, 204801);
GLFW.glfwWindowHint(139270, 1);
- this.handle = GLFW.glfwCreateWindow(this.width, this.height, p_85376_, this.fullscreen && monitor != null ? monitor.getMonitor() : 0L, 0L);
+ this.handle = net.minecraftforge.fml.loading.ImmediateWindowHandler.setupMinecraftWindow(()->this.width, ()->this.height, ()->p_85376_, ()->this.fullscreen && monitor != null ? monitor.getMonitor() : 0L);
- this.window = GLFW.glfwCreateWindow(this.width, this.height, p_85376_, this.fullscreen && monitor != null ? monitor.getMonitor() : 0L, 0L);
+ this.window = net.minecraftforge.fml.loading.ImmediateWindowHandler.setupMinecraftWindow(()->this.width, ()->this.height, ()->p_85376_, ()->this.fullscreen && monitor != null ? monitor.getMonitor() : 0L);
+ if (!net.minecraftforge.fml.loading.ImmediateWindowHandler.positionWindow(Optional.ofNullable(monitor), w->this.width = this.windowedWidth = w, h->this.height = this.windowedHeight = h, x->this.x = this.windowedX = x, y->this.y = this.windowedY = y)) {
if (monitor != null) {
VideoMode videomode = monitor.getPreferredVidMode(this.fullscreen ? this.preferredFullscreenVideoMode : Optional.empty());
this.windowedX = this.x = monitor.getX() + videomode.getWidth() / 2 - this.width / 2;
@@ -103,6 +_,7 @@
@@ -96,6 +_,7 @@
this.windowedX = this.x = aint1[0];
this.windowedY = this.y = aint[0];
}
+ }
this.setMode();
this.refreshFramebufferSize();
@@ -269,6 +_,9 @@
GLFW.glfwGetFramebufferSize(this.handle, aint, aint1);
GLFW.glfwMakeContextCurrent(this.window);
GL.createCapabilities();
@@ -254,6 +_,9 @@
GLFW.glfwGetFramebufferSize(this.window, aint, aint1);
this.framebufferWidth = aint[0] > 0 ? aint[0] : 1;
this.framebufferHeight = aint1[0] > 0 ? aint1[0] : 1;
+ if (this.framebufferHeight == 0 || this.framebufferWidth == 0) {
@@ -1,22 +0,0 @@
--- a/com/mojang/blaze3d/systems/GpuDevice.java
+++ b/com/mojang/blaze3d/systems/GpuDevice.java
@@ -36,9 +_,19 @@
int p_407070_
);
+ /** Forge: same as {@link #createTexture(Supplier, int, TextureFormat, int, int, int)} but with stencil support */
+ default GpuTexture createTexture(@Nullable Supplier<String> p_394357_, int p_395623_, TextureFormat p_395807_, int p_395802_, int p_396157_, int p_408810_, int p_407070_, boolean stencil) {
+ return this.createTexture(p_394357_, p_395623_, p_395807_, p_395802_, p_396157_, p_408810_, p_407070_);
+ }
+
GpuTexture createTexture(
@Nullable String p_391798_, @GpuTexture.Usage int p_391800_, TextureFormat p_393333_, int p_395600_, int p_394065_, int p_408732_, int p_407125_
);
+
+ /** Forge: same as {@link #createTexture(String, int, TextureFormat, int, int, int)} but with stencil support */
+ default GpuTexture createTexture(@Nullable String p_391798_, int p_391800_, TextureFormat p_393333_, int p_395600_, int p_394065_, int p_408732_, int p_407125_, boolean stencil) {
+ return this.createTexture(p_391798_, p_391800_, p_393333_, p_395600_, p_394065_, p_408732_, p_407125_);
+ }
GpuTextureView createTextureView(GpuTexture p_409045_);
@@ -1,11 +0,0 @@
--- a/com/mojang/blaze3d/textures/GpuTexture.java
+++ b/com/mojang/blaze3d/textures/GpuTexture.java
@@ -10,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;
@@ -1,11 +1,11 @@
--- a/com/mojang/blaze3d/vertex/PoseStack.java
+++ b/com/mojang/blaze3d/vertex/PoseStack.java
@@ -15,7 +_,7 @@
import org.joml.Vector3fc;
@@ -13,7 +_,7 @@
import org.joml.Vector3f;
@OnlyIn(Dist.CLIENT)
-public class PoseStack {
+public class PoseStack implements net.minecraftforge.client.extensions.IForgePoseStack {
private final List<PoseStack.Pose> poses = new ArrayList<>(16);
private int lastIndex;
private final Deque<PoseStack.Pose> poseStack = Util.make(Queues.newArrayDeque(), p_85848_ -> {
Matrix4f matrix4f = new Matrix4f();
Matrix3f matrix3f = new Matrix3f();
Loaded 100 of 1832 files, more files were not shown because too many files have changed in this diff. Show more