mirror of
https://github.com/MinecraftForge/MinecraftForge.git
synced 2026-11-14 01:04:24 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
753d68d20b | ||
|
|
4cd832fc4d | ||
|
|
8f074e1da1 | ||
|
|
1596190810 | ||
|
|
1fa795a103 | ||
|
|
fcbd3593f6 | ||
|
|
68a8d3b830 | ||
|
|
783c8b7d2b | ||
|
|
dd2591b5f3 | ||
|
|
4cf787d7dd | ||
|
|
b805fa74f8 | ||
|
|
fa109380f7 | ||
|
|
59979642e9 | ||
|
|
90e8fb1c33 | ||
|
|
f09c6d0a8e | ||
|
|
56eb44f1cb | ||
|
|
2e7e537029 | ||
|
|
b9600a27dc | ||
|
|
d736300566 | ||
|
|
fea0d014b8 | ||
|
|
ea355d9bb6 | ||
|
|
a5293855d6 | ||
|
|
4ab4dacf6c | ||
|
|
442f4723c8 | ||
|
|
12682da09a | ||
|
|
da4112efa9 | ||
|
|
ca6094c73d | ||
|
|
9f4d3ef1d7 | ||
|
|
622e560b21 | ||
|
|
f7e5db7149 | ||
|
|
29986615fe | ||
|
|
e615774a0e | ||
|
|
9779f8eaad | ||
|
|
e643bbb764 | ||
|
|
25bc8679b5 | ||
|
|
361e99805e | ||
|
|
0d3921efb5 | ||
|
|
7eee9dcbcd | ||
|
|
3098f463a0 | ||
|
|
862d557279 | ||
|
|
e1f057278d | ||
|
|
6bbe2c64a9 | ||
|
|
684cd96e32 | ||
|
|
1f4e13fce0 | ||
|
|
55ad9e6d7d | ||
|
|
5c8d1618bc | ||
|
|
a6f5057840 | ||
|
|
1b0360928f | ||
|
|
921771bd11 | ||
|
|
089bb1fc84 | ||
|
|
602f1ef947 | ||
|
|
3b18e003f6 | ||
|
|
c33f2d1be6 | ||
|
|
022116141c | ||
|
|
a8b243c0cd | ||
|
|
c22355cc45 | ||
|
|
74bc36307e | ||
|
|
88d2dc6c8f | ||
|
|
de13ba8ab8 | ||
|
|
4f838a87d4 | ||
|
|
70d700e630 | ||
|
|
78fffcf45a | ||
|
|
cdba403ab6 | ||
|
|
6821d7b1cc | ||
|
|
9524ce4580 | ||
|
|
f2bbeab3b9 | ||
|
|
4a8adf5465 | ||
|
|
f5fa2f2a57 | ||
|
|
b33e168969 | ||
|
|
1f6e1ff257 | ||
|
|
7f8897141e | ||
|
|
b9a097c9e1 | ||
|
|
62f00266c5 | ||
|
|
6986cd7ab3 | ||
|
|
e7051b3484 | ||
|
|
f667d25d0b | ||
|
|
48d72fb7c6 | ||
|
|
2fb2bbeed0 |
No files matched your search
@@ -3,7 +3,6 @@
|
||||
*.patch text eol=lf
|
||||
*.java text eol=lf
|
||||
*.gradle text eol=crlf
|
||||
**/MANIFEST.MF text eol=crlf
|
||||
*.png binary
|
||||
*.gif binary
|
||||
*.exe binary
|
||||
@@ -22,6 +21,3 @@ src/generated/**/*.json text eol=lf
|
||||
src/generated_test/**/.cache/cache text eol=lf
|
||||
src/generated_test/**/*.json text eol=lf
|
||||
|
||||
# We force eclipse to use unix line endings
|
||||
ide/eclipse/template/.settings/*.prefs text eol=lf
|
||||
|
||||
@@ -10,4 +10,4 @@ assignees: ''
|
||||
<!-- Please note, feature requests that are submitted as GitHub issues here are rarely implemented. -->
|
||||
<!-- If you'd like to see something in Forge, you should be ready to implement it as a PR. -->
|
||||
<!-- If you're not sure, open a Draft PR or discuss it in the Discord server. -->
|
||||
<!-- Discord: https://discord..minecraftforge.net/ -->
|
||||
<!-- Discord: https://discord.gg/UvedJ9m -->
|
||||
@@ -11,7 +11,6 @@ assignees: ''
|
||||
|
||||
<!-- For support with crashes and issues, please create a post on the forum instead: -->
|
||||
<!-- https://www.minecraftforge.net/forum/forum/18-support-bug-reports/ -->
|
||||
<!-- You can also use the discord for support, https://discord.minecraftforge.net/ -->
|
||||
|
||||
**Minecraft Version:** {Minecraft version}
|
||||
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
name: Publish
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ '26.1.1' ]
|
||||
|
||||
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 }}
|
||||
@@ -7,7 +7,7 @@ jobs:
|
||||
name: Upload payload
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/upload-artifact@v4 # Payload artifact is consumed in Actionable
|
||||
- uses: actions/upload-artifact@v3 # Payload artifact is consumed in Actionable
|
||||
with:
|
||||
name: payload
|
||||
path: ${{ github.event_path }}
|
||||
@@ -7,7 +7,7 @@ jobs:
|
||||
name: Upload payload
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/upload-artifact@v4 # Payload artifact is consumed in Actionable
|
||||
- uses: actions/upload-artifact@v3 # Payload artifact is consumed in Actionable
|
||||
with:
|
||||
name: payload
|
||||
path: ${{ github.event_path }}
|
||||
+14
-14
@@ -13,9 +13,6 @@
|
||||
/classes
|
||||
*.iml
|
||||
|
||||
# We however WANT the ide folder as it has tempaltes/configs for developing Forge.
|
||||
!ide/**
|
||||
|
||||
#gradle
|
||||
/build
|
||||
/.gradle
|
||||
@@ -26,6 +23,8 @@
|
||||
/projects/mcp/
|
||||
/projects/clean/
|
||||
/projects/forge/
|
||||
/projects/fmlonly/
|
||||
#/patches/*
|
||||
|
||||
#occupational hazards
|
||||
/projects/**/build/
|
||||
@@ -33,17 +32,18 @@
|
||||
/projects/**/run/
|
||||
/projects/**/*.launch
|
||||
/repo/
|
||||
src/*/generated/**/.cache/
|
||||
src/generated/resources/.cache/
|
||||
src/generated_test/resources/.cache/
|
||||
src/generated_mdk/resources/.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/
|
||||
|
||||
/ForgeRoot.ipr
|
||||
/ForgeRoot.iws
|
||||
# FML changelog
|
||||
changelog.txt
|
||||
*.py
|
||||
*.7z
|
||||
/logs/
|
||||
|
||||
# APT stuff
|
||||
/*/.gitignore
|
||||
/*/.factorypath
|
||||
/*/.apt_generated/
|
||||
/fmlcore/logs/
|
||||
Vendored
+104
@@ -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>
|
||||
Vendored
+208
@@ -0,0 +1,208 @@
|
||||
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.8", 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.20.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_FML_ARTIFACT_ID", "fmlonly", label = "Published fmlonly artifact id", description = "The maven coordinate artifact id for fml only that has been published by this build. Can not be empty.", allowEmpty = false)
|
||||
text("env.PUBLISHED_JAVA_FML_ARTIFACT_VERSION", "0.0.0-SNAPSHOT", label = "Published fmlonly artifact version", description = "The version for fml only 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."
|
||||
|
||||
features {
|
||||
feature {
|
||||
id = "trigger_fml_only_files_generator"
|
||||
type = "triggerBuildFeature"
|
||||
param("triggers", "MinecraftForge_FilesGenerator_GeneratePages")
|
||||
param("parameters", """
|
||||
env.PUBLISHED_JAVA_FML_ARTIFACT_ID~env.PUBLISHED_JAVA_ARTIFACT_ID
|
||||
env.PUBLISHED_JAVA_FML_ARTIFACT_VERSION~env.PUBLISHED_JAVA_ARTIFACT_VERSION
|
||||
env.PUBLISHED_JAVA_GROUP
|
||||
""".trimIndent())
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
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()
|
||||
}
|
||||
})
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
Minecraft Forge: Credits/Thank You
|
||||
|
||||
Forge is a set of tools and modifications to the Minecraft base game code to assist
|
||||
mod developers in creating new and exciting content. It has been in development for
|
||||
several years now, but I would like to take this time thank a few people who have
|
||||
helped it along it's way.
|
||||
|
||||
First, the people who originally created the Forge projects way back in Minecraft
|
||||
alpha. Eloraam of RedPower, and SpaceToad of Buildcraft, without their acceptiance
|
||||
of me taking over the project, who knows what Minecraft modding would be today.
|
||||
|
||||
Secondly, someone who has worked with me, and developed some of the core features
|
||||
that allow modding to be as functional, and as simple as it is, cpw. For developing
|
||||
FML, which stabelized the client and server modding ecosystem. As well as the base
|
||||
loading system that allows us to modify Minecraft's code as elegently as possible.
|
||||
|
||||
Mezz, who has stepped up as the issue and pull request manager. Helping to keep me
|
||||
sane as well as guiding the community into creating better additions to Forge.
|
||||
|
||||
Searge, Bspks, Fesh0r, ProfMobious, and all the rest over on the MCP team {of which
|
||||
I am a part}. For creating some of the core tools needed to make Minecraft modding
|
||||
both possible, and as stable as can be.
|
||||
On that note, here is some specific information of the MCP data we use:
|
||||
* Minecraft Coder Pack (MCP) *
|
||||
Forge Mod Loader and Minecraft Forge have permission to distribute and automatically
|
||||
download components of MCP and distribute MCP data files. This permission is not
|
||||
transitive and others wishing to redistribute the Minecraft Forge source independently
|
||||
should seek permission of MCP or remove the MCP data files and request their users
|
||||
to download MCP separately.
|
||||
|
||||
And lastly, the countless community members who have spent time submitting bug reports,
|
||||
pull requests, and just helping out the community in general. Thank you.
|
||||
|
||||
--LexManos
|
||||
|
||||
=========================================================================
|
||||
|
||||
This is Forge Mod Loader.
|
||||
|
||||
You can find the source code at all times at https://github.com/MinecraftForge/MinecraftForge/tree/1.12.x/src/main/java/net/minecraftforge/fml
|
||||
|
||||
This minecraft mod is a clean open source implementation of a mod loader for minecraft servers
|
||||
and minecraft clients.
|
||||
|
||||
The code is authored by cpw.
|
||||
|
||||
It began by partially implementing an API defined by the client side ModLoader, authored by Risugami.
|
||||
http://www.minecraftforum.net/topic/75440-
|
||||
This support has been dropped as of Minecraft release 1.7, as Risugami no longer maintains ModLoader.
|
||||
|
||||
It also contains suggestions and hints and generous helpings of code from LexManos, author of MinecraftForge.
|
||||
http://www.minecraftforge.net/
|
||||
|
||||
Additionally, it contains an implementation of topological sort based on that
|
||||
published at http://keithschwarz.com/interesting/code/?dir=topological-sort
|
||||
|
||||
It also contains code from the Maven project for performing versioned dependency
|
||||
resolution. http://maven.apache.org/
|
||||
|
||||
It also contains a partial repackaging of the javaxdelta library from http://sourceforge.net/projects/javaxdelta/
|
||||
with credit to it's authors.
|
||||
|
||||
Forge Mod Loader downloads components from the Minecraft Coder Pack
|
||||
(http://mcp.ocean-labs.de/index.php/Main_Page) with kind permission from the MCP team.
|
||||
|
||||
+1810
-25
File diff suppressed because it is too large.
Load diff
@@ -1,14 +1,13 @@
|
||||
repositories {
|
||||
maven { url = 'https://maven.minecraftforge.net/' }
|
||||
mavenCentral()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.ow2.asm:asm:9.9.1'
|
||||
implementation 'org.ow2.asm:asm-tree:9.9.1'
|
||||
implementation 'net.minecraftforge:srgutils:0.6.2'
|
||||
implementation 'net.minecraftforge:JarJarMetadata:0.3.27'
|
||||
implementation 'commons-io:commons-io:2.13.0'
|
||||
implementation 'org.ow2.asm:asm:9.5'
|
||||
implementation 'org.ow2.asm:asm-tree:9.5'
|
||||
implementation 'net.minecraftforge:srgutils:0.5.+'
|
||||
implementation 'commons-io:commons-io:2.12.0'
|
||||
implementation 'com.google.code.gson:gson:2.10.1'
|
||||
implementation 'org.eclipse.jgit:org.eclipse.jgit:6.7.0.202309050840-r'
|
||||
}
|
||||
implementation 'org.eclipse.jgit:org.eclipse.jgit:6.5.0.202303070854-r'
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import org.gradle.api.tasks.*
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.tasks.bundling.AbstractArchiveTask
|
||||
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
abstract class BundleList extends DefaultTask {
|
||||
@InputFiles abstract ConfigurableFileCollection getConfig()
|
||||
@InputFile abstract RegularFileProperty getServerBundle()
|
||||
@OutputFile abstract RegularFileProperty getOutput()
|
||||
|
||||
BundleList() {
|
||||
config.setFrom(project.configurations.installer)
|
||||
output.convention(project.layout.buildDirectory.file("$name/output.list"))
|
||||
configure {
|
||||
dependsOn(project.tasks.universalJar)
|
||||
inputs.file(project.tasks.universalJar.archiveFile)
|
||||
dependsOn(project.tasks.applyServerBinPatches)
|
||||
inputs.file(project.tasks.applyServerBinPatches.output)
|
||||
}
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
void run() {
|
||||
def entries = [:] as TreeMap
|
||||
def resolved = project.configurations.installer.resolvedConfiguration.resolvedArtifacts
|
||||
for (def dep : resolved) {
|
||||
def info = Util.getMavenInfoFromDep(dep)
|
||||
//println("$dep.file.sha1\t$info.name\t$info.path")
|
||||
entries.put("$info.art.group:$info.art.name", "$dep.file.sha256\t$info.name\t$info.path")
|
||||
}
|
||||
|
||||
var packed = (AbstractArchiveTask) project.tasks.universalJar
|
||||
var info = Util.getMavenInfoFromTask(packed)
|
||||
def file = packed.archiveFile.get().asFile
|
||||
entries.put("$info.art.group:$info.art.name:$info.art.classifier", "$file.sha256\t$info.name\t$info.path")
|
||||
|
||||
var classifier = 'server'
|
||||
var genned = project.tasks.applyServerBinPatches
|
||||
info = Util.getMavenInfoFromTask(genned, classifier)
|
||||
file = genned.output.get().asFile
|
||||
entries.put("$info.art.group:$info.art.name:$info.art.classifier", "$file.sha256\t$info.name\t$info.path")
|
||||
|
||||
try (def zip = new ZipFile(serverBundle.get().asFile)) {
|
||||
def entry = zip.getEntry('META-INF/libraries.list')
|
||||
def data = zip.getInputStream(entry).text.split('\n')
|
||||
for (def line : data) {
|
||||
def (sha, artifact, path) = line.split('\t')
|
||||
def (group, name, other) = artifact.split(':', 3)
|
||||
//println("Group: $group Name: $name")
|
||||
def key = "$group:$name"
|
||||
if (other.indexOf(':') != -1){
|
||||
key += ':' + other.split(':', 2)[1]
|
||||
//println("\tKey: $key")
|
||||
}
|
||||
if (!entries.containsKey(key))
|
||||
entries.put(key, line)
|
||||
}
|
||||
}
|
||||
|
||||
output.get().asFile.text = entries.values().join('\n')
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import groovy.json.JsonBuilder
|
||||
import groovy.transform.CompileStatic
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.tasks.InputFile
|
||||
@@ -11,7 +10,6 @@ import org.objectweb.asm.tree.ClassNode
|
||||
import org.objectweb.asm.tree.FieldNode
|
||||
import org.objectweb.asm.tree.MethodNode
|
||||
|
||||
@CompileStatic
|
||||
abstract class BytecodeFinder extends DefaultTask {
|
||||
@InputFile abstract RegularFileProperty getJar()
|
||||
// It should be fine to mark the output as internal as we want to control when we run it anyways.
|
||||
@@ -26,13 +24,13 @@ abstract class BytecodeFinder extends DefaultTask {
|
||||
protected void exec() {
|
||||
Util.init()
|
||||
|
||||
var outputFile = output.get().asFile
|
||||
def outputFile = output.get().asFile
|
||||
if (outputFile.exists())
|
||||
outputFile.delete()
|
||||
|
||||
pre()
|
||||
|
||||
Util.processClassNodes(jar.get().asFile, this.&process)
|
||||
Util.processClassNodes(jar.get().asFile, this::process)
|
||||
|
||||
post()
|
||||
outputFile.text = new JsonBuilder(getData()).toPrettyString()
|
||||
@@ -40,8 +38,8 @@ abstract class BytecodeFinder extends DefaultTask {
|
||||
|
||||
|
||||
protected process(ClassNode node) {
|
||||
if (node.fields !== null) node.fields.each { process(node, it) }
|
||||
if (node.methods !== null) node.methods.each { process(node, it) }
|
||||
if (node.fields != null) node.fields.each { process(node, it) }
|
||||
if (node.methods != null) node.methods.each { process(node, it) }
|
||||
}
|
||||
|
||||
protected pre() {}
|
||||
|
||||
+3
-19
@@ -3,7 +3,6 @@ package net.minecraftforge.forge.tasks
|
||||
import groovy.transform.CompileStatic
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.Internal
|
||||
import org.objectweb.asm.Opcodes
|
||||
import org.objectweb.asm.tree.ClassNode
|
||||
import org.objectweb.asm.tree.MethodNode
|
||||
|
||||
@@ -13,19 +12,13 @@ abstract class BytecodePredicateFinder extends BytecodeFinder {
|
||||
@Internal
|
||||
abstract Property<Closure<Boolean>> getPredicate()
|
||||
|
||||
private final Map<String, List<String>> matches = new TreeMap<>(Comparator.naturalOrder())
|
||||
private final Set<String> matches = new HashSet()
|
||||
|
||||
@Override
|
||||
protected process(ClassNode parent, MethodNode node) {
|
||||
for (final current : node.instructions) {
|
||||
if (predicate.get().call(parent, node, current)) {
|
||||
var methods = matches.compute(parent.name, { k, v -> v ?: new ArrayList<>() })
|
||||
|
||||
// only add non-synthetic methods
|
||||
if ((node.access & Opcodes.ACC_SYNTHETIC) == 0) {
|
||||
methods.add(node.name + node.desc)
|
||||
}
|
||||
|
||||
matches.add(parent.name)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -34,15 +27,6 @@ abstract class BytecodePredicateFinder extends BytecodeFinder {
|
||||
@Internal
|
||||
@Override
|
||||
protected Object getData() {
|
||||
var array = new ArrayList<Object>()
|
||||
matches.forEach { c, m ->
|
||||
{
|
||||
array.add(
|
||||
'class': c,
|
||||
'methods': m
|
||||
)
|
||||
}
|
||||
}
|
||||
return array ?: { throw new RuntimeException('Failed to find any targets, please ensure that method names and descriptors are correct.') }()
|
||||
return matches.stream().sorted().toList() ?: {throw new RuntimeException('Failed to find any targets, please ensure that method names and descriptors are correct.')}()
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks;
|
||||
|
||||
import groovy.transform.CompileDynamic;
|
||||
import groovy.transform.CompileStatic;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Map;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.Reader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Eclipse config files are literally just java properties, with the header cleaned up.
|
||||
* https://github.com/eclipse/buildship/blob/5b2c7fca7fa86cd74d71b3c099c7b1559eba038e/org.eclipse.buildship.core/src/main/java/org/eclipse/buildship/core/internal/configuration/PreferenceStore.java#L238
|
||||
*
|
||||
* This does the same thing, as well as sorting alphabetically.
|
||||
* It also ignores all comments. We can add them latter if someone cares.
|
||||
*/
|
||||
@CompileStatic
|
||||
public class CleanProperties extends Properties {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String LINE_SEP = System.getProperty("line.separator");
|
||||
private static final String UNIX_LINE_SEP = "\n";
|
||||
private static final Charset ENCODING = StandardCharsets.UTF_8;
|
||||
|
||||
public CleanProperties load(File input) throws IOException {
|
||||
if (input.exists()) {
|
||||
try (Reader is = new InputStreamReader(new FileInputStream(input), ENCODING)) {
|
||||
super.load(is);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public void store(File out) throws IOException {
|
||||
if (!out.getParentFile().exists())
|
||||
out.getParentFile().mkdirs();
|
||||
|
||||
try (OutputStream os = new FileOutputStream(out)) {
|
||||
store(os, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Enumeration<Object> keys() {
|
||||
Set<Object> ret = new TreeSet<>();
|
||||
for (Enumeration<?> e = super.keys(); e.hasMoreElements();)
|
||||
ret.add(e.nextElement());
|
||||
return Collections.enumeration(ret);
|
||||
}
|
||||
|
||||
@CompileDynamic
|
||||
@Override
|
||||
public Set<Map.Entry<Object, Object>> entrySet() {
|
||||
Set<Map.Entry<Object, Object>> ret = new TreeSet<>((l, r) -> ((String)l.getKey()).compareTo((String)r.getKey()));
|
||||
ret.addAll(super.entrySet());
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void store(OutputStream out, String comments) throws IOException {
|
||||
out.write(clean().getBytes(ENCODING));
|
||||
out.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void store(Writer out, String comments) throws IOException {
|
||||
out.write(clean());
|
||||
out.flush();
|
||||
}
|
||||
|
||||
private String clean() throws IOException {
|
||||
ByteArrayOutputStream tmp = new ByteArrayOutputStream();
|
||||
try {
|
||||
super.store(tmp, null);
|
||||
} finally {
|
||||
tmp.close();
|
||||
}
|
||||
|
||||
String ret = tmp.toString(ENCODING).replace(LINE_SEP, UNIX_LINE_SEP);
|
||||
ret = ret.substring(ret.indexOf(UNIX_LINE_SEP) + 1);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import groovy.transform.stc.FirstParam
|
||||
|
||||
import java.util.function.BiConsumer
|
||||
|
||||
public class ClosureHelper {
|
||||
@@ -24,7 +22,7 @@ public class ClosureHelper {
|
||||
}
|
||||
}
|
||||
|
||||
static <T> T apply(T obj, @DelegatesTo(value = FirstParam, strategy = Closure.DELEGATE_FIRST) Closure cl) {
|
||||
static <T> T apply(T obj, Closure cl) {
|
||||
cl.delegate = obj
|
||||
cl.resolveStrategy = Closure.DELEGATE_FIRST
|
||||
cl()
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.Optional
|
||||
import org.gradle.api.tasks.OutputFile
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
abstract class Crowdin extends DefaultTask {
|
||||
@Input abstract Property<String> getId()
|
||||
@Input @Optional abstract Property<String> getKey()
|
||||
@Input boolean json = true
|
||||
@OutputFile abstract RegularFileProperty getOutput()
|
||||
@OutputFile abstract RegularFileProperty getExport()
|
||||
|
||||
Crowdin() {
|
||||
outputs.upToDateWhen{ false }
|
||||
id.convention('minecraft-forge')
|
||||
output.convention(project.layout.buildDirectory.dir(name).map {it.file("output.zip") })
|
||||
export.convention(project.layout.buildDirectory.dir(name).map {it.file("export.json") })
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
def run() {
|
||||
File outputFile = output.get().asFile
|
||||
File exportFile = export.get().asFile
|
||||
if (outputFile.exists())
|
||||
outputFile.delete()
|
||||
|
||||
if (!key.isPresent())
|
||||
return
|
||||
String key = this.key.get()
|
||||
String id = this.id.get()
|
||||
|
||||
// Force an export
|
||||
new URL("https://api.crowdin.com/api/project/${id}/export?key=${key}").withInputStream { i ->
|
||||
exportFile.withOutputStream { it << i }
|
||||
}
|
||||
|
||||
if (!exportFile.text.contains('success')) {
|
||||
throw new RuntimeException("Crowdin export failed, see ${exportFile} for more info")
|
||||
}
|
||||
|
||||
new URL("https://api.crowdin.com/api/project/${id}/download/all.zip?key=${key}").withInputStream { i ->
|
||||
new ZipInputStream(i).withCloseable { zin ->
|
||||
outputFile.withOutputStream { out ->
|
||||
new ZipOutputStream(out).withCloseable { zout ->
|
||||
ZipEntry zein
|
||||
while ((zein = zin.nextEntry) != null) {
|
||||
if (zein.isDirectory()) {
|
||||
zout.putNextEntry(new ZipEntry(zein.name))
|
||||
} else {
|
||||
// 1.13+ uses json
|
||||
if (zein.name.endsWith('.json') == json) {
|
||||
ZipEntry zeout = new ZipEntry(json ? zein.name.toLowerCase() : zein.name)
|
||||
zeout.time = 1
|
||||
zout.putNextEntry(zeout)
|
||||
zout << zin
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ abstract class DownloadLibraries extends DefaultTask {
|
||||
def run() {
|
||||
Util.init()
|
||||
File outputDir = output.get().asFile
|
||||
var libraries = new ArrayList<String>()
|
||||
def libraries = new ArrayList()
|
||||
|
||||
def json = input.get().asFile.json().libraries.each { lib ->
|
||||
//TODO: Thread?
|
||||
@@ -35,7 +35,7 @@ abstract class DownloadLibraries extends DefaultTask {
|
||||
artifacts.each{ art ->
|
||||
def target = new File(outputDir, art.path)
|
||||
libraries.add(target.absolutePath)
|
||||
if (!target.exists() || art.sha1 != target.sha1()) {
|
||||
if (!target.exists() || !art.sha1.equals(target.sha1())) {
|
||||
project.logger.lifecycle("Downloading ${art.url}")
|
||||
if (!target.parentFile.exists()) {
|
||||
target.parentFile.mkdirs()
|
||||
@@ -43,7 +43,7 @@ abstract class DownloadLibraries extends DefaultTask {
|
||||
new URL(art.url).withInputStream { i ->
|
||||
target.withOutputStream { it << i }
|
||||
}
|
||||
if (art.sha1 != target.sha1()) {
|
||||
if (!art.sha1.equals(target.sha1())) {
|
||||
throw new IllegalStateException("Failed to download ${art.url} to ${target.canonicalPath} SHA Mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.*
|
||||
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
@CompileStatic
|
||||
abstract class ExtractFile extends DefaultTask {
|
||||
@InputFile abstract RegularFileProperty getInput()
|
||||
@Input abstract Property<String> getTarget()
|
||||
@OutputFile abstract RegularFileProperty getOutput()
|
||||
|
||||
@TaskAction
|
||||
protected void exec() {
|
||||
var zip = new ZipFile(input.get().asFile);
|
||||
var entry = zip.getEntry(target.get())
|
||||
if (entry == null)
|
||||
throw new IllegalStateException(input.get().asFile.absolutePath + " does not contain " + target.get())
|
||||
var stream = zip.getInputStream(entry)
|
||||
output.get().asFile.withOutputStream { out ->
|
||||
stream.transferTo(out)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,20 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.EqualsAndHashCode
|
||||
|
||||
import java.util.ArrayList
|
||||
import java.util.HashMap
|
||||
import java.util.TreeMap
|
||||
import java.util.TreeSet
|
||||
import java.util.function.BiConsumer
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.tasks.*
|
||||
import org.objectweb.asm.tree.AbstractInsnNode
|
||||
|
||||
import org.objectweb.asm.Opcodes
|
||||
import org.objectweb.asm.Type
|
||||
import org.objectweb.asm.tree.ClassNode
|
||||
import org.objectweb.asm.tree.FieldNode
|
||||
import org.objectweb.asm.tree.MethodNode
|
||||
|
||||
import static org.objectweb.asm.Opcodes.*
|
||||
@@ -25,17 +34,17 @@ abstract class FieldCompareFinder extends BytecodeFinder {
|
||||
|
||||
@Override
|
||||
protected process(ClassNode parent, MethodNode node) {
|
||||
AbstractInsnNode last = null
|
||||
def last = null
|
||||
def parentInstance = new ObjectTarget(owner: parent.name, name: '', desc: '')
|
||||
for (int x = 0; x < node.instructions.size(); x++) {
|
||||
def current = node.instructions.get(x)
|
||||
if (current.opcode === IF_ACMPEQ || current.opcode === IF_ACMPNE) {
|
||||
if (last !== null && (last.opcode === GETSTATIC || last.opcode === GETFIELD)) {
|
||||
if (current.opcode == IF_ACMPEQ || current.opcode == IF_ACMPNE) {
|
||||
if (last != null && (last.opcode == GETSTATIC || last.opcode == GETFIELD)) {
|
||||
def target = new Search(cls: last.owner, name: last.name)
|
||||
def wanted = fieldsReverse.get(target)
|
||||
def original = fields.get(wanted)
|
||||
def instance = new ObjectTarget(owner: parent.name, name: node.name, desc: node.desc)
|
||||
if (wanted !== null && (original.blacklist === null || (!original.blacklist.contains(instance) && !original.blacklist.contains(parentInstance)))) {
|
||||
if (wanted != null && (original.blacklist == null || (!original.blacklist.contains(instance) && !original.blacklist.contains(parentInstance)))) {
|
||||
targets.computeIfAbsent(wanted, { k -> new TreeSet() }).add(instance)
|
||||
}
|
||||
}
|
||||
@@ -59,10 +68,9 @@ abstract class FieldCompareFinder extends BytecodeFinder {
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
@CompileStatic
|
||||
|
||||
@EqualsAndHashCode(excludes = ['replacement', 'blacklist'])
|
||||
static class Search {
|
||||
public static class Search {
|
||||
@Input
|
||||
String cls
|
||||
|
||||
@@ -81,17 +89,17 @@ abstract class FieldCompareFinder extends BytecodeFinder {
|
||||
return cls + '.' + name
|
||||
}
|
||||
|
||||
def blacklist(String owner, String name, String desc) {
|
||||
if (blacklist === null)
|
||||
blacklist = new HashSet<>()
|
||||
def blacklist(def owner, def name, def desc) {
|
||||
if (blacklist == null)
|
||||
blacklist = new HashSet()
|
||||
blacklist.add(new ObjectTarget(owner: owner, name: name, desc: desc))
|
||||
}
|
||||
def blacklist(String owner) {
|
||||
def blacklist(def owner) {
|
||||
blacklist(owner, '', '')
|
||||
}
|
||||
}
|
||||
|
||||
void fields(Closure cl) {
|
||||
def fields(Closure cl) {
|
||||
new ClosureHelper(cl, {name, ccl ->
|
||||
def search = ClosureHelper.apply(new Search(), ccl)
|
||||
this.fields.put(name, search)
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.DirectoryProperty
|
||||
import org.gradle.api.tasks.InputDirectory
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
|
||||
import java.nio.file.Files
|
||||
|
||||
abstract class FixPatchImports extends DefaultTask {
|
||||
|
||||
@InputDirectory abstract DirectoryProperty getClean()
|
||||
@InputDirectory abstract DirectoryProperty getPatched()
|
||||
|
||||
@TaskAction
|
||||
void run() {
|
||||
final patchedPath = getPatched().get().asFile.toPath()
|
||||
final cleanPath = getClean().get().asFile.toPath()
|
||||
|
||||
try (final var stream = Files.find(patchedPath, Integer.MAX_VALUE, (path, attr) -> path.toString().endsWith('.java'))) {
|
||||
final itr = stream.iterator()
|
||||
while (itr.hasNext()) {
|
||||
final next = itr.next()
|
||||
final clean = cleanPath.resolve(patchedPath.relativize(next).toString())
|
||||
|
||||
final patchedLines = Files.readAllLines(next)
|
||||
final cleanLines = Files.readAllLines(clean)
|
||||
|
||||
final patchedImport = patchedLines.findLastIndexOf(2) { it.startsWith('import ') }
|
||||
final cleanImport = cleanLines.findLastIndexOf(2) { it.startsWith('import ') }
|
||||
|
||||
if (cleanImport !== patchedImport) {
|
||||
patchedLines.removeIf { it.startsWith('import ') }
|
||||
patchedLines.addAll(2, cleanLines.subList(2, cleanImport + 1))
|
||||
Files.write(next, patchedLines)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,158 +1,44 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import org.gradle.api.file.DuplicatesStrategy
|
||||
import org.gradle.api.tasks.bundling.AbstractArchiveTask
|
||||
import org.gradle.api.tasks.bundling.Zip
|
||||
import org.gradle.api.tasks.*
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.provider.Property
|
||||
import groovy.json.JsonSlurper
|
||||
import org.gradle.api.provider.SetProperty
|
||||
|
||||
abstract class InstallerJar extends Zip {
|
||||
@Input @Optional abstract Property<Boolean> getFat()
|
||||
@Input @Optional abstract Property<Boolean> getOffline()
|
||||
@Input @Optional abstract SetProperty<String> getPackedDependencies()
|
||||
|
||||
InstallerJar() {
|
||||
archiveClassifier.set('installer')
|
||||
archiveExtension.set('jar') // Needs to be Zip task to not override Manifest, so set extension
|
||||
destinationDirectory.set(project.layout.buildDirectory.dir('libs'))
|
||||
fat.convention(false)
|
||||
offline.convention(false)
|
||||
|
||||
def installerJson = project.tasks.installerJson
|
||||
def launcherJson = project.tasks.launcherJson
|
||||
def downloadInstaller = project.tasks.downloadInstaller
|
||||
|
||||
dependsOn(installerJson, launcherJson, downloadInstaller, project.configurations.installer)
|
||||
dependsOn(installerJson, launcherJson, downloadInstaller)
|
||||
from(installerJson, launcherJson)
|
||||
|
||||
from(project.rootProject.file('/src/main/resources/url.png'))
|
||||
project.afterEvaluate {
|
||||
from(project.zipTree(downloadInstaller.output)) {
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
duplicatesStrategy = 'exclude'
|
||||
}
|
||||
|
||||
if (fat.get() || offline.get()) {
|
||||
def cfg = project.tasks.register(name + "Config", Configure)
|
||||
cfg.get().configure {
|
||||
parent = this
|
||||
dependsOn(project.tasks.installerJson, project.tasks.launcherJson)
|
||||
|
||||
if (!System.env.TEAMCITY_VERSION) {
|
||||
dependsOn(project.universalJar)
|
||||
from(project.universalJar) {
|
||||
into '/maven/' + project.group.replace('.', '/') + '/' + project.universalJar.archiveBaseName.get() + '/' + project.version + '/'
|
||||
}
|
||||
dependsOn(cfg)
|
||||
} else {
|
||||
// Things we ALWAYS bundle, this just the server shim jar, because the installer spec only says to extract the file.
|
||||
// I should make it allow downloads but thats a spec break, and this is just a ~14KB jar
|
||||
[
|
||||
project.tasks.serverShimJar // Server bootstrap executable jar
|
||||
].forEach { AbstractArchiveTask packed ->
|
||||
def path = Util.getMavenInfoFromTask(packed).path
|
||||
from(packed) {
|
||||
rename { "maven/$path" }
|
||||
|
||||
packedDependencies.get().forEach {
|
||||
def jarTask = project.rootProject.getTasks().findByPath(it)
|
||||
dependsOn(jarTask)
|
||||
from(jarTask) {
|
||||
into '/maven/' + jarTask.project.group.replace('.', '/') + '/' + jarTask.archiveBaseName.get() + '/' + jarTask.project.version + '/'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static abstract class Configure extends DefaultTask {
|
||||
public Zip parent
|
||||
private int count = 0;
|
||||
|
||||
@TaskAction
|
||||
protected void exec() {
|
||||
def deps = [:] as java.util.TreeMap
|
||||
|
||||
// Gather all things that need downloading
|
||||
[
|
||||
project.tasks.installerJson,
|
||||
project.tasks.launcherJson
|
||||
].each { task ->
|
||||
def json = task.output.get().asFile.json
|
||||
json.libraries.each { lib ->
|
||||
if (lib.downloads?.artifact?.url !== null && !lib.downloads.artifact.url.isEmpty())
|
||||
deps.put(lib.name, lib.downloads.artifact)
|
||||
}
|
||||
}
|
||||
|
||||
// First find things we build in this project.
|
||||
[
|
||||
project.tasks.universalJar, // Forge runtime code
|
||||
project.tasks.serverShimJar // Shim jar for dedicated server
|
||||
].forEach { AbstractArchiveTask packed ->
|
||||
def name = Util.getMavenInfoFromTask(packed).name
|
||||
def info = deps.remove(name)
|
||||
if (info !== null) {
|
||||
println("Adding: $packed.path $name")
|
||||
parent.from(packed) {
|
||||
rename { "maven/$info.path" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find any artifacts from the 'installer' config
|
||||
// This config specifies the runtime files we intend for the interaller to have.
|
||||
// And are typically what we would be developing and testing alongside Forge.
|
||||
// So we may have local modified versions
|
||||
def cfg = project.configurations.installer
|
||||
while (cfg != null) {
|
||||
//println('')
|
||||
def resolved = cfg.resolvedConfiguration.resolvedArtifacts
|
||||
int found = 0
|
||||
for (def dep : resolved) {
|
||||
def name = Util.getMavenInfoFromDep(dep).name
|
||||
def info = deps.remove(name)
|
||||
if (info == null) {
|
||||
//println("Skipping: $name")
|
||||
continue
|
||||
}
|
||||
//println("-$name")
|
||||
found++
|
||||
addFile(dep.file, info)
|
||||
}
|
||||
|
||||
if (deps.isEmpty()) {
|
||||
cfg = null
|
||||
continue
|
||||
}
|
||||
|
||||
// Prevent infinite loops if something fucky happens
|
||||
if (found == 0)
|
||||
throw new IllegalStateException("Failed to find any installer dependencies")
|
||||
|
||||
def seen = [] as Set
|
||||
cfg = project.configurations.detachedConfiguration()
|
||||
cfg.transitive = false
|
||||
for (def key : deps.keySet()) {
|
||||
def (group, artifact, other) = key.split(':', 3)
|
||||
// Only resolve unique group:artifact so we don't get version overrides
|
||||
if (seen.add(group + ':' + artifact)) {
|
||||
//println("+$key")
|
||||
cfg.dependencies.add(project.dependencies.create(key))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addFile(file, info) {
|
||||
boolean pack = parent.offline.get() || info.url.isEmpty()
|
||||
|
||||
// If it's a offline jar just always pack
|
||||
if (!pack) {
|
||||
try {
|
||||
var remote = new URL("${info.url}.sha1").getText('UTF-8')
|
||||
pack = info.sha1 != remote
|
||||
} catch (FileNotFoundException e) {
|
||||
pack = !info.url.startsWith('https://libraries.minecraft.net/')
|
||||
// Oh noes its not there!
|
||||
}
|
||||
}
|
||||
|
||||
if (pack) {
|
||||
println("Adding: $file.absolutePath")
|
||||
parent.from(file) {
|
||||
rename { "maven/$info.path" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,13 +7,13 @@ import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.provider.SetProperty
|
||||
import org.gradle.api.tasks.*
|
||||
import org.gradle.api.tasks.bundling.AbstractArchiveTask
|
||||
|
||||
import java.nio.file.Files
|
||||
|
||||
abstract class InstallerJson extends DefaultTask {
|
||||
@OutputFile abstract RegularFileProperty getOutput()
|
||||
@InputFiles abstract ConfigurableFileCollection getInput()
|
||||
@Input @Optional abstract SetProperty<String> getPackedDependencies()
|
||||
@Input @Optional final Map<String, Object> libraries = new LinkedHashMap<>()
|
||||
@Input Map<String, Object> json = new LinkedHashMap<>()
|
||||
@InputFile abstract RegularFileProperty getIcon()
|
||||
@@ -23,19 +23,29 @@ abstract class InstallerJson extends DefaultTask {
|
||||
@Input abstract Property<String> getWelcome()
|
||||
|
||||
InstallerJson() {
|
||||
launcherJsonName.convention('/version.json')
|
||||
logo.convention('/big_logo.png')
|
||||
mirrors.convention('https://files.minecraftforge.net/mirrors-2.0.json')
|
||||
welcome.convention("Welcome to the ${project.name.capitalize()} installer.")
|
||||
output.convention(project.layout.buildDirectory.file('libs/install_profile.json'))
|
||||
getLauncherJsonName().convention('/version.json')
|
||||
getLogo().convention('/big_logo.png')
|
||||
getMirrors().convention('https://files.minecraftforge.net/mirrors-2.0.json')
|
||||
getWelcome().convention("Welcome to the simple ${project.name.capitalize()} installer.")
|
||||
|
||||
getOutput().convention(project.layout.buildDirectory.file('install_profile.json'))
|
||||
|
||||
['client', 'server'].each { side ->
|
||||
['slim', 'extra'].each { type ->
|
||||
def tsk = project.tasks.getByName("download${side.capitalize()}${type.capitalize()}")
|
||||
dependsOn(tsk)
|
||||
input.from(tsk.output)
|
||||
}
|
||||
def tsk = project.tasks.getByName("create${side.capitalize()}SRG")
|
||||
dependsOn(tsk)
|
||||
input.from(tsk.output)
|
||||
}
|
||||
|
||||
|
||||
project.afterEvaluate {
|
||||
[
|
||||
project.tasks.universalJar,
|
||||
project.tasks.serverShimJar
|
||||
].forEach { packed ->
|
||||
dependsOn(packed)
|
||||
input.from packed.archiveFile
|
||||
(packedDependencies.get().collect{ project.rootProject.tasks.findByPath(it) } + [project.universalJar]).forEach {
|
||||
dependsOn(it)
|
||||
input.from it.archiveFile
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,19 +53,17 @@ abstract class InstallerJson extends DefaultTask {
|
||||
@TaskAction
|
||||
protected void exec() {
|
||||
def libs = libraries
|
||||
[
|
||||
project.tasks.universalJar,
|
||||
project.tasks.serverShimJar
|
||||
].forEach { AbstractArchiveTask packed ->
|
||||
def info = Util.getMavenInfoFromTask(packed)
|
||||
libs.put(info.name, [
|
||||
name: info.name,
|
||||
for (def child : packedDependencies.get().collect{ project.rootProject.tasks.findByPath(it) } + [project.universalJar]) {
|
||||
def dep = Util.getMavenDep(child)
|
||||
def path = Util.getMavenPath(child)
|
||||
libs.put(dep.toString(), [
|
||||
name: dep,
|
||||
downloads: [
|
||||
artifact: [
|
||||
path: info.path,
|
||||
url: "https://maven.minecraftforge.net/$info.path",
|
||||
sha1: packed.archiveFile.get().asFile.sha1(),
|
||||
size: packed.archiveFile.get().asFile.length()
|
||||
path: path,
|
||||
url: "https://maven.minecraftforge.net/${path}",
|
||||
sha1: child.archiveFile.get().asFile.sha1(),
|
||||
size: child.archiveFile.get().asFile.length()
|
||||
]
|
||||
]
|
||||
])
|
||||
|
||||
@@ -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 + ']';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.provider.SetProperty
|
||||
import org.gradle.api.tasks.*
|
||||
import org.gradle.api.tasks.bundling.AbstractArchiveTask
|
||||
|
||||
import java.nio.file.Files
|
||||
|
||||
@@ -17,78 +16,59 @@ abstract class LauncherJson extends DefaultTask {
|
||||
@OutputFile abstract RegularFileProperty getOutput()
|
||||
@InputFiles abstract ConfigurableFileCollection getInput()
|
||||
@Input Map<String, Object> json = new LinkedHashMap<>()
|
||||
@Input @Optional abstract SetProperty<String> getPackedDependencies()
|
||||
|
||||
@Internal final vanilla = project.project(':mcp').file('build/mcp/downloadJson/version.json')
|
||||
@Internal final timestamp = iso8601Now()
|
||||
@Internal final comment = [
|
||||
"Please do not automate the download and installation of Forge.",
|
||||
"Our efforts are supported by ads from the download page.",
|
||||
"If you MUST automate this, please consider supporting the project through https://www.patreon.com/LexManos/"
|
||||
]
|
||||
@Internal final id = "${project.rootProject.ext.MC_VERSION}-${project.name}${project.version.substring(project.rootProject.ext.MC_VERSION.length())}"
|
||||
|
||||
LauncherJson() {
|
||||
output.convention(project.layout.buildDirectory.file('libs/version.json'))
|
||||
getOutput().convention(project.layout.buildDirectory.file('version.json'))
|
||||
|
||||
dependsOn(project.tasks.universalJar)
|
||||
input.from(project.tasks.universalJar.archiveFile)
|
||||
input.from(project.configurations.installer)
|
||||
configure {
|
||||
def mc = project.rootProject.ext.MC_VERSION
|
||||
def forge = project.rootProject.ext.FORGE_VERSION
|
||||
def timestamp = iso8601Now()
|
||||
json.putAll([
|
||||
_comment: [
|
||||
"Please do not automate the download and installation of Forge.",
|
||||
"Our efforts are supported by ads from the download page.",
|
||||
"If you MUST automate this, please consider supporting the project through https://www.patreon.com/LexManos/"
|
||||
],
|
||||
id: "$mc-$project.name-$forge",
|
||||
time: timestamp,
|
||||
releaseTime: timestamp,
|
||||
inheritsFrom: mc,
|
||||
type: 'release',
|
||||
logging: [:],
|
||||
mainClass: '',
|
||||
libraries: []
|
||||
] as LinkedHashMap)
|
||||
|
||||
[
|
||||
project.tasks.universalJar
|
||||
].forEach { packed ->
|
||||
dependsOn(packed)
|
||||
input.from packed.archiveFile
|
||||
dependsOn(':fmlloader:jar', 'universalJar')
|
||||
getInput().from(project.tasks.universalJar.archiveFile,
|
||||
project.project(':fmlloader').jar.archiveFile,
|
||||
vanilla)
|
||||
|
||||
project.afterEvaluate {
|
||||
packedDependencies.get().forEach {
|
||||
def jarTask = project.rootProject.tasks.findByPath(it)
|
||||
dependsOn(jarTask)
|
||||
input.from jarTask.archiveFile
|
||||
}
|
||||
|
||||
def patched = project.tasks.applyClientBinPatches
|
||||
dependsOn(patched)
|
||||
input.from patched.output
|
||||
}
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
protected void exec() {
|
||||
var packed = (AbstractArchiveTask) project.tasks.universalJar
|
||||
def info = Util.getMavenInfoFromTask(packed)
|
||||
json.libraries.add([
|
||||
name: info.name,
|
||||
downloads: [
|
||||
artifact: [
|
||||
path: info.path,
|
||||
url: "https://maven.minecraftforge.net/$info.path",
|
||||
sha1: packed.archiveFile.get().asFile.sha1(),
|
||||
size: packed.archiveFile.get().asFile.length()
|
||||
if (!json.libraries)
|
||||
json.libraries = []
|
||||
def libs = [:]
|
||||
getArtifacts(project, project.configurations.installer, false).each { key, lib -> libs[key] = lib }
|
||||
getArtifacts(project, project.configurations.moduleonly, false).each { key, lib -> libs[key] = lib }
|
||||
|
||||
packedDependencies.get().collect{ project.rootProject.tasks.findByPath(it) }.forEach {
|
||||
def path = Util.getMavenPath(it)
|
||||
def key = Util.getMavenDep(it)
|
||||
|
||||
libs[key] = [
|
||||
name: key,
|
||||
downloads: [
|
||||
artifact: [
|
||||
path: path,
|
||||
url: "https://maven.minecraftforge.net/${path}",
|
||||
sha1: it.archiveFile.get().asFile.sha1(),
|
||||
size: it.archiveFile.get().asFile.length()
|
||||
]
|
||||
]
|
||||
]
|
||||
])
|
||||
|
||||
var classifier = 'client'
|
||||
var genned = project.tasks.applyClientBinPatches
|
||||
info = Util.getMavenInfoFromTask(genned, classifier)
|
||||
json.libraries.add([
|
||||
name: info.name,
|
||||
downloads: [
|
||||
artifact: [
|
||||
path: info.path,
|
||||
url: "",
|
||||
sha1: genned.output.get().asFile.sha1(),
|
||||
size: genned.output.get().asFile.length()
|
||||
]
|
||||
]
|
||||
])
|
||||
|
||||
json.libraries.addAll(getArtifacts(project.configurations.installer).values())
|
||||
}
|
||||
libs.each { key, lib -> json.libraries.add(lib) }
|
||||
Files.writeString(output.get().asFile.toPath(), new JsonBuilder(json).toPrettyString())
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
@@ -13,7 +12,6 @@ import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
@CompileStatic
|
||||
abstract class MergeJars extends DefaultTask {
|
||||
MergeJars() {
|
||||
output.convention(project.layout.buildDirectory.dir(name).map { it.file('output.jar') })
|
||||
@@ -26,8 +24,8 @@ abstract class MergeJars extends DefaultTask {
|
||||
try (ZipOutputStream zout = new ZipOutputStream(new FileOutputStream(output.get().asFile))) {
|
||||
for (def jar : jars) {
|
||||
try (ZipInputStream zin = new ZipInputStream(new FileInputStream(jar))) {
|
||||
ZipEntry entry
|
||||
while ((entry = zin.getNextEntry()) !== null) {
|
||||
def entry
|
||||
while ((entry = zin.getNextEntry()) != null) {
|
||||
ZipEntry _new = new ZipEntry(entry.getName())
|
||||
_new.setTime(0) //SHOULD be the same time as the main entry, but NOOOO _new.setTime(entry.getTime()) throws DateTimeException, so you get 0, screw you!
|
||||
zout.putNextEntry(_new)
|
||||
|
||||
@@ -27,6 +27,6 @@ public class ObjectTarget implements Comparable<ObjectTarget> {
|
||||
|
||||
@Override
|
||||
int compareTo(ObjectTarget o) {
|
||||
return toString() <=> o.toString()
|
||||
return toString().compareTo(o.toString())
|
||||
}
|
||||
}
|
||||
+11
-1
@@ -16,7 +16,7 @@ abstract class SetupCheckJarCompatibility extends DefaultTask {
|
||||
SetupCheckJarCompatibility() {
|
||||
group = 'jar compatibility'
|
||||
onlyIf {
|
||||
inputVersion.getOrNull() !== null
|
||||
inputVersion.getOrNull() != null
|
||||
}
|
||||
outputs.upToDateWhen { false } // Never up to date, because this setup task should always run
|
||||
|
||||
@@ -26,6 +26,16 @@ abstract class SetupCheckJarCompatibility extends DefaultTask {
|
||||
@TaskAction
|
||||
void run() {
|
||||
def inputVersion = inputVersion.get()
|
||||
def fmlLibs = project.configurations.detachedConfiguration(project.PACKED_DEPS.collect {
|
||||
def artifactId = it.split(':')[1]
|
||||
return project.dependencies.create("net.minecraftforge:${artifactId}:${inputVersion}")
|
||||
}.toArray(Dependency[]::new))
|
||||
|
||||
project.tasks.named('checkJarCompatibility') {
|
||||
baseLibraries.from(project.provider {
|
||||
fmlLibs.resolvedConfiguration.lenientConfiguration.files
|
||||
})
|
||||
}
|
||||
|
||||
def baseForgeUserdev = project.layout.buildDirectory.dir(name).map { it.file("forge-${inputVersion}-userdev.jar") }.get().asFile
|
||||
project.rootProject.extensions.download.run {
|
||||
|
||||
@@ -2,224 +2,159 @@ package net.minecraftforge.forge.tasks
|
||||
|
||||
import groovy.json.JsonBuilder
|
||||
import groovy.json.JsonSlurper
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.stc.ClosureParams
|
||||
import groovy.transform.stc.SimpleType
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.Task
|
||||
import org.gradle.api.artifacts.Configuration
|
||||
import org.gradle.api.artifacts.ResolvedArtifact
|
||||
import org.gradle.api.tasks.bundling.AbstractArchiveTask
|
||||
import org.objectweb.asm.ClassReader
|
||||
import org.objectweb.asm.Opcodes
|
||||
import org.objectweb.asm.tree.ClassNode
|
||||
|
||||
import java.net.http.HttpClient
|
||||
import java.net.http.HttpRequest
|
||||
import java.net.http.HttpResponse
|
||||
import java.io.File
|
||||
import java.security.MessageDigest
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.Semaphore
|
||||
import java.util.Date
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
final class Util {
|
||||
public static final int ASM_LEVEL = Opcodes.ASM9
|
||||
private static final HttpClient HTTP = HttpClient.newBuilder().build()
|
||||
public class Util {
|
||||
static final ASM_LEVEL = Opcodes.ASM9
|
||||
|
||||
static void init() {
|
||||
File.metaClass.sha1 = { ->
|
||||
MessageDigest md = MessageDigest.getInstance('SHA-1')
|
||||
delegate.eachByte(4096) { byte[] bytes, int size ->
|
||||
md.update(bytes, 0, size)
|
||||
}
|
||||
return md.digest().collect(this.&toHex).join('')
|
||||
}
|
||||
static void init() {
|
||||
File.metaClass.sha1 = { ->
|
||||
MessageDigest md = MessageDigest.getInstance('SHA-1')
|
||||
delegate.eachByte 4096, {bytes, size ->
|
||||
md.update(bytes, 0, size)
|
||||
}
|
||||
return md.digest().collect {String.format "%02x", it}.join()
|
||||
}
|
||||
File.metaClass.getSha1 = { !delegate.exists() ? null : delegate.sha1() }
|
||||
File.metaClass.sha256 = { ->
|
||||
MessageDigest md = MessageDigest.getInstance('SHA-256')
|
||||
delegate.eachByte(4096) { byte[] bytes, int size ->
|
||||
md.update(bytes, 0, size)
|
||||
}
|
||||
return md.digest().collect(this.&toHex).join('')
|
||||
}
|
||||
File.metaClass.getSha256 = { !delegate.exists() ? null : delegate.sha256() }
|
||||
|
||||
File.metaClass.json = { -> new JsonSlurper().parseText(delegate.text) }
|
||||
File.metaClass.json = { -> new JsonSlurper().parseText(delegate.text) }
|
||||
File.metaClass.getJson = { return delegate.exists() ? new JsonSlurper().parse(delegate) : [:] }
|
||||
File.metaClass.setJson = { json -> delegate.text = new JsonBuilder(json).toPrettyString() }
|
||||
|
||||
Date.metaClass.iso8601 = { ->
|
||||
var format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
|
||||
var result = format.format(delegate)
|
||||
return result[0..21] + ':' + result[22..-1]
|
||||
}
|
||||
Date.metaClass.iso8601 = { ->
|
||||
def format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
|
||||
def result = format.format(delegate)
|
||||
return result[0..21] + ':' + result[22..-1]
|
||||
}
|
||||
|
||||
String.metaClass.rsplit = { String del, int limit = -1 ->
|
||||
var lst = new ArrayList<String>()
|
||||
int x = 0
|
||||
int idx
|
||||
String tmp = delegate
|
||||
while ((idx = tmp.lastIndexOf(del)) != -1 && (limit === -1 || x++ < limit)) {
|
||||
def lst = new ArrayList()
|
||||
def x = 0, idx
|
||||
def tmp = delegate
|
||||
while ((idx = tmp.lastIndexOf(del)) != -1 && (limit == -1 || x++ < limit)) {
|
||||
lst.add(0, tmp.substring(idx + del.length(), tmp.length()))
|
||||
tmp = tmp.substring(0, idx)
|
||||
}
|
||||
lst.add(0, tmp)
|
||||
return lst
|
||||
}
|
||||
}
|
||||
|
||||
public static String[] getClasspath(project, libs, artifact) {
|
||||
def ret = []
|
||||
artifactTree(project, artifact).each { key, lib ->
|
||||
libs[lib.name] = lib
|
||||
if (lib.name != artifact)
|
||||
ret.add(lib.name)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
public static def getArtifacts(project, config, classifiers) {
|
||||
def ret = [:]
|
||||
config.resolvedConfiguration.resolvedArtifacts.each {
|
||||
def art = [
|
||||
group: it.moduleVersion.id.group,
|
||||
name: it.moduleVersion.id.name,
|
||||
version: it.moduleVersion.id.version,
|
||||
classifier: it.classifier,
|
||||
extension: it.extension,
|
||||
file: it.file
|
||||
]
|
||||
def key = art.group + ':' + art.name
|
||||
def folder = "${art.group.replace('.', '/')}/${art.name}/${art.version}/"
|
||||
def filename = "${art.name}-${art.version}"
|
||||
if (art.classifier != null)
|
||||
filename += "-${art.classifier}"
|
||||
filename += ".${art.extension}"
|
||||
def path = "${folder}${filename}"
|
||||
def url = "https://libraries.minecraft.net/${path}"
|
||||
if (!checkExists(url)) {
|
||||
url = "https://maven.minecraftforge.net/${path}"
|
||||
}
|
||||
ret[key] = [
|
||||
name: "${art.group}:${art.name}:${art.version}" + (art.classifier == null ? '' : ":${art.classifier}") + (art.extension == 'jar' ? '' : "@${art.extension}"),
|
||||
downloads: [
|
||||
artifact: [
|
||||
path: path,
|
||||
url: url,
|
||||
sha1: sha1(art.file),
|
||||
size: art.file.length()
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
public static def getMavenPath(task) {
|
||||
def classifier = task.archiveClassifier.get()
|
||||
def dep = "${task.project.group}:${task.project.name}:${task.project.version}" + (classifier == '' ? '' : ':' + classifier)
|
||||
return "${task.project.group.replace('.', '/')}/${task.project.name}/${task.project.version}/${task.project.name}-${task.project.version}".toString() + (classifier == '' ? '' : '-' + classifier) + '.jar'
|
||||
}
|
||||
|
||||
static String[] getClasspath(Project project, Map libs, String artifact) {
|
||||
def ret = []
|
||||
artifactTree(project, artifact).each { key, lib ->
|
||||
libs[lib.name] = lib
|
||||
if (lib.name != artifact)
|
||||
ret.add(lib.name)
|
||||
}
|
||||
return ret
|
||||
public static def getMavenDep(task) {
|
||||
def classifier = task.archiveClassifier.get()
|
||||
return "${task.project.group}:${task.project.name}:${task.project.version}" + (classifier == '' ? '' : ':' + classifier)
|
||||
}
|
||||
|
||||
@CompileStatic
|
||||
static Map getArtifacts(Configuration config) {
|
||||
var ret = [:]
|
||||
var semaphore = new Semaphore(1, true)
|
||||
config.resolvedConfiguration.resolvedArtifacts.parallelStream().forEachOrdered(dep -> {
|
||||
var info = getMavenInfoFromDep(dep)
|
||||
var domain = 'libraries.minecraft.net'
|
||||
var url = "https://$domain/$info.path"
|
||||
if (!checkExists(url))
|
||||
url.values[0] = 'maven.minecraftforge.net'
|
||||
public static def iso8601Now() { new Date().iso8601() }
|
||||
|
||||
var sha1 = sha1(dep.file)
|
||||
public static def sha1(file) {
|
||||
MessageDigest md = MessageDigest.getInstance('SHA-1')
|
||||
file.eachByte 4096, {bytes, size ->
|
||||
md.update(bytes, 0, size)
|
||||
}
|
||||
return md.digest().collect {String.format "%02x", it}.join()
|
||||
}
|
||||
|
||||
semaphore.acquire()
|
||||
ret[info.key] = [
|
||||
name: info.name,
|
||||
downloads: [
|
||||
artifact: [
|
||||
path: info.path,
|
||||
url: url.toString(),
|
||||
sha1: sha1,
|
||||
size: dep.file.length()
|
||||
]
|
||||
]
|
||||
]
|
||||
semaphore.release()
|
||||
})
|
||||
return ret
|
||||
}
|
||||
private static def artifactTree(project, artifact) {
|
||||
if (!project.ext.has('tree_resolver'))
|
||||
project.ext.tree_resolver = 1
|
||||
def cfg = project.configurations.create('tree_resolver_' + project.ext.tree_resolver++)
|
||||
def dep = project.dependencies.create(artifact)
|
||||
cfg.dependencies.add(dep)
|
||||
def files = cfg.resolve()
|
||||
return getArtifacts(project, cfg, true)
|
||||
}
|
||||
|
||||
@CompileStatic
|
||||
static Map getMavenInfoFromDep(ResolvedArtifact dep) {
|
||||
return getMavenInfoFromMap([
|
||||
group: dep.moduleVersion.id.group,
|
||||
name: dep.moduleVersion.id.name,
|
||||
version: dep.moduleVersion.id.version,
|
||||
classifier: dep.classifier,
|
||||
extension: dep.extension
|
||||
])
|
||||
}
|
||||
private static boolean checkExists(url) {
|
||||
try {
|
||||
def code = new URL(url).openConnection().with {
|
||||
requestMethod = 'HEAD'
|
||||
connect()
|
||||
responseCode
|
||||
}
|
||||
return code == 200
|
||||
} catch (Exception e) {
|
||||
if (e.toString().contains('unable to find valid certification path to requested target'))
|
||||
throw new RuntimeException('Failed to connect to ' + url + ': Missing certificate root authority, try updating java')
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
@CompileStatic
|
||||
static Map getMavenInfoFromTask(AbstractArchiveTask task) {
|
||||
return getMavenInfoFromMap([
|
||||
group: task.project.group.toString(),
|
||||
name: task.project.name,
|
||||
version: task.project.version.toString(),
|
||||
classifier: task.archiveClassifier.get(),
|
||||
extension: task.archiveExtension.get()
|
||||
])
|
||||
}
|
||||
|
||||
@CompileStatic
|
||||
static Map getMavenInfoFromTask(Task task, String classifier) {
|
||||
return getMavenInfoFromMap([
|
||||
group: task.project.group.toString(),
|
||||
name: task.project.name,
|
||||
version: task.project.version.toString(),
|
||||
classifier: classifier,
|
||||
extension: 'jar'
|
||||
])
|
||||
}
|
||||
|
||||
@CompileStatic
|
||||
private static Map getMavenInfoFromMap(Map<String, String> art) {
|
||||
var key = "$art.group:$art.name"
|
||||
var name = "$art.group:$art.name:$art.version"
|
||||
var path = "${art.group.replace('.', '/')}/$art.name/$art.version/$art.name-$art.version"
|
||||
if (art.classifier !== null) {
|
||||
name += ":$art.classifier"
|
||||
path += "-$art.classifier"
|
||||
}
|
||||
if ('jar' != art.extension) {
|
||||
name += "@$art.extension"
|
||||
path += ".$art.extension"
|
||||
} else {
|
||||
path += ".jar"
|
||||
}
|
||||
return [
|
||||
key: key.toString(),
|
||||
name: name.toString(),
|
||||
path: path.toString(),
|
||||
art: art
|
||||
]
|
||||
}
|
||||
|
||||
static String iso8601Now() { new Date().iso8601() }
|
||||
|
||||
@CompileStatic
|
||||
static String sha1(File file) {
|
||||
MessageDigest md = MessageDigest.getInstance('SHA-1')
|
||||
file.eachByte(4096) { byte[] bytes, int size ->
|
||||
md.update(bytes, 0, size)
|
||||
}
|
||||
return md.digest().collect(this.&toHex).join('')
|
||||
}
|
||||
|
||||
@CompileStatic
|
||||
private static String toHex(byte bite) {
|
||||
return String.format('%02x', bite)
|
||||
}
|
||||
|
||||
private static Map artifactTree(Project project, String artifact, boolean transitive = true) {
|
||||
if (!project.ext.has('tree_resolver'))
|
||||
project.ext.tree_resolver = 1
|
||||
def cfg = project.configurations.create('tree_resolver_' + project.ext.tree_resolver++)
|
||||
cfg.transitive = transitive
|
||||
def dep = project.dependencies.create(artifact)
|
||||
cfg.dependencies.add(dep)
|
||||
def files = cfg.resolve()
|
||||
return getArtifacts(cfg)
|
||||
}
|
||||
|
||||
@CompileStatic
|
||||
static boolean checkExists(String url) {
|
||||
try {
|
||||
return HTTP.send(HttpRequest.newBuilder(new URI(url))
|
||||
.method('HEAD', HttpRequest.BodyPublishers.noBody()).build(), HttpResponse.BodyHandlers.discarding()
|
||||
).statusCode() === 200
|
||||
} catch (Exception e) {
|
||||
if (e.toString().contains('unable to find valid certification path to requested target'))
|
||||
throw new RuntimeException("Failed to connect to $url: Missing certificate root authority, try updating Java")
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
static String getLatestForgeVersion(String mcVersion) {
|
||||
static String getLatestForgeVersion(mcVersion) {
|
||||
final json = new JsonSlurper().parseText(new URL('https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json').getText('UTF-8'))
|
||||
final ver = json.promos["$mcVersion-latest"]
|
||||
ver === null ? null : (mcVersion + '-' + ver)
|
||||
}
|
||||
|
||||
@CompileStatic
|
||||
static void processClassNodes(File file, @ClosureParams(value = SimpleType, options = 'org.objectweb.asm.tree.ClassNode') Closure process) {
|
||||
static void processClassNodes(File file, Closure process) {
|
||||
file.withInputStream { i ->
|
||||
new ZipInputStream(i).withCloseable { zin ->
|
||||
ZipEntry zein
|
||||
while ((zein = zin.nextEntry) !== null) {
|
||||
while ((zein = zin.nextEntry) != null) {
|
||||
if (zein.name.endsWith('.class')) {
|
||||
var node = new ClassNode(ASM_LEVEL)
|
||||
def node = new ClassNode(ASM_LEVEL)
|
||||
new ClassReader(zin).accept(node, 0)
|
||||
process(node)
|
||||
}
|
||||
@@ -227,4 +162,4 @@ final class Util {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import net.minecraftforge.srgutils.MinecraftVersion
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
@@ -29,8 +28,8 @@ abstract class ValidateDeprecations extends DefaultTask {
|
||||
|
||||
@TaskAction
|
||||
protected void exec() {
|
||||
var mcVer = MinecraftVersion.from(mcVersion.get())
|
||||
List<String> errors = []
|
||||
def mcVer = MinecraftVersion.from(mcVersion.get())
|
||||
def errors = []
|
||||
|
||||
Util.processClassNodes(input.get().asFile) {
|
||||
processNode(mcVer, errors, it)
|
||||
@@ -66,7 +65,7 @@ abstract class ValidateDeprecations extends DefaultTask {
|
||||
}
|
||||
}
|
||||
|
||||
private static void processAnnotations(AnnotationNode annotation, MinecraftVersion mcVer, List<String> errors, Closure<String> context) {
|
||||
private static void processAnnotations(AnnotationNode annotation, MinecraftVersion mcVer, List<String> errors, Closure context) {
|
||||
def values = annotation.values
|
||||
if (values === null)
|
||||
return
|
||||
@@ -74,7 +73,7 @@ abstract class ValidateDeprecations extends DefaultTask {
|
||||
int since = values.indexOf('since')
|
||||
if (annotation.desc == 'Ljava/lang/Deprecated;' && forRemoval !== -1 && since !== -1 && values.size() >= 4 && values[forRemoval + 1] === true) {
|
||||
def oldVersion = MinecraftVersion.from(values[since + 1])
|
||||
int[] split = ValidateDeprecations.splitDots(oldVersion.toString())
|
||||
def split = ValidateDeprecations.splitDots(oldVersion.toString())
|
||||
if (split.length < 2)
|
||||
return
|
||||
def removeVersion = MinecraftVersion.from("${split[0]}.${split[1] + 1}")
|
||||
@@ -83,7 +82,6 @@ abstract class ValidateDeprecations extends DefaultTask {
|
||||
}
|
||||
}
|
||||
|
||||
@CompileStatic
|
||||
private static int[] splitDots(String version) {
|
||||
String[] pts = version.split('\\.')
|
||||
int[] values = new int[pts.length]
|
||||
|
||||
@@ -17,15 +17,17 @@ abstract class CheckATs extends CheckTask {
|
||||
|
||||
@InputFile abstract RegularFileProperty getInheritance()
|
||||
@InputFiles abstract ConfigurableFileCollection getAts()
|
||||
@InputFile @Optional abstract RegularFileProperty getMappings()
|
||||
|
||||
@Override
|
||||
void check(Reporter reporter, boolean fix) {
|
||||
final IMappingFile mappings = mappings.map { RegularFile it -> IMappingFile.load(it.asFile) }.getOrNull()
|
||||
final inheritance = InheritanceData.parse(this.inheritance.get().asFile)
|
||||
|
||||
ats.each {
|
||||
final lines = process(it, reporter, inheritance)
|
||||
if (fix) {
|
||||
it.text = joinBack(lines, inheritance).join('\n')
|
||||
it.text = joinBack(lines, inheritance, mappings).join('\n')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -168,18 +170,45 @@ abstract class CheckATs extends CheckTask {
|
||||
return lines
|
||||
}
|
||||
|
||||
private static List<String> joinBack(TreeMap<String, ATParser.Entry> lines, Map<String, InheritanceData> inheritance) {
|
||||
private static List<String> joinBack(TreeMap<String, ATParser.Entry> lines, Map<String, InheritanceData> inheritance, IMappingFile mappings) {
|
||||
final data = [] as List<String>
|
||||
final remapComment = { ATParser.Entry entry ->
|
||||
if (!mappings || !entry || !entry.desc) return null
|
||||
final comment = entry.comment?.substring(1)?.trim()
|
||||
final jsonCls = inheritance.get(entry.cls.replaceAll('\\.', '/'))
|
||||
final mappingsClass = mappings?.getClass(jsonCls.name)
|
||||
if (mappingsClass === null) return entry.comment
|
||||
final idx = entry.desc.indexOf('(')
|
||||
|
||||
String mappedName = idx == -1
|
||||
? mappingsClass.remapField(entry.desc)
|
||||
: mappingsClass.remapMethod(entry.desc.substring(0, idx), entry.desc.substring(idx))
|
||||
if (!mappedName) return entry.comment
|
||||
if (mappedName == '<init>')
|
||||
mappedName = 'constructor'
|
||||
|
||||
if (comment?.startsWith(mappedName))
|
||||
return '# ' + comment
|
||||
if (comment && comment.indexOf(' ') !== -1) {
|
||||
def split = comment.split(' - ').toList()
|
||||
if (split[0].indexOf(' ') !== -1)
|
||||
// The first string is more than one word, so append before it
|
||||
return "# ${mappedName} - ${comment}"
|
||||
split.remove(0)
|
||||
return "# ${mappedName} - ${String.join(' - ', split)}"
|
||||
}
|
||||
return '# ' + mappedName
|
||||
}
|
||||
lines.each { key, value ->
|
||||
if (!value.group) {
|
||||
def comment = null //value.comment
|
||||
def comment = remapComment.call(value)
|
||||
data.add(value.modifier + ' ' + key + (comment ? ' ' + comment : ''))
|
||||
} else {
|
||||
data.add(('#group ' + value.modifier + ' ' + key + ' ' + (value.comment ?: '')).trim())
|
||||
value.children.each {
|
||||
final line = value.modifier + ' ' + it
|
||||
final entry = ATParser.parseEntry(line)
|
||||
final comment = null //entry.comment
|
||||
final comment = remapComment(entry)
|
||||
data.add(line + (comment ? ' ' + comment : ''))
|
||||
}
|
||||
data.add('#endgroup')
|
||||
@@ -260,7 +289,6 @@ class ATParser {
|
||||
final String comment = idx === -1 ? null : line.substring(idx)
|
||||
if (idx !== -1) line = line.substring(0, idx - 1)
|
||||
final data = (line.trim() + ' ').split(' ', -1)
|
||||
data[1] = data[1].replaceAll('/', '.') // Convert to Source names, internal names are fine by spec, but not supported by old AST based AT implementations.
|
||||
new Entry(data[0], data[1], data[2], comment)
|
||||
}
|
||||
|
||||
|
||||
@@ -26,12 +26,12 @@ abstract class CheckExcs extends CheckTask {
|
||||
excs.each { f ->
|
||||
final lines = []
|
||||
f.eachLine { line ->
|
||||
int idx = line.indexOf('#')
|
||||
if (idx === 0 || line.isEmpty()) {
|
||||
def idx = line.indexOf('#')
|
||||
if (idx == 0 || line.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (idx !== -1) line = line.substring(0, idx - 1)
|
||||
if (idx != -1) line = line.substring(0, idx - 1)
|
||||
|
||||
if (!line.contains('=')) {
|
||||
reporter.report("Invalid: $line")
|
||||
@@ -58,8 +58,6 @@ abstract class CheckExcs extends CheckTask {
|
||||
return
|
||||
}
|
||||
lines.add(line)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (fix) f.text = lines.sort().join('\n')
|
||||
|
||||
@@ -6,6 +6,4 @@ import groovy.transform.CompileStatic
|
||||
enum CheckMode {
|
||||
CHECK,
|
||||
FIX
|
||||
|
||||
CheckMode() {}
|
||||
}
|
||||
@@ -36,13 +36,13 @@ abstract class CheckPatches extends CheckTask {
|
||||
}
|
||||
}
|
||||
|
||||
static boolean accessChange(String previous, String current) {
|
||||
def accessChange(previous, current) {
|
||||
//return ACCESS_MAP[previous] < ACCESS_MAP[current]
|
||||
return previous != current
|
||||
}
|
||||
|
||||
void verifyPatch(Path patch, Reporter reporter, boolean fix, String patchPath, boolean hasS2SArtifact) {
|
||||
int oldFixedErrors = reporter.fixed.size()
|
||||
final oldFixedErrors = reporter.fixed.size()
|
||||
|
||||
final lines = Files.readAllLines(patch)
|
||||
|
||||
@@ -140,7 +140,7 @@ abstract class CheckPatches extends CheckTask {
|
||||
final nextLineChange = i + 1 < lines.size() && lines[i + 1].startsWithAny('+','-')
|
||||
|
||||
if (!prevLineChange && !nextLineChange) {
|
||||
reporter.report("Patch contains white space change in valid hunk at line ${i + 1}, file: $patchPath \n$prevLine\n$line\n${lines[i+1]}", false)
|
||||
reporter.report("Patch contains white space change in valid hunk at line ${i + 1}, file: $patchPath", false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ abstract class CheckSAS extends CheckTask {
|
||||
}
|
||||
}
|
||||
|
||||
protected static boolean isSided(Annotatable annotatable) {
|
||||
protected static isSided(Annotatable annotatable) {
|
||||
if (annotatable === null) return false
|
||||
for (ann in annotatable.annotations) {
|
||||
if ('Lnet/minecraftforge/api/distmarker/OnlyIn;' == ann.desc)
|
||||
@@ -104,7 +104,7 @@ abstract class CheckSAS extends CheckTask {
|
||||
}
|
||||
|
||||
protected static findChildMethods(Map<String, InheritanceData> json, String cls, String desc) {
|
||||
return json.values().findAll{ it.methods !== null && it.methods[desc] !== null && it.methods[desc].override == cls && isSided(it.methods[desc]) }
|
||||
return json.values().findAll{ it.methods != null && it.methods[desc] != null && it.methods[desc].override == cls && isSided(it.methods[desc]) }
|
||||
.collect { it.name + ' ' + desc.replace(' ', '') } as TreeSet
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
package net.minecraftforge.forge.tasks.checks
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.stc.ClosureParams
|
||||
import groovy.transform.stc.ThirdParam
|
||||
import net.minecraftforge.forge.tasks.Util
|
||||
import org.gradle.api.Action
|
||||
import org.gradle.api.DefaultTask
|
||||
@@ -35,7 +33,7 @@ abstract class CheckTask extends DefaultTask implements VerificationTask {
|
||||
void run() {
|
||||
Util.init()
|
||||
|
||||
boolean doFix = getMode().get() === CheckMode.FIX
|
||||
final doFix = getMode().get() === CheckMode.FIX
|
||||
final Reporter reporter = new Reporter(doFix)
|
||||
check(reporter, doFix)
|
||||
|
||||
@@ -90,8 +88,7 @@ abstract class CheckTask extends DefaultTask implements VerificationTask {
|
||||
}
|
||||
|
||||
static <T extends CheckTask> void registerTask(TaskContainer tasks, String taskName, @DelegatesTo.Target('type') Class<T> clazz,
|
||||
@DelegatesTo(genericTypeIndex = 0, target = 'type')
|
||||
@ClosureParams(ThirdParam.FirstGenericType) Closure configuration) {
|
||||
@DelegatesTo(genericTypeIndex = 0, target = 'type') Closure configuration) {
|
||||
taskName = taskName.capitalize()
|
||||
tasks.register("check$taskName", clazz) { CheckTask task ->
|
||||
def castedTask = task as T
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
plugins {
|
||||
id 'java-library'
|
||||
id 'net.minecraftforge.gradle.patcher'
|
||||
}
|
||||
|
||||
evaluationDependsOn(':mcp')
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven gradleutils.forgeMaven
|
||||
maven gradleutils.minecraftLibsMaven
|
||||
}
|
||||
|
||||
java.toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION)
|
||||
|
||||
dependencies {
|
||||
implementation(libs.forgespi)
|
||||
}
|
||||
|
||||
patcher {
|
||||
parent = project(':mcp')
|
||||
mcVersion = MC_VERSION
|
||||
patchedSrc = file('src/main/java')
|
||||
|
||||
mappings channel: MAPPING_CHANNEL, version: MAPPING_VERSION
|
||||
|
||||
runs {
|
||||
clean_client {
|
||||
client true
|
||||
taskName 'clean_client'
|
||||
ideaModule "${rootProject.name}.${project.name}.main"
|
||||
|
||||
main 'net.minecraft.client.main.Main'
|
||||
workingDirectory project.file('run/client')
|
||||
|
||||
args '--gameDir', '.'
|
||||
args '--version', MC_VERSION
|
||||
args '--assetsDir', downloadAssets.output
|
||||
args '--assetIndex', '{asset_index}'
|
||||
args '--accessToken', '0'
|
||||
}
|
||||
|
||||
clean_server {
|
||||
client false
|
||||
taskName 'clean_server'
|
||||
ideaModule "${rootProject.name}.${project.name}.main"
|
||||
|
||||
main 'net.minecraft.server.Main'
|
||||
workingDirectory project.file('run/server')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation
|
||||
options.warnings = false // Shutup deprecated for removal warnings
|
||||
}
|
||||
|
||||
eclipse.classpath.file.whenMerged {
|
||||
// Disable optional warnings on the minecraft decompiled source code. It just muddies the warning window and hides warnings in our own codebase
|
||||
def src = entries.find { it.path == 'src/main/java' }
|
||||
if (src == null) throw new IllegalStateException("You must run `setup` task before importing")
|
||||
src.entryAttributes['ignore_optional_problems'] = 'true'
|
||||
}
|
||||
|
||||
tasks.named('genPatches').configure {
|
||||
onlyIf { false }
|
||||
}
|
||||
|
||||
tasks.named('extractRangeMap').configure {
|
||||
onlyIf { false }
|
||||
tool = 'net.minecraftforge:Srg2Source:8.1.0:fatjar'
|
||||
}
|
||||
-1021
File diff suppressed because it is too large.
Load diff
@@ -1,89 +0,0 @@
|
||||
import net.minecraftforge.forge.tasks.CleanProperties
|
||||
|
||||
apply plugin: 'eclipse'
|
||||
apply plugin: 'idea'
|
||||
apply plugin: 'net.minecraftforge.gradleutils'
|
||||
|
||||
group = 'net.minecraftforge'
|
||||
version = VERSION
|
||||
println("Version: $version")
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven gradleutils.forgeMaven
|
||||
maven gradleutils.minecraftLibsMaven
|
||||
//mavenLocal()
|
||||
}
|
||||
|
||||
tasks.withType(Javadoc).configureEach {
|
||||
options.tags = [
|
||||
'apiNote:a:<em>API Note:</em>',
|
||||
'implSpec:a:<em>Implementation Requirements:</em>',
|
||||
'implNote:a:<em>Implementation Note:</em>'
|
||||
]
|
||||
options.addStringOption('Xdoclint:all,-missing', '-public')
|
||||
}
|
||||
|
||||
// We need to write the manifest to the binary file so we have properly versioned packaged at dev time.
|
||||
tasks.register('writeManifest') {
|
||||
doLast {
|
||||
if (plugins.findPlugin('net.minecraftforge.gradle.patcher')) // Forge project
|
||||
universalJar.manifest.writeTo(rootProject.file('src/main/resources/META-INF/MANIFEST.MF'))
|
||||
else
|
||||
jar.manifest.writeTo(project.file('src/main/resources/META-INF/MANIFEST.MF'))
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
}
|
||||
|
||||
// Merge the resources and classes into the same directory. We'll need to split them at runtime because
|
||||
// Minecraft and Forge are in the same sourceSet as they are inter dependent.. for now..
|
||||
sourceSets.each {
|
||||
def dir = layout.buildDirectory.dir("classes/java/$it.name")
|
||||
it.output.resourcesDir = dir
|
||||
it.java.destinationDirectory = dir
|
||||
}
|
||||
|
||||
tasks.register('copyEclipseSettings') {
|
||||
doLast {
|
||||
rootProject.fileTree('ide/eclipse/template/.settings/').matching { include '**/*.prefs' }.each { file ->
|
||||
def target = project.file('.settings/' + file.name)
|
||||
def temp = new CleanProperties().load(file)
|
||||
def exst = new CleanProperties().load(target)
|
||||
exst.put('eclipse.preferences.version', '1')
|
||||
exst.putAll(temp)
|
||||
exst.store(target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: [Gradle][IntelliJ] Auto trigger these tasks on import.
|
||||
eclipse {
|
||||
// Run everytime eclipse builds the code
|
||||
//autoBuildTasks writeManifest
|
||||
// Run when importing the project
|
||||
synchronizationTasks generateResources, copyEclipseSettings, eclipseClasspath, eclipseProject
|
||||
}
|
||||
|
||||
idea {
|
||||
module {
|
||||
// IntelliJ IDEA does not do this by itself anymore...
|
||||
downloadJavadoc = true
|
||||
downloadSources = true
|
||||
}
|
||||
}
|
||||
+13
-21
@@ -2,8 +2,8 @@
|
||||
|
||||
MinecraftForge
|
||||
=============
|
||||
[][Download]
|
||||
[][Download]
|
||||
[][Download] [][Discord] [][Patreon]
|
||||
|
||||
Forge is a free, open-source modding API all of your favourite mods use!
|
||||
@@ -26,7 +26,7 @@ You can download the installer for the *Recommended Build* or the
|
||||
*Latest build* there. Latest builds may have newer features but may be
|
||||
more unstable as a result. The installer will attempt to install Forge
|
||||
into your vanilla launcher environment, where you can then create a new
|
||||
profile using that version and play the game!
|
||||
profile using that version and play the game!
|
||||
|
||||
For support and questions, visit [the Support Forum][Support-Forum] or [the Forge Discord server][Discord].
|
||||
|
||||
@@ -34,13 +34,12 @@ For support and questions, visit [the Support Forum][Support-Forum] or [the Forg
|
||||
|
||||
We require the version of Java that Minecraft requires, which is currently:
|
||||
|
||||
| Minecraft | Java |
|
||||
|------------------|-------------------|
|
||||
| 26.1+ | [25][Adoptium-25] |
|
||||
| 1.20.6 - 1.21.11 | [21][Adoptium-21] |
|
||||
| 1.18 - 1.20.4 | [17][Adoptium-17] |
|
||||
| 1.17.1 | [16][Adoptium-16] |
|
||||
| 1.16.5- | [8][Adoptium-8] |
|
||||
| Minecraft | Java |
|
||||
|---------------|-------------------|
|
||||
| 1.20.6+ | [21][Adoptium-21] |
|
||||
| 1.18 - 1.20.4 | [17][Adoptium-17] |
|
||||
| 1.17.1 | [16][Adoptium-16] |
|
||||
| 1.16.5- | [8][Adoptium-8] |
|
||||
|
||||
**If you are on Windows and have difficulty using Java to launch JAR files, consider using [Jarfix by Johann Löfflmann][Jarfix].**
|
||||
|
||||
@@ -71,17 +70,11 @@ Please ensure you have a valid email address associated with your GitHub account
|
||||
to use and modify. However, it costs money to run such a large project as this, so please consider
|
||||
[becoming a patron][Patreon].*
|
||||
|
||||
### Thanks
|
||||
[](https://www.yourkit.com/)
|
||||
|
||||
YourKit for providing us accesss to their [YourKit Java Profiler] which helps identify bottlenecks and places for improvment.
|
||||
|
||||
[Contributing]: ./CONTRIBUTING.md
|
||||
[CLA]: https://cla-assistant.io/MinecraftForge/MinecraftForge
|
||||
|
||||
[Download]: https://files.minecraftforge.net/
|
||||
|
||||
[Adoptium-25]: https://adoptium.net/temurin/releases/?version=25
|
||||
[Adoptium-21]: https://adoptium.net/temurin/releases/?version=21
|
||||
[Adoptium-17]: https://adoptium.net/temurin/releases/?version=17
|
||||
[Adoptium-16]: https://adoptium.net/temurin/releases/?version=16
|
||||
@@ -94,10 +87,9 @@ YourKit for providing us accesss to their [YourKit Java Profiler] which helps id
|
||||
|
||||
[Discord]: https://discord.minecraftforge.net/
|
||||
|
||||
[Documentation]: https://docs.minecraftforge.net/
|
||||
[Getting-Started]: https://docs.minecraftforge.net/en/latest/gettingstarted/
|
||||
[ForgeDev]: https://docs.minecraftforge.net/en/latest/forgedev/
|
||||
[Pull-Requests]: https://docs.minecraftforge.net/en/latest/forgedev/#making-changes-and-pull-requests
|
||||
[Documentation]: https://docs.minecraftforge.net/en/1.20.1/
|
||||
[Getting-Started]: https://docs.minecraftforge.net/en/1.20.1/gettingstarted/
|
||||
[ForgeDev]: https://docs.minecraftforge.net/en/1.20.1/forgedev/
|
||||
[Pull-Requests]: https://docs.minecraftforge.net/en/1.20.1/forgedev/#making-changes-and-pull-requests
|
||||
|
||||
[Patreon]: https://www.patreon.com/LexManos
|
||||
[YourKit Java Profiler]: https://www.yourkit.com/java/profiler/
|
||||
@@ -1,24 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
for file in $(find repo | grep --invert-match -e md5 -e sha | grep -e .jar -e .zip); do
|
||||
file_path=$(realpath $file)
|
||||
file_name=$(basename $file)
|
||||
|
||||
echo "$file_name"
|
||||
files=$(unzip -l $file | awk '{print $4}')
|
||||
for f in $files; do
|
||||
out="_actual/$file_name/$f"
|
||||
parent=$(dirname "$out")
|
||||
echo $out
|
||||
|
||||
if [ ! -d "$parent" ]; then
|
||||
mkdir -p "$parent"
|
||||
fi
|
||||
|
||||
if [[ $f == *.class ]]; then
|
||||
javap -v -p jar:file://$file_path!/$f | tail -n +4 >$out.txt
|
||||
elif [[ $f != */ && $f != "Name" && $f != "----" ]]; then
|
||||
unzip -o -q $file $f -d _actual/$file_name
|
||||
fi
|
||||
done
|
||||
done
|
||||
+49
-43
@@ -1,68 +1,74 @@
|
||||
import net.minecraftforge.gradleutils.PomUtils
|
||||
|
||||
plugins {
|
||||
id 'java-library'
|
||||
id 'maven-publish'
|
||||
id 'net.minecraftforge.licenser'
|
||||
id 'net.minecraftforge.gradleutils'
|
||||
id 'com.github.ben-manes.versions'
|
||||
id 'org.javamodularity.moduleplugin' version '1.8.3' apply false
|
||||
id 'org.cadixdev.licenser'
|
||||
}
|
||||
apply plugin: 'java-library'
|
||||
apply plugin: 'jacoco'
|
||||
apply plugin: 'org.javamodularity.moduleplugin'
|
||||
|
||||
apply from: rootProject.file('build_shared.gradle')
|
||||
dependencyUpdates.rejectVersionIf { isNonStable(it.candidate.version) }
|
||||
|
||||
dependencies {
|
||||
compileOnly(libs.jetbrains.annotations)
|
||||
compileOnly("org.jetbrains:annotations:${JETBRAINS_ANNOTATIONS_VERSION}")
|
||||
|
||||
api(libs.eventbus)
|
||||
annotationProcessor(libs.eventbus.validator)
|
||||
api("net.minecraftforge:eventbus:${EVENTBUS_VERSION}")
|
||||
|
||||
implementation(project(':fmlloader'))
|
||||
implementation(libs.commons.io)
|
||||
implementation("commons-io:commons-io:${COMMONS_IO_VERSION}")
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION)
|
||||
withSourcesJar()
|
||||
task sourcesJar(type: Jar) {
|
||||
archiveClassifier = 'sources'
|
||||
from sourceSets.main.allSource
|
||||
}
|
||||
|
||||
tasks.named('jar', Jar).configure {
|
||||
manifest {
|
||||
attributes([
|
||||
'Automatic-Module-Name': 'net.minecraftforge.fmlcore',
|
||||
'FMLModType': 'LIBRARY'
|
||||
] as LinkedHashMap)
|
||||
attributes([
|
||||
'Specification-Title': 'FML',
|
||||
ext {
|
||||
MANIFESTS = [
|
||||
'': [
|
||||
'FMLModType': 'LIBRARY',
|
||||
'Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"),
|
||||
'Git-Commit': GIT_INFO.abbreviatedId,
|
||||
'Git-Branch': GIT_INFO.branch,
|
||||
'Build-Number': "${System.getenv('BUILD_NUMBER')?:0}"
|
||||
] as LinkedHashMap,
|
||||
'net/minecraftforge/fml/loading/': [
|
||||
'Specification-Title': 'FMLLoader',
|
||||
'Specification-Vendor': 'Forge Development LLC',
|
||||
'Specification-Version': '1',
|
||||
'Implementation-Title': 'FML',
|
||||
'Implementation-Title': 'FML Loader',
|
||||
'Implementation-Version': '1.0',
|
||||
'Implementation-Vendor': 'Forge Development LLC'
|
||||
] as LinkedHashMap, 'net/minecraftforge/fml/')
|
||||
'Implementation-Vendor': 'Forge'
|
||||
] as LinkedHashMap
|
||||
]
|
||||
}
|
||||
|
||||
jar.doFirst {
|
||||
manifest.attributes['Automatic-Module-Name'] = project.name
|
||||
MANIFESTS.each { pkg, values ->
|
||||
if (pkg == '')
|
||||
manifest.attributes(values)
|
||||
else
|
||||
manifest.attributes(values, pkg)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
options.compilerArgs << '-Xlint:-unchecked'
|
||||
tasks.withType(JavaCompile) {
|
||||
options.compilerArgs << '-Xlint:unchecked'
|
||||
}
|
||||
|
||||
license {
|
||||
header = rootProject.file('LICENSE-header.txt')
|
||||
|
||||
include 'net/minecraftforge/'
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications.register('mavenJava', MavenPublication).configure {
|
||||
from components.java
|
||||
artifactId = 'fmlcore'
|
||||
pom {
|
||||
name = project.name
|
||||
description = 'Modifactions to Minecraft to enable mod developers.'
|
||||
url = 'https://github.com/MinecraftForge/MinecraftForge'
|
||||
PomUtils.setGitHubDetails(pom, 'MinecraftForge')
|
||||
license PomUtils.Licenses.LGPLv2_1
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven gradleutils.publishingForgeMaven
|
||||
}
|
||||
artifacts {
|
||||
archives jar
|
||||
archives sourcesJar
|
||||
}
|
||||
|
||||
publishing.publications.mavenJava(MavenPublication) {
|
||||
from components.java
|
||||
artifact sourcesJar
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -57,9 +57,7 @@ public class DeferredWorkQueue
|
||||
LOGGER.debug(LOADING, "Dispatching synchronous work for work queue {}: {} jobs", modLoadingStage, tasks.size());
|
||||
RuntimeException aggregate = new RuntimeException();
|
||||
Stopwatch timer = Stopwatch.createStarted();
|
||||
for (TaskInfo t : tasks) {
|
||||
makeRunnable(t, Runnable::run, aggregate);
|
||||
}
|
||||
tasks.forEach(t -> makeRunnable(t, Runnable::run, aggregate));
|
||||
timer.stop();
|
||||
if (aggregate.getSuppressed().length > 0) {
|
||||
LOGGER.fatal(
|
||||
@@ -116,7 +114,7 @@ public class DeferredWorkQueue
|
||||
return future;
|
||||
}
|
||||
|
||||
private static final class TaskInfo {
|
||||
private static class TaskInfo {
|
||||
private final ModContainer owner;
|
||||
private Runnable task;
|
||||
private CompletableFuture<?> future;
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.fml.loading.FMLEnvironment;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.invoke.SerializedLambda;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Use to execute code conditionally based on sidedness.
|
||||
* <ul>
|
||||
* <li>When you want to call something on one side and return a result {@link #safeCallWhenOn(Dist, Supplier)}</li>
|
||||
* <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>
|
||||
*/
|
||||
public final class DistExecutor
|
||||
{
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
private DistExecutor() {}
|
||||
|
||||
/**
|
||||
* Run the callable in the supplier only on the specified {@link Dist}.
|
||||
* This method is NOT sided-safe and special care needs to be taken in code using this method that implicit class
|
||||
* loading is not triggered by the Callable.
|
||||
*
|
||||
* This method can cause unexpected ClassNotFound exceptions.
|
||||
*
|
||||
* Use {@link #safeCallWhenOn(Dist, Supplier)} where possible.
|
||||
*
|
||||
* @param dist The dist to run on
|
||||
* @param toRun A supplier of the callable to run (Supplier wrapper to ensure classloading only on the appropriate dist)
|
||||
* @param <T> The return type from the callable
|
||||
* @return The callable's result
|
||||
* @deprecated use {@link #safeCallWhenOn(Dist, Supplier)} instead. This remains for advanced use cases.
|
||||
*/
|
||||
@Deprecated
|
||||
public static <T> T callWhenOn(Dist dist, Supplier<Callable<T>> toRun) {
|
||||
return unsafeCallWhenOn(dist, toRun);
|
||||
}
|
||||
|
||||
public static <T> T unsafeCallWhenOn(Dist dist, Supplier<Callable<T>> toRun) {
|
||||
if (dist == FMLEnvironment.dist) {
|
||||
try
|
||||
{
|
||||
return toRun.get().call();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the SafeCallable when on the correct {@link Dist}.
|
||||
*
|
||||
* <strong>The lambda supplied here is required to be a method reference to a method defined in
|
||||
* another class, otherwise an invalid SafeReferent error will be thrown</strong>
|
||||
* @param dist the dist which this will run on
|
||||
* @param toRun the SafeCallable to run and return the result from
|
||||
* @param <T> The type of the SafeCallable
|
||||
* @return the result of the SafeCallable or null if on the wrong side
|
||||
*/
|
||||
public static <T> T safeCallWhenOn(Dist dist, Supplier<SafeCallable<T>> toRun) {
|
||||
validateSafeReferent(toRun);
|
||||
return callWhenOn(dist, toRun::get);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the supplied Runnable on the speicified side. Same warnings apply as {@link #callWhenOn(Dist, Supplier)}.
|
||||
*
|
||||
* This method can cause unexpected ClassNotFound exceptions.
|
||||
*
|
||||
* @see #callWhenOn(Dist, Supplier)
|
||||
* @param dist Dist to run this code on
|
||||
* @param toRun The code to run
|
||||
* @deprecated use {@link #safeRunWhenOn(Dist, Supplier)} where possible. Advanced uses only.
|
||||
*/
|
||||
@Deprecated
|
||||
public static void runWhenOn(Dist dist, Supplier<Runnable> toRun) {
|
||||
unsafeRunWhenOn(dist, toRun);
|
||||
}
|
||||
/**
|
||||
* Runs the supplied Runnable on the speicified side. Same warnings apply as {@link #unsafeCallWhenOn(Dist, Supplier)}.
|
||||
*
|
||||
* This method can cause unexpected ClassNotFoundException problems in common scenarios. Understand the pitfalls of
|
||||
* the way the class verifier works to load classes before using this.
|
||||
*
|
||||
* Use {@link #safeRunWhenOn(Dist, Supplier)} if you can.
|
||||
*
|
||||
* @see #unsafeCallWhenOn(Dist, Supplier)
|
||||
* @param dist Dist to run this code on
|
||||
* @param toRun The code to run
|
||||
*/
|
||||
public static void unsafeRunWhenOn(Dist dist, Supplier<Runnable> toRun) {
|
||||
if (dist == FMLEnvironment.dist) {
|
||||
toRun.get().run();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the supplied SafeRunnable when on the correct Dist.
|
||||
* @param dist The dist to run on
|
||||
* @param toRun The code to run
|
||||
*/
|
||||
public static void safeRunWhenOn(Dist dist, Supplier<SafeRunnable> toRun) {
|
||||
validateSafeReferent(toRun);
|
||||
if (dist == FMLEnvironment.dist) {
|
||||
toRun.get().run();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Executes one of the two suppliers, based on which side is active.
|
||||
*
|
||||
* <p>
|
||||
* Example (replacement for old SidedProxy):<br/>
|
||||
* {@code Proxy p = DistExecutor.runForDist(()->ClientProxy::new, ()->ServerProxy::new);}
|
||||
*
|
||||
* NOTE: the double supplier is required to avoid classloading the secondary target.
|
||||
*
|
||||
* @param clientTarget The supplier supplier to run when on the {@link Dist#CLIENT}
|
||||
* @param serverTarget The supplier supplier to run when on the {@link Dist#DEDICATED_SERVER}
|
||||
* @param <T> The common type to return
|
||||
* @return The returned instance
|
||||
* @deprecated Use {@link #safeRunForDist(Supplier, Supplier)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static <T> T runForDist(Supplier<Supplier<T>> clientTarget, Supplier<Supplier<T>> serverTarget) {
|
||||
return unsafeRunForDist(clientTarget, serverTarget);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsafe version of {@link #safeRunForDist(Supplier, Supplier)}. Use only when you know what you're doing
|
||||
* and understand why the verifier can cause unexpected ClassNotFoundException crashes even when code is apparently
|
||||
* not sided. Ensure you test both sides fully to be confident in using this.
|
||||
*
|
||||
* @param clientTarget The supplier supplier to run when on the {@link Dist#CLIENT}
|
||||
* @param serverTarget The supplier supplier to run when on the {@link Dist#DEDICATED_SERVER}
|
||||
* @param <T> The common type to return
|
||||
* @return The returned instance
|
||||
*/
|
||||
public static <T> T unsafeRunForDist(Supplier<Supplier<T>> clientTarget, Supplier<Supplier<T>> serverTarget) {
|
||||
switch (FMLEnvironment.dist)
|
||||
{
|
||||
case CLIENT:
|
||||
return clientTarget.get().get();
|
||||
case DEDICATED_SERVER:
|
||||
return serverTarget.get().get();
|
||||
default:
|
||||
throw new IllegalArgumentException("UNSIDED?");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Executes one of the two suppliers, based on which side is active.
|
||||
*
|
||||
* <p>
|
||||
* Example (replacement for old SidedProxy):<br/>
|
||||
* {@code Proxy p = DistExecutor.safeRunForDist(()->ClientProxy::new, ()->ServerProxy::new);}
|
||||
*
|
||||
* NOTE: the double supplier is required to avoid classloading the secondary target.
|
||||
*
|
||||
* @param clientTarget The supplier supplier to run when on the {@link Dist#CLIENT}
|
||||
* @param serverTarget The supplier supplier to run when on the {@link Dist#DEDICATED_SERVER}
|
||||
* @param <T> The common type to return
|
||||
* @return The returned instance
|
||||
*/
|
||||
public static <T> T safeRunForDist(Supplier<SafeSupplier<T>> clientTarget, Supplier<SafeSupplier<T>> serverTarget) {
|
||||
validateSafeReferent(clientTarget);
|
||||
validateSafeReferent(serverTarget);
|
||||
switch (FMLEnvironment.dist)
|
||||
{
|
||||
case CLIENT:
|
||||
return clientTarget.get().get();
|
||||
case DEDICATED_SERVER:
|
||||
return serverTarget.get().get();
|
||||
default:
|
||||
throw new IllegalArgumentException("UNSIDED?");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A safe referent. This will assert that it is being called via a separated class method reference. This will
|
||||
* avoid the common pitfalls of {@link #callWhenOn(Dist, Supplier)} above.
|
||||
*
|
||||
* SafeReferents assert that they are defined as a separate method outside the scope of the calling class.
|
||||
*
|
||||
* <strong>Implementations need to be defined in a separate class to the referring site, with appropriate
|
||||
* visibility to be accessible at the callsite (generally, avoid private methods).</strong>
|
||||
*
|
||||
* <p>
|
||||
* Valid:<br/>
|
||||
*
|
||||
* {@code DistExecutor.safeCallWhenOn(Dist.CLIENT, ()->AnotherClass::clientOnlyMethod);}
|
||||
*
|
||||
* <p>
|
||||
* Invalid:<br/>
|
||||
*
|
||||
* {@code DistExecutor.safeCallWhenOn(Dist.CLIENT, ()->()->Minecraft.getInstance().world);}
|
||||
*/
|
||||
public interface SafeReferent {}
|
||||
|
||||
/**
|
||||
* SafeCallable version of {@link SafeReferent}.
|
||||
* @see SafeReferent
|
||||
* @param <T> The return type of the Callable
|
||||
*/
|
||||
public interface SafeCallable<T> extends SafeReferent, Callable<T>, Serializable {}
|
||||
|
||||
/**
|
||||
* SafeSupplier version of {@link SafeReferent}
|
||||
* @param <T> The return type of the Supplier
|
||||
*/
|
||||
public interface SafeSupplier<T> extends SafeReferent, Supplier<T>, Serializable {}
|
||||
|
||||
/**
|
||||
* SafeRunnable version of {@link SafeReferent}
|
||||
*/
|
||||
public interface SafeRunnable extends SafeReferent, Runnable, Serializable {}
|
||||
|
||||
private static final void validateSafeReferent(Supplier<? extends SafeReferent> safeReferentSupplier) {
|
||||
if (FMLEnvironment.production) return;
|
||||
final SafeReferent setter;
|
||||
try {
|
||||
setter = safeReferentSupplier.get();
|
||||
} catch (Exception e) {
|
||||
// Typically a class cast exception, just return out, expected.
|
||||
return;
|
||||
}
|
||||
|
||||
for (Class<?> cl = setter.getClass(); cl != null; cl = cl.getSuperclass()) {
|
||||
try {
|
||||
Method m = cl.getDeclaredMethod("writeReplace");
|
||||
m.setAccessible(true);
|
||||
Object replacement = m.invoke(setter);
|
||||
if (!(replacement instanceof SerializedLambda))
|
||||
break;// custom interface implementation
|
||||
SerializedLambda l = (SerializedLambda) replacement;
|
||||
if (Objects.equals(l.getCapturingClass(), l.getImplClass())) {
|
||||
LOGGER.fatal("Detected unsafe referent usage, please view the code at {}",Thread.currentThread().getStackTrace()[3]);
|
||||
throw new RuntimeException("Unsafe Referent usage found in safe referent method");
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -25,7 +25,6 @@ import java.util.function.Supplier;
|
||||
* @param <T> the type of the record which is held by the extension point
|
||||
*/
|
||||
@SuppressWarnings("unused") // Type parameter T
|
||||
// TODO: Complete re-write, this hasnt actually be used in a generic way by mods and we could make it a lot simpler.
|
||||
public interface IExtensionPoint<T extends Record>
|
||||
{
|
||||
/**
|
||||
@@ -41,7 +40,7 @@ public interface IExtensionPoint<T extends Record>
|
||||
* where {@code true} means it is from the server and {@code false} means it is from the world save. The return
|
||||
* value of the predicate determines whether the remote version is "compatible" for the purposes of the display test.</p>
|
||||
*
|
||||
* <p>The local compatibility version may be of the value {@link DisplayTest#IGNORESERVERONLY},
|
||||
* <p>The local compatibility version may be of the value {@link net.minecraftforge.network.NetworkConstants#IGNORESERVERONLY},
|
||||
* in which case clients will ignore the mod's presence if it is present on the server but not on the client.
|
||||
* However, the remote version test predicate must still accept this value as a remote version in order to display
|
||||
* as compatible if the mod is present on the client.</p>
|
||||
@@ -95,7 +94,7 @@ public interface IExtensionPoint<T extends Record>
|
||||
*/
|
||||
@SuppressWarnings("JavadocReference") // reference to NetworkConstants, ForgeHooksClient
|
||||
record DisplayTest(Supplier<String> suppliedVersion, BiPredicate<String, Boolean> remoteVersionTest) implements IExtensionPoint<DisplayTest> {
|
||||
public static final String IGNORESERVERONLY = "SERVER_ONLY";
|
||||
public static final String IGNORESERVERONLY = "OHNOES\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31";
|
||||
|
||||
/**
|
||||
* Ignores any version information coming from the server - use for server only mods
|
||||
@@ -104,8 +103,6 @@ public interface IExtensionPoint<T extends Record>
|
||||
|
||||
/**
|
||||
* Ignores all information and provides no information
|
||||
* <p>Note: If your mod is purely client-side and has no multiplayer functionality (be it dedicated servers or
|
||||
* Open to LAN), consider setting {@code clientSideOnly=true} in the root of your mods.toml.</p>
|
||||
*/
|
||||
public static final Supplier<DisplayTest> IGNORE_ALL_VERSION = () -> new DisplayTest("", (remoteVersion, isFromServer) -> true);
|
||||
|
||||
|
||||
@@ -5,15 +5,16 @@
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.fml.loading.progress.ProgressMeter;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.IntSupplier;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.function.ToIntFunction;
|
||||
|
||||
/**
|
||||
* A mod loading state. During mod loading, the mod loader transitions between states in a defined sorted list of states,
|
||||
@@ -21,7 +22,6 @@ import java.util.function.Supplier;
|
||||
*
|
||||
* @see IModStateProvider
|
||||
*/
|
||||
@NullMarked
|
||||
public interface IModLoadingState {
|
||||
/**
|
||||
* {@return the name of this state}
|
||||
@@ -44,46 +44,44 @@ public interface IModLoadingState {
|
||||
/**
|
||||
* {@return a function returning a human-friendly message for this state}
|
||||
*/
|
||||
Supplier<String> message();
|
||||
Function<ModList, String> message();
|
||||
|
||||
/**
|
||||
* @return a function that computes the size of this transition based on the size of the modlist.
|
||||
* Used to compute progress.
|
||||
*/
|
||||
default IntSupplier size() {
|
||||
return ModList::size;
|
||||
}
|
||||
ToIntFunction<ModList> size();
|
||||
|
||||
/**
|
||||
* {@return an optional runnable, which runs before starting the transition from this state to the next}
|
||||
* @see #buildTransition(Executor, Executor, ProgressMeter, Function, Function)
|
||||
*/
|
||||
@Nullable Runnable inlineRunnable();
|
||||
Optional<Consumer<ModList>> inlineRunnable();
|
||||
|
||||
/**
|
||||
* 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 @Nullable 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,
|
||||
_ -> CompletableFuture.completedFuture(null),
|
||||
_ -> 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
|
||||
@@ -91,7 +89,8 @@ 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
|
||||
*/
|
||||
@Nullable CompletableFuture<Void> buildTransition(final Executor syncExecutor,
|
||||
<T extends Event & IModBusEvent>
|
||||
Optional<CompletableFuture<Void>> buildTransition(final Executor syncExecutor,
|
||||
final Executor parallelExecutor,
|
||||
final ProgressMeter progressBar,
|
||||
final Function<Executor, CompletableFuture<Void>> preSyncTask,
|
||||
|
||||
@@ -5,47 +5,116 @@
|
||||
|
||||
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;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import net.minecraftforge.fml.loading.progress.ProgressMeter;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public interface IModStateTransition {
|
||||
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;
|
||||
}
|
||||
|
||||
@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();
|
||||
|
||||
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,20 +5,63 @@
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Spliterator;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
public final class InterModComms {
|
||||
public record IMCMessage(String senderModId, String modId, String method, Supplier<?> messageSupplier) {}
|
||||
public class InterModComms
|
||||
{
|
||||
public record IMCMessage(String senderModId, String modId, String method, Supplier<?> messageSupplier) {
|
||||
|
||||
private static final ConcurrentHashMap<String, ConcurrentLinkedQueue<IMCMessage>> containerQueues = new ConcurrentHashMap<>();
|
||||
/**
|
||||
* Deprecated: use {@link #senderModId()}
|
||||
* @return The modid of the sender. This is supplied by the caller, or by the active mod container context.
|
||||
* Consider it unreliable.
|
||||
*/
|
||||
@Deprecated
|
||||
public final String getSenderModId() {
|
||||
return this.senderModId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated: use {@link #modId()}
|
||||
* @return The modid being sent to.
|
||||
*/
|
||||
@Deprecated
|
||||
public final String getModId() {
|
||||
return this.modId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated: use {@link #method()}
|
||||
* @return The method being sent to.
|
||||
*/
|
||||
@Deprecated
|
||||
public final String getMethod() {
|
||||
return this.method;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param <T> The type of the message.
|
||||
* @return A {@link Supplier} of the message.
|
||||
* Use {@link #messageSupplier()}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Deprecated
|
||||
public final <T> Supplier<T> getMessageSupplier() {
|
||||
return (Supplier<T>) this.messageSupplier;
|
||||
}
|
||||
}
|
||||
|
||||
private static ConcurrentMap<String, ConcurrentLinkedQueue<IMCMessage>> containerQueues = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Send IMC to remote. Sender will default to the active modcontainer, or minecraft if not.
|
||||
@@ -29,9 +72,8 @@ public final class InterModComms {
|
||||
* @return true if the message was enqueued for sending (the target modid is loaded)
|
||||
*/
|
||||
public static boolean sendTo(final String modId, final String method, final Supplier<?> thing) {
|
||||
if (!ModList.isLoaded(modId)) return false;
|
||||
containerQueues.computeIfAbsent(modId, _ -> new ConcurrentLinkedQueue<>())
|
||||
.add(new IMCMessage(ModLoadingContext.get().getContainer().getModId(), modId, method, thing));
|
||||
if (!ModList.get().isLoaded(modId)) return false;
|
||||
containerQueues.computeIfAbsent(modId, k->new ConcurrentLinkedQueue<>()).add(new IMCMessage(ModLoadingContext.get().getActiveContainer().getModId(), modId, method, thing));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -45,9 +87,8 @@ public final class InterModComms {
|
||||
* @return true if the message was enqueued for sending (the target modid is loaded)
|
||||
*/
|
||||
public static boolean sendTo(final String senderModId, final String modId, final String method, final Supplier<?> thing) {
|
||||
if (!ModList.isLoaded(modId)) return false;
|
||||
containerQueues.computeIfAbsent(modId, _ -> new ConcurrentLinkedQueue<>())
|
||||
.add(new IMCMessage(senderModId, modId, method, thing));
|
||||
if (!ModList.get().isLoaded(modId)) return false;
|
||||
containerQueues.computeIfAbsent(modId, k->new ConcurrentLinkedQueue<>()).add(new IMCMessage(senderModId, modId, method, thing));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -71,17 +112,19 @@ public final class InterModComms {
|
||||
* @return All messages
|
||||
*/
|
||||
public static Stream<IMCMessage> getMessages(final String modId) {
|
||||
return getMessages(modId, _ -> true);
|
||||
return getMessages(modId, s->Boolean.TRUE);
|
||||
}
|
||||
|
||||
private record QueueFilteringSpliterator(
|
||||
ConcurrentLinkedQueue<IMCMessage> queue,
|
||||
Predicate<String> methodFilter,
|
||||
Iterator<IMCMessage> iterator
|
||||
) implements Spliterator<IMCMessage> {
|
||||
private static class QueueFilteringSpliterator implements Spliterator<IMCMessage>
|
||||
{
|
||||
private final ConcurrentLinkedQueue<IMCMessage> queue;
|
||||
private final Predicate<String> methodFilter;
|
||||
private final Iterator<IMCMessage> iterator;
|
||||
|
||||
public QueueFilteringSpliterator(final ConcurrentLinkedQueue<IMCMessage> queue, final Predicate<String> methodFilter) {
|
||||
this(queue, methodFilter, queue.iterator());
|
||||
this.queue = queue;
|
||||
this.iterator = queue.iterator();
|
||||
this.methodFilter = methodFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -95,10 +138,13 @@ public final class InterModComms {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean tryAdvance(final Consumer<? super IMCMessage> action) {
|
||||
public boolean tryAdvance(final Consumer<? super IMCMessage> action)
|
||||
{
|
||||
IMCMessage next;
|
||||
do {
|
||||
if (!iterator.hasNext()) {
|
||||
do
|
||||
{
|
||||
if (!iterator.hasNext())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
next = this.iterator.next();
|
||||
@@ -113,5 +159,6 @@ public final class InterModComms {
|
||||
public Spliterator<IMCMessage> trySplit() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,8 @@ import net.minecraftforge.api.distmarker.Dist;
|
||||
*
|
||||
* @see Dist
|
||||
*/
|
||||
public enum LogicalSide {
|
||||
public enum LogicalSide
|
||||
{
|
||||
/**
|
||||
* The logical client of the Minecraft game, which interfaces with the player's inputs and renders the player's
|
||||
* viewpoint.
|
||||
@@ -26,7 +27,6 @@ public enum LogicalSide {
|
||||
* @see Dist#CLIENT
|
||||
*/
|
||||
CLIENT,
|
||||
|
||||
/**
|
||||
* The logical server of the Minecraft game, responsible for connecting to clients and running the simulation logic
|
||||
* on the level.
|
||||
@@ -41,14 +41,16 @@ public enum LogicalSide {
|
||||
/**
|
||||
* {@return if this logical side is the server}
|
||||
*/
|
||||
public boolean isServer() {
|
||||
public boolean isServer()
|
||||
{
|
||||
return !isClient();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return if the logical side is the client}
|
||||
*/
|
||||
public boolean isClient() {
|
||||
public boolean isClient()
|
||||
{
|
||||
return this == CLIENT;
|
||||
}
|
||||
}
|
||||
@@ -5,20 +5,25 @@
|
||||
|
||||
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.moddiscovery.ModFileInfo;
|
||||
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;
|
||||
|
||||
/**
|
||||
@@ -29,19 +34,26 @@ 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;
|
||||
protected ModLoadingStage modLoadingStage;
|
||||
protected Supplier<?> contextExtension;
|
||||
protected final Map<ModLoadingStage, Runnable> activityMap = new EnumMap<>(ModLoadingStage.class);
|
||||
protected final Map<ModLoadingStage, Runnable> activityMap = new HashMap<>();
|
||||
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<>();
|
||||
@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;
|
||||
@@ -49,7 +61,7 @@ public abstract class ModContainer {
|
||||
this.modLoadingStage = ModLoadingStage.CONSTRUCT;
|
||||
|
||||
final String displayTestString = info.getConfig().<String>getConfigElement("displayTest")
|
||||
.orElseGet(() -> info.getOwningFile().isClientSideOnly() ? "IGNORE_ALL_VERSION" : "MATCH_VERSION");
|
||||
.orElseGet(() -> (Boolean) info.getOwningFile().getFileProperties().getOrDefault(ModFileInfo.CLIENT_SIDE_ONLY_PROP, Boolean.FALSE) ? "IGNORE_ALL_VERSION" : "MATCH_VERSION");
|
||||
Supplier<IExtensionPoint.DisplayTest> displayTestSupplier = switch (displayTestString) {
|
||||
case "MATCH_VERSION" -> // default displaytest checks for version string match
|
||||
() -> new IExtensionPoint.DisplayTest(() -> this.modInfo.getVersion().toString(),
|
||||
@@ -71,35 +83,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;
|
||||
}
|
||||
|
||||
@@ -108,7 +143,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);
|
||||
}
|
||||
|
||||
@@ -132,7 +168,9 @@ public abstract class ModContainer {
|
||||
configs.put(modConfig.getType(), modConfig);
|
||||
}
|
||||
|
||||
public void dispatchConfigEvent(IConfigEvent event) {}
|
||||
public void dispatchConfigEvent(IConfigEvent event) {
|
||||
configHandler.ifPresent(configHandler->configHandler.accept(event));
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this mod match the supplied mod?
|
||||
@@ -147,17 +185,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,22 +5,37 @@
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.fml.loading.LoadingModList;
|
||||
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;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
|
||||
import net.minecraftforge.forgespi.locating.IModFile;
|
||||
import org.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,131 +45,195 @@ 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 final class ModList {
|
||||
private ModList() {}
|
||||
public class ModList
|
||||
{
|
||||
private static Logger LOGGER = LogManager.getLogger();
|
||||
private static ModList INSTANCE;
|
||||
private final List<IModFileInfo> modFiles;
|
||||
private final List<IModInfo> sortedList;
|
||||
private final Map<String, ModFileInfo> fileById;
|
||||
private List<ModContainer> mods;
|
||||
private Map<String, ModContainer> indexedMods;
|
||||
private List<ModFileScanData> modFileScanData;
|
||||
private List<ModContainer> sortedContainers;
|
||||
|
||||
private static final List<IModFileInfo> MOD_FILES;
|
||||
private static final List<IModInfo> SORTED_LIST;
|
||||
private static final Map<String, IModFileInfo> FILE_BY_ID;
|
||||
private static List<ModContainer> mods;
|
||||
private static Map<String, ModContainer> indexedMods;
|
||||
private static List<ModContainer> sortedContainers;
|
||||
|
||||
static {
|
||||
var loadingModFiles = LoadingModList.getModFiles().stream().map(ModFileInfo::getFile).toList();
|
||||
var loadingSortedList = LoadingModList.getMods();
|
||||
|
||||
var modFileInfos = MOD_FILES = loadingModFiles.stream().map(ModFile::getModFileInfo).toList();
|
||||
SORTED_LIST = loadingSortedList.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());
|
||||
}
|
||||
FILE_BY_ID = Map.copyOf(byId);
|
||||
CrashReportCallables.registerCrashCallable("Mod List", ModList::crashReport);
|
||||
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);
|
||||
}
|
||||
|
||||
private static String getModContainerState(String modId) {
|
||||
private String getModContainerState(String modId) {
|
||||
return getModContainerById(modId).map(ModContainer::getCurrentState).map(Object::toString).orElse("NONE");
|
||||
}
|
||||
|
||||
private static String fileToLine(IModFile mf) {
|
||||
var mainMod = mf.getModInfos().getFirst();
|
||||
private String fileToLine(IModFile mf) {
|
||||
return String.format(Locale.ENGLISH, "%-50.50s|%-30.30s|%-30.30s|%-20.20s|%-10.10s|Manifest: %s", mf.getFileName(),
|
||||
mainMod.getDisplayName(),
|
||||
mainMod.getModId(),
|
||||
mainMod.getVersion(),
|
||||
getModContainerState(mainMod.getModId()),
|
||||
mf.getModInfos().get(0).getDisplayName(),
|
||||
mf.getModInfos().get(0).getModId(),
|
||||
mf.getModInfos().get(0).getVersion(),
|
||||
getModContainerState(mf.getModInfos().get(0).getModId()),
|
||||
((ModFileInfo)mf.getModFileInfo()).getCodeSigningFingerprint().orElse("NOSIGNATURE"));
|
||||
}
|
||||
|
||||
private static String crashReport() {
|
||||
return "\n" + applyForEachModFile(ModList::fileToLine).collect(Collectors.joining("\n\t\t", "\t\t", ""));
|
||||
private String crashReport() {
|
||||
return "\n"+applyForEachModFile(this::fileToLine).collect(Collectors.joining("\n\t\t", "\t\t", ""));
|
||||
}
|
||||
|
||||
public static void init() {}
|
||||
|
||||
public static List<IModFileInfo> getModFiles() {
|
||||
return MOD_FILES;
|
||||
public static ModList of(List<ModFile> modFiles, List<ModInfo> sortedList)
|
||||
{
|
||||
INSTANCE = new ModList(modFiles, sortedList);
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public static IModFileInfo getModFileById(String modid) {
|
||||
return FILE_BY_ID.get(modid);
|
||||
public static ModList get() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
static void setLoadedMods(final List<ModContainer> modContainers) {
|
||||
mods = modContainers;
|
||||
sortedContainers = modContainers.stream().sorted(Comparator.comparingInt(c -> SORTED_LIST.indexOf(c.getModInfo()))).toList();
|
||||
indexedMods = modContainers.stream().collect(Collectors.toUnmodifiableMap(ModContainer::getModId, Function.identity()));
|
||||
private static ForkJoinWorkerThread newForkJoinWorkerThread(ForkJoinPool pool) {
|
||||
ForkJoinWorkerThread thread = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool);
|
||||
thread.setName("modloading-worker-" + thread.getPoolIndex());
|
||||
// The default sets it to the SystemClassloader, so copy the current one.
|
||||
thread.setContextClassLoader(Thread.currentThread().getContextClassLoader());
|
||||
return thread;
|
||||
}
|
||||
|
||||
static void clearLoadedMods() {
|
||||
mods = List.of();
|
||||
indexedMods = Map.of();
|
||||
sortedContainers = List.of();
|
||||
public List<IModFileInfo> getModFiles()
|
||||
{
|
||||
return modFiles;
|
||||
}
|
||||
|
||||
public IModFileInfo getModFileById(String modid)
|
||||
{
|
||||
return this.fileById.get(modid);
|
||||
}
|
||||
|
||||
<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))
|
||||
.collect(Collectors.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).collect(Collectors.toList());
|
||||
CompletableFuture<Void> cf = new CompletableFuture<>();
|
||||
final RuntimeException accumulator = new RuntimeException();
|
||||
cf.completeExceptionally(accumulator);
|
||||
throwables.forEach(exception -> {
|
||||
if (exception instanceof CompletionException) {
|
||||
exception = exception.getCause();
|
||||
}
|
||||
if (exception.getSuppressed().length!=0) {
|
||||
Arrays.stream(exception.getSuppressed()).forEach(accumulator::addSuppressed);
|
||||
} 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()];
|
||||
futures.forEach(future -> {
|
||||
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.indexedMods = modContainers.stream().collect(Collectors.toMap(ModContainer::getModId, Function.identity()));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> Optional<T> getModObjectById(String modId) {
|
||||
public <T> Optional<T> getModObjectById(String modId)
|
||||
{
|
||||
return getModContainerById(modId).map(ModContainer::getMod).map(o -> (T) o);
|
||||
}
|
||||
|
||||
public static Optional<? extends ModContainer> getModContainerById(String modId) {
|
||||
return Optional.ofNullable(indexedMods.get(modId));
|
||||
public Optional<? extends ModContainer> getModContainerById(String modId)
|
||||
{
|
||||
return Optional.ofNullable(this.indexedMods.get(modId));
|
||||
}
|
||||
|
||||
public static Optional<? extends ModContainer> getModContainerByObject(Object obj) {
|
||||
public Optional<? extends ModContainer> getModContainerByObject(Object obj)
|
||||
{
|
||||
return mods.stream().filter(mc -> mc.getMod() == obj).findFirst();
|
||||
}
|
||||
|
||||
public static List<IModInfo> getMods() {
|
||||
return SORTED_LIST;
|
||||
public List<IModInfo> getMods()
|
||||
{
|
||||
return this.sortedList;
|
||||
}
|
||||
|
||||
public static boolean isLoaded(String modTarget) {
|
||||
return indexedMods.containsKey(modTarget);
|
||||
public boolean isLoaded(String modTarget)
|
||||
{
|
||||
return this.indexedMods.containsKey(modTarget);
|
||||
}
|
||||
|
||||
public static int size() {
|
||||
public int size()
|
||||
{
|
||||
return mods.size();
|
||||
}
|
||||
|
||||
public static List<ModFileScanData> getAllScanData() {
|
||||
final class LazyInit {
|
||||
private LazyInit() {}
|
||||
private static final List<ModFileScanData> MOD_FILE_SCAN_DATA = SORTED_LIST.stream()
|
||||
.map(IModInfo::getOwningFile)
|
||||
.filter(Objects::nonNull)
|
||||
.map(IModFileInfo::getFile)
|
||||
.distinct()
|
||||
.map(IModFile::getScanResult)
|
||||
.toList();
|
||||
public List<ModFileScanData> getAllScanData()
|
||||
{
|
||||
if (modFileScanData == null)
|
||||
{
|
||||
modFileScanData = this.sortedList.stream().
|
||||
map(IModInfo::getOwningFile).
|
||||
filter(Objects::nonNull).
|
||||
map(IModFileInfo::getFile).
|
||||
distinct().
|
||||
map(IModFile::getScanResult).
|
||||
collect(Collectors.toList());
|
||||
}
|
||||
return LazyInit.MOD_FILE_SCAN_DATA;
|
||||
return modFileScanData;
|
||||
|
||||
}
|
||||
|
||||
public static void forEachModFile(Consumer<IModFile> fileConsumer) {
|
||||
MOD_FILES.stream().map(IModFileInfo::getFile).forEach(fileConsumer);
|
||||
public void forEachModFile(Consumer<IModFile> fileConsumer)
|
||||
{
|
||||
modFiles.stream().map(IModFileInfo::getFile).forEach(fileConsumer);
|
||||
}
|
||||
|
||||
public static <T> Stream<T> applyForEachModFile(Function<IModFile, T> function) {
|
||||
return MOD_FILES.stream().map(IModFileInfo::getFile).map(function);
|
||||
public <T> Stream<T> applyForEachModFile(Function<IModFile, T> function) {
|
||||
return modFiles.stream().map(IModFileInfo::getFile).map(function);
|
||||
}
|
||||
|
||||
public static void forEachModContainer(BiConsumer<String, ModContainer> modContainerConsumer) {
|
||||
public void forEachModContainer(BiConsumer<String, ModContainer> modContainerConsumer) {
|
||||
indexedMods.forEach(modContainerConsumer);
|
||||
}
|
||||
|
||||
public static void forEachModInOrder(Consumer<ModContainer> containerConsumer) {
|
||||
sortedContainers.forEach(containerConsumer);
|
||||
public void forEachModInOrder(Consumer<ModContainer> containerConsumer) {
|
||||
this.sortedContainers.forEach(containerConsumer);
|
||||
}
|
||||
|
||||
public static List<ModContainer> getLoadedMods() {
|
||||
return sortedContainers;
|
||||
}
|
||||
|
||||
public static <T> Stream<T> applyForEachModContainer(Function<ModContainer, T> function) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,16 @@
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.eventbus.api.bus.BusGroup;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.fml.loading.*;
|
||||
import net.minecraftforge.fml.loading.FMLEnvironment;
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
import net.minecraftforge.fml.loading.ImmediateWindowHandler;
|
||||
import net.minecraftforge.fml.loading.LoadingModList;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.InvalidModIdentifier;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
|
||||
import net.minecraftforge.fml.loading.progress.ProgressMeter;
|
||||
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
@@ -27,6 +32,7 @@ import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static net.minecraftforge.fml.Logging.CORE;
|
||||
import static net.minecraftforge.fml.Logging.LOADING;
|
||||
|
||||
@@ -66,239 +72,197 @@ import static net.minecraftforge.fml.Logging.LOADING;
|
||||
* and completes the mod loading sequence.</dd>
|
||||
* </dl>
|
||||
*/
|
||||
public final class ModLoader {
|
||||
private ModLoader() {}
|
||||
|
||||
public class ModLoader
|
||||
{
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
private static final ArrayList<ModLoadingException> LOADING_EXCEPTIONS = new ArrayList<>();
|
||||
private static final ArrayList<ModLoadingWarning> LOADING_WARNINGS = new ArrayList<>();
|
||||
private static final Consumer<String> STATUS_CONSUMER = StartupNotificationManager.modLoaderConsumer().orElse(_ -> {});
|
||||
private static final HashSet<IModLoadingState> COMPLETED_STATES = new HashSet<>();
|
||||
private static boolean loadingStateValid;
|
||||
private static ModLoader INSTANCE;
|
||||
private final LoadingModList loadingModList;
|
||||
|
||||
private static String computeLanguageList() {
|
||||
return "\n" + LanguageLoadingProvider
|
||||
.applyForEach(lp -> lp.name() + '@' + lp.getClass().getPackage().getImplementationVersion())
|
||||
.collect(Collectors.joining("\n\t\t", "\t\t", ""));
|
||||
private final List<ModLoadingException> loadingExceptions;
|
||||
private final List<ModLoadingWarning> loadingWarnings;
|
||||
private final ModStateManager stateManager;
|
||||
private boolean loadingStateValid;
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
private final Optional<Consumer<String>> statusConsumer = StartupNotificationManager.modLoaderConsumer();
|
||||
private final Set<IModLoadingState> completedStates = new HashSet<>();
|
||||
private ModList modList;
|
||||
|
||||
private ModLoader()
|
||||
{
|
||||
INSTANCE = this;
|
||||
this.loadingModList = FMLLoader.getLoadingModList();
|
||||
this.loadingExceptions = FMLLoader.getLoadingModList().getErrors().stream()
|
||||
.flatMap(ModLoadingException::fromEarlyException)
|
||||
.collect(Collectors.toList());
|
||||
this.loadingWarnings = FMLLoader.getLoadingModList().getBrokenFiles().stream()
|
||||
.map(file -> new ModLoadingWarning(null, ModLoadingStage.VALIDATE, InvalidModIdentifier.identifyJarProblem(file.getFilePath()).orElse("fml.modloading.brokenfile"), file.getFileName()))
|
||||
.collect(Collectors.toList());
|
||||
FMLLoader.getLoadingModList().getModFiles().stream()
|
||||
.filter(ModFileInfo::missingLicense)
|
||||
.filter(modFileInfo -> modFileInfo.getMods().stream().noneMatch(thisModInfo -> this.loadingExceptions.stream().map(ModLoadingException::getModInfo).anyMatch(otherInfo -> otherInfo == thisModInfo))) //Ignore files where any other mod already encountered an error
|
||||
.map(modFileInfo -> new ModLoadingException(null, ModLoadingStage.VALIDATE, "fml.modloading.missinglicense", null, modFileInfo.getFile()))
|
||||
.forEach(this.loadingExceptions::add);
|
||||
this.stateManager = new ModStateManager();
|
||||
CrashReportCallables.registerCrashCallable("ModLauncher", FMLLoader::getLauncherInfo);
|
||||
CrashReportCallables.registerCrashCallable("ModLauncher launch target", FMLLoader::launcherHandlerName);
|
||||
CrashReportCallables.registerCrashCallable("ModLauncher naming", FMLLoader::getNaming);
|
||||
CrashReportCallables.registerCrashCallable("ModLauncher services", this::computeModLauncherServiceList);
|
||||
CrashReportCallables.registerCrashCallable("FML Language Providers", this::computeLanguageList);
|
||||
}
|
||||
|
||||
private static String computeModLauncherServiceList() {
|
||||
private String computeLanguageList() {
|
||||
return "\n"+FMLLoader.getLanguageLoadingProvider().applyForEach(lp->lp.name() +"@"+ lp.getClass().getPackage().getImplementationVersion()).collect(Collectors.joining("\n\t\t", "\t\t", ""));
|
||||
}
|
||||
|
||||
private 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",""));
|
||||
}
|
||||
|
||||
public static ModLoader get()
|
||||
{
|
||||
return INSTANCE == null ? INSTANCE = new ModLoader() : INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run on the primary starting thread by ClientModLoader and ServerModLoader
|
||||
* @param syncExecutor An executor to run tasks on the main thread
|
||||
* @param parallelExecutor An executor to run tasks on a parallel loading thread pool
|
||||
* @param periodicTask Optional periodic task to perform on the main thread while other activities run
|
||||
*/
|
||||
public static void gatherAndInitializeMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
|
||||
var loadingExceptions = LoadingModList.getErrors().stream()
|
||||
.flatMap(ModLoadingException::fromEarlyException)
|
||||
.toList();
|
||||
ModLoader.LOADING_EXCEPTIONS.addAll(loadingExceptions);
|
||||
LOADING_WARNINGS.addAll(LoadingModList.getBrokenFiles().stream()
|
||||
.map(file -> new ModLoadingWarning(null, ModLoadingStage.VALIDATE, InvalidModIdentifier.identifyJarProblem(file.getFilePath()).orElse("fml.modloading.brokenfile"), file.getFileName()))
|
||||
.toList());
|
||||
Set<IModInfo> erroredModInfos;
|
||||
if (loadingExceptions.isEmpty()) {
|
||||
erroredModInfos = Collections.emptySet();
|
||||
} else {
|
||||
erroredModInfos = Collections.newSetFromMap(new IdentityHashMap<>());
|
||||
erroredModInfos.addAll(loadingExceptions.stream().map(ModLoadingException::getModInfo).toList());
|
||||
}
|
||||
LoadingModList.getModFiles().stream()
|
||||
.filter(ModFileInfo::missingLicense)
|
||||
.filter(modFileInfo -> modFileInfo.getMods().stream().noneMatch(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()))
|
||||
.forEach(LOADING_EXCEPTIONS::add);
|
||||
CrashReportCallables.registerCrashCallable("ModLauncher", FMLLoader::getLauncherInfo);
|
||||
CrashReportCallables.registerCrashCallable("ModLauncher launch target", FMLLoader::launcherHandlerName);
|
||||
CrashReportCallables.registerCrashCallable("ModLauncher naming", FMLLoader::getNaming);
|
||||
CrashReportCallables.registerCrashCallable("ModLauncher services", ModLoader::computeModLauncherServiceList);
|
||||
CrashReportCallables.registerCrashCallable("FML Language Providers", ModLoader::computeLanguageList);
|
||||
|
||||
public void gatherAndInitializeMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
|
||||
ForgeFeature.registerFeature("javaVersion", ForgeFeature.VersionFeatureTest.forVersionString(IModInfo.DependencySide.BOTH, System.getProperty("java.version")));
|
||||
ForgeFeature.registerFeature("openGLVersion", ForgeFeature.VersionFeatureTest.forVersionString(IModInfo.DependencySide.CLIENT, ImmediateWindowHandler.getGLVersion()));
|
||||
loadingStateValid = true;
|
||||
FMLLoader.backgroundScanHandler.waitForScanToComplete(periodicTask);
|
||||
if (!LOADING_EXCEPTIONS.isEmpty()) {
|
||||
LOGGER.fatal(CORE, "Error during pre-loading phase", LOADING_EXCEPTIONS.getFirst());
|
||||
STATUS_CONSUMER.accept("ERROR DURING MOD LOADING");
|
||||
ModList.clearLoadedMods();
|
||||
final ModList modList = ModList.of(loadingModList.getModFiles().stream().map(ModFileInfo::getFile).toList(),
|
||||
loadingModList.getMods());
|
||||
if (!this.loadingExceptions.isEmpty()) {
|
||||
LOGGER.fatal(CORE, "Error during pre-loading phase", loadingExceptions.get(0));
|
||||
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
|
||||
modList.setLoadedMods(Collections.emptyList());
|
||||
loadingStateValid = false;
|
||||
throw new LoadingFailedException(LOADING_EXCEPTIONS);
|
||||
}
|
||||
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);
|
||||
}
|
||||
throw new LoadingFailedException(loadingExceptions);
|
||||
}
|
||||
List<? extends ForgeFeature.Bound> failedBounds = loadingModList.getMods().stream()
|
||||
.map(ModInfo::getForgeFeatures)
|
||||
.flatMap(Collection::stream)
|
||||
.filter(bound -> !ForgeFeature.testFeature(FMLEnvironment.dist, bound))
|
||||
.toList();
|
||||
|
||||
if (!failedBounds.isEmpty()) {
|
||||
LOGGER.fatal(CORE, "Failed to validate feature bounds for mods: {}", failedBounds);
|
||||
STATUS_CONSUMER.accept("ERROR DURING MOD LOADING");
|
||||
ModList.clearLoadedMods();
|
||||
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
|
||||
modList.setLoadedMods(Collections.emptyList());
|
||||
loadingStateValid = false;
|
||||
throw new LoadingFailedException(failedBounds.stream()
|
||||
.map(fb -> new ModLoadingException(fb.modInfo(), ModLoadingStage.CONSTRUCT, "fml.modloading.feature.missing", null, fb, ForgeFeature.featureValue(fb)))
|
||||
.toList());
|
||||
}
|
||||
|
||||
final var modContainers = new HashMap<String, ModContainer>();
|
||||
for (var file : LoadingModList.getModFiles()) {
|
||||
var containers = buildMods(file.getFile());
|
||||
for (var container : containers)
|
||||
modContainers.put(container.getModId(), container);
|
||||
}
|
||||
|
||||
if (!LOADING_EXCEPTIONS.isEmpty()) {
|
||||
LOGGER.fatal(CORE, "Failed to initialize mod containers", LOADING_EXCEPTIONS.getFirst());
|
||||
STATUS_CONSUMER.accept("ERROR DURING MOD LOADING");
|
||||
ModList.clearLoadedMods();
|
||||
final List<ModContainer> modContainers = loadingModList.getModFiles().stream()
|
||||
.map(ModFileInfo::getFile)
|
||||
.map(this::buildMods)
|
||||
.<ModContainer>mapMulti(Iterable::forEach)
|
||||
.toList();
|
||||
if (!loadingExceptions.isEmpty()) {
|
||||
LOGGER.fatal(CORE, "Failed to initialize mod containers", loadingExceptions.get(0));
|
||||
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
|
||||
modList.setLoadedMods(Collections.emptyList());
|
||||
loadingStateValid = false;
|
||||
throw new LoadingFailedException(LOADING_EXCEPTIONS);
|
||||
}
|
||||
|
||||
// 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());
|
||||
var stateList = ModStateManager.getStates(ModLoadingPhase.GATHER);
|
||||
var progress = StartupMessageManager.addProgressBar("Mod Gather", stateList.stream().mapToInt(mls -> mls.size().getAsInt()).sum());
|
||||
for (IModLoadingState mls : stateList) {
|
||||
dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress);
|
||||
throw new LoadingFailedException(loadingExceptions);
|
||||
}
|
||||
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(modList)).sum());
|
||||
stateList.forEach(mls->dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress));
|
||||
progress.complete();
|
||||
}
|
||||
|
||||
public static void loadMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
|
||||
var stateList = ModStateManager.getStates(ModLoadingPhase.LOAD);
|
||||
var progress = StartupMessageManager.addProgressBar("Mod Loading", stateList.stream().mapToInt(mls -> mls.size().getAsInt()).sum());
|
||||
for (IModLoadingState mls : stateList) {
|
||||
dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress);
|
||||
}
|
||||
public void loadMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
|
||||
var stateList = stateManager.getStates(ModLoadingPhase.LOAD);
|
||||
var progress = StartupMessageManager.addProgressBar("Mod Loading", stateList.stream().mapToInt(mls -> mls.size().applyAsInt(modList)).sum());
|
||||
stateList.forEach(mls->dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress));
|
||||
progress.complete();
|
||||
}
|
||||
|
||||
public static void finishMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
|
||||
var stateList = ModStateManager.getStates(ModLoadingPhase.COMPLETE);
|
||||
var progress = StartupMessageManager.addProgressBar("Mod Complete", stateList.stream().mapToInt(mls -> mls.size().getAsInt()).sum());
|
||||
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);
|
||||
|
||||
STATUS_CONSUMER.accept("Mod loading complete - %d mods loaded".formatted(ModList.size()));
|
||||
public void finishMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
|
||||
var stateList = stateManager.getStates(ModLoadingPhase.COMPLETE);
|
||||
var progress = StartupMessageManager.addProgressBar("Mod Complete", stateList.stream().mapToInt(mls -> mls.size().applyAsInt(modList)).sum());
|
||||
stateList.forEach(mls->dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress));
|
||||
statusConsumer.ifPresent(c->c.accept(String.format("Mod loading complete - %d mods loaded", this.modList.size())));
|
||||
progress.complete();
|
||||
}
|
||||
|
||||
private static void dispatchAndHandleError(IModLoadingState state, ModWorkManager.DrivenExecutor syncExecutor, Executor parallelExecutor, final Runnable ticker, final ProgressMeter progressBar) {
|
||||
private void dispatchAndHandleError(IModLoadingState state, ModWorkManager.DrivenExecutor syncExecutor, Executor parallelExecutor, final Runnable ticker, final ProgressMeter progressBar) {
|
||||
if (!isLoadingStateValid()) {
|
||||
LOGGER.error("Cowardly refusing to process mod state change request from {}", state);
|
||||
return;
|
||||
}
|
||||
progressBar.label(progressBar.name() + " working");
|
||||
progressBar.label(progressBar.name()+ " working");
|
||||
syncExecutor.drive(ticker);
|
||||
|
||||
var inlineRunnable = state.inlineRunnable();
|
||||
if (inlineRunnable != null) handleInlineTransition(inlineRunnable, state, syncExecutor, ticker);
|
||||
|
||||
var transition = state.buildTransition(syncExecutor, parallelExecutor, progressBar);
|
||||
if (transition != null) waitForTransition(state, syncExecutor, ticker, transition);
|
||||
|
||||
COMPLETED_STATES.add(state);
|
||||
state.inlineRunnable().ifPresent(a->this.handleInlineTransition(a, state, syncExecutor, ticker));
|
||||
state.buildTransition(syncExecutor, parallelExecutor, progressBar).ifPresent(t->waitForTransition(state, syncExecutor, ticker, t));
|
||||
completedStates.add(state);
|
||||
}
|
||||
|
||||
private static void handleInlineTransition(final Runnable transition, final IModLoadingState state, final ModWorkManager.DrivenExecutor syncExecutor, final Runnable ticker) {
|
||||
var pb = StartupMessageManager.addProgressBar("State transition " + state.name() + " running", 0);
|
||||
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);
|
||||
syncExecutor.drive(ticker);
|
||||
transition.run();
|
||||
transition.accept(this.modList);
|
||||
syncExecutor.drive(ticker);
|
||||
pb.complete();
|
||||
syncExecutor.drive(ticker);
|
||||
}
|
||||
|
||||
private static void waitForTransition(final IModLoadingState state, final ModWorkManager.DrivenExecutor syncExecutor, final Runnable ticker, final CompletableFuture<Void> transition) {
|
||||
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) {
|
||||
loadingStateValid = false;
|
||||
Throwable t = e.getCause();
|
||||
boolean hasNotModLoadingEx = Arrays.stream(t.getSuppressed())
|
||||
.anyMatch(obj -> !(obj instanceof ModLoadingException));
|
||||
if (hasNotModLoadingEx) {
|
||||
final List<Throwable> notModLoading = Arrays.stream(t.getSuppressed())
|
||||
.filter(obj -> !(obj instanceof ModLoadingException))
|
||||
.collect(Collectors.toList());
|
||||
if (!notModLoading.isEmpty()) {
|
||||
LOGGER.fatal("Encountered non-modloading exceptions!", e);
|
||||
STATUS_CONSUMER.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());
|
||||
STATUS_CONSUMER.accept("ERROR DURING MOD LOADING");
|
||||
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
|
||||
throw new LoadingFailedException(modLoadingExceptions);
|
||||
}
|
||||
}
|
||||
|
||||
private static 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)
|
||||
.collect(Collectors.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();
|
||||
var modIds = modInfoMap.values().stream().map(IModInfo::getModId).sorted().collect(Collectors.toList());
|
||||
var containerIds = containers.stream().map(c -> c != null ? c.getModId() : "(null)").sorted().collect(Collectors.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);
|
||||
@@ -311,26 +275,23 @@ public final class ModLoader {
|
||||
missingMods.removeAll(modIds);
|
||||
LOGGER.fatal(LOADING, "The following mods are missing, but have classes in the jar: {}", missingMods);
|
||||
|
||||
LOADING_EXCEPTIONS.add(new ModLoadingException(null, ModLoadingStage.CONSTRUCT, "fml.modloading.missingclasses", null, modFile.getFilePath()));
|
||||
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();
|
||||
return containers.stream().filter(mc -> mc.modLoadingStage != ModLoadingStage.ERROR).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static 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 {
|
||||
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)
|
||||
throw new ModLoadingException(null, ModLoadingStage.CONSTRUCT, "fml.modloading.missingmetadata", null, modId);
|
||||
|
||||
final String modId = idToProviderEntry.getKey();
|
||||
final IModLanguageProvider.IModLanguageLoader languageLoader = idToProviderEntry.getValue();
|
||||
IModInfo info = Optional.ofNullable(modInfoMap.get(modId)).
|
||||
// throw a missing metadata error if there is no matching modid in the modInfoMap from the mods.toml file
|
||||
orElseThrow(()->new ModLoadingException(null, ModLoadingStage.CONSTRUCT, "fml.modloading.missingmetadata", null, modId));
|
||||
return languageLoader.loadMod(info, modFile.getScanResult(), FMLLoader.getGameLayer());
|
||||
} catch (ModLoadingException mle) {
|
||||
// exceptions are caught and added to the error list for later handling
|
||||
LOADING_EXCEPTIONS.add(mle);
|
||||
loadingExceptions.add(mle);
|
||||
// return an errored container instance here, because we tried and failed building a container.
|
||||
return new ErroredModContainer();
|
||||
}
|
||||
@@ -341,67 +302,60 @@ public final class ModLoader {
|
||||
* and don't want to cause extraneous crashes due to trying to do things that aren't possible in a "broken load"
|
||||
*/
|
||||
public static boolean isLoadingStateValid() {
|
||||
return loadingStateValid;
|
||||
return get().loadingStateValid;
|
||||
}
|
||||
|
||||
public static boolean hasCompletedState(IModLoadingState state) {
|
||||
return COMPLETED_STATES.contains(state);
|
||||
public boolean hasCompletedState(final String stateName) {
|
||||
IModLoadingState state = stateManager.findState(stateName);
|
||||
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.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.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.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.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 static List<ModLoadingWarning> getWarnings() {
|
||||
return List.copyOf(LOADING_WARNINGS);
|
||||
public List<ModLoadingWarning> getWarnings()
|
||||
{
|
||||
return ImmutableList.copyOf(this.loadingWarnings);
|
||||
}
|
||||
|
||||
public static void addWarning(ModLoadingWarning warning) {
|
||||
LOADING_WARNINGS.add(warning);
|
||||
public void addWarning(ModLoadingWarning warning)
|
||||
{
|
||||
this.loadingWarnings.add(warning);
|
||||
}
|
||||
|
||||
private static boolean runningDataGen = false;
|
||||
@@ -410,7 +364,7 @@ public final class ModLoader {
|
||||
return runningDataGen;
|
||||
}
|
||||
|
||||
private static final class ErroredModContainer extends ModContainer {
|
||||
private static class ErroredModContainer extends ModContainer {
|
||||
public ErroredModContainer() {
|
||||
super();
|
||||
}
|
||||
|
||||
@@ -9,46 +9,51 @@ import com.mojang.logging.LogUtils;
|
||||
import net.minecraftforge.fml.config.IConfigSpec;
|
||||
import net.minecraftforge.fml.config.ModConfig;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
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")
|
||||
@Deprecated(forRemoval = true, since = "1.21.1")
|
||||
public static ModLoadingContext get() {
|
||||
return context.get();
|
||||
}
|
||||
|
||||
void setActiveContainer(final ModContainer container) {
|
||||
/**
|
||||
* @deprecated Going to be moved to ForgeHooks for internal use.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "1.21.1")
|
||||
public void setActiveContainer(final ModContainer container) {
|
||||
this.activeContainer = container;
|
||||
this.languageExtension = container == null ? null : container.contextExtension.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Going to be moved to ForgeHooks for Internal use.
|
||||
* Going to be moved to ForgeHooks for internal use.
|
||||
* @deprecated Override/Use {@link ModLoadingContext#getContainer()}
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since="1.21.1")
|
||||
@Deprecated(forRemoval = true, since = "1.21.1")
|
||||
public ModContainer getActiveContainer() {
|
||||
return activeContainer == null ? ModList.get().getModContainerById("minecraft").orElseThrow(()->new RuntimeException("Where is minecraft???!")) : activeContainer;
|
||||
}
|
||||
|
||||
@Deprecated(forRemoval = true, since = "1.21.1")
|
||||
public String getActiveNamespace() {
|
||||
return activeContainer == null ? "minecraft" : activeContainer.getNamespace();
|
||||
}
|
||||
|
||||
public ModContainer getContainer() {
|
||||
return activeContainer == null
|
||||
? ModList.getModContainerById("minecraft").orElseThrow(()->new RuntimeException("Where is minecraft???!"))
|
||||
: activeContainer;
|
||||
return getActiveContainer();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -100,7 +105,8 @@ public class ModLoadingContext {
|
||||
}
|
||||
|
||||
public void registerConfig(ModConfig.Type type, IConfigSpec<?> spec) {
|
||||
if (spec.isEmpty()) {
|
||||
if (spec.isEmpty())
|
||||
{
|
||||
// This handles the case where a mod tries to register a config, without any options configured inside it.
|
||||
LOGGER.debug("Attempted to register an empty config for type {} on mod {}", type, getContainer().getModId());
|
||||
return;
|
||||
@@ -110,7 +116,8 @@ public class ModLoadingContext {
|
||||
}
|
||||
|
||||
public void registerConfig(ModConfig.Type type, IConfigSpec<?> spec, String fileName) {
|
||||
if (spec.isEmpty()) {
|
||||
if (spec.isEmpty())
|
||||
{
|
||||
// This handles the case where a mod tries to register a config, without any options configured inside it.
|
||||
LOGGER.debug("Attempted to register an empty config for type {} on mod {} using file name {}", type, getContainer().getModId(), fileName);
|
||||
return;
|
||||
@@ -119,6 +126,7 @@ public class ModLoadingContext {
|
||||
getContainer().addConfig(new ModConfig(type, spec, getContainer(), fileName));
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T extension() {
|
||||
return (T)languageExtension;
|
||||
|
||||
@@ -5,16 +5,16 @@
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.fml.loading.progress.ProgressMeter;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.IntSupplier;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.function.ToIntFunction;
|
||||
|
||||
/**
|
||||
* Implementation of the {@link IModLoadingState} interface.
|
||||
@@ -26,26 +26,20 @@ import java.util.function.Supplier;
|
||||
* @param inlineRunnable an optional runnable, which runs before starting the transition from this state to the next
|
||||
* @param transition optional state transition information
|
||||
*/
|
||||
@NullMarked
|
||||
public record ModLoadingState(
|
||||
String name,
|
||||
String previous,
|
||||
Supplier<String> message,
|
||||
IntSupplier size,
|
||||
ModLoadingPhase phase,
|
||||
@Nullable Runnable inlineRunnable,
|
||||
@Nullable 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 @Nullable CompletableFuture<Void> buildTransition(
|
||||
final Executor syncExecutor,
|
||||
final Executor parallelExecutor,
|
||||
final ProgressMeter progressBar,
|
||||
final Function<Executor, CompletableFuture<Void>> preSyncTask,
|
||||
final Function<Executor, CompletableFuture<Void>> postSyncTask
|
||||
) {
|
||||
if (transition == null) return null;
|
||||
return transition.build(name, syncExecutor, parallelExecutor, progressBar, preSyncTask, 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) {
|
||||
return transition.map(t -> t.build(name, syncExecutor, parallelExecutor, progressBar, preSyncTask, postSyncTask));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,12 +49,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, () -> "", () -> 0, phase, null, null);
|
||||
return new ModLoadingState(name, previous, ml -> "", f->0, phase, Optional.empty(), Optional.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,13 +62,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 +78,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 Supplier<String> message, final ModLoadingPhase phase,
|
||||
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 +94,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 Runnable inline) {
|
||||
return of(name, phase).after(previous).withInline(inline);
|
||||
final Consumer<ModList> inline) {
|
||||
return new ModLoadingState(name, previous, ml -> "Processing work " + name, ml->0, phase, Optional.of(inline), Optional.empty());
|
||||
}
|
||||
|
||||
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)
|
||||
&& this.previous.equals(that.previous);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = phase.hashCode();
|
||||
result = 31 * result + name.hashCode();
|
||||
result = 31 * result + previous.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private final String name;
|
||||
private final ModLoadingPhase phase;
|
||||
private String after = "";
|
||||
private @Nullable Supplier<String> message = null;
|
||||
private @Nullable IntSupplier 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(() -> value); }
|
||||
public Builder message(final Supplier<String> value) {
|
||||
this.message = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder size(final int value) { return size(() -> value); }
|
||||
public Builder size(final IntSupplier value) {
|
||||
this.size = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ModLoadingState empty() {
|
||||
return new ModLoadingState(name, after, message != null ? message : () -> "", size != null ? size : () -> 0, phase, null, null);
|
||||
}
|
||||
|
||||
public ModLoadingState withTransition(final IModStateTransition transition) {
|
||||
return new ModLoadingState(name, after, message != null ? message : () -> "Processing transition " + name, size != null ? size : ModList::size, phase, null, transition);
|
||||
}
|
||||
|
||||
public ModLoadingState withInline(final Runnable inline) {
|
||||
return new ModLoadingState(name, after, message != null ? message : () -> "Processing work " + name, size != null ? size : () -> 0, phase, inline, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,27 +5,42 @@
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import com.google.common.collect.Streams;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @param modInfo Mod info for mod with warning
|
||||
* @param warningStage The stage where this warning was encountered
|
||||
* @param i18nMessage I18N message to use for display
|
||||
* @param context Context for message display
|
||||
*/
|
||||
public record ModLoadingWarning(IModInfo modInfo, ModLoadingStage warningStage, String i18nMessage, List<Object> context) {
|
||||
public ModLoadingWarning(final IModInfo modInfo, final ModLoadingStage warningStage, final String i18nMessage, Object... context) {
|
||||
this(modInfo, warningStage, i18nMessage, List.of(context));
|
||||
}
|
||||
public class ModLoadingWarning
|
||||
{
|
||||
/**
|
||||
* Mod Info for mod with warning
|
||||
*/
|
||||
private final IModInfo modInfo;
|
||||
/**
|
||||
* The stage where this warning was encountered
|
||||
*/
|
||||
private final ModLoadingStage warningStage;
|
||||
|
||||
public ModLoadingWarning {
|
||||
context = List.copyOf(context);
|
||||
/**
|
||||
* I18N message to use for display
|
||||
*/
|
||||
private final String i18nMessage;
|
||||
|
||||
/**
|
||||
* Context for message display
|
||||
*/
|
||||
private final List<Object> context;
|
||||
|
||||
public ModLoadingWarning(final IModInfo modInfo, final ModLoadingStage warningStage, final String i18nMessage, Object... context) {
|
||||
this.modInfo = modInfo;
|
||||
this.warningStage = warningStage;
|
||||
this.i18nMessage = i18nMessage;
|
||||
this.context = Arrays.asList(context);
|
||||
}
|
||||
|
||||
public String formatToString() {
|
||||
return Bindings.getMessageParser().get().parseMessage(i18nMessage, Stream.concat(Stream.of(modInfo, warningStage), context.stream()).toArray());
|
||||
return Bindings.getMessageParser().get().parseMessage(i18nMessage, Streams.concat(Stream.of(modInfo, warningStage), context.stream()).toArray());
|
||||
}
|
||||
}
|
||||
@@ -15,19 +15,20 @@ import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
public final class ModStateManager {
|
||||
private ModStateManager() {}
|
||||
private static final EnumMap<ModLoadingPhase, List<IModLoadingState>> stateMap;
|
||||
public class ModStateManager {
|
||||
static ModStateManager INSTANCE;
|
||||
private final EnumMap<ModLoadingPhase, List<IModLoadingState>> stateMap;
|
||||
|
||||
static {
|
||||
public ModStateManager() {
|
||||
INSTANCE = this;
|
||||
final var sp = ServiceLoader.load(FMLLoader.getGameLayer(), IModStateProvider.class);
|
||||
stateMap = ServiceLoaderUtils.streamWithErrorHandling(sp, _ -> {})
|
||||
this.stateMap = ServiceLoaderUtils.streamWithErrorHandling(sp, sce->{})
|
||||
.map(IModStateProvider::getAllStates)
|
||||
.flatMap(List::stream)
|
||||
.collect(Collectors.groupingBy(IModLoadingState::phase, () -> new EnumMap<>(ModLoadingPhase.class), Collectors.toUnmodifiableList()));
|
||||
.<IModLoadingState>mapMulti(Iterable::forEach)
|
||||
.collect(Collectors.groupingBy(IModLoadingState::phase, ()->new EnumMap<>(ModLoadingPhase.class), Collectors.toUnmodifiableList()));
|
||||
}
|
||||
|
||||
public static List<IModLoadingState> getStates(final ModLoadingPhase phase) {
|
||||
public List<IModLoadingState> getStates(final ModLoadingPhase phase) {
|
||||
var nodes = stateMap.get(phase);
|
||||
var lookup = nodes.stream().collect(Collectors.toMap(IModLoadingState::name, Function.identity()));
|
||||
|
||||
@@ -35,20 +36,16 @@ public final class ModStateManager {
|
||||
var dummy = ModLoadingState.empty("", "", phase);
|
||||
nodes.forEach(graph::addNode);
|
||||
graph.addNode(dummy);
|
||||
for (IModLoadingState node : nodes) {
|
||||
graph.putEdge(lookup.getOrDefault(node.previous(), dummy), node);
|
||||
}
|
||||
nodes.forEach(n->graph.putEdge(lookup.getOrDefault(n.previous(), dummy), n));
|
||||
return TopologicalSort.topologicalSort(graph, Comparator.comparingInt(nodes::indexOf)).stream().filter(st->st!=dummy).toList();
|
||||
}
|
||||
|
||||
public static IModLoadingState findState(final String stateName) {
|
||||
var result = stateMap.values()
|
||||
public IModLoadingState findState(final String stateName) {
|
||||
return stateMap.values()
|
||||
.stream()
|
||||
.flatMap(List::stream)
|
||||
.flatMap(Collection::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,180 +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
|
||||
final 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as [#completableFutureFromExceptionList(List)] but specialised for a single future
|
||||
static <V> CompletionStage<Void> completableFutureFromException(FutureResult<V> t) {
|
||||
if (t.exception() == null) {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
} else {
|
||||
var exception = t.exception();
|
||||
CompletableFuture<Void> cf = new CompletableFuture<>();
|
||||
RuntimeException accumulator = new RuntimeException();
|
||||
cf.completeExceptionally(accumulator);
|
||||
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> CompletableFuture<Void> addCompletableFutureTaskForModDispatch(
|
||||
final Executor executor,
|
||||
final ProgressMeter progressBar,
|
||||
final EventGenerator<T> eventGenerator,
|
||||
final BiFunction<ModLoadingStage, Throwable, ModLoadingStage> nextState
|
||||
) {
|
||||
var modFutures = new LinkedHashMap<String, CompletableFuture<Void>>();
|
||||
for (var mod : ModList.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);
|
||||
}
|
||||
|
||||
return gather(modFutures.values()).thenComposeAsync(ModStateTransitionHelper::completableFutureFromExceptionList, executor);
|
||||
}
|
||||
|
||||
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
|
||||
) {
|
||||
CompletableFuture<Void> future;
|
||||
final var executor = transition.threadSelector().apply(syncExecutor, parallelExecutor);
|
||||
|
||||
final CompletableFuture<Void> preSyncTaskCF = preSyncTask.apply(syncExecutor);
|
||||
CompletableFuture<Void> postEventDispatchCF = preSyncTaskCF
|
||||
.thenRunAsync(() -> progressBar.label(progressBar.name() + ": dispatching " + name), parallelExecutor);
|
||||
|
||||
var eventGenerator = transition.eventFunction();
|
||||
if (eventGenerator != null) {
|
||||
BiFunction<ModLoadingStage, Throwable, ModLoadingStage> state = transition.nextModLoadingStage();
|
||||
future = addCompletableFutureTaskForModDispatch(executor, progressBar, eventGenerator, state);
|
||||
|
||||
final CompletableFuture<Void> eventDispatchCF = future.handle(FutureResult::new)
|
||||
.thenCompose(ModStateTransitionHelper::completableFutureFromException);
|
||||
postEventDispatchCF = postEventDispatchCF.thenComposeAsync(_ -> eventDispatchCF, parallelExecutor);
|
||||
}
|
||||
|
||||
postEventDispatchCF = postEventDispatchCF.thenRun(() -> postSyncTask.apply(syncExecutor));
|
||||
|
||||
return transition.finalActivityGenerator().apply(syncExecutor, postEventDispatchCF);
|
||||
}
|
||||
}
|
||||
@@ -14,20 +14,30 @@ import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
import static net.minecraftforge.fml.Logging.LOADING;
|
||||
|
||||
public final class ModWorkManager {
|
||||
public class ModWorkManager {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
private static final long PARK_TIME = TimeUnit.MILLISECONDS.toNanos(1);
|
||||
public interface DrivenExecutor extends Executor {
|
||||
boolean selfDriven();
|
||||
boolean driveOne();
|
||||
|
||||
public sealed interface DrivenExecutor extends Executor {
|
||||
void drive(Runnable ticker);
|
||||
}
|
||||
|
||||
private record SyncExecutor(ConcurrentLinkedDeque<Runnable> tasks) implements DrivenExecutor {
|
||||
public SyncExecutor() {
|
||||
this(new ConcurrentLinkedDeque<>());
|
||||
default void drive(Runnable ticker) {
|
||||
if (!selfDriven()) {
|
||||
ticker.run();
|
||||
while (true) {
|
||||
if (!driveOne()) break;
|
||||
}
|
||||
} else {
|
||||
// park for a bit so other threads can schedule
|
||||
LockSupport.parkNanos(PARK_TIME);
|
||||
}
|
||||
}
|
||||
}
|
||||
private static class SyncExecutor implements DrivenExecutor {
|
||||
private ConcurrentLinkedDeque<Runnable> tasks = new ConcurrentLinkedDeque<>();
|
||||
|
||||
private boolean driveOne() {
|
||||
@Override
|
||||
public boolean driveOne() {
|
||||
final Runnable task = tasks.pollFirst();
|
||||
if (task != null) {
|
||||
task.run();
|
||||
@@ -36,35 +46,44 @@ public final class ModWorkManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(final Runnable command) {
|
||||
tasks.addLast(command);
|
||||
public boolean selfDriven() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drive(Runnable ticker) {
|
||||
ticker.run();
|
||||
while (true) {
|
||||
if (!driveOne()) break;
|
||||
}
|
||||
public void execute(final Runnable command) {
|
||||
tasks.addLast(command);
|
||||
}
|
||||
}
|
||||
|
||||
private record WrappingExecutor(Executor wrapped) implements DrivenExecutor {
|
||||
private static class WrappingExecutor implements DrivenExecutor {
|
||||
private final Executor wrapped;
|
||||
|
||||
public WrappingExecutor(final Executor executor) {
|
||||
this.wrapped = executor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean selfDriven() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean driveOne() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(final Runnable command) {
|
||||
wrapped.execute(command);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drive(Runnable ticker) {
|
||||
// Park for a bit so other threads can schedule
|
||||
LockSupport.parkNanos(PARK_TIME);
|
||||
}
|
||||
}
|
||||
|
||||
private static final SyncExecutor syncExecutor = new SyncExecutor();
|
||||
private static SyncExecutor syncExecutor;
|
||||
|
||||
public static DrivenExecutor syncExecutor() {
|
||||
if (syncExecutor == null)
|
||||
syncExecutor = new SyncExecutor();
|
||||
return syncExecutor;
|
||||
}
|
||||
|
||||
@@ -72,8 +91,14 @@ public final class ModWorkManager {
|
||||
return new WrappingExecutor(executor);
|
||||
}
|
||||
|
||||
private static ForkJoinPool parallelThreadPool;
|
||||
public static Executor parallelExecutor() {
|
||||
return LazyInit.PARALLEL_EXECUTOR;
|
||||
if (parallelThreadPool == null) {
|
||||
final int loadingThreadCount = FMLConfig.getIntConfigValue(FMLConfig.ConfigValue.MAX_THREADS);
|
||||
LOGGER.debug(LOADING, "Using {} threads for parallel mod-loading", loadingThreadCount);
|
||||
parallelThreadPool = new ForkJoinPool(loadingThreadCount, ModWorkManager::newForkJoinWorkerThread, null, false);
|
||||
}
|
||||
return parallelThreadPool;
|
||||
}
|
||||
|
||||
private static ForkJoinWorkerThread newForkJoinWorkerThread(ForkJoinPool pool) {
|
||||
@@ -84,14 +109,4 @@ public final class ModWorkManager {
|
||||
return thread;
|
||||
}
|
||||
|
||||
private static final class LazyInit {
|
||||
private LazyInit() {}
|
||||
private static final ForkJoinPool PARALLEL_EXECUTOR;
|
||||
|
||||
static {
|
||||
final int loadingThreadCount = FMLConfig.getIntConfigValue(FMLConfig.ConfigValue.MAX_THREADS);
|
||||
LOGGER.debug(LOADING, "Using {} threads for parallel mod-loading", loadingThreadCount);
|
||||
PARALLEL_EXECUTOR = new ForkJoinPool(loadingThreadCount, ModWorkManager::newForkJoinWorkerThread, null, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ public final class OptionalMod<T>
|
||||
return new OptionalMod<>(modId);
|
||||
}
|
||||
|
||||
private static final OptionalMod<?> EMPTY = new OptionalMod<>(true);
|
||||
private static OptionalMod<?> EMPTY = new OptionalMod<>(true);
|
||||
private static <T> OptionalMod<T> empty() {
|
||||
@SuppressWarnings("unchecked")
|
||||
OptionalMod<T> t = (OptionalMod<T>) EMPTY;
|
||||
@@ -43,7 +43,7 @@ public final class OptionalMod<T>
|
||||
private T getValue()
|
||||
{
|
||||
if (!searched) {
|
||||
this.value = ModList.<T>getModObjectById(this.modId).orElse(null);
|
||||
this.value = ModList.get().<T>getModObjectById(this.modId).orElse(null);
|
||||
searched = true;
|
||||
}
|
||||
return this.value;
|
||||
@@ -213,8 +213,8 @@ public final class OptionalMod<T>
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj) return true;
|
||||
if (obj instanceof OptionalMod<?> optionalMod) {
|
||||
return Objects.equals(optionalMod.modId, modId);
|
||||
if (obj instanceof OptionalMod) {
|
||||
return Objects.equals(((OptionalMod)obj).modId, modId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ package net.minecraftforge.fml;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import net.minecraftforge.fml.loading.FMLConfig;
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@@ -34,42 +35,59 @@ import java.util.zip.GZIPInputStream;
|
||||
|
||||
import static net.minecraftforge.fml.VersionChecker.Status.*;
|
||||
|
||||
public class VersionChecker {
|
||||
public class VersionChecker
|
||||
{
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
private static final int MAX_HTTP_REDIRECTS = Integer.getInteger("http.maxRedirects", 20);
|
||||
private static final int HTTP_TIMEOUT_SECS = Integer.getInteger("http.timeoutSecs", 15);
|
||||
|
||||
public enum Status {
|
||||
public enum Status
|
||||
{
|
||||
PENDING(),
|
||||
FAILED(),
|
||||
UP_TO_DATE(),
|
||||
OUTDATED(3, true, true),
|
||||
OUTDATED(3, true),
|
||||
AHEAD(),
|
||||
BETA(),
|
||||
BETA_OUTDATED(6, true, true);
|
||||
BETA_OUTDATED(6, true);
|
||||
|
||||
private final int sheetOffset;
|
||||
private final boolean draw, animated;
|
||||
|
||||
Status() {
|
||||
Status()
|
||||
{
|
||||
this(0, false, false);
|
||||
}
|
||||
|
||||
Status(int sheetOffset, boolean draw, boolean animated) {
|
||||
Status(int sheetOffset)
|
||||
{
|
||||
this(sheetOffset, true, false);
|
||||
}
|
||||
|
||||
Status(int sheetOffset, boolean animated)
|
||||
{
|
||||
this(sheetOffset, true, animated);
|
||||
}
|
||||
|
||||
Status(int sheetOffset, boolean draw, boolean animated)
|
||||
{
|
||||
this.sheetOffset = sheetOffset;
|
||||
this.draw = draw;
|
||||
this.animated = animated;
|
||||
}
|
||||
|
||||
public int getSheetOffset() {
|
||||
public int getSheetOffset()
|
||||
{
|
||||
return sheetOffset;
|
||||
}
|
||||
|
||||
public boolean shouldDraw() {
|
||||
public boolean shouldDraw()
|
||||
{
|
||||
return draw;
|
||||
}
|
||||
|
||||
public boolean isAnimated() {
|
||||
public boolean isAnimated()
|
||||
{
|
||||
return animated;
|
||||
}
|
||||
|
||||
@@ -80,26 +98,21 @@ public class VersionChecker {
|
||||
|
||||
public record CheckResult(VersionChecker.Status status, ComparableVersion target, Map<ComparableVersion, String> changes, String url) {}
|
||||
|
||||
public static void startVersionCheck() {
|
||||
new Thread("Forge Version Check") {
|
||||
public static void startVersionCheck()
|
||||
{
|
||||
new Thread("Forge Version Check")
|
||||
{
|
||||
private HttpClient client;
|
||||
private String mcVersion;
|
||||
private String agent;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (!FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.VERSION_CHECK)) {
|
||||
public void run()
|
||||
{
|
||||
if (!FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.VERSION_CHECK))
|
||||
{
|
||||
LOGGER.info("Global Forge version check system disabled, no further processing.");
|
||||
return;
|
||||
}
|
||||
|
||||
var mc = ModList.getModContainerById("minecraft");
|
||||
var forge = ModList.getModContainerById("forge");
|
||||
this.mcVersion = mc.get().getModInfo().getVersion().toString();
|
||||
|
||||
this.agent = "Java-http-client/" + System.getProperty("java.version")
|
||||
+ " MinecraftForge/" + mcVersion + '-' + forge.get().getModInfo().getVersion();
|
||||
|
||||
client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(HTTP_TIMEOUT_SECS)).build();
|
||||
gatherMods().forEach(this::process);
|
||||
}
|
||||
@@ -109,9 +122,15 @@ public class VersionChecker {
|
||||
*/
|
||||
private String openUrlString(URL url, IModInfo mod) throws IOException, URISyntaxException, InterruptedException {
|
||||
URL currentUrl = url;
|
||||
var userAgent = this.agent + ' ' + mod.getModId() + '/' + mod.getVersion();
|
||||
|
||||
for (int redirects = 0; redirects < MAX_HTTP_REDIRECTS; redirects++) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("Java-http-client/").append(System.getProperty("java.version")).append(' ');
|
||||
sb.append("MinecraftForge/").append(FMLLoader.versionInfo().mcAndForgeVersion()).append(' ');
|
||||
sb.append(mod.getModId()).append('/').append(mod.getVersion());
|
||||
final String userAgent = sb.toString();
|
||||
|
||||
for (int redirects = 0; redirects < MAX_HTTP_REDIRECTS; redirects++)
|
||||
{
|
||||
var request = HttpRequest.newBuilder()
|
||||
.uri(currentUrl.toURI())
|
||||
.timeout(Duration.ofSeconds(HTTP_TIMEOUT_SECS))
|
||||
@@ -123,7 +142,8 @@ public class VersionChecker {
|
||||
final HttpResponse<InputStream> response = client.send(request, HttpResponse.BodyHandlers.ofInputStream());
|
||||
|
||||
int responseCode = response.statusCode();
|
||||
if (responseCode >= 300 && responseCode <= 399) {
|
||||
if (responseCode >= 300 && responseCode <= 399)
|
||||
{
|
||||
String newLocation = response.headers().firstValue("Location")
|
||||
.orElseThrow(() -> new IOException("Got a 3xx response code but Location header was null while trying to fetch " + url));
|
||||
currentUrl = new URL(currentUrl, newLocation);
|
||||
@@ -133,22 +153,26 @@ public class VersionChecker {
|
||||
final boolean isGzipEncoded = response.headers().firstValue("Content-Encoding").orElse("").equals("gzip");
|
||||
|
||||
final String bodyStr;
|
||||
try (var inStream = isGzipEncoded ? new GZIPInputStream(response.body()) : response.body();
|
||||
var bufferedReader = new BufferedReader(new InputStreamReader(inStream))
|
||||
) {
|
||||
bodyStr = bufferedReader.lines().collect(Collectors.joining("\n"));
|
||||
try (InputStream inStream = isGzipEncoded ? new GZIPInputStream(response.body()) : response.body())
|
||||
{
|
||||
try (var bufferedReader = new BufferedReader(new InputStreamReader(inStream)))
|
||||
{
|
||||
bodyStr = bufferedReader.lines().collect(Collectors.joining("\n"));
|
||||
}
|
||||
}
|
||||
return bodyStr;
|
||||
}
|
||||
throw new IOException("Too many redirects while trying to fetch " + url);
|
||||
}
|
||||
|
||||
private void process(IModInfo mod) {
|
||||
private void process(IModInfo mod)
|
||||
{
|
||||
Status status = PENDING;
|
||||
ComparableVersion target = null;
|
||||
Map<ComparableVersion, String> changes = null;
|
||||
String display_url = null;
|
||||
try {
|
||||
try
|
||||
{
|
||||
if (mod.getUpdateURL().isEmpty()) return;
|
||||
URL url = mod.getUpdateURL().get();
|
||||
LOGGER.info("[{}] Starting version check at {}", mod.getModId(), url.toString());
|
||||
@@ -157,40 +181,54 @@ public class VersionChecker {
|
||||
|
||||
LOGGER.debug("[{}] Received version check data:\n{}", mod.getModId(), data);
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> json = new Gson().fromJson(data, Map.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> promos = (Map<String, String>)json.get("promos");
|
||||
display_url = (String)json.get("homepage");
|
||||
|
||||
var mcVersion = FMLLoader.versionInfo().mcVersion();
|
||||
String rec = promos.get(mcVersion + "-recommended");
|
||||
String lat = promos.get(mcVersion + "-latest");
|
||||
ComparableVersion current = new ComparableVersion(mod.getVersion().toString());
|
||||
|
||||
if (rec != null) {
|
||||
if (rec != null)
|
||||
{
|
||||
ComparableVersion recommended = new ComparableVersion(rec);
|
||||
int diff = recommended.compareTo(current);
|
||||
|
||||
if (diff == 0)
|
||||
status = UP_TO_DATE;
|
||||
else if (diff < 0) {
|
||||
else if (diff < 0)
|
||||
{
|
||||
status = AHEAD;
|
||||
if (lat != null) {
|
||||
if (lat != null)
|
||||
{
|
||||
ComparableVersion latest = new ComparableVersion(lat);
|
||||
if (current.compareTo(latest) < 0) {
|
||||
if (current.compareTo(latest) < 0)
|
||||
{
|
||||
status = OUTDATED;
|
||||
target = latest;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
status = OUTDATED;
|
||||
target = recommended;
|
||||
}
|
||||
} else if (lat != null) {
|
||||
}
|
||||
else if (lat != null)
|
||||
{
|
||||
ComparableVersion latest = new ComparableVersion(lat);
|
||||
status = current.compareTo(latest) < 0 ? BETA_OUTDATED : BETA;
|
||||
if (current.compareTo(latest) < 0)
|
||||
status = BETA_OUTDATED;
|
||||
else
|
||||
status = BETA;
|
||||
target = latest;
|
||||
} else
|
||||
}
|
||||
else
|
||||
status = BETA;
|
||||
|
||||
LOGGER.info("[{}] Found status: {} Current: {} Target: {}", mod.getModId(), status, current, target);
|
||||
@@ -198,20 +236,27 @@ public class VersionChecker {
|
||||
changes = new LinkedHashMap<>();
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> tmp = (Map<String, String>)json.get(mcVersion);
|
||||
if (tmp != null) {
|
||||
if (tmp != null)
|
||||
{
|
||||
List<ComparableVersion> ordered = new ArrayList<>();
|
||||
for (String key : tmp.keySet()) {
|
||||
for (String key : tmp.keySet())
|
||||
{
|
||||
ComparableVersion ver = new ComparableVersion(key);
|
||||
if (ver.compareTo(current) > 0 && (target == null || ver.compareTo(target) < 1))
|
||||
{
|
||||
ordered.add(ver);
|
||||
}
|
||||
}
|
||||
Collections.sort(ordered);
|
||||
|
||||
for (ComparableVersion ver : ordered) {
|
||||
for (ComparableVersion ver : ordered)
|
||||
{
|
||||
changes.put(ver, tmp.get(ver.toString()));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.warn("Failed to process update information", e);
|
||||
status = FAILED;
|
||||
}
|
||||
@@ -221,9 +266,10 @@ public class VersionChecker {
|
||||
}
|
||||
|
||||
// Gather a list of mods that have opted in to this update system by providing a URL.
|
||||
private static List<IModInfo> gatherMods() {
|
||||
private static List<IModInfo> gatherMods()
|
||||
{
|
||||
List<IModInfo> ret = new LinkedList<>();
|
||||
for (IModInfo info : ModList.getMods()) {
|
||||
for (IModInfo info : ModList.get().getMods()) {
|
||||
if (info.getUpdateURL().isPresent())
|
||||
ret.add(info);
|
||||
}
|
||||
@@ -233,7 +279,9 @@ public class VersionChecker {
|
||||
private static final Map<IModInfo, CheckResult> RESULTS = new ConcurrentHashMap<>();
|
||||
private static final CheckResult PENDING_CHECK = new CheckResult(PENDING, null, null, null);
|
||||
|
||||
public static CheckResult getResult(IModInfo mod) {
|
||||
public static CheckResult getResult(IModInfo mod)
|
||||
{
|
||||
return RESULTS.getOrDefault(mod, PENDING_CHECK);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,7 +14,6 @@ import com.mojang.logging.LogUtils;
|
||||
import net.minecraftforge.fml.loading.FMLConfig;
|
||||
import net.minecraftforge.fml.loading.FMLPaths;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -26,66 +25,9 @@ import static net.minecraftforge.fml.config.ConfigTracker.CONFIG;
|
||||
|
||||
public class ConfigFileTypeHandler {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
static ConfigFileTypeHandler TOML = new ConfigFileTypeHandler();
|
||||
private static final Path defaultConfigPath = FMLPaths.GAMEDIR.get().resolve(FMLConfig.getConfigValue(FMLConfig.ConfigValue.DEFAULT_CONFIG_PATH));
|
||||
|
||||
private static final ConfigFileTypeHandler CLIENT = new ConfigFileTypeHandler(ModConfig.Type.CLIENT);
|
||||
private static final ConfigFileTypeHandler COMMON = new ConfigFileTypeHandler(ModConfig.Type.COMMON);
|
||||
private static final ConfigFileTypeHandler SERVER = new ConfigFileTypeHandler(ModConfig.Type.SERVER);
|
||||
|
||||
private final @Nullable ModConfig.Type type;
|
||||
private @Nullable FileWatcher watcher;
|
||||
|
||||
// exists for bin compat
|
||||
public ConfigFileTypeHandler() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
private ConfigFileTypeHandler(@Nullable ModConfig.Type type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the handler for the given {@link ModConfig.Type}.
|
||||
*
|
||||
* @param type The type to get the handler for
|
||||
* @return The handler
|
||||
*/
|
||||
static ConfigFileTypeHandler get(ModConfig.Type type) {
|
||||
return switch (type) {
|
||||
case CLIENT -> CLIENT;
|
||||
case COMMON -> COMMON;
|
||||
case SERVER -> SERVER;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link FileWatcher} for this handler, creating it if it doesn't exist and/or has been stopped.
|
||||
*
|
||||
* @return The watcher
|
||||
*
|
||||
* @apiNote This is package-private so modders can't just call {@link FileWatcher#stop()} and fuck up everything.
|
||||
*/
|
||||
FileWatcher getWatcher() {
|
||||
if (this.watcher == null) {
|
||||
LOGGER.debug(CONFIG, "Starting watcher for handler: {}", this);
|
||||
this.watcher = new FileWatcher();
|
||||
}
|
||||
|
||||
return this.watcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the {@link FileWatcher} for this handler, and sets it to null afterward. Use this instead of
|
||||
* {@link FileWatcher#stop()}.
|
||||
*/
|
||||
void stopWatcher() {
|
||||
if (this.watcher == null) return;
|
||||
|
||||
LOGGER.debug(CONFIG, "Stopping watcher for hander: {}", this);
|
||||
this.watcher.stop();
|
||||
this.watcher = null;
|
||||
}
|
||||
|
||||
public Function<ModConfig, CommentedFileConfig> reader(Path configBasePath) {
|
||||
return (c) -> {
|
||||
final Path configPath = configBasePath.resolve(c.getFileName());
|
||||
@@ -95,7 +37,7 @@ public class ConfigFileTypeHandler {
|
||||
onFileNotFound((newfile, configFormat)-> setupConfigFile(c, newfile, configFormat)).
|
||||
writingMode(WritingMode.REPLACE).
|
||||
build();
|
||||
LOGGER.debug(CONFIG, "Built TOML config for {}", configPath);
|
||||
LOGGER.debug(CONFIG, "Built TOML config for {}", configPath.toString());
|
||||
try
|
||||
{
|
||||
configData.load();
|
||||
@@ -104,9 +46,13 @@ public class ConfigFileTypeHandler {
|
||||
{
|
||||
throw new ConfigLoadingException(c, ex);
|
||||
}
|
||||
LOGGER.debug(CONFIG, "Loaded TOML config file {}", configPath);
|
||||
this.getWatcher().addWatch(configPath, new ConfigWatcher(c, configData, Thread.currentThread().getContextClassLoader()));
|
||||
LOGGER.debug(CONFIG, "Watching TOML config file {} for changes", configPath);
|
||||
LOGGER.debug(CONFIG, "Loaded TOML config file {}", configPath.toString());
|
||||
try {
|
||||
FileWatcher.defaultInstance().addWatch(configPath, new ConfigWatcher(c, configData, Thread.currentThread().getContextClassLoader()));
|
||||
LOGGER.debug(CONFIG, "Watching TOML config file {} for changes", configPath.toString());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Couldn't watch config file", e);
|
||||
}
|
||||
return configData;
|
||||
};
|
||||
}
|
||||
@@ -114,16 +60,14 @@ public class ConfigFileTypeHandler {
|
||||
public void unload(Path configBasePath, ModConfig config) {
|
||||
Path configPath = configBasePath.resolve(config.getFileName());
|
||||
try {
|
||||
this.getWatcher().removeWatch(configPath);
|
||||
FileWatcher.defaultInstance().removeWatch(configBasePath.resolve(config.getFileName()));
|
||||
} catch (RuntimeException e) {
|
||||
LOGGER.error("Failed to remove config {} from tracker!", configPath, e);
|
||||
LOGGER.error("Failed to remove config {} from tracker!", configPath.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean setupConfigFile(final ModConfig modConfig, final Path file, final ConfigFormat<?> conf) throws IOException {
|
||||
if (!Files.isDirectory(file.getParent())) {
|
||||
Files.createDirectories(file.getParent());
|
||||
}
|
||||
private boolean setupConfigFile(final ModConfig modConfig, final Path file, final ConfigFormat<?> conf) throws IOException {
|
||||
Files.createDirectories(file.getParent());
|
||||
Path p = defaultConfigPath.resolve(modConfig.getFileName());
|
||||
if (Files.exists(p)) {
|
||||
LOGGER.info(CONFIG, "Loading default config file from path {}", p);
|
||||
@@ -167,16 +111,17 @@ public class ConfigFileTypeHandler {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ConfigFileTypeHandler[" + (type != null ? type : "UNKNOWN") + "]";
|
||||
}
|
||||
private static class ConfigWatcher implements Runnable {
|
||||
private final ModConfig modConfig;
|
||||
private final CommentedFileConfig commentedFileConfig;
|
||||
private final ClassLoader realClassLoader;
|
||||
|
||||
ConfigWatcher(final ModConfig modConfig, final CommentedFileConfig commentedFileConfig, final ClassLoader classLoader) {
|
||||
this.modConfig = modConfig;
|
||||
this.commentedFileConfig = commentedFileConfig;
|
||||
this.realClassLoader = classLoader;
|
||||
}
|
||||
|
||||
private record ConfigWatcher(
|
||||
ModConfig modConfig,
|
||||
CommentedFileConfig commentedFileConfig,
|
||||
ClassLoader realClassLoader
|
||||
) implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
// Force the regular classloader onto the special thread
|
||||
|
||||
@@ -8,8 +8,6 @@ package net.minecraftforge.fml.config;
|
||||
import com.electronwill.nightconfig.core.CommentedConfig;
|
||||
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraftforge.fml.loading.FMLEnvironment;
|
||||
import net.minecraftforge.fml.loading.FMLPaths;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.Marker;
|
||||
import org.slf4j.MarkerFactory;
|
||||
@@ -18,60 +16,46 @@ import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public final class ConfigTracker {
|
||||
private ConfigTracker() {}
|
||||
|
||||
public class ConfigTracker {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
static final Marker CONFIG = MarkerFactory.getMarker("CONFIG");
|
||||
private static final ConcurrentHashMap<String, ModConfig> FILE_MAP = new ConcurrentHashMap<>();
|
||||
private static final Map<ModConfig.Type, Set<ModConfig>> CONFIG_SETS = Map.of(
|
||||
ModConfig.Type.CLIENT, Collections.synchronizedSet(new LinkedHashSet<>()),
|
||||
ModConfig.Type.COMMON, Collections.synchronizedSet(new LinkedHashSet<>()),
|
||||
ModConfig.Type.SERVER, Collections.synchronizedSet(new LinkedHashSet<>())
|
||||
);
|
||||
private static final ConcurrentHashMap<String, Map<ModConfig.Type, ModConfig>> CONFIGS_BY_MOD = new ConcurrentHashMap<>();
|
||||
public static final ConfigTracker INSTANCE = new ConfigTracker();
|
||||
private final ConcurrentHashMap<String, ModConfig> fileMap;
|
||||
private final EnumMap<ModConfig.Type, Set<ModConfig>> configSets;
|
||||
private final ConcurrentHashMap<String, Map<ModConfig.Type, ModConfig>> configsByMod;
|
||||
|
||||
static void trackConfig(final ModConfig config) {
|
||||
if (FILE_MAP.containsKey(config.getFileName())) {
|
||||
LOGGER.error(CONFIG,"Detected config file conflict {} between {} and {}", config.getFileName(), FILE_MAP.get(config.getFileName()).getModId(), config.getModId());
|
||||
private ConfigTracker() {
|
||||
this.fileMap = new ConcurrentHashMap<>();
|
||||
this.configSets = new EnumMap<>(ModConfig.Type.class);
|
||||
this.configsByMod = new ConcurrentHashMap<>();
|
||||
this.configSets.put(ModConfig.Type.CLIENT, Collections.synchronizedSet(new LinkedHashSet<>()));
|
||||
this.configSets.put(ModConfig.Type.COMMON, Collections.synchronizedSet(new LinkedHashSet<>()));
|
||||
// this.configSets.put(ModConfig.Type.PLAYER, new ConcurrentSkipListSet<>());
|
||||
this.configSets.put(ModConfig.Type.SERVER, Collections.synchronizedSet(new LinkedHashSet<>()));
|
||||
}
|
||||
|
||||
void trackConfig(final ModConfig config) {
|
||||
if (this.fileMap.containsKey(config.getFileName())) {
|
||||
LOGGER.error(CONFIG,"Detected config file conflict {} between {} and {}", config.getFileName(), this.fileMap.get(config.getFileName()).getModId(), config.getModId());
|
||||
throw new RuntimeException("Config conflict detected!");
|
||||
}
|
||||
FILE_MAP.put(config.getFileName(), config);
|
||||
CONFIG_SETS.get(config.getType()).add(config);
|
||||
CONFIGS_BY_MOD.computeIfAbsent(config.getModId(), _ -> new EnumMap<>(ModConfig.Type.class)).put(config.getType(), config);
|
||||
this.fileMap.put(config.getFileName(), config);
|
||||
this.configSets.get(config.getType()).add(config);
|
||||
this.configsByMod.computeIfAbsent(config.getModId(), (k)->new EnumMap<>(ModConfig.Type.class)).put(config.getType(), config);
|
||||
LOGGER.debug(CONFIG, "Config file {} for {} tracking", config.getFileName(), config.getModId());
|
||||
}
|
||||
|
||||
public static void loadConfigs(ModConfig.Type type, Path configBasePath) {
|
||||
public void loadConfigs(ModConfig.Type type, Path configBasePath) {
|
||||
LOGGER.debug(CONFIG, "Loading configs type {}", type);
|
||||
for (ModConfig config : CONFIG_SETS.get(type)) {
|
||||
openConfig(config, configBasePath);
|
||||
}
|
||||
this.configSets.get(type).forEach(config -> openConfig(config, configBasePath));
|
||||
}
|
||||
|
||||
// TODO: [FML] This is only called for the server (outside of forceUnload)
|
||||
// rethink config implementation for eventual FML rewrite
|
||||
public static void unloadConfigs(ModConfig.Type type, Path configBasePath) {
|
||||
public void unloadConfigs(ModConfig.Type type, Path configBasePath) {
|
||||
LOGGER.debug(CONFIG, "Unloading configs type {}", type);
|
||||
for (ModConfig config : CONFIG_SETS.get(type)) {
|
||||
closeConfig(config, configBasePath);
|
||||
}
|
||||
ConfigFileTypeHandler.get(type).stopWatcher();
|
||||
this.configSets.get(type).forEach(config -> closeConfig(config, configBasePath));
|
||||
}
|
||||
|
||||
// If there is a better way to do this, please tell me. Because this is FUCKED
|
||||
public static void forceUnload() {
|
||||
// This is how ModStateProvider handles loading configs. So we're doing the same but with unloadConfigs instead
|
||||
if (FMLEnvironment.dist.isClient())
|
||||
unloadConfigs(ModConfig.Type.CLIENT, FMLPaths.CONFIGDIR.get());
|
||||
|
||||
unloadConfigs(ModConfig.Type.COMMON, FMLPaths.CONFIGDIR.get());
|
||||
|
||||
// just in case server watcher is still alive somehow...
|
||||
ConfigFileTypeHandler.get(ModConfig.Type.SERVER).stopWatcher();
|
||||
}
|
||||
|
||||
private static void openConfig(final ModConfig config, final Path configBasePath) {
|
||||
private void openConfig(final ModConfig config, final Path configBasePath) {
|
||||
LOGGER.trace(CONFIG, "Loading config file type {} at {} for {}", config.getType(), config.getFileName(), config.getModId());
|
||||
final CommentedFileConfig configData = config.getHandler().reader(configBasePath).apply(config);
|
||||
config.setConfigData(configData);
|
||||
@@ -79,7 +63,7 @@ public final class ConfigTracker {
|
||||
config.save();
|
||||
}
|
||||
|
||||
private static void closeConfig(final ModConfig config, final Path configBasePath) {
|
||||
private void closeConfig(final ModConfig config, final Path configBasePath) {
|
||||
if (config.getConfigData() != null) {
|
||||
LOGGER.trace(CONFIG, "Closing config file type {} at {} for {}", config.getType(), config.getFileName(), config.getModId());
|
||||
// stop the filewatcher before we save the file and close it, so reload doesn't fire
|
||||
@@ -92,8 +76,8 @@ public final class ConfigTracker {
|
||||
}
|
||||
}
|
||||
|
||||
public static void loadDefaultServerConfigs() {
|
||||
CONFIG_SETS.get(ModConfig.Type.SERVER).forEach(modConfig -> {
|
||||
public void loadDefaultServerConfigs() {
|
||||
configSets.get(ModConfig.Type.SERVER).forEach(modConfig -> {
|
||||
final CommentedConfig commentedConfig = CommentedConfig.inMemory();
|
||||
modConfig.getSpec().correct(commentedConfig);
|
||||
modConfig.setConfigData(commentedConfig);
|
||||
@@ -101,16 +85,16 @@ public final class ConfigTracker {
|
||||
});
|
||||
}
|
||||
|
||||
public static String getConfigFileName(String modId, ModConfig.Type type) {
|
||||
return Optional.ofNullable(CONFIGS_BY_MOD.getOrDefault(modId, Collections.emptyMap()).getOrDefault(type, null)).
|
||||
public String getConfigFileName(String modId, ModConfig.Type type) {
|
||||
return Optional.ofNullable(configsByMod.getOrDefault(modId, Collections.emptyMap()).getOrDefault(type, null)).
|
||||
map(ModConfig::getFullPath).map(Object::toString).orElse(null);
|
||||
}
|
||||
|
||||
public static Map<ModConfig.Type, Set<ModConfig>> configSets() {
|
||||
return CONFIG_SETS;
|
||||
public Map<ModConfig.Type, Set<ModConfig>> configSets() {
|
||||
return configSets;
|
||||
}
|
||||
|
||||
public static ConcurrentHashMap<String, ModConfig> fileMap() {
|
||||
return FILE_MAP;
|
||||
public ConcurrentHashMap<String, ModConfig> fileMap() {
|
||||
return fileMap;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -13,7 +13,6 @@ import java.util.function.Function;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface IConfigEvent {
|
||||
// TODO: [FML] Get rid of this indirection somehow
|
||||
record ConfigConfig(Function<ModConfig, IConfigEvent> loading, Function<ModConfig, IConfigEvent> reloading, @Nullable Function<ModConfig, IConfigEvent> unloading) {}
|
||||
|
||||
ConfigConfig CONFIGCONFIG = Bindings.getConfigConfiguration().get();
|
||||
@@ -30,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;
|
||||
}
|
||||
}
|
||||
@@ -24,14 +24,15 @@ public class ModConfig
|
||||
private final ModContainer container;
|
||||
private final ConfigFileTypeHandler configHandler;
|
||||
private CommentedConfig configData;
|
||||
private Callable<Void> saveHandler;
|
||||
|
||||
public ModConfig(final Type type, final IConfigSpec<?> spec, final ModContainer container, final String fileName) {
|
||||
this.type = type;
|
||||
this.spec = spec;
|
||||
this.fileName = fileName;
|
||||
this.container = container;
|
||||
this.configHandler = ConfigFileTypeHandler.get(type);
|
||||
ConfigTracker.trackConfig(this);
|
||||
this.configHandler = ConfigFileTypeHandler.TOML;
|
||||
ConfigTracker.INSTANCE.trackConfig(this);
|
||||
}
|
||||
|
||||
public ModConfig(final Type type, final IConfigSpec<?> spec, final ModContainer activeContainer) {
|
||||
@@ -40,9 +41,8 @@ public class ModConfig
|
||||
|
||||
private static String defaultConfigName(Type type, String modId) {
|
||||
// config file name would be "forge-client.toml" and "forge-server.toml"
|
||||
return modId + '-' + type.extension() + ".toml";
|
||||
return String.format(Locale.ROOT, "%s-%s.toml", modId, type.extension());
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@@ -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,15 +1,14 @@
|
||||
import net.minecraftforge.gradleutils.PomUtils
|
||||
|
||||
plugins {
|
||||
id 'java-library'
|
||||
id 'maven-publish'
|
||||
id 'net.minecraftforge.licenser'
|
||||
id 'net.minecraftforge.gradleutils'
|
||||
id 'com.github.ben-manes.versions'
|
||||
id 'org.javamodularity.moduleplugin' version '1.8.7' apply false
|
||||
id 'org.cadixdev.licenser'
|
||||
}
|
||||
|
||||
apply from: rootProject.file('build_shared.gradle')
|
||||
apply plugin: 'java-library'
|
||||
apply plugin: 'jacoco'
|
||||
apply plugin: 'org.javamodularity.moduleplugin'
|
||||
|
||||
import org.gradle.internal.os.OperatingSystem
|
||||
|
||||
switch (OperatingSystem.current()) {
|
||||
case OperatingSystem.LINUX:
|
||||
project.ext.lwjglNatives = "natives-linux"
|
||||
@@ -22,18 +21,22 @@ switch (OperatingSystem.current()) {
|
||||
break
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION)
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
dependencyUpdates.rejectVersionIf { isNonStable(it.candidate.version) }
|
||||
|
||||
java.withSourcesJar()
|
||||
|
||||
dependencies {
|
||||
compileOnly(libs.jetbrains.annotations)
|
||||
compileOnly('org.jetbrains:annotations:23.0.0')
|
||||
implementation(project(':fmlloader'))
|
||||
implementation(project(':fmlcore'))
|
||||
implementation(libs.bundles.lwjgl)
|
||||
implementation('org.lwjgl:lwjgl:3.3.1')
|
||||
implementation('org.lwjgl:lwjgl-glfw:3.3.1')
|
||||
implementation('org.lwjgl:lwjgl-opengl:3.3.1')
|
||||
implementation('org.lwjgl:lwjgl-stb:3.3.1')
|
||||
implementation('org.lwjgl:lwjgl-tinyfd:3.3.1')
|
||||
implementation('org.slf4j:slf4j-api:1.8.0-beta4')
|
||||
implementation(libs.jopt.simple)
|
||||
implementation("net.sf.jopt-simple:jopt-simple:${JOPT_SIMPLE_VERSION}")
|
||||
testImplementation('org.junit.jupiter:junit-jupiter-api:5.8.2')
|
||||
testImplementation('org.powermock:powermock-core:2.0.9')
|
||||
testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine:5.8.2')
|
||||
@@ -44,49 +47,47 @@ dependencies {
|
||||
testRuntimeOnly("org.lwjgl:lwjgl-stb::$lwjglNatives")
|
||||
}
|
||||
|
||||
tasks.named('test', Test).configure {
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
tasks.named('jar', Jar).configure {
|
||||
manifest {
|
||||
attributes([
|
||||
'Automatic-Module-Name': 'net.minecraftforge.earlydisplay',
|
||||
'Forge-Module-Layer': 'boot'
|
||||
] as LinkedHashMap)
|
||||
attributes([
|
||||
'Specification-Title': 'FML Early Display',
|
||||
ext {
|
||||
MANIFESTS = [
|
||||
'': [
|
||||
'Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"),
|
||||
'Git-Commit': GIT_INFO.abbreviatedId,
|
||||
'Git-Branch': GIT_INFO.branch,
|
||||
'Build-Number': "${System.getenv('BUILD_NUMBER')?:0}",
|
||||
] as LinkedHashMap,
|
||||
'net/minecraftforge/fml/earlydisplay/': [
|
||||
'Specification-Title': 'FMLEarlyDisplay',
|
||||
'Specification-Vendor': 'Forge Development LLC',
|
||||
'Specification-Version': '1',
|
||||
'Implementation-Title': 'FML Early Display',
|
||||
'Implementation-Vendor': 'Forge Development LLC',
|
||||
'Implementation-Version': '1.0'
|
||||
] as LinkedHashMap, 'net/minecraftforge/fml/earlydisplay/')
|
||||
'Implementation-Version': '1.0',
|
||||
'Implementation-Vendor': 'Forge'
|
||||
] as LinkedHashMap
|
||||
]
|
||||
}
|
||||
|
||||
jar.doFirst {
|
||||
MANIFESTS.each { pkg, values ->
|
||||
if (pkg == '')
|
||||
manifest.attributes(values)
|
||||
else
|
||||
manifest.attributes(values, pkg)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
tasks.withType(JavaCompile) {
|
||||
options.compilerArgs << '-Xlint:unchecked'
|
||||
}
|
||||
|
||||
license {
|
||||
header = rootProject.file('LICENSE-header.txt')
|
||||
include 'net/minecraftforge/'
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications.register('mavenJava', MavenPublication).configure {
|
||||
from components.java
|
||||
artifactId = 'fmlearlydisplay'
|
||||
pom {
|
||||
name = project.name
|
||||
description = 'A pretty looking, but prone to erroring screen displayed during Forge loading.'
|
||||
url = 'https://github.com/MinecraftForge/MinecraftForge'
|
||||
PomUtils.setGitHubDetails(pom, 'MinecraftForge')
|
||||
license PomUtils.Licenses.LGPLv2_1
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven gradleutils.publishingForgeMaven
|
||||
}
|
||||
}
|
||||
publishing.publications.mavenJava(MavenPublication) {
|
||||
from components.java
|
||||
}
|
||||
+55
-61
@@ -22,7 +22,9 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.awt.Desktop;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.net.URI;
|
||||
@@ -46,6 +48,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.IntConsumer;
|
||||
import java.util.function.IntSupplier;
|
||||
import java.util.function.LongSupplier;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -68,7 +72,7 @@ import static org.lwjgl.opengl.GL32C.*;
|
||||
* Based on the prior ClientVisualization, with some personal touches.
|
||||
*/
|
||||
public class DisplayWindow implements ImmediateWindowProvider {
|
||||
private static final int[][] GL_VERSIONS = new int[][] {{4,6}, {4,5}, {4,4}, {4,3}, {4,2}, {4,1}, {4,0}, {3,3}};
|
||||
private static final int[][] GL_VERSIONS = new int[][] {{4,6}, {4,5}, {4,4}, {4,3}, {4,2}, {4,1}, {4,0}, {3,3}, {3,2}};
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger("EARLYDISPLAY");
|
||||
private final AtomicBoolean animationTimerTrigger = new AtomicBoolean(true);
|
||||
|
||||
@@ -83,17 +87,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;
|
||||
@@ -112,13 +110,11 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
public String name() {
|
||||
return "fmlearlywindow";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Runnable initialize(String[] arguments) {
|
||||
String mcVersion = FMLLoader.versionInfo().mcVersion();
|
||||
String forgeVersion = FMLLoader.versionInfo().forgeVersion();
|
||||
|
||||
final OptionParser parser = new OptionParser();
|
||||
var mcversionopt = parser.accepts("fml.mcVersion").withRequiredArg().ofType(String.class);
|
||||
var forgeversionopt = parser.accepts("fml.forgeVersion").withRequiredArg().ofType(String.class);
|
||||
var widthopt = parser.accepts("width")
|
||||
.withRequiredArg().ofType(Integer.class)
|
||||
.defaultsTo(FMLConfig.getIntConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_WIDTH));
|
||||
@@ -133,7 +129,7 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
FMLConfig.updateConfig(FMLConfig.ConfigValue.EARLY_WINDOW_WIDTH, winWidth);
|
||||
FMLConfig.updateConfig(FMLConfig.ConfigValue.EARLY_WINDOW_HEIGHT, winHeight);
|
||||
fbScale = FMLConfig.getIntConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_FBSCALE);
|
||||
if (System.getenv("FML_EARLY_WINDOW_DARK") != null) {
|
||||
if (System.getenv("FML_EARLY_WINDOW_DARK")!= null) {
|
||||
this.colourScheme = ColourScheme.BLACK;
|
||||
} else {
|
||||
try {
|
||||
@@ -152,9 +148,10 @@ 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()));
|
||||
var forgeVersion = parsed.valueOf(forgeversionopt);
|
||||
StartupNotificationManager.modLoaderConsumer().ifPresent(c->c.accept("Forge loading "+ forgeVersion));
|
||||
performanceInfo = new PerformanceInfo();
|
||||
return start(mcVersion, forgeVersion);
|
||||
return start(parsed.valueOf(mcversionopt), forgeVersion);
|
||||
}
|
||||
|
||||
private static final long MINFRAMETIME = TimeUnit.MILLISECONDS.toNanos(10); // This is the FPS cap on the window - note animation is capped at 20FPS via the tickTimer
|
||||
@@ -232,16 +229,16 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
crashElegantly("An error occurred initializing a font for rendering. "+t.getMessage());
|
||||
}
|
||||
this.elements = new ArrayList<>(List.of(
|
||||
RenderElement.anvil(font),
|
||||
RenderElement.logMessageOverlay(font),
|
||||
RenderElement.forgeVersionOverlay(font, mcVersion + "-" + forgeVersion),
|
||||
RenderElement.performanceBar(font),
|
||||
RenderElement.progressBars(font)
|
||||
RenderElement.anvil(font),
|
||||
RenderElement.logMessageOverlay(font),
|
||||
RenderElement.forgeVersionOverlay(font, mcVersion+"-"+forgeVersion.split("-")[0]),
|
||||
RenderElement.performanceBar(font),
|
||||
RenderElement.progressBars(font)
|
||||
));
|
||||
|
||||
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);
|
||||
@@ -261,11 +258,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++;
|
||||
}
|
||||
@@ -290,6 +283,11 @@ 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.setDaemon(true);
|
||||
return thread;
|
||||
});
|
||||
initWindow(mcVersion);
|
||||
this.initializationFuture = renderScheduler.schedule(() -> initRender(mcVersion, forgeVersion), 1, TimeUnit.MILLISECONDS);
|
||||
return this::periodicTick;
|
||||
@@ -301,7 +299,14 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
return this.glVersion;
|
||||
}
|
||||
|
||||
private static void crashElegantly(String errorDetails) {
|
||||
private void crashElegantly(String errorDetails) {
|
||||
String qrText;
|
||||
try (var is = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream("/glfailure.txt")))) {
|
||||
qrText = is.lines().collect(Collectors.joining("\n"));
|
||||
} catch (IOException ioe) {
|
||||
qrText = "";
|
||||
}
|
||||
|
||||
StringBuilder msgBuilder = new StringBuilder(2000);
|
||||
msgBuilder.append("Failed to initialize graphics window with current settings.\n");
|
||||
msgBuilder.append("\n\n");
|
||||
@@ -312,18 +317,17 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
LOGGER.error("ERROR DISPLAY\n"+msgBuilder);
|
||||
// we show the display on a new dedicated thread
|
||||
Executors.newSingleThreadExecutor().submit(()-> {
|
||||
var res = TinyFileDialogs.tinyfd_messageBox("Minecraft: Forge", msgBuilder.toString(), "yesno", "error", 0);
|
||||
if (res != 0) {
|
||||
var res = TinyFileDialogs.tinyfd_messageBox("Minecraft: Forge", msgBuilder.toString(), "yesno", "error", false);
|
||||
if (res) {
|
||||
try {
|
||||
Desktop.getDesktop().browse(URI.create(ERROR_URL));
|
||||
} catch (IOException ioe) {
|
||||
TinyFileDialogs.tinyfd_messageBox("Minecraft: Forge", "Sadly, we couldn't open your browser.\nVisit " + ERROR_URL, "ok", "error", 0);
|
||||
TinyFileDialogs.tinyfd_messageBox("Minecraft: Forge", "Sadly, we couldn't open your browser.\nVisit " + ERROR_URL, "ok", "error", false);
|
||||
}
|
||||
}
|
||||
System.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to initialize the window when preparing for the Render Thread.
|
||||
*
|
||||
@@ -401,13 +405,13 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
if (showHelpLog && versidx == 0) {
|
||||
LOGGER.info("""
|
||||
If this message is the only thing at the bottom of your log before a crash, you probably have a driver issue.
|
||||
|
||||
|
||||
Possible solutions:
|
||||
A) Make sure Minecraft is set to prefer high performance graphics in the OS and/or driver control panel
|
||||
B) Check for driver updates on the graphics brand's website
|
||||
C) Try reinstalling your graphics drivers
|
||||
D) If still not working after trying all of the above, ask for further help on the Forge forums or Discord
|
||||
|
||||
|
||||
You can safely ignore this message if the game starts up successfully.""");
|
||||
}
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, GL_VERSIONS[versidx][0]); // we try our versions one at a time
|
||||
@@ -491,6 +495,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;
|
||||
@@ -510,7 +518,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);
|
||||
@@ -528,7 +536,7 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
*
|
||||
* @return the Window we own.
|
||||
*/
|
||||
public long setupMinecraftWindow(final int width, final int height, final String title, final long monitorSupplier, final Supplier<Object> backend) {
|
||||
public long setupMinecraftWindow(final IntSupplier width, final IntSupplier height, final Supplier<String> title, final LongSupplier monitorSupplier) {
|
||||
// wait for the window to actually be initialized
|
||||
try {
|
||||
this.initializationFuture.get(30, TimeUnit.SECONDS);
|
||||
@@ -548,11 +556,11 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
var renderlockticket = false;
|
||||
do {
|
||||
try {
|
||||
renderlockticket = renderLock.tryAcquire(100, TimeUnit.MILLISECONDS);
|
||||
if (++tries > 9) {
|
||||
Thread.dumpStack();
|
||||
crashElegantly("We seem to be having trouble handing off the window, tried for 1 second");
|
||||
}
|
||||
renderlockticket = renderLock.tryAcquire(100, TimeUnit.MILLISECONDS);
|
||||
if (++tries > 9) {
|
||||
Thread.dumpStack();
|
||||
crashElegantly("We seem to be having trouble handing off the window, tried for 1 second");
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.interrupted();
|
||||
}
|
||||
@@ -562,7 +570,7 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
|
||||
glfwMakeContextCurrent(window);
|
||||
// Set the title to what the game wants
|
||||
glfwSetWindowTitle(window, title);
|
||||
glfwSetWindowTitle(window, title.get());
|
||||
glfwSwapInterval(0);
|
||||
// Clean up our hooks
|
||||
glfwSetFramebufferSizeCallback(window, null).free();
|
||||
@@ -602,25 +610,10 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
|
||||
@Override
|
||||
public void updateModuleReads(final ModuleLayer layer) {
|
||||
final var FORGE_MODULE = "net.minecraftforge.forge";
|
||||
var forge_module = layer.findModule(FORGE_MODULE).orElse(null);
|
||||
if (forge_module == null)
|
||||
throw new IllegalStateException("Could not find " + FORGE_MODULE + " in " + layer);
|
||||
|
||||
getClass().getModule().addReads(forge_module);
|
||||
|
||||
|
||||
var clz = Class.forName(forge_module, "net.minecraftforge.client.loading.ForgeLoadingOverlay");
|
||||
|
||||
for (var mtd : clz.getDeclaredMethods()) {
|
||||
if (Modifier.isStatic(mtd.getModifiers()) && "newInstance".equals(mtd.getName())) {
|
||||
this.loadingOverlay = mtd;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (loadingOverlay == null)
|
||||
throw new IllegalStateException("Could not find static newInstace method in " + clz.getName());
|
||||
var fm = layer.findModule("forge").orElseThrow();
|
||||
getClass().getModule().addReads(fm);
|
||||
var clz = FMLLoader.getGameLayer().findModule("forge").map(l->Class.forName(l, "net.minecraftforge.client.loading.ForgeLoadingOverlay")).orElseThrow();
|
||||
loadingOverlay = Arrays.stream(clz.getMethods()).filter(m-> Modifier.isStatic(m.getModifiers()) && m.getName().equals("newInstance")).findFirst().orElseThrow();
|
||||
}
|
||||
|
||||
public int getFramebufferTextureId() {
|
||||
@@ -638,7 +631,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() {
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ public class PerformanceInfo {
|
||||
private final boolean showCPUUsage;
|
||||
private final OperatingSystemMXBean osBean;
|
||||
|
||||
private float memory;
|
||||
float memory;
|
||||
private String text;
|
||||
|
||||
PerformanceInfo() {
|
||||
|
||||
+39
-30
@@ -22,8 +22,8 @@ public class RenderElement {
|
||||
static int globalAlpha = 255;
|
||||
private int retireCount;
|
||||
|
||||
@FunctionalInterface
|
||||
interface Renderer {
|
||||
|
||||
void accept(SimpleBufferBuilder bb, DisplayContext context, int frame);
|
||||
|
||||
default Renderer then(Renderer r) {
|
||||
@@ -38,9 +38,6 @@ public class RenderElement {
|
||||
interface TextureRenderer {
|
||||
void accept(SimpleBufferBuilder bb, DisplayContext context, int[] size, int frame);
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link Renderer} directly. This was always eagerly resolved, making it an unnecessary wrapper. */
|
||||
@Deprecated(since = "1.21.5", forRemoval = true)
|
||||
@FunctionalInterface
|
||||
interface Initializer extends Supplier<Renderer> {}
|
||||
|
||||
@@ -59,18 +56,11 @@ public class RenderElement {
|
||||
}
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link RenderElement#RenderElement(Renderer)} instead */
|
||||
@Deprecated(since = "1.21.5", forRemoval = true)
|
||||
public RenderElement(final Initializer rendererInitializer) {
|
||||
this.bb = new SimpleBufferBuilder(1);
|
||||
this.renderer = rendererInitializer.get();
|
||||
}
|
||||
|
||||
public RenderElement(Renderer renderer) {
|
||||
this.bb = new SimpleBufferBuilder(1);
|
||||
this.renderer = renderer;
|
||||
}
|
||||
|
||||
public boolean render(DisplayContext ctx, int count) {
|
||||
this.renderer.accept(bb, ctx, count);
|
||||
return this.retireCount == 0 || this.retireCount < count;
|
||||
@@ -85,7 +75,7 @@ public class RenderElement {
|
||||
List<SimpleFont.DisplayText> texts = new ArrayList<>();
|
||||
for (int i = messages.size() - 1; i >= 0; i--) {
|
||||
final StartupNotificationManager.AgeMessage pair = messages.get(i);
|
||||
final float fade = Math.clamp((4000.0f - (float) pair.age() - ( i - 4 ) * 1000.0f) / 5000.0f, 0.0f, 1.0f);
|
||||
final float fade = clamp((4000.0f - (float) pair.age() - ( i - 4 ) * 1000.0f) / 5000.0f, 0.0f, 1.0f);
|
||||
if (fade <0.01f) continue;
|
||||
Message msg = pair.message();
|
||||
int colour = Math.min((int)(fade * 255f), globalAlpha) << 24 | 0xFFFFFF;
|
||||
@@ -94,9 +84,19 @@ public class RenderElement {
|
||||
|
||||
font.generateVerticesForTexts(10, context.scaledHeight() - texts.size() * font.lineSpacing() + font.descent() - 10, bb, texts.toArray(SimpleFont.DisplayText[]::new));
|
||||
}
|
||||
public static RenderElement monag() {
|
||||
return new RenderElement(RenderElement.initializeTexture("monagstudios.png", 45000, 4, (bb, ctx, sz, frame) -> {
|
||||
var size = 256;
|
||||
var x0 = (ctx.width() - 2 * size) / 2;
|
||||
var y0 = 64;
|
||||
QuadHelper.loadQuad(bb, x0, x0+size, y0, y0+size/2f, 0f, 1f, 0f, 0.5f, 0xFFFFFFFF);
|
||||
QuadHelper.loadQuad(bb, x0+size, x0+2*size, y0, y0+size/2f, 0f, 1f, 0.5f, 1f, 0xFFFFFFFF);
|
||||
}));
|
||||
|
||||
}
|
||||
|
||||
public static RenderElement mojang(final int textureId, final int frameStart) {
|
||||
return new RenderElement((bb, ctx, frame) -> {
|
||||
return new RenderElement(()->(bb, ctx, frame) -> {
|
||||
var size = 256 * ctx.scale();
|
||||
var x0 = (ctx.scaledWidth() - 2 * size) / 2;
|
||||
var y0 = 64 * ctx.scale() + 32;
|
||||
@@ -148,11 +148,11 @@ public class RenderElement {
|
||||
}));
|
||||
}
|
||||
public static RenderElement progressBars(SimpleFont font) {
|
||||
return new RenderElement((bb, ctx, frame) -> RenderElement.startupProgressBars(font, bb, ctx, frame));
|
||||
return new RenderElement(() -> (bb, ctx, frame) -> RenderElement.startupProgressBars(font, bb, ctx, frame));
|
||||
}
|
||||
|
||||
public static RenderElement performanceBar(SimpleFont font) {
|
||||
return new RenderElement((bb, ctx, frame) -> RenderElement.memoryInfo(font, bb, ctx, frame));
|
||||
return new RenderElement(() -> (bb, ctx, frame) -> RenderElement.memoryInfo(font, bb, ctx, frame));
|
||||
}
|
||||
|
||||
public static void startupProgressBars(SimpleFont font, final SimpleBufferBuilder buffer, final DisplayContext context, final int frameNumber) {
|
||||
@@ -187,10 +187,10 @@ public class RenderElement {
|
||||
}
|
||||
private static float[] indeterminateBar(int frame, boolean isActive) {
|
||||
if (RenderElement.globalAlpha != 0xFF || !isActive) {
|
||||
return new float[] { 0f, 1f };
|
||||
return new float[] {0f,1f};
|
||||
} else {
|
||||
int progress = frame % 100;
|
||||
return new float[] { Math.clamp((progress - 2) / 100f, 0f, 1f), Math.clamp((progress + 2) / 100f, 0f, 1f) };
|
||||
var progress = frame % 100;
|
||||
return new float[]{clamp((progress - 2) / 100f, 0f, 1f), clamp((progress + 2) / 100f, 0f, 1f)};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,8 +249,8 @@ public class RenderElement {
|
||||
};
|
||||
}
|
||||
|
||||
private static Renderer initializeText(SimpleFont font, TextGenerator textGenerator) {
|
||||
return (bb, context, frame) -> renderText(font, textGenerator, bb, context);
|
||||
private static Initializer initializeText(SimpleFont font, TextGenerator textGenerator) {
|
||||
return () -> (bb, context, frame) -> renderText(font, textGenerator, bb, context);
|
||||
}
|
||||
|
||||
private static void renderText(final SimpleFont font, final TextGenerator textGenerator, final SimpleBufferBuilder bb, final DisplayContext context) {
|
||||
@@ -264,13 +264,14 @@ public class RenderElement {
|
||||
private static TextGenerator text(int x, int y, String text, int colour) {
|
||||
return (bb, font, context) -> font.generateVerticesForTexts(x, y, bb, new SimpleFont.DisplayText(text, colour));
|
||||
}
|
||||
|
||||
private static Renderer initializeTexture(final String textureFileName, int size, int textureNumber, TextureRenderer positionAndColour) {
|
||||
int[] imgSize = STBHelper.loadTextureFromClasspath(textureFileName, size, GL_TEXTURE0 + textureNumber + INDEX_TEXTURE_OFFSET);
|
||||
return (bb, ctx, frame) -> {
|
||||
ctx.elementShader().updateTextureUniform(textureNumber + INDEX_TEXTURE_OFFSET);
|
||||
ctx.elementShader().updateRenderTypeUniform(ElementShader.RenderType.TEXTURE);
|
||||
renderTexture(bb, ctx, frame, imgSize, positionAndColour);
|
||||
private static Initializer initializeTexture(final String textureFileName, int size, int textureNumber, TextureRenderer positionAndColour) {
|
||||
return ()->{
|
||||
int[] imgSize = STBHelper.loadTextureFromClasspath(textureFileName, size, GL_TEXTURE0 + textureNumber + INDEX_TEXTURE_OFFSET);
|
||||
return (bb, ctx, frame) -> {
|
||||
ctx.elementShader().updateTextureUniform(textureNumber + INDEX_TEXTURE_OFFSET);
|
||||
ctx.elementShader().updateRenderTypeUniform(ElementShader.RenderType.TEXTURE);
|
||||
renderTexture(bb, ctx, frame, imgSize, positionAndColour);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -280,13 +281,21 @@ public class RenderElement {
|
||||
bb.draw();
|
||||
}
|
||||
|
||||
@Deprecated(since = "1.21.5", forRemoval = true)
|
||||
|
||||
public static float clamp(float num, float min, float max) {
|
||||
return Math.clamp(num, min, max);
|
||||
if (num < min) {
|
||||
return min;
|
||||
} else {
|
||||
return Math.min(num, max);
|
||||
}
|
||||
}
|
||||
|
||||
public static int clamp(int num, int min, int max) {
|
||||
return Math.min(max, Math.max(num, min));
|
||||
if (num < min) {
|
||||
return min;
|
||||
} else {
|
||||
return Math.min(num, max);
|
||||
}
|
||||
}
|
||||
|
||||
public static int hsvToRGB(float hue, float saturation, float value) {
|
||||
|
||||
@@ -11,6 +11,8 @@ import org.lwjgl.stb.STBTTFontinfo;
|
||||
import org.lwjgl.stb.STBTTPackContext;
|
||||
import org.lwjgl.stb.STBTTPackRange;
|
||||
import org.lwjgl.stb.STBTTPackedchar;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@@ -22,8 +24,8 @@ 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 Glyph[] glyphs;
|
||||
private final int GLYPH_COUNT = 127-32;
|
||||
private Glyph[] glyphs;
|
||||
|
||||
private record Glyph(char c, int charwidth, int[] pos, float[] uv) {
|
||||
Pos loadQuad(Pos pos, int colour, SimpleBufferBuilder bb) {
|
||||
@@ -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;
|
||||
|
||||
+72
-121
@@ -1,146 +1,97 @@
|
||||
import net.minecraftforge.gradleutils.PomUtils
|
||||
|
||||
plugins {
|
||||
id 'java-library'
|
||||
id 'maven-publish'
|
||||
id 'net.minecraftforge.licenser'
|
||||
id 'net.minecraftforge.gradleutils'
|
||||
alias(libs.plugins.apt)
|
||||
id 'com.github.ben-manes.versions'
|
||||
id 'org.javamodularity.moduleplugin' version '1.8.3' apply false
|
||||
id 'org.cadixdev.licenser'
|
||||
}
|
||||
apply plugin: 'java-library'
|
||||
apply plugin: 'jacoco'
|
||||
apply plugin: 'org.javamodularity.moduleplugin'
|
||||
|
||||
apply from: rootProject.file('build_shared.gradle')
|
||||
|
||||
dependencyUpdates.rejectVersionIf { isNonStable(it.candidate.version) }
|
||||
configurations.forEach{ it.transitive = false }
|
||||
|
||||
dependencies {
|
||||
compileOnly(libs.jetbrains.annotations)
|
||||
compileOnly("org.jetbrains:annotations:${JETBRAINS_ANNOTATIONS_VERSION}")
|
||||
|
||||
api(libs.bundles.asm) // Needed by all the black magic
|
||||
api(libs.forgespi)
|
||||
api(libs.mergetool.api)
|
||||
api(libs.log4j.api)
|
||||
api(libs.slf4j.api)
|
||||
api(libs.guava)
|
||||
api(libs.gson)
|
||||
api(libs.maven.artifact)
|
||||
api(libs.apache.commons)
|
||||
api(libs.bundles.night.config)
|
||||
api(libs.modlauncher)
|
||||
api(libs.mojang.logging)
|
||||
api(libs.jarjar.selector)
|
||||
api(libs.jarjar.meta)
|
||||
api("org.ow2.asm:asm:${ASM_VERSION}")
|
||||
api("org.ow2.asm:asm-tree:${ASM_VERSION}")
|
||||
api("org.ow2.asm:asm-commons:${ASM_VERSION}")
|
||||
api("net.minecraftforge:forgespi:${SPI_VERSION}")
|
||||
api("net.minecraftforge:mergetool:${MERGETOOL_VERSION}:api")
|
||||
api("org.apache.logging.log4j:log4j-api:${LOG4J_VERSION}")
|
||||
api("org.slf4j:slf4j-api:${SLF4J_API_VERSION}")
|
||||
api("com.google.guava:guava:${GUAVA_VERSION}")
|
||||
api("com.google.code.gson:gson:${GSON_VERSION}")
|
||||
api("org.apache.maven:maven-artifact:${APACHE_MAVEN_ARTIFACT_VERSION}")
|
||||
api("org.apache.commons:commons-lang3:${APACHE_COMMONS_LANG3_VERSION}")
|
||||
api("com.electronwill.night-config:core:${NIGHTCONFIG_VERSION}")
|
||||
api("com.electronwill.night-config:toml:${NIGHTCONFIG_VERSION}")
|
||||
api("cpw.mods:modlauncher:${MODLAUNCHER_VERSION}")
|
||||
api("net.minecraftforge:coremods:${COREMODS_VERSION}")
|
||||
api("com.mojang:logging:${MOJANG_LOGGING_VERSION}")
|
||||
api "net.minecraftforge:JarJarSelector:${JARJAR_VERSION}"
|
||||
api "net.minecraftforge:JarJarMetadata:${JARJAR_VERSION}"
|
||||
|
||||
implementation(libs.jopt.simple)
|
||||
implementation(libs.securemodules)
|
||||
implementation(libs.accesstransformers)
|
||||
implementation(libs.terminalconsoleappender)
|
||||
implementation(libs.jimfs)
|
||||
implementation(libs.roimfs)
|
||||
implementation("net.sf.jopt-simple:jopt-simple:${JOPT_SIMPLE_VERSION}")
|
||||
implementation("cpw.mods:securejarhandler:${SECUREJARHANDLER_VERSION}")
|
||||
implementation("net.minecraftforge:accesstransformers:${ACCESSTRANSFORMERS_VERSION}")
|
||||
implementation("net.minecrell:terminalconsoleappender:${TERMINALCONSOLEAPPENDER_VERSION}")
|
||||
// implementation("org.jline:jline:${JLINE_VERSION}")
|
||||
|
||||
// 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)
|
||||
implementation("org.apache.logging.log4j:log4j-core:${LOG4J_VERSION}")
|
||||
annotationProcessor("org.apache.logging.log4j:log4j-core:${LOG4J_VERSION}")
|
||||
|
||||
testCompileOnly(libs.jetbrains.annotations)
|
||||
testRuntimeOnly(libs.bootstrap)
|
||||
testCompileOnly("org.jetbrains:annotations:${JETBRAINS_ANNOTATIONS_VERSION}")
|
||||
testRuntimeOnly("cpw.mods:bootstraplauncher:${BOOTSTRAPLAUNCHER_VERSION}")
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION)
|
||||
withSourcesJar()
|
||||
task sourcesJar(type: Jar) {
|
||||
archiveClassifier = 'sources'
|
||||
from sourceSets.main.allSource
|
||||
}
|
||||
|
||||
tasks.named('jar', Jar).configure {
|
||||
manifest {
|
||||
attributes([
|
||||
'Automatic-Module-Name': 'net.minecraftforge.fmlloader',
|
||||
'Forge-Module-Layer': 'boot'
|
||||
] as LinkedHashMap)
|
||||
attributes([
|
||||
'Specification-Title': 'FMLLoader',
|
||||
'Specification-Vendor': 'Forge Development LLC',
|
||||
'Specification-Version': '1',
|
||||
'Implementation-Title': 'FMLLoader',
|
||||
'Implementation-Vendor': 'Forge Development LLC',
|
||||
'Implementation-Version': FORGE_VERSION
|
||||
] as LinkedHashMap, 'net/minecraftforge/fml/loading/')
|
||||
ext {
|
||||
MANIFESTS = [
|
||||
'/': [
|
||||
'Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"),
|
||||
'Git-Commit': GIT_INFO.abbreviatedId,
|
||||
'Git-Branch': GIT_INFO.branch,
|
||||
'Build-Number': "${System.getenv('BUILD_NUMBER')?:0}",
|
||||
] as LinkedHashMap,
|
||||
'net/minecraftforge/fml/loading/': [
|
||||
'Specification-Title': 'FMLLoader',
|
||||
'Specification-Vendor': 'Forge Development LLC',
|
||||
'Specification-Version': '1',
|
||||
'Implementation-Title': 'FML Loader',
|
||||
'Implementation-Version': '1.0',
|
||||
'Implementation-Vendor': 'Forge'
|
||||
] as LinkedHashMap
|
||||
]
|
||||
}
|
||||
|
||||
jar.doFirst {
|
||||
manifest.attributes['Automatic-Module-Name'] = project.name
|
||||
MANIFESTS.each { pkg, values ->
|
||||
manifest.attributes(values, pkg)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
options.compilerArgs << '-Xlint:unchecked'
|
||||
tasks.withType(JavaCompile) {
|
||||
options.compilerArgs << '-Xlint:-unchecked'
|
||||
}
|
||||
|
||||
license {
|
||||
header = rootProject.file('LICENSE-header.txt')
|
||||
|
||||
include 'net/minecraftforge/'
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications.register('mavenJava', MavenPublication).configure {
|
||||
from components.java
|
||||
artifactId = 'fmlloader'
|
||||
pom {
|
||||
name = project.name
|
||||
description = 'Modifactions to Minecraft to enable mod developers.'
|
||||
url = 'https://github.com/MinecraftForge/MinecraftForge'
|
||||
PomUtils.setGitHubDetails(pom, 'MinecraftForge')
|
||||
license PomUtils.Licenses.LGPLv2_1
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven gradleutils.publishingForgeMaven
|
||||
}
|
||||
artifacts {
|
||||
archives jar
|
||||
archives sourcesJar
|
||||
}
|
||||
|
||||
tasks.register('writeForgeVersionJson') {
|
||||
doLast {
|
||||
file('src/main/resources/forge_version.json').json = [
|
||||
forge: FORGE_VERSION,
|
||||
mc: MC_VERSION,
|
||||
mcp: MCP_VERSION
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
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'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
publishing.publications.mavenJava(MavenPublication) {
|
||||
from components.java
|
||||
artifact sourcesJar
|
||||
}
|
||||
+34
-18
@@ -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() {
|
||||
@@ -48,48 +49,56 @@ public final class CapabilityTokenSubclass implements ILaunchPluginService {
|
||||
private static final EnumSet<Phase> NAY = EnumSet.noneOf(Phase.class);
|
||||
|
||||
@Override
|
||||
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty) {
|
||||
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty)
|
||||
{
|
||||
if (isEmpty)
|
||||
return NAY;
|
||||
|
||||
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
|
||||
public int processClassWithFlags(final Phase phase, final ClassNode classNode, final Type classType, final String reason) {
|
||||
if (CAP_INJECT.equals(classNode.name)) {
|
||||
for (MethodNode mtd : classNode.methods) {
|
||||
if (FUNC_NAME.equals(mtd.name) && FUNC_DESC.equals(mtd.desc)) {
|
||||
public int processClassWithFlags(final Phase phase, final ClassNode classNode, final Type classType, final String reason)
|
||||
{
|
||||
if (CAP_INJECT.equals(classNode.name))
|
||||
{
|
||||
for (MethodNode mtd : classNode.methods)
|
||||
{
|
||||
if (FUNC_NAME.equals(mtd.name) && FUNC_DESC.equals(mtd.desc))
|
||||
{
|
||||
mtd.access &= ~Opcodes.ACC_FINAL; // We have it final in code so people don't override it, cuz that'd be stupid, and make our transformer more complicated.
|
||||
}
|
||||
}
|
||||
return ComputeFlags.SIMPLE_REWRITE;
|
||||
} else if (CAP_INJECT.equals(classNode.superName)) {
|
||||
var cls = new Object() { String value = null; };
|
||||
}
|
||||
else if (CAP_INJECT.equals(classNode.superName))
|
||||
{
|
||||
Holder cls = new Holder();
|
||||
|
||||
SignatureReader reader = new SignatureReader(classNode.signature); // Having a node version of this would probably be useful.
|
||||
reader.accept(new SignatureVisitor(Opcodes.ASM9) {
|
||||
reader.accept(new SignatureVisitor(Opcodes.ASM9)
|
||||
{
|
||||
final Deque<String> stack = new ArrayDeque<>();
|
||||
|
||||
@Override
|
||||
public void visitClassType(final String name) {
|
||||
public void visitClassType(final String name)
|
||||
{
|
||||
stack.push(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitInnerClassType(final String name) {
|
||||
public void visitInnerClassType(final String name)
|
||||
{
|
||||
stack.push(stack.pop() + '$' + name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
public void visitEnd()
|
||||
{
|
||||
var val = stack.pop();
|
||||
if (!stack.isEmpty() && CAP_INJECT.equals(stack.peek()))
|
||||
cls.value = val;
|
||||
@@ -104,8 +113,15 @@ public final class CapabilityTokenSubclass implements ILaunchPluginService {
|
||||
mtd.visitInsn(Opcodes.ARETURN);
|
||||
mtd.visitEnd();
|
||||
return ComputeFlags.COMPUTE_MAXS;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
return ComputeFlags.NO_REWRITE;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Holder {
|
||||
String value;
|
||||
}
|
||||
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.common.asm;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.tree.AnnotationNode;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
|
||||
|
||||
/**
|
||||
* Removes the final modifier from fields with the @ObjectHolder annotation, prevents the JITer from in lining them so our runtime replacements can work.
|
||||
* Will also de-finalize all fields in on class level annotations.
|
||||
*/
|
||||
public class ObjectHolderDefinalize implements ILaunchPluginService {
|
||||
// Hardcoded map of vanilla classes that should have object holders for each field of the given registry type.
|
||||
// IMPORTANT: Updates to this collection must be reflected in ObjectHolderRegistry. Duplicated cuz classloaders, yay!
|
||||
// Classnames are validated in ObjectHolderRegistry.
|
||||
private static final Map<String, VanillaObjectHolderData> VANILLA_OBJECT_HOLDERS = Stream.of(
|
||||
new VanillaObjectHolderData("net.minecraft.world.level.block.Blocks", "block", "net.minecraft.world.level.block.Block"),
|
||||
new VanillaObjectHolderData("net.minecraft.world.item.Items", "item", "net.minecraft.world.item.Item"),
|
||||
new VanillaObjectHolderData("net.minecraft.world.item.enchantment.Enchantments", "enchantment", "net.minecraft.world.item.enchantment.Enchantment"),
|
||||
new VanillaObjectHolderData("net.minecraft.world.effect.MobEffects", "mob_effect", "net.minecraft.world.effect.MobEffect"),
|
||||
new VanillaObjectHolderData("net.minecraft.core.particles.ParticleTypes", "particle_type", "net.minecraft.core.particles.ParticleType"),
|
||||
new VanillaObjectHolderData("net.minecraft.sounds.SoundEvents", "sound_event", "net.minecraft.sounds.SoundEvent")
|
||||
).collect(Collectors.toMap(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")
|
||||
&& !internalName.equals("net/minecraftforge/common/crafting/ConditionalRecipe"))
|
||||
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 boolean hasHolder(List<AnnotationNode> lst) {
|
||||
return lst != null && lst.stream().anyMatch(n -> n.desc.equals(OBJECT_HOLDER));
|
||||
}
|
||||
|
||||
private String getValue(List<AnnotationNode> lst)
|
||||
{
|
||||
AnnotationNode ann = lst.stream().filter(n -> n.desc.equals(OBJECT_HOLDER)).findFirst().get();
|
||||
if (ann.values != null)
|
||||
{
|
||||
for (int x = 0; x < ann.values.size() - 1; x += 2) {
|
||||
if (ann.values.get(x).equals("value")) {
|
||||
return (String)ann.values.get(x + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int processClassWithFlags(final Phase phase, final ClassNode classNode, final Type classType, final String reason)
|
||||
{
|
||||
final AtomicBoolean changes = new AtomicBoolean();
|
||||
//Must be public static finals, and non-array objects
|
||||
|
||||
//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;
|
||||
@@ -20,6 +20,8 @@ import org.objectweb.asm.tree.MethodNode;
|
||||
|
||||
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.Marker;
|
||||
import org.slf4j.MarkerFactory;
|
||||
|
||||
/**
|
||||
* Modifies specified enums to allow runtime extension by making the $VALUES field non-final and
|
||||
@@ -47,21 +49,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.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
|
||||
|
||||
@@ -5,14 +5,17 @@
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
public class BackgroundWaiter {
|
||||
private static ExecutorService runner = Executors.newSingleThreadExecutor();
|
||||
|
||||
public static void runAndTick(Runnable r, Runnable tick) {
|
||||
var runner = Executors.newSingleThreadExecutor();
|
||||
ImmediateWindowHandler.updateProgress("Loading bootstrap resources");
|
||||
final Future<?> work = runner.submit(r);
|
||||
do {
|
||||
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import cpw.mods.modlauncher.api.NamedPath;
|
||||
import cpw.mods.modlauncher.serviceapi.ITransformerDiscoveryService;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
public class ClasspathTransformerDiscoverer implements ITransformerDiscoveryService {
|
||||
|
||||
private final List<Path> legacyClasspath = Arrays.stream(System.getProperty("legacyClassPath", "").split(File.pathSeparator)).map(Path::of).toList();
|
||||
|
||||
@Override
|
||||
public List<NamedPath> candidates(Path gameDirectory) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NamedPath> candidates(final Path gameDirectory, final String launchTarget) {
|
||||
if (launchTarget != null && launchTarget.contains("dev")) {
|
||||
this.scan(gameDirectory);
|
||||
}
|
||||
return List.copyOf(found);
|
||||
}
|
||||
|
||||
private final static List<NamedPath> found = new ArrayList<>();
|
||||
|
||||
public static List<Path> allExcluded() {
|
||||
return found.stream().map(np->np.paths()[0]).toList();
|
||||
}
|
||||
|
||||
private void scan(final Path gameDirectory) {
|
||||
try {
|
||||
locateTransformers("META-INF/services/cpw.mods.modlauncher.api.ITransformationService");
|
||||
locateTransformers("META-INF/services/net.minecraftforge.forgespi.locating.IModLocator");
|
||||
} catch (IOException e) {
|
||||
LogManager.getLogger().error("Error during discovery of transform services from the classpath", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void locateTransformers(String resource) throws IOException {
|
||||
final Enumeration<URL> resources = ClassLoader.getSystemClassLoader().getResources(resource);
|
||||
while (resources.hasMoreElements()) {
|
||||
URL url = resources.nextElement();
|
||||
Path path = ClasspathLocatorUtils.findJarPathFor(resource, url.toString(), url);
|
||||
if (legacyClasspath.stream().anyMatch(path::equals) || !Files.exists(path) || Files.isDirectory(path))
|
||||
continue;
|
||||
found.add(new NamedPath(path.toUri().toString(), path));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,8 +14,6 @@ import java.util.List;
|
||||
* or server.
|
||||
*/
|
||||
public class EarlyLoadingException extends RuntimeException {
|
||||
private static final long serialVersionUID = 2302192216172508006L;
|
||||
|
||||
public static class ExceptionData {
|
||||
private final IModInfo modInfo;
|
||||
private final String i18message;
|
||||
|
||||
@@ -44,6 +44,7 @@ public class FMLConfig
|
||||
private final String entry;
|
||||
private final Object defaultValue;
|
||||
private final String comment;
|
||||
private final Class<?> valueType;
|
||||
private final Function<Object, Object> entryFunction;
|
||||
|
||||
ConfigValue(final String entry, final Object defaultValue, final String comment) {
|
||||
@@ -53,6 +54,7 @@ public class FMLConfig
|
||||
this.entry = entry;
|
||||
this.defaultValue = defaultValue;
|
||||
this.comment = comment;
|
||||
this.valueType = defaultValue.getClass();
|
||||
this.entryFunction = entryFunction;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,36 +8,44 @@ package net.minecraftforge.fml.loading;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import cpw.mods.modlauncher.Launcher;
|
||||
import cpw.mods.modlauncher.api.*;
|
||||
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
|
||||
import cpw.mods.modlauncher.util.ServiceLoaderUtils;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.BackgroundScanHandler;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModDiscoverer;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModValidator;
|
||||
import net.minecraftforge.accesstransformer.service.AccessTransformerService;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
|
||||
import net.minecraftforge.fml.loading.targets.CommonLaunchHandler;
|
||||
import net.minecraftforge.forgespi.Environment;
|
||||
import net.minecraftforge.forgespi.coremod.ICoreModProvider;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ServiceConfigurationError;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.Set;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
|
||||
import static net.minecraftforge.fml.loading.LogMarkers.SCAN;
|
||||
|
||||
public final class FMLLoader {
|
||||
public class FMLLoader
|
||||
{
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static AccessTransformerService accessTransformer;
|
||||
private static ModDiscoverer modDiscoverer;
|
||||
private static ICoreModProvider coreModProvider;
|
||||
private static ILaunchPluginService eventBus;
|
||||
private static LanguageLoadingProvider languageLoadingProvider;
|
||||
private static Dist dist;
|
||||
private static String naming;
|
||||
private static LoadingModList loadingModList;
|
||||
private static RuntimeDistCleaner runtimeDistCleaner;
|
||||
private static Path gamePath;
|
||||
private static final VersionInfo versionInfo = VersionInfo.detect();
|
||||
private static VersionInfo versionInfo;
|
||||
private static String launchHandlerName;
|
||||
private static CommonLaunchHandler commonLaunchHandler;
|
||||
public static Runnable progressWindowTick;
|
||||
@@ -46,73 +54,89 @@ public final class FMLLoader {
|
||||
private static boolean production;
|
||||
private static IModuleLayerManager moduleLayerManager;
|
||||
|
||||
static void onInitialLoad(IEnvironment env, Set<String> otherServices) throws IncompatibleEnvironmentException {
|
||||
LOGGER.debug(CORE, "Detected version data : {}", versionInfo);
|
||||
LOGGER.debug(CORE, "FML {} loading", LauncherVersion.getVersion());
|
||||
static void onInitialLoad(IEnvironment environment, Set<String> otherServices) throws IncompatibleEnvironmentException
|
||||
{
|
||||
final String version = LauncherVersion.getVersion();
|
||||
LOGGER.debug(CORE,"FML {} loading", version);
|
||||
final Package modLauncherPackage = ITransformationService.class.getPackage();
|
||||
LOGGER.debug(CORE,"FML found ModLauncher version : {}", modLauncherPackage.getImplementationVersion());
|
||||
if (!modLauncherPackage.isCompatibleWith("4.0")) {
|
||||
LOGGER.error(CORE, "Found incompatible ModLauncher specification : {}, version {} from {}", modLauncherPackage.getSpecificationVersion(), modLauncherPackage.getImplementationVersion(), modLauncherPackage.getImplementationVendor());
|
||||
throw new IncompatibleEnvironmentException("Incompatible modlauncher found "+modLauncherPackage.getSpecificationVersion());
|
||||
}
|
||||
|
||||
checkPackage(ITransformationService.class, "4.0", "ModLauncher");
|
||||
accessTransformer = getPlugin(env, "accesstransformer", "1.0", "AccessTransformer");
|
||||
runtimeDistCleaner = getPlugin(env, "runtimedistcleaner", "1.0", "RuntimeDistCleaner");
|
||||
checkPackage(Environment.class, "2.0", "ForgeSPI");
|
||||
// Allows us to communicate properties with other services through ModLauncher
|
||||
setupBlackboardKeys();
|
||||
|
||||
accessTransformer = (AccessTransformerService) environment.findLaunchPlugin("accesstransformer").orElseThrow(()-> {
|
||||
LOGGER.error(CORE, "Access Transformer library is missing, we need this to run");
|
||||
return new IncompatibleEnvironmentException("Missing AccessTransformer, cannot run");
|
||||
});
|
||||
|
||||
final Package atPackage = accessTransformer.getClass().getPackage();
|
||||
LOGGER.debug(CORE,"FML found AccessTransformer version : {}", atPackage.getImplementationVersion());
|
||||
if (!atPackage.isCompatibleWith("1.0")) {
|
||||
LOGGER.error(CORE, "Found incompatible AccessTransformer specification : {}, version {} from {}", atPackage.getSpecificationVersion(), atPackage.getImplementationVersion(), atPackage.getImplementationVendor());
|
||||
throw new IncompatibleEnvironmentException("Incompatible accesstransformer found "+atPackage.getSpecificationVersion());
|
||||
}
|
||||
|
||||
eventBus = environment.findLaunchPlugin("eventbus").orElseThrow(()-> {
|
||||
LOGGER.error(CORE, "Event Bus library is missing, we need this to run");
|
||||
return new IncompatibleEnvironmentException("Missing EventBus, cannot run");
|
||||
});
|
||||
|
||||
final Package eventBusPackage = eventBus.getClass().getPackage();
|
||||
LOGGER.debug(CORE,"FML found EventBus version : {}", eventBusPackage.getImplementationVersion());
|
||||
if (!eventBusPackage.isCompatibleWith("1.0")) {
|
||||
LOGGER.error(CORE, "Found incompatible EventBus specification : {}, version {} from {}", eventBusPackage.getSpecificationVersion(), eventBusPackage.getImplementationVersion(), eventBusPackage.getImplementationVendor());
|
||||
throw new IncompatibleEnvironmentException("Incompatible eventbus found "+eventBusPackage.getSpecificationVersion());
|
||||
}
|
||||
|
||||
runtimeDistCleaner = (RuntimeDistCleaner)environment.findLaunchPlugin("runtimedistcleaner").orElseThrow(()-> {
|
||||
LOGGER.error(CORE, "Dist Cleaner is missing, we need this to run");
|
||||
return new IncompatibleEnvironmentException("Missing DistCleaner, cannot run!");
|
||||
});
|
||||
LOGGER.debug(CORE, "Found Runtime Dist Cleaner");
|
||||
|
||||
var coreModProviders = ServiceLoaderUtils.streamWithErrorHandling(ServiceLoader.load(FMLLoader.class.getModule().getLayer(), ICoreModProvider.class), sce -> LOGGER.error(CORE, "Failed to load a coremod library, expect problems", sce)).toList();
|
||||
|
||||
if (coreModProviders.isEmpty()) {
|
||||
LOGGER.error(CORE, "Found no coremod provider. Cannot run");
|
||||
throw new IncompatibleEnvironmentException("No coremod library found");
|
||||
} else if (coreModProviders.size() > 1) {
|
||||
LOGGER.error(CORE, "Found multiple coremod providers : {}. Cannot run", coreModProviders.stream().map(p -> p.getClass().getName()).collect(Collectors.toList()));
|
||||
throw new IncompatibleEnvironmentException("Multiple coremod libraries found");
|
||||
}
|
||||
|
||||
coreModProvider = coreModProviders.get(0);
|
||||
final Package coremodPackage = coreModProvider.getClass().getPackage();
|
||||
LOGGER.debug(CORE,"FML found CoreMod version : {}", coremodPackage.getImplementationVersion());
|
||||
|
||||
|
||||
LOGGER.debug(CORE, "Found ForgeSPI package implementation version {}", Environment.class.getPackage().getImplementationVersion());
|
||||
LOGGER.debug(CORE, "Found ForgeSPI package specification {}", Environment.class.getPackage().getSpecificationVersion());
|
||||
if (Integer.parseInt(Environment.class.getPackage().getSpecificationVersion()) < 2) {
|
||||
LOGGER.error(CORE, "Found an out of date ForgeSPI implementation: {}, loading cannot continue", Environment.class.getPackage().getSpecificationVersion());
|
||||
throw new IncompatibleEnvironmentException("ForgeSPI is out of date, we cannot continue");
|
||||
}
|
||||
|
||||
try {
|
||||
Class.forName("com.electronwill.nightconfig.core.Config", false, env.getClass().getClassLoader());
|
||||
Class.forName("com.electronwill.nightconfig.toml.TomlFormat", false, env.getClass().getClassLoader());
|
||||
Class.forName("com.electronwill.nightconfig.core.Config", false, environment.getClass().getClassLoader());
|
||||
Class.forName("com.electronwill.nightconfig.toml.TomlFormat", false, environment.getClass().getClassLoader());
|
||||
} catch (ClassNotFoundException e) {
|
||||
LOGGER.error(CORE, "Failed to load NightConfig");
|
||||
throw new IncompatibleEnvironmentException("Missing NightConfig");
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> T getPlugin(IEnvironment env, String id, String version, String name) throws IncompatibleEnvironmentException {
|
||||
var plugin = env.findLaunchPlugin(id).orElse(null);
|
||||
if (plugin == null) {
|
||||
LOGGER.error(CORE, "{} library is missing, we need this to run", name);
|
||||
throw new IncompatibleEnvironmentException("Missing " + name + ", cannot run");
|
||||
}
|
||||
checkPackage(plugin.getClass(), version, name);
|
||||
return (T) plugin;
|
||||
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);
|
||||
}
|
||||
|
||||
private static void checkPackage(Class<?> cls, String version, String name) throws IncompatibleEnvironmentException {
|
||||
var pkg = cls.getPackage();
|
||||
var info = JarVersionLookupHandler.getInfo(pkg);
|
||||
LOGGER.debug(CORE, "Found {} version: {}", name, info.impl().version().orElse("MISSING"));
|
||||
|
||||
if (!pkg.isCompatibleWith(version)) {
|
||||
LOGGER.error(CORE, "Found incompatible {} specification: {}, version {} from {}", name,
|
||||
info.spec().version().orElse("MISSING"),
|
||||
info.impl().version().orElse("MISSING"),
|
||||
info.impl().vendor().orElse("MISSING")
|
||||
);
|
||||
throw new IncompatibleEnvironmentException("Incompatible " + name + " found " + info.spec().version().orElse("MISSING"));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static <T> T getSingleService(Class<T> clazz, String name) throws IncompatibleEnvironmentException {
|
||||
var providers = new ArrayList<T>();
|
||||
for (var itr = ServiceLoader.load(FMLLoader.class.getModule().getLayer(), clazz).iterator(); itr.hasNext(); ) {
|
||||
try {
|
||||
providers.add(itr.next());
|
||||
} catch (ServiceConfigurationError e) {
|
||||
LOGGER.error(CORE, "Failed to load a " + name + " library, expect problems", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (providers.isEmpty()) {
|
||||
LOGGER.error(CORE, "Found no {} provider. Cannot run", name);
|
||||
throw new IncompatibleEnvironmentException("No " + name + " library found");
|
||||
} else if (providers.size() > 1) {
|
||||
LOGGER.error(CORE, "Found multiple {} providers: {}. Cannot run", name, providers.stream().map(p -> p.getClass().getName()).toList());
|
||||
throw new IncompatibleEnvironmentException("Multiple " + name + " libraries found");
|
||||
}
|
||||
|
||||
return providers.get(0);
|
||||
}
|
||||
|
||||
static void setupLaunchHandler(final IEnvironment environment, final Map<String, Object> arguments) {
|
||||
static void setupLaunchHandler(final IEnvironment environment, final Map<String, Object> arguments)
|
||||
{
|
||||
final String launchTarget = environment.getProperty(IEnvironment.Keys.LAUNCHTARGET.get()).orElse("MISSING");
|
||||
arguments.put("launchTarget", launchTarget);
|
||||
final Optional<ILaunchHandlerService> launchHandler = environment.findLaunchHandler(launchTarget);
|
||||
@@ -122,58 +146,85 @@ public final class FMLLoader {
|
||||
throw new RuntimeException("Missing launch handler: " + launchTarget);
|
||||
}
|
||||
|
||||
// TODO: [FML][Loader] What the fuck is the point of using a service if you require a specific concrete class
|
||||
if (!(launchHandler.get() instanceof CommonLaunchHandler)) {
|
||||
LOGGER.error(CORE, "Incompatible Launch handler found - type {}, cannot continue", launchHandler.get().getClass().getName());
|
||||
throw new RuntimeException("Incompatible launch handler found");
|
||||
}
|
||||
commonLaunchHandler = (CommonLaunchHandler)launchHandler.get();
|
||||
launchHandlerName = launchHandler.get().name();
|
||||
gamePath = environment.getProperty(IEnvironment.Keys.GAMEDIR.get()).orElse(Path.of(".").toAbsolutePath());
|
||||
gamePath = environment.getProperty(IEnvironment.Keys.GAMEDIR.get()).orElse(Paths.get(".").toAbsolutePath());
|
||||
|
||||
naming = commonLaunchHandler.getNaming();
|
||||
dist = commonLaunchHandler.getDist();
|
||||
production = commonLaunchHandler.isProduction();
|
||||
|
||||
accessTransformer.getExtension().accept(Map.entry(naming, "srg"));
|
||||
versionInfo = new VersionInfo(arguments);
|
||||
|
||||
accessTransformer.getExtension().accept(Pair.of(naming, "srg"));
|
||||
|
||||
LOGGER.debug(CORE,"Received command line version data : {}", versionInfo);
|
||||
|
||||
runtimeDistCleaner.getExtension().accept(dist);
|
||||
}
|
||||
|
||||
public static List<ITransformationService.Resource> beginModScan(final Map<String,?> arguments) {
|
||||
LOGGER.debug(SCAN, "Scanning for Mod Locators");
|
||||
modValidator = ModDiscoverer.discoverMods(arguments);
|
||||
public static List<ITransformationService.Resource> beginModScan(final Map<String,?> arguments)
|
||||
{
|
||||
LOGGER.debug(SCAN,"Scanning for Mod Locators");
|
||||
modDiscoverer = new ModDiscoverer(arguments);
|
||||
modValidator = modDiscoverer.discoverMods();
|
||||
var pluginResources = modValidator.getPluginResources();
|
||||
return List.of(pluginResources);
|
||||
}
|
||||
|
||||
public static List<ITransformationService.Resource> completeScan(IModuleLayerManager layerManager) {
|
||||
moduleLayerManager = layerManager;
|
||||
LanguageLoadingProvider.reload();
|
||||
languageLoadingProvider = new LanguageLoadingProvider();
|
||||
backgroundScanHandler = modValidator.stage2Validation();
|
||||
loadingModList = backgroundScanHandler.getLoadingModList();
|
||||
return List.of(modValidator.getModResources());
|
||||
}
|
||||
|
||||
public static ICoreModProvider getCoreModProvider() {
|
||||
return coreModProvider;
|
||||
}
|
||||
|
||||
public static LanguageLoadingProvider getLanguageLoadingProvider()
|
||||
{
|
||||
return languageLoadingProvider;
|
||||
}
|
||||
|
||||
static ModDiscoverer getModDiscoverer() {
|
||||
return modDiscoverer;
|
||||
}
|
||||
|
||||
public static CommonLaunchHandler getLaunchHandler() {
|
||||
return commonLaunchHandler;
|
||||
}
|
||||
|
||||
public static void addAccessTransformer(Path atPath, ModFile modName) {
|
||||
LOGGER.debug(SCAN, "Adding Access Transformer {} in {}", atPath, modName.getFilePath());
|
||||
public static void addAccessTransformer(Path atPath, ModFile modName)
|
||||
{
|
||||
LOGGER.debug(SCAN, "Adding Access Transformer in {}", modName.getFilePath());
|
||||
accessTransformer.offerResource(atPath, modName.getFileName());
|
||||
}
|
||||
|
||||
public static Dist getDist() {
|
||||
public static Dist getDist()
|
||||
{
|
||||
return dist;
|
||||
}
|
||||
|
||||
public static void beforeStart(ModuleLayer gameLayer) {
|
||||
public static void beforeStart(ModuleLayer gameLayer)
|
||||
{
|
||||
ImmediateWindowHandler.acceptGameLayer(gameLayer);
|
||||
ImmediateWindowHandler.updateProgress("Launching minecraft");
|
||||
progressWindowTick.run();
|
||||
}
|
||||
|
||||
public static Path getGamePath() {
|
||||
public static LoadingModList getLoadingModList()
|
||||
{
|
||||
return loadingModList;
|
||||
}
|
||||
|
||||
public static Path getGamePath()
|
||||
{
|
||||
return gamePath;
|
||||
}
|
||||
|
||||
@@ -190,7 +241,7 @@ public final class FMLLoader {
|
||||
}
|
||||
|
||||
public static List<Map<String, String>> modLauncherModList() {
|
||||
return Launcher.INSTANCE.environment().getProperty(IEnvironment.Keys.MODLIST.get()).orElse(List.of());
|
||||
return Launcher.INSTANCE.environment().getProperty(IEnvironment.Keys.MODLIST.get()).orElseGet(Collections::emptyList);
|
||||
}
|
||||
|
||||
public static String launcherHandlerName() {
|
||||
|
||||
@@ -7,31 +7,53 @@ package net.minecraftforge.fml.loading;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import cpw.mods.modlauncher.api.*;
|
||||
import joptsimple.ArgumentAcceptingOptionSpec;
|
||||
import joptsimple.OptionSpecBuilder;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
|
||||
import net.minecraftforge.forgespi.Environment;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
|
||||
|
||||
public class FMLServiceProvider implements ITransformationService {
|
||||
public class FMLServiceProvider implements ITransformationService
|
||||
{
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private final Map<String, Object> arguments = new HashMap<>();
|
||||
private ArgumentAcceptingOptionSpec<String> modsOption;
|
||||
private ArgumentAcceptingOptionSpec<String> modListsOption;
|
||||
private ArgumentAcceptingOptionSpec<String> mavenRootsOption;
|
||||
private ArgumentAcceptingOptionSpec<String> forgeOption;
|
||||
private ArgumentAcceptingOptionSpec<String> mcOption;
|
||||
private ArgumentAcceptingOptionSpec<String> forgeGroupOption;
|
||||
private ArgumentAcceptingOptionSpec<String> mcpOption;
|
||||
private ArgumentAcceptingOptionSpec<String> mappingsOption;
|
||||
private List<String> modsArgumentList;
|
||||
private List<String> modListsArgumentList;
|
||||
private List<String> mavenRootsArgumentList;
|
||||
private String targetForgeVersion;
|
||||
private String targetMcVersion;
|
||||
private String targetMcpVersion;
|
||||
private String targetMcpMappings;
|
||||
private String targetForgeGroup;
|
||||
private Map<String, Object> arguments;
|
||||
|
||||
public FMLServiceProvider() {
|
||||
var markers = System.getProperty("forge.logging.markers", "").split(",");
|
||||
for (var marker : markers)
|
||||
System.setProperty("forge.logging.marker." + marker.toLowerCase(Locale.ROOT), "ACCEPT");
|
||||
public FMLServiceProvider()
|
||||
{
|
||||
final String markerselection = System.getProperty("forge.logging.markers", "");
|
||||
Arrays.stream(markerselection.split(",")).forEach(marker -> System.setProperty("forge.logging.marker." + marker.toLowerCase(Locale.ROOT), "ACCEPT"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
public String name()
|
||||
{
|
||||
return "fml";
|
||||
}
|
||||
|
||||
@@ -43,6 +65,15 @@ public class FMLServiceProvider implements ITransformationService {
|
||||
FMLConfig.load();
|
||||
LOGGER.debug(CORE, "Preparing ModFile");
|
||||
environment.computePropertyIfAbsent(Environment.Keys.MODFILEFACTORY.get(), k->ModFile::new);
|
||||
arguments = new HashMap<>();
|
||||
arguments.put("modLists", modListsArgumentList);
|
||||
arguments.put("mods", modsArgumentList);
|
||||
arguments.put("mavenRoots", mavenRootsArgumentList);
|
||||
arguments.put("forgeVersion", targetForgeVersion);
|
||||
arguments.put("forgeGroup", targetForgeGroup);
|
||||
arguments.put("mcVersion", targetMcVersion);
|
||||
arguments.put("mcpVersion", targetMcpVersion);
|
||||
arguments.put("mcpMappings", targetMcpMappings);
|
||||
LOGGER.debug(CORE, "Preparing launch handler");
|
||||
FMLLoader.setupLaunchHandler(environment, arguments);
|
||||
FMLEnvironment.setupInteropEnvironment(environment);
|
||||
@@ -61,13 +92,45 @@ public class FMLServiceProvider implements ITransformationService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoad(IEnvironment environment, Set<String> otherServices) throws IncompatibleEnvironmentException {
|
||||
public void onLoad(IEnvironment environment, Set<String> otherServices) throws IncompatibleEnvironmentException
|
||||
{
|
||||
// LOGGER.debug("Injecting tracing printstreams for STDOUT/STDERR.");
|
||||
// System.setOut(new TracingPrintStream(LogManager.getLogger("STDOUT"), System.out));
|
||||
// System.setErr(new TracingPrintStream(LogManager.getLogger("STDERR"), System.err));
|
||||
FMLLoader.onInitialLoad(environment, otherServices);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public @NotNull List<ITransformer> transformers() {
|
||||
return List.of();
|
||||
public void arguments(BiFunction<String, String, OptionSpecBuilder> argumentBuilder)
|
||||
{
|
||||
forgeOption = argumentBuilder.apply("forgeVersion", "Forge Version number").withRequiredArg().ofType(String.class).required();
|
||||
forgeGroupOption = argumentBuilder.apply("forgeGroup", "Forge Group (for testing)").withRequiredArg().ofType(String.class).defaultsTo("net.minecraftforge");
|
||||
mcOption = argumentBuilder.apply("mcVersion", "Minecraft Version number").withRequiredArg().ofType(String.class).required();
|
||||
mcpOption = argumentBuilder.apply("mcpVersion", "MCP Version number").withRequiredArg().ofType(String.class).required();
|
||||
mappingsOption = argumentBuilder.apply("mcpMappings", "MCP Mappings Channel and Version").withRequiredArg().ofType(String.class);
|
||||
modsOption = argumentBuilder.apply("mods", "List of mods to add").withRequiredArg().ofType(String.class).withValuesSeparatedBy(",");
|
||||
modListsOption = argumentBuilder.apply("modLists", "JSON modlists").withRequiredArg().ofType(String.class).withValuesSeparatedBy(",");
|
||||
mavenRootsOption = argumentBuilder.apply("mavenRoots", "Maven root directories").withRequiredArg().ofType(String.class).withValuesSeparatedBy(",");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void argumentValues(OptionResult option)
|
||||
{
|
||||
modsArgumentList = option.values(modsOption);
|
||||
modListsArgumentList = option.values(modListsOption);
|
||||
mavenRootsArgumentList = option.values(mavenRootsOption);
|
||||
targetForgeVersion = option.value(forgeOption);
|
||||
targetForgeGroup = option.value(forgeGroupOption);
|
||||
targetMcVersion = option.value(mcOption);
|
||||
targetMcpVersion = option.value(mcpOption);
|
||||
targetMcpMappings = option.value(mappingsOption);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<ITransformer> transformers()
|
||||
{
|
||||
LOGGER.debug(CORE, "Loading coremod transformers");
|
||||
return new ArrayList<>(FMLLoader.getCoreModProvider().getCoreModTransformers());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class FileUtils
|
||||
{
|
||||
|
||||
public static String fileExtension(final Path path) {
|
||||
String fileName = path.getFileName().toString();
|
||||
int idx = fileName.lastIndexOf('.');
|
||||
if (idx > -1) {
|
||||
return fileName.substring(idx+1);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean matchFileName(String path, String... matches) {
|
||||
// Extract file name from path
|
||||
String name = path.substring(Math.min(path.lastIndexOf(File.separatorChar) + 1, path.length()));
|
||||
// Check if it contains any of the desired keywords
|
||||
for (String match : matches) {
|
||||
if (name.contains(match)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+14
-15
@@ -23,7 +23,7 @@ public class ImmediateWindowHandler {
|
||||
|
||||
private static ProgressMeter earlyProgress;
|
||||
public static void load(final String launchTarget, final String[] arguments) {
|
||||
if (!launchTarget.contains("client")) {
|
||||
if (!List.of("forgeclient", "forgeclientuserdev", "forgeclientdev").contains(launchTarget)) {
|
||||
provider = new DummyProvider();
|
||||
LOGGER.info("ImmediateWindowProvider not loading because launch target is {}", launchTarget);
|
||||
} else if (!FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_CONTROL)) {
|
||||
@@ -32,16 +32,15 @@ public class ImmediateWindowHandler {
|
||||
} else {
|
||||
final var providername = FMLConfig.getConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_PROVIDER);
|
||||
LOGGER.info("Loading ImmediateWindowProvider {}", providername);
|
||||
for (var service : ServiceLoader.load(ImmediateWindowProvider.class)) {
|
||||
if (providername.equals(service.name())) {
|
||||
provider = service;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (provider == null) {
|
||||
final var maybeProvider = ServiceLoader.load(ImmediateWindowProvider.class)
|
||||
.stream()
|
||||
.map(ServiceLoader.Provider::get)
|
||||
.filter(p -> Objects.equals(p.name(), providername))
|
||||
.findFirst();
|
||||
provider = maybeProvider.or(() -> {
|
||||
LOGGER.info("Failed to find ImmediateWindowProvider {}, disabling", providername);
|
||||
provider = new DummyProvider();
|
||||
}
|
||||
return Optional.of(new DummyProvider());
|
||||
}).orElseThrow();
|
||||
}
|
||||
// Only update config if the provider isn't the dummy provider
|
||||
if (!Objects.equals(provider.name(), "dummyprovider"))
|
||||
@@ -51,8 +50,8 @@ public class ImmediateWindowHandler {
|
||||
earlyProgress.label("Bootstrapping Minecraft");
|
||||
}
|
||||
|
||||
public static long setupMinecraftWindow(final int width, final int height, final String title, final long monitor, final Supplier<Object> backend) {
|
||||
return provider.setupMinecraftWindow(width, height, title, monitor, backend);
|
||||
public static long setupMinecraftWindow(final IntSupplier width, final IntSupplier height, final Supplier<String> title, final LongSupplier monitor) {
|
||||
return provider.setupMinecraftWindow(width, height, title, monitor);
|
||||
}
|
||||
|
||||
public static boolean positionWindow(Optional<Object> monitor,IntConsumer widthSetter, IntConsumer heightSetter, IntConsumer xSetter, IntConsumer ySetter) {
|
||||
@@ -104,9 +103,9 @@ public class ImmediateWindowHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public long setupMinecraftWindow(final int width, final int height, final String title, final long monitor, final Supplier<Object> backend) {
|
||||
public long setupMinecraftWindow(final IntSupplier width, final IntSupplier height, final Supplier<String> title, final LongSupplier monitor) {
|
||||
try {
|
||||
var longsupplier = (LongSupplier)NV_HANDOFF.invoke(null, width, height, title, monitor, backend);
|
||||
var longsupplier = (LongSupplier)NV_HANDOFF.invoke(null, width, height, title, monitor);
|
||||
return longsupplier.getAsLong();
|
||||
} catch (Throwable e) {
|
||||
throw new IllegalStateException("How did you get here?", e);
|
||||
@@ -140,7 +139,7 @@ public class ImmediateWindowHandler {
|
||||
|
||||
@Override
|
||||
public void updateModuleReads(final ModuleLayer layer) {
|
||||
var fm = layer.findModule("net.minecraftforge.forge");
|
||||
var fm = layer.findModule("forge");
|
||||
if (fm.isPresent()) {
|
||||
getClass().getModule().addReads(fm.get());
|
||||
var clz = fm.map(l -> Class.forName(l, "net.minecraftforge.client.loading.NoVizFallback")).orElseThrow();
|
||||
|
||||
@@ -8,6 +8,8 @@ package net.minecraftforge.fml.loading;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.IntConsumer;
|
||||
import java.util.function.IntSupplier;
|
||||
import java.util.function.LongSupplier;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
@@ -64,7 +66,7 @@ public interface ImmediateWindowProvider {
|
||||
* @param monitor This is the monitor it should appear on.
|
||||
* @return The window id
|
||||
*/
|
||||
long setupMinecraftWindow(final int width, final int height, final String title, final long monitor, final Supplier<Object> backend);
|
||||
long setupMinecraftWindow(final IntSupplier width, final IntSupplier height, final Supplier<String> title, final LongSupplier monitor);
|
||||
|
||||
/**
|
||||
* This is called after window handoff to allow us to tell Mojang about our window's position. This might give a
|
||||
|
||||
+1
-42
@@ -11,72 +11,31 @@ import java.util.Optional;
|
||||
* Finds Version data from a package, with possible default values
|
||||
*/
|
||||
public class JarVersionLookupHandler {
|
||||
public record Version(Optional<String> title, Optional<String> vendor, Optional<String> version) {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" +
|
||||
"title=" + title.orElse(null) +
|
||||
", vendor=" + vendor.orElse(null) +
|
||||
", version=" + version.orElse(null) +
|
||||
"]";
|
||||
}
|
||||
}
|
||||
public record Info(Version spec, Version impl) {}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static Info getInfo(String pkgName) {
|
||||
return getInfo(Package.getPackage(pkgName));
|
||||
}
|
||||
|
||||
public static Info getInfo(Class<?> clazz) {
|
||||
return getInfo(clazz.getPackage());
|
||||
}
|
||||
|
||||
public static Info getInfo(Package pkg) {
|
||||
return new Info(
|
||||
new Version(
|
||||
Optional.ofNullable(pkg.getSpecificationTitle()),
|
||||
Optional.ofNullable(pkg.getSpecificationVendor()),
|
||||
Optional.ofNullable(pkg.getSpecificationVersion())
|
||||
),
|
||||
new Version(
|
||||
Optional.ofNullable(pkg.getImplementationTitle()),
|
||||
Optional.ofNullable(pkg.getImplementationVendor()),
|
||||
Optional.ofNullable(pkg.getImplementationVersion())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@Deprecated(forRemoval = true, since = "1.20.2")
|
||||
public static Optional<String> getImplementationVersion(final String pkgName) {
|
||||
// Note that with Java 9, you'll probably want the module's version data, hence pulling this out
|
||||
final String pkgVersion = Package.getPackage(pkgName).getImplementationVersion();
|
||||
return Optional.ofNullable(pkgVersion);
|
||||
}
|
||||
|
||||
@Deprecated(forRemoval = true, since = "1.20.2")
|
||||
public static Optional<String> getSpecificationVersion(final String pkgName) {
|
||||
// Note that with Java 9, you'll probably want the module's version data, hence pulling this out
|
||||
final String pkgVersion = Package.getPackage(pkgName).getSpecificationVersion();
|
||||
return Optional.ofNullable(pkgVersion);
|
||||
}
|
||||
|
||||
@Deprecated(forRemoval = true, since = "1.20.2")
|
||||
public static Optional<String> getImplementationVersion(final Class<?> clazz) {
|
||||
// With java 9 we'll use the module's version if it exists in preference.
|
||||
final String pkgVersion = clazz.getPackage().getImplementationVersion();
|
||||
return Optional.ofNullable(pkgVersion);
|
||||
}
|
||||
|
||||
@Deprecated(forRemoval = true, since = "1.20.2")
|
||||
public static Optional<String> getImplementationTitle(final Class<?> clazz) {
|
||||
// With java 9 we'll use the module's version if it exists in preference.
|
||||
final String pkgVersion = clazz.getPackage().getImplementationTitle();
|
||||
return Optional.ofNullable(pkgVersion);
|
||||
}
|
||||
|
||||
@Deprecated(forRemoval = true, since = "1.20.2")
|
||||
|
||||
public static Optional<String> getSpecificationVersion(final Class<?> clazz) {
|
||||
// With java 9 we'll use the module's version if it exists in preference.
|
||||
final String pkgVersion = clazz.getPackage().getSpecificationVersion();
|
||||
|
||||
+110
-76
@@ -7,18 +7,27 @@ package net.minecraftforge.fml.loading;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import cpw.mods.modlauncher.Launcher;
|
||||
import cpw.mods.modlauncher.api.IModuleLayerManager.Layer;
|
||||
import cpw.mods.modlauncher.api.IModuleLayerManager;
|
||||
import cpw.mods.modlauncher.util.ServiceLoaderUtils;
|
||||
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
|
||||
import net.minecraftforge.forgespi.language.IModLanguageProvider;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ExplodedDirectoryLocator;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
|
||||
import org.apache.maven.artifact.versioning.ArtifactVersion;
|
||||
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
|
||||
import org.apache.maven.artifact.versioning.VersionRange;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.ServiceConfigurationError;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
@@ -27,90 +36,115 @@ import java.util.stream.Stream;
|
||||
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
|
||||
import static net.minecraftforge.fml.loading.LogMarkers.LOADING;
|
||||
|
||||
public final class LanguageLoadingProvider {
|
||||
public static void forEach(Consumer<IModLanguageProvider> consumer) {
|
||||
providers.forEach(consumer);
|
||||
public class LanguageLoadingProvider
|
||||
{
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private final List<IModLanguageProvider> languageProviders = new ArrayList<>();
|
||||
private final ServiceLoader<IModLanguageProvider> serviceLoader;
|
||||
private final Map<String, ModLanguageWrapper> languageProviderMap = new HashMap<>();
|
||||
private List<Path> languagePaths = new ArrayList<>();
|
||||
|
||||
public void forEach(final Consumer<IModLanguageProvider> consumer)
|
||||
{
|
||||
languageProviders.forEach(consumer);
|
||||
}
|
||||
|
||||
public static <T> Stream<T> applyForEach(Function<IModLanguageProvider, T> function) {
|
||||
return providers.stream().map(function);
|
||||
public <T> Stream<T> applyForEach(final Function<IModLanguageProvider, T> function) {
|
||||
return languageProviders.stream().map(function);
|
||||
}
|
||||
|
||||
public static void reload() {
|
||||
private static class ModLanguageWrapper {
|
||||
private final IModLanguageProvider modLanguageProvider;
|
||||
|
||||
private final ArtifactVersion version;
|
||||
public ModLanguageWrapper(IModLanguageProvider modLanguageProvider, ArtifactVersion version)
|
||||
{
|
||||
this.modLanguageProvider = modLanguageProvider;
|
||||
this.version = version;
|
||||
}
|
||||
public ArtifactVersion getVersion()
|
||||
{
|
||||
return version;
|
||||
}
|
||||
|
||||
public IModLanguageProvider getModLanguageProvider()
|
||||
{
|
||||
return modLanguageProvider;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
LanguageLoadingProvider() {
|
||||
var sl = Launcher.INSTANCE.environment().findModuleLayerManager().flatMap(lm->lm.getLayer(IModuleLayerManager.Layer.PLUGIN)).orElseThrow();
|
||||
serviceLoader = ServiceLoader.load(sl, IModLanguageProvider.class);
|
||||
loadLanguageProviders();
|
||||
}
|
||||
private void loadLanguageProviders() {
|
||||
LOGGER.debug(CORE, "Found {} language providers", ServiceLoaderUtils.streamServiceLoader(()->serviceLoader, sce->LOGGER.error("Problem with language loaders")).count());
|
||||
serviceLoader.forEach(languageProviders::add);
|
||||
ImmediateWindowHandler.updateProgress("Loading language providers");
|
||||
languageProviders.forEach(lp -> {
|
||||
final Path lpPath;
|
||||
try {
|
||||
lpPath = Paths.get(lp.getClass().getProtectionDomain().getCodeSource().getLocation().toURI());
|
||||
} catch (URISyntaxException e) {
|
||||
throw new RuntimeException("Huh?", e);
|
||||
}
|
||||
Optional<String> implementationVersion = JarVersionLookupHandler.getImplementationVersion(lp.getClass());
|
||||
String impl = implementationVersion.orElse(Files.isDirectory(lpPath) ? FMLLoader.versionInfo().forgeVersion().split("\\.")[0] : null);
|
||||
if (impl == null) {
|
||||
LOGGER.error(CORE, "Found unversioned language provider {}", lp.name());
|
||||
throw new RuntimeException("Failed to find implementation version for language provider "+ lp.name());
|
||||
}
|
||||
LOGGER.debug(CORE, "Found language provider {}, version {}", lp.name(), impl);
|
||||
ImmediateWindowHandler.updateProgress("Loaded language provider "+lp.name()+ " " + impl);
|
||||
languageProviderMap.put(lp.name(), new ModLanguageWrapper(lp, new DefaultArtifactVersion(impl)));
|
||||
});
|
||||
}
|
||||
|
||||
void addForgeLanguage(final Path forgePath) {
|
||||
if (!languageProviderMap.containsKey("javafml")) {
|
||||
LOGGER.debug(CORE,"Adding forge as a language from {}", forgePath.toString());
|
||||
addLanguagePaths(Stream.of(forgePath));
|
||||
serviceLoader.reload();
|
||||
loadLanguageProviders();
|
||||
} else {
|
||||
LOGGER.debug(CORE, "Skipping adding forge jar - javafml is already present");
|
||||
}
|
||||
}
|
||||
|
||||
private void addLanguagePaths(final Stream<Path> langPaths) {
|
||||
languageProviders.clear();
|
||||
languageProviderMap.clear();
|
||||
// langPaths.peek(languagePaths::add).map(Path::toFile).map(File::toURI).map(rethrowFunction(URI::toURL)).forEach(languageClassLoader::addURL);
|
||||
}
|
||||
|
||||
public void addAdditionalLanguages(List<ModFile> modFiles)
|
||||
{
|
||||
if (modFiles==null) return;
|
||||
Stream<Path> langPaths = modFiles.stream().map(ModFile::getFilePath);
|
||||
addLanguagePaths(langPaths);
|
||||
serviceLoader.reload();
|
||||
loadLanguageProviders();
|
||||
}
|
||||
|
||||
/*==========================================================================*
|
||||
* INTERNAL *
|
||||
*==========================================================================*/
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static final ServiceLoader<IModLanguageProvider> serviceLoader;
|
||||
private static final ArrayList<IModLanguageProvider> providers = new ArrayList<>();
|
||||
private static final HashMap<String, Wrapper> providersByName = new HashMap<>();
|
||||
private static final DefaultArtifactVersion IN_DEV = new DefaultArtifactVersion("0.0-dev");
|
||||
|
||||
private record Wrapper(ArtifactVersion version, IModLanguageProvider service) {}
|
||||
|
||||
static {
|
||||
var sl = Launcher.INSTANCE.environment().findModuleLayerManager().flatMap(lm -> lm.getLayer(Layer.PLUGIN)).orElseThrow();
|
||||
serviceLoader = ServiceLoader.load(sl, IModLanguageProvider.class);
|
||||
Stream<Path> getLibraries() {
|
||||
return languagePaths.stream();
|
||||
}
|
||||
|
||||
private static void loadLanguageProviders() {
|
||||
providers.clear();
|
||||
providersByName.clear();
|
||||
|
||||
serviceLoader.reload();
|
||||
for (var itr = serviceLoader.iterator(); itr.hasNext(); ) {
|
||||
try {
|
||||
var srvc = itr.next();
|
||||
providers.add(srvc);
|
||||
} catch (ServiceConfigurationError sce) {
|
||||
LOGGER.error(CORE, "Failed to load IModLauguageProvider Service: " + sce.getMessage());
|
||||
}
|
||||
public IModLanguageProvider findLanguage(ModFile mf, String modLoader, VersionRange modLoaderVersion) {
|
||||
final String languageFileName = mf.getProvider() instanceof ExplodedDirectoryLocator ? "in-development" : mf.getFileName();
|
||||
final ModLanguageWrapper mlw = languageProviderMap.get(modLoader);
|
||||
if (mlw == null) {
|
||||
LOGGER.error(LOADING,"Missing language {} version {} wanted by {}", modLoader, modLoaderVersion, languageFileName);
|
||||
throw new EarlyLoadingException("Missing language "+modLoader, null, Collections.singletonList(new EarlyLoadingException.ExceptionData("fml.language.missingversion", modLoader, modLoaderVersion, languageFileName, "null")));
|
||||
}
|
||||
if (!VersionSupportMatrix.testVersionSupportMatrix(modLoaderVersion, modLoader, "languageloader", (llid, range) -> range.containsVersion(mlw.getVersion()))) {
|
||||
LOGGER.error(LOADING,"Missing language {} version {} wanted by {}, found {}", modLoader, modLoaderVersion, languageFileName, mlw.getVersion());
|
||||
throw new EarlyLoadingException("Missing language "+ modLoader + " matching range "+modLoaderVersion + " found "+mlw.getVersion(), null, Collections.singletonList(new EarlyLoadingException.ExceptionData("fml.language.missingversion", modLoader, modLoaderVersion, languageFileName, mlw.getVersion())));
|
||||
}
|
||||
|
||||
LOGGER.debug(CORE, "Found {} language providers", providers.size());
|
||||
|
||||
ImmediateWindowHandler.updateProgress("Loading language providers");
|
||||
for (var provider : providers) {
|
||||
var info = JarVersionLookupHandler.getInfo(provider.getClass());
|
||||
|
||||
if (info.impl().version().isEmpty() && FMLEnvironment.production) {
|
||||
LOGGER.error(CORE, "Found unversioned language provider {}: {}", provider.name(), provider.getClass().getName());
|
||||
throw new RuntimeException("Failed to find implementation version for language provider " + provider.name() + " of type " + provider.getClass().getName());
|
||||
}
|
||||
|
||||
var version = info.impl().version().map(DefaultArtifactVersion::new).orElse(IN_DEV);
|
||||
|
||||
LOGGER.debug(CORE, "Found language provider {}, version {}", provider.name(), version);
|
||||
ImmediateWindowHandler.updateProgress("Loaded language provider " + provider.name() + " " + version);
|
||||
providersByName.put(provider.name(), new Wrapper(version, provider));
|
||||
}
|
||||
}
|
||||
|
||||
public static IModLanguageProvider findLanguage(ModFile mod, String language, VersionRange versionRange) {
|
||||
var consumer = mod.getFileName();
|
||||
var wrapper = providersByName.get(language);
|
||||
|
||||
if (wrapper == null) {
|
||||
LOGGER.error(LOADING, "Missing language {} version {} wanted by {}", language, versionRange, consumer);
|
||||
var data = new EarlyLoadingException.ExceptionData("fml.language.missingversion", language, versionRange, consumer, "null");
|
||||
throw new EarlyLoadingException("Missing language " + language, null, List.of(data));
|
||||
}
|
||||
|
||||
var version = wrapper.version();
|
||||
if (version == IN_DEV) {
|
||||
LOGGER.debug(LOADING, "Language Provider {} version is unknown, but {} was requested by {}. Proceeding as this is development environement", language, versionRange, consumer);
|
||||
return wrapper.service();
|
||||
}
|
||||
|
||||
if (!versionRange.containsVersion(version)) {
|
||||
LOGGER.error(LOADING, "Missing language {} version {} wanted by {}, found {}", language, versionRange, consumer, version);
|
||||
var data = new EarlyLoadingException.ExceptionData("fml.language.missingversion", language, versionRange, consumer, version);
|
||||
throw new EarlyLoadingException("Missing language " + language + " matching range " + versionRange + " found " + version, null, List.of(data));
|
||||
}
|
||||
|
||||
return wrapper.service();
|
||||
return mlw.getModLanguageProvider();
|
||||
}
|
||||
}
|
||||
@@ -8,19 +8,21 @@ package net.minecraftforge.fml.loading;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
|
||||
|
||||
public class LauncherVersion {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static final String launcherVersion;
|
||||
|
||||
static {
|
||||
var info = JarVersionLookupHandler.getInfo(LauncherVersion.class);
|
||||
if (info.impl().version().isEmpty())
|
||||
throw new IllegalStateException("Failed to find version for package " + LauncherVersion.class.getPackageName() + " This is an invalid environment");
|
||||
launcherVersion = info.impl().version().get();
|
||||
LOGGER.debug(LogMarkers.CORE, "Found FMLLauncher version {}", launcherVersion);
|
||||
String vers = JarVersionLookupHandler.getImplementationVersion(LauncherVersion.class).orElse(System.getenv("LAUNCHER_VERSION"));
|
||||
if (vers == null) throw new RuntimeException("Missing FMLLauncher version, cannot continue");
|
||||
launcherVersion = vers;
|
||||
LOGGER.debug(CORE, "Found FMLLauncher version {}", launcherVersion);
|
||||
}
|
||||
|
||||
public static String getVersion() {
|
||||
public static String getVersion()
|
||||
{
|
||||
return launcherVersion;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class LibraryFinder {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static Path libsPath;
|
||||
static Path findLibsPath() {
|
||||
if (libsPath == null) {
|
||||
libsPath = Path.of(System.getProperty("libraryDirectory","crazysnowmannonsense/cheezwhizz"));
|
||||
if (!Files.isDirectory(libsPath)) {
|
||||
throw new IllegalStateException("Missing libraryDirectory system property, cannot continue");
|
||||
}
|
||||
}
|
||||
return libsPath;
|
||||
}
|
||||
|
||||
static Path getForgeLibraryPath(final String mcVersion, final String forgeVersion, final String forgeGroup) {
|
||||
Path forgePath = findLibsPath().resolve(MavenCoordinateResolver.get(forgeGroup, "forge", "", "universal", mcVersion+"-"+forgeVersion));
|
||||
LOGGER.debug(LogMarkers.CORE, "Found forge path {} is {}", forgePath, pathStatus(forgePath));
|
||||
return forgePath;
|
||||
}
|
||||
|
||||
static String pathStatus(final Path path) {
|
||||
return Files.exists(path) ? "present" : "missing";
|
||||
}
|
||||
|
||||
static Path[] getMCPaths(final String mcVersion, final String mcpVersion, final String forgeVersion, final String forgeGroup, final String type) {
|
||||
Path srgMcPath = findLibsPath().resolve(MavenCoordinateResolver.get("net.minecraft", type, "", "srg", mcVersion+"-"+mcpVersion));
|
||||
Path mcExtrasPath = findLibsPath().resolve(MavenCoordinateResolver.get("net.minecraft", type, "", "extra", mcVersion+"-"+mcpVersion));
|
||||
Path patchedBinariesPath = findLibsPath().resolve(MavenCoordinateResolver.get(forgeGroup, "forge", "", type, mcVersion+"-"+forgeVersion));
|
||||
LOGGER.debug(LogMarkers.CORE,"SRG MC at {} is {}", srgMcPath.toString(), pathStatus(srgMcPath));
|
||||
LOGGER.debug(LogMarkers.CORE,"MC Extras at {} is {}", mcExtrasPath.toString(), pathStatus(mcExtrasPath));
|
||||
LOGGER.debug(LogMarkers.CORE,"Forge patches at {} is {}", patchedBinariesPath.toString(), pathStatus(patchedBinariesPath));
|
||||
return new Path[] { patchedBinariesPath, mcExtrasPath, srgMcPath };
|
||||
}
|
||||
|
||||
public static Path findPathForMaven(final String group, final String artifact, final String extension, final String classifier, final String version) {
|
||||
return findLibsPath().resolve(MavenCoordinateResolver.get(group, artifact, extension, classifier, version));
|
||||
}
|
||||
public static Path findPathForMaven(final String maven) {
|
||||
return findLibsPath().resolve(MavenCoordinateResolver.get(maven));
|
||||
}
|
||||
}
|
||||
@@ -5,46 +5,167 @@
|
||||
|
||||
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 org.jetbrains.annotations.Nullable;
|
||||
|
||||
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 in the loading context
|
||||
public sealed interface LoadingModList permits LoadingModListImpl {
|
||||
static ModFileInfo getModFileById(String modId) {
|
||||
return get().fileById().get(modId);
|
||||
/**
|
||||
* Master list of all mods <em>in the loading context. This class cannot refer outside the
|
||||
* loading package</em>
|
||||
*/
|
||||
public class LoadingModList
|
||||
{
|
||||
private static LoadingModList INSTANCE;
|
||||
private final List<ModFileInfo> modFiles;
|
||||
private final List<ModInfo> sortedList;
|
||||
private final Map<String, ModFileInfo> fileById;
|
||||
private final List<EarlyLoadingException> preLoadErrors;
|
||||
private List<IModFile> brokenFiles;
|
||||
|
||||
private LoadingModList(final List<ModFile> modFiles, final List<ModInfo> sortedList)
|
||||
{
|
||||
this.modFiles = modFiles.stream()
|
||||
.map(ModFile::getModFileInfo)
|
||||
.map(ModFileInfo.class::cast)
|
||||
.collect(Collectors.toList());
|
||||
this.sortedList = sortedList.stream()
|
||||
.map(ModInfo.class::cast)
|
||||
.collect(Collectors.toList());
|
||||
this.fileById = this.modFiles.stream()
|
||||
.map(ModFileInfo::getMods)
|
||||
.flatMap(Collection::stream)
|
||||
.map(ModInfo.class::cast)
|
||||
.collect(Collectors.toMap(ModInfo::getModId, ModInfo::getOwningFile));
|
||||
this.preLoadErrors = new ArrayList<>();
|
||||
}
|
||||
|
||||
static List<ModFileInfo> getModFiles() {
|
||||
return get().getModFiles();
|
||||
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;
|
||||
}
|
||||
|
||||
static List<ModInfo> getMods() {
|
||||
return get().getMods();
|
||||
public static LoadingModList get() {
|
||||
return INSTANCE;
|
||||
}
|
||||
public void addCoreMods()
|
||||
{
|
||||
modFiles.stream()
|
||||
.map(ModFileInfo::getFile)
|
||||
.map(ModFile::getCoreMods)
|
||||
.flatMap(List::stream)
|
||||
.forEach(FMLLoader.getCoreModProvider()::addCoreMod);
|
||||
}
|
||||
|
||||
static @Nullable Path findResource(String className) {
|
||||
for (ModFileInfo modFileInfo : get().getModFiles()) {
|
||||
Path resource = modFileInfo.getFile().findResource(className);
|
||||
public void addAccessTransformers()
|
||||
{
|
||||
modFiles.stream()
|
||||
.map(ModFileInfo::getFile)
|
||||
.forEach(mod -> mod.getAccessTransformer().ifPresent(path -> FMLLoader.addAccessTransformer(path, mod)));
|
||||
}
|
||||
|
||||
public void addForScanning(BackgroundScanHandler backgroundScanHandler)
|
||||
{
|
||||
backgroundScanHandler.setLoadingModList(this);
|
||||
modFiles.stream()
|
||||
.map(ModFileInfo::getFile)
|
||||
.forEach(backgroundScanHandler::submitForScanning);
|
||||
}
|
||||
|
||||
public List<ModFileInfo> getModFiles()
|
||||
{
|
||||
return modFiles;
|
||||
}
|
||||
|
||||
public Path findResource(final String className)
|
||||
{
|
||||
for (ModFileInfo mf : modFiles) {
|
||||
final Path resource = mf.getFile().findResource(className);
|
||||
if (Files.exists(resource)) return resource;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<EarlyLoadingException> getErrors() {
|
||||
return get().getErrors();
|
||||
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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static List<IModFile> getBrokenFiles() {
|
||||
return get().getBrokenFiles();
|
||||
public ModFileInfo getModFileById(String modid)
|
||||
{
|
||||
return this.fileById.get(modid);
|
||||
}
|
||||
|
||||
private static LoadingModListImpl get() {
|
||||
return LoadingModListImpl.get();
|
||||
public List<ModInfo> getMods()
|
||||
{
|
||||
return this.sortedList;
|
||||
}
|
||||
|
||||
public List<EarlyLoadingException> getErrors() {
|
||||
return preLoadErrors;
|
||||
}
|
||||
|
||||
public void setBrokenFiles(final List<IModFile> brokenFiles) {
|
||||
this.brokenFiles = brokenFiles;
|
||||
}
|
||||
|
||||
public List<IModFile> getBrokenFiles() {
|
||||
return this.brokenFiles;
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import net.minecraftforge.fml.loading.EarlyLoadingException.ExceptionData;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
|
||||
import net.minecraftforge.forgespi.locating.IModFile;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
|
||||
/**
|
||||
* Master list of all mods <em>in the loading context. This class cannot refer outside the
|
||||
* loading package</em>
|
||||
*/
|
||||
record LoadingModListImpl(
|
||||
List<ModFileInfo> getModFiles,
|
||||
List<ModInfo> getMods,
|
||||
Map<String, ModFileInfo> fileById,
|
||||
ArrayList<EarlyLoadingException> getErrors,
|
||||
ArrayList<IModFile> getBrokenFiles
|
||||
) implements LoadingModList {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static ModSorter.State temp;
|
||||
|
||||
LoadingModListImpl(List<ModFile> modFiles, List<ModInfo> sortedList) {
|
||||
var modFileInfos = modFiles.stream()
|
||||
.map(ModFile::getModFileInfo)
|
||||
.map(ModFileInfo.class::cast)
|
||||
.toList();
|
||||
sortedList = sortedList.stream().toList();
|
||||
var fileById = modFileInfos.stream()
|
||||
.map(ModFileInfo::getMods)
|
||||
.flatMap(Collection::stream)
|
||||
.map(ModInfo.class::cast)
|
||||
.collect(Collectors.toUnmodifiableMap(ModInfo::getModId, ModInfo::getOwningFile));
|
||||
this(modFileInfos, sortedList, fileById, new ArrayList<>(), new ArrayList<>());
|
||||
}
|
||||
|
||||
static LoadingModListImpl init(ModSorter.State modSorterState, EarlyLoadingException earlyLoadingException) {
|
||||
temp = modSorterState;
|
||||
var instance = get();
|
||||
if (earlyLoadingException != null)
|
||||
instance.getErrors.add(earlyLoadingException);
|
||||
temp = null;
|
||||
instance.addAccessTransformers();
|
||||
return instance;
|
||||
}
|
||||
|
||||
static LoadingModListImpl get() {
|
||||
final class LazyInit {
|
||||
private LazyInit() {}
|
||||
private static final LoadingModListImpl INSTANCE
|
||||
= new LoadingModListImpl(LoadingModListImpl.temp.files(), LoadingModListImpl.temp.mods());
|
||||
}
|
||||
return LazyInit.INSTANCE;
|
||||
}
|
||||
|
||||
private void addAccessTransformers() {
|
||||
var errors = new ArrayList<ExceptionData>();
|
||||
|
||||
for (ModFileInfo modFile : getModFiles) {
|
||||
ModFile mod = modFile.getFile();
|
||||
for (var at : mod.getAccessTransformers()) {
|
||||
if (!Files.exists(at)) {
|
||||
var message = "Invalid mod file: " + modFile.getFile().getFileName() + ". Missing Access Transformer: " + at;
|
||||
errors.add(new ExceptionData(message));
|
||||
LOGGER.error(message);
|
||||
} else
|
||||
FMLLoader.addAccessTransformer(at, mod);
|
||||
}
|
||||
}
|
||||
|
||||
if (!errors.isEmpty())
|
||||
getErrors.add(new EarlyLoadingException("Invalid Access Transformers", null, errors));
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ public class MCPNamingService implements INameMappingService {
|
||||
|
||||
@Override
|
||||
public Map.Entry<String, String> understanding() {
|
||||
return Map.entry("srg", "mcp");
|
||||
return Pair.of("srg", "mcp");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -77,7 +77,7 @@ public class MCPNamingService implements INameMappingService {
|
||||
}
|
||||
|
||||
private static void loadMappings(final String mappingFileName, BiConsumer<String, String> mapStore) {
|
||||
URL path = MCPNamingService.class.getClassLoader().getResource(mappingFileName);
|
||||
URL path = ClassLoader.getSystemResource(mappingFileName); //We EXPLICITLY go through the SystemClassLoader here because this is dev-time only. And will be on the root classpath.
|
||||
if (path == null)
|
||||
return;
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ public class MavenCoordinateResolver {
|
||||
(!extension.isEmpty() ? "." + extension : ".jar");
|
||||
|
||||
String[] groups = groupId.split("\\.");
|
||||
Path result = Path.of(groups[0]);
|
||||
Path result = Paths.get(groups[0]);
|
||||
for (int i = 1; i < groups.length; i++) {
|
||||
result = result.resolve(groups[i]);
|
||||
}
|
||||
|
||||
+2
-26
@@ -10,10 +10,7 @@ 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;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -22,9 +19,10 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.zip.ZipFile;
|
||||
import java.util.Set;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public class ModDirTransformerDiscoverer implements ITransformerDiscoveryService {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static final Set<String> SERVICES = Set.of(
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -92,15 +79,4 @@ public class ModDirTransformerDiscoverer implements ITransformerDiscoveryService
|
||||
.filter(SERVICES::contains)
|
||||
.forEach(s -> found.add(new NamedPath(s, path)));
|
||||
}
|
||||
|
||||
public static boolean isServiceProvider(Path path) {
|
||||
var jar = SecureJar.from(path);
|
||||
|
||||
for (var providers : jar.moduleDataProvider().descriptor().provides()) {
|
||||
if (SERVICES.contains(providers.service()))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLStreamHandler;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
|
||||
|
||||
public class ModJarURLHandler extends URLStreamHandler
|
||||
{
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
// modjar://modid/path/to/file
|
||||
@Override
|
||||
protected URLConnection openConnection(URL url) {
|
||||
return new ModJarURLConnection(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int hashCode(final URL u) {
|
||||
return Objects.hash(u.getHost(), u.getFile());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean equals(final URL u1, final URL u2) {
|
||||
return Objects.equals(u1.getProtocol(), u2.getProtocol())
|
||||
&& Objects.equals(u1.getHost(), u2.getHost())
|
||||
&& Objects.equals(u1.getFile(), u2.getFile());
|
||||
}
|
||||
|
||||
static class ModJarURLConnection extends URLConnection {
|
||||
private Path resource;
|
||||
private String modpath;
|
||||
private String modid;
|
||||
private Optional<Manifest> manifest;
|
||||
|
||||
public ModJarURLConnection(final URL url) {
|
||||
super(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect()
|
||||
{
|
||||
// if (resource == null) {
|
||||
// modid = url.getHost();
|
||||
// // trim first char
|
||||
// modpath = url.getPath().substring(1);
|
||||
// resource = FMLLoader.getLoadingModList().getModFileById(modid).getFile().findResource(modpath);
|
||||
// manifest = FMLLoader.getLoadingModList().getModFileById(modid).getManifest();
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException
|
||||
{
|
||||
connect();
|
||||
LOGGER.trace(CORE, "Loading modjar URL {} got resource {} {}", url, resource, resource != null ? Files.exists(resource) : "missing");
|
||||
return Files.newInputStream(resource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getContentLengthLong() {
|
||||
try {
|
||||
connect();
|
||||
return Files.size(resource);
|
||||
} catch (IOException e) {
|
||||
return -1L;
|
||||
}
|
||||
}
|
||||
|
||||
// Used to cache protectiondomains by "top level object" aka the modid
|
||||
@Override
|
||||
public URL getURL() {
|
||||
return LamdbaExceptionUtils.uncheck(()->new URL("modjar://"+modid));
|
||||
}
|
||||
|
||||
public Optional<Manifest> getManifest() {
|
||||
return manifest;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,10 @@ 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 cpw.mods.jarhandling.SecureJar;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator;
|
||||
import net.minecraftforge.forgespi.language.IModFileInfo;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import net.minecraftforge.fml.loading.EarlyLoadingException.ExceptionData;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
|
||||
@@ -19,251 +22,238 @@ import org.apache.maven.artifact.versioning.ArtifactVersion;
|
||||
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
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.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Function;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.util.stream.Collectors.*;
|
||||
import static net.minecraftforge.fml.loading.LogMarkers.LOADING;
|
||||
|
||||
public final 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;
|
||||
|
||||
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 LoadingModListImpl.init(systemMods, e);
|
||||
return LoadingModList.of(ms.systemMods, ms.systemMods.stream().map(mf->(ModInfo)mf.getModInfos().get(0)).collect(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 LoadingModListImpl.init(systemMods, new EarlyLoadingException("failure to validate mod list", null, failedList));
|
||||
return LoadingModList.of(ms.systemMods, ms.systemMods.stream().map(mf->(ModInfo)mf.getModInfos().get(0)).collect(toList()), new EarlyLoadingException("failure to validate mod list", null, failedList));
|
||||
} else {
|
||||
// Otherwise, lets try and sort the modlist and proceed
|
||||
EarlyLoadingException earlyLoadingException = null;
|
||||
try {
|
||||
var sorted = sort(modFiles, named);
|
||||
return LoadingModListImpl.init(sorted, 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 LoadingModListImpl.init(systemMods, 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();
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
private void sort()
|
||||
{
|
||||
// lambdas are identity based, so sorting them is impossible unless you hold reference to them
|
||||
final MutableGraph<ModFileInfo> graph = GraphBuilder.directed().build();
|
||||
AtomicInteger counter = new AtomicInteger();
|
||||
Map<ModFileInfo, Integer> infos = modFiles.stream()
|
||||
.map(ModFile::getModFileInfo)
|
||||
.filter(ModFileInfo.class::isInstance)
|
||||
.map(ModFileInfo.class::cast)
|
||||
.collect(toMap(Function.identity(), e -> counter.incrementAndGet()));
|
||||
infos.keySet().forEach(graph::addNode);
|
||||
modFiles.stream()
|
||||
.map(ModFile::getModInfos)
|
||||
.<IModInfo>mapMulti(Iterable::forEach)
|
||||
.map(IModInfo::getDependencies)
|
||||
.<IModInfo.ModVersion>mapMulti(Iterable::forEach)
|
||||
.forEach(dep -> addDependency(graph, dep));
|
||||
|
||||
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()
|
||||
.<ModFileInfo>mapMulti(Iterable::forEach)
|
||||
.<IModInfo>mapMulti((mf,c)->mf.getMods().forEach(c))
|
||||
.map(IModInfo::getModId)
|
||||
.map(list -> new ExceptionData("fml.modloading.cycle", list))
|
||||
.toList();
|
||||
throw new EarlyLoadingException("Sorting error", e, dataList);
|
||||
}
|
||||
|
||||
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)
|
||||
.<IModInfo>mapMulti(Iterable::forEach)
|
||||
.map(ModInfo.class::cast)
|
||||
.collect(toList());
|
||||
this.modFiles = sorted.stream()
|
||||
.map(ModFileInfo::getFile)
|
||||
.collect(toList());
|
||||
}
|
||||
|
||||
private static State detectSystemMods(final List<ModFile> modFiles) {
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
private void addDependency(MutableGraph<ModFileInfo> topoGraph, IModInfo.ModVersion dep)
|
||||
{
|
||||
final ModFileInfo self = (ModFileInfo)dep.getOwner().getOwningFile();
|
||||
final IModInfo targetModInfo = modIdNameLookup.get(dep.getModId());
|
||||
// soft dep that doesn't exist. Just return. No edge required.
|
||||
if (targetModInfo == null || !(targetModInfo.getOwningFile() instanceof final ModFileInfo target)) return;
|
||||
if (self == target)
|
||||
return; // in case a jar has two mods that have dependencies between
|
||||
switch (dep.getOrdering()) {
|
||||
case BEFORE -> topoGraph.putEdge(self, target);
|
||||
case AFTER -> topoGraph.putEdge(target, self);
|
||||
}
|
||||
}
|
||||
|
||||
private void buildUniqueList()
|
||||
{
|
||||
final UniqueModListBuilder.UniqueModListData uniqueModListData = uniqueModListBuilder.buildUniqueList();
|
||||
|
||||
this.modFiles = uniqueModListData.modFiles();
|
||||
|
||||
detectSystemMods(uniqueModListData.modFilesByFirstId());
|
||||
|
||||
modIdNameLookup = uniqueModListData.modFilesByFirstId().entrySet().stream()
|
||||
.filter(e -> !e.getValue().get(0).getModInfos().isEmpty())
|
||||
.collect(Collectors.toMap(
|
||||
Map.Entry::getKey,
|
||||
e -> e.getValue().get(0).getModInfos().get(0)
|
||||
));
|
||||
}
|
||||
|
||||
private void detectSystemMods(final Map<String, List<ModFile>> modFilesByFirstId)
|
||||
{
|
||||
// Capture system mods (ex. MC, Forge) here, so we can keep them for later
|
||||
var systemMods = List.of("minecraft", "forge");
|
||||
final Set<String> systemMods = new HashSet<>();
|
||||
// The minecraft mod is always a system mod
|
||||
systemMods.add("minecraft");
|
||||
// Find mod file from MinecraftLocator to define the system mods
|
||||
modFiles.stream()
|
||||
.filter(modFile -> modFile.getProvider().getClass() == MinecraftLocator.class)
|
||||
.map(ModFile::getSecureJar)
|
||||
.map(SecureJar::moduleDataProvider)
|
||||
.map(SecureJar.ModuleDataProvider::getManifest)
|
||||
.map(Manifest::getMainAttributes)
|
||||
.map(mf -> mf.getValue("FML-System-Mods"))
|
||||
.filter(Objects::nonNull)
|
||||
.findFirst()
|
||||
.ifPresent(value -> systemMods.addAll(Arrays.asList(value.split(","))));
|
||||
LOGGER.debug("Configured system mods: {}", systemMods);
|
||||
|
||||
var mods = new ArrayList<ModInfo>();
|
||||
var files = new ArrayList<ModFile>();
|
||||
for (var systemMod : systemMods) {
|
||||
var mod = findMod(modFiles, systemMod);
|
||||
if (mod == null)
|
||||
this.systemMods = new ArrayList<>();
|
||||
for (String systemMod : systemMods) {
|
||||
var container = modFilesByFirstId.get(systemMod);
|
||||
if (container != null && !container.isEmpty()) {
|
||||
LOGGER.debug("Found system mod: {}", systemMod);
|
||||
this.systemMods.add((ModFile) container.get(0));
|
||||
} else {
|
||||
throw new IllegalStateException("Failed to find system mod: " + systemMod);
|
||||
|
||||
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)
|
||||
.<IModInfo>mapMulti(Iterable::forEach)
|
||||
.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)
|
||||
.<IModInfo>mapMulti(Iterable::forEach)
|
||||
.collect(groupingBy(Function.identity(), flatMapping(e -> e.getDependencies().stream(), toList())));
|
||||
|
||||
final var modRequirements = modVersionDependencies.values().stream()
|
||||
.<IModInfo.ModVersion>mapMulti(Iterable::forEach)
|
||||
.filter(mv -> mv.getSide().isCorrectSide())
|
||||
.collect(toSet());
|
||||
|
||||
final long mandatoryRequired = modRequirements.stream().filter(IModInfo.ModVersion::isMandatory).count();
|
||||
LOGGER.debug(LOADING, "Found {} mod requirements ({} mandatory, {} optional)", modRequirements.size(), mandatoryRequired, modRequirements.size() - mandatoryRequired);
|
||||
final var missingVersions = modRequirements.stream()
|
||||
.filter(mv -> (mv.isMandatory() || modVersions.containsKey(mv.getModId())) && this.modVersionNotContained(mv, modVersions))
|
||||
.collect(toSet());
|
||||
final long mandatoryMissing = missingVersions.stream().filter(IModInfo.ModVersion::isMandatory).count();
|
||||
LOGGER.debug(LOADING, "Found {} mod requirements missing ({} mandatory, {} optional)", missingVersions.size(), mandatoryMissing, missingVersions.size() - mandatoryMissing);
|
||||
|
||||
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 boolean modVersionNotContained(final IModInfo.ModVersion mv, final Map<String, ArtifactVersion> modVersions)
|
||||
{
|
||||
return !(VersionSupportMatrix.testVersionSupportMatrix(mv.getVersionRange(), mv.getModId(), "mod", (modId, range) -> modVersions.containsKey(modId) &&
|
||||
(range.containsVersion(modVersions.get(modId)) || modVersions.get(modId).toString().equals("0.0NONE"))));
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,14 @@ package net.minecraftforge.fml.loading;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.google.common.collect.Streams;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
@@ -22,21 +28,141 @@ import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||
import org.objectweb.asm.tree.AnnotationNode;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
import org.objectweb.asm.tree.FieldNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.Marker;
|
||||
import org.slf4j.MarkerFactory;
|
||||
|
||||
public 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;
|
||||
private static final String ONLYIN = Type.getDescriptor(OnlyIn.class);
|
||||
private static final String ONLYINS = Type.getDescriptor(OnlyIns.class);
|
||||
@Override
|
||||
public String name()
|
||||
{
|
||||
return "runtimedistcleaner";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "runtimedistcleaner";
|
||||
public int processClassWithFlags(final Phase phase, final ClassNode classNode, final Type classType, final String reason)
|
||||
{
|
||||
AtomicBoolean changes = new AtomicBoolean();
|
||||
if (remove(classNode.visibleAnnotations, DIST))
|
||||
{
|
||||
LOGGER.error(DISTXFORM, "Attempted to load class {} for invalid dist {}", classNode.name, DIST);
|
||||
throw new RuntimeException("Attempted to load class "+ classNode.name + " for invalid dist "+ DIST);
|
||||
}
|
||||
|
||||
if (classNode.interfaces != null )
|
||||
{
|
||||
unpack(classNode.visibleAnnotations).stream()
|
||||
.filter(ann->Objects.equals(ann.desc, ONLYIN))
|
||||
.filter(ann->ann.values.indexOf("_interface") != -1)
|
||||
.filter(ann->!Objects.equals(((String[])ann.values.get(ann.values.indexOf("value") + 1))[1], DIST))
|
||||
.map(ann -> ((Type)ann.values.get(ann.values.indexOf("_interface") + 1)).getInternalName())
|
||||
.forEach(intf -> {
|
||||
if (classNode.interfaces.remove(intf)) {
|
||||
LOGGER.debug(DISTXFORM,"Removing Interface: {} implements {}", classNode.name, intf);
|
||||
changes.compareAndSet(false, true);
|
||||
}
|
||||
});
|
||||
|
||||
//Remove Class level @OnlyIn/@OnlyIns annotations, this is important if anyone gets ambitious and tries to reflect an annotation with _interface set.
|
||||
if (classNode.visibleAnnotations != null) {
|
||||
Iterator<AnnotationNode> itr = classNode.visibleAnnotations.iterator();
|
||||
while (itr.hasNext()) {
|
||||
AnnotationNode ann = itr.next();
|
||||
if (Objects.equals(ann.desc, ONLYIN) || Objects.equals(ann.desc, ONLYINS)) {
|
||||
LOGGER.debug(DISTXFORM,"Removing Class Annotation: {} @{}", classNode.name, ann.desc);
|
||||
itr.remove();
|
||||
changes.compareAndSet(false, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Iterator<FieldNode> fields = classNode.fields.iterator();
|
||||
while(fields.hasNext())
|
||||
{
|
||||
FieldNode field = fields.next();
|
||||
if (remove(field.visibleAnnotations, DIST))
|
||||
{
|
||||
LOGGER.debug(DISTXFORM,"Removing field: {}.{}", classNode.name, field.name);
|
||||
fields.remove();
|
||||
changes.compareAndSet(false, true);
|
||||
}
|
||||
}
|
||||
|
||||
LambdaGatherer lambdaGatherer = new LambdaGatherer();
|
||||
Iterator<MethodNode> methods = classNode.methods.iterator();
|
||||
while(methods.hasNext())
|
||||
{
|
||||
MethodNode method = methods.next();
|
||||
if (remove(method.visibleAnnotations, DIST))
|
||||
{
|
||||
LOGGER.debug(DISTXFORM,"Removing method: {}.{}{}", classNode.name, method.name, method.desc);
|
||||
methods.remove();
|
||||
lambdaGatherer.accept(method);
|
||||
changes.compareAndSet(false, true);
|
||||
}
|
||||
}
|
||||
|
||||
// remove dynamic synthetic lambda methods that are inside of removed methods
|
||||
for (List<Handle> dynamicLambdaHandles = lambdaGatherer.getDynamicLambdaHandles();
|
||||
!dynamicLambdaHandles.isEmpty(); dynamicLambdaHandles = lambdaGatherer.getDynamicLambdaHandles())
|
||||
{
|
||||
lambdaGatherer = new LambdaGatherer();
|
||||
methods = classNode.methods.iterator();
|
||||
while (methods.hasNext())
|
||||
{
|
||||
MethodNode method = methods.next();
|
||||
if ((method.access & Opcodes.ACC_SYNTHETIC) == 0) continue;
|
||||
for (Handle dynamicLambdaHandle : dynamicLambdaHandles)
|
||||
{
|
||||
if (method.name.equals(dynamicLambdaHandle.getName()) && method.desc.equals(dynamicLambdaHandle.getDesc()))
|
||||
{
|
||||
LOGGER.debug(DISTXFORM,"Removing lambda method: {}.{}{}", classNode.name, method.name, method.desc);
|
||||
methods.remove();
|
||||
lambdaGatherer.accept(method);
|
||||
changes.compareAndSet(false, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return changes.get() ? ComputeFlags.SIMPLE_REWRITE : ComputeFlags.NO_REWRITE;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static List<AnnotationNode> unpack(final List<AnnotationNode> anns) {
|
||||
if (anns == null) return Collections.emptyList();
|
||||
List<AnnotationNode> ret = anns.stream().filter(ann->Objects.equals(ann.desc, ONLYIN)).collect(Collectors.toList());
|
||||
anns.stream().filter(ann->Objects.equals(ann.desc, ONLYINS) && ann.values != null)
|
||||
.map( ann -> (List<AnnotationNode>)ann.values.get(ann.values.indexOf("value") + 1))
|
||||
.filter(v -> v != null)
|
||||
.forEach(v -> v.forEach(ret::add));
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static boolean remove(final List<AnnotationNode> anns, final String side)
|
||||
{
|
||||
return unpack(anns).stream().
|
||||
filter(ann->Objects.equals(ann.desc, ONLYIN)).
|
||||
filter(ann->ann.values.indexOf("_interface") == -1).
|
||||
anyMatch(ann -> !Objects.equals(((String[])ann.values.get(ann.values.indexOf("value")+1))[1], side));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Consumer<Dist> getExtension()
|
||||
{
|
||||
return (s)-> {
|
||||
DIST = s.name();
|
||||
LOGGER.debug(DISTXFORM, "Configuring for Dist {}", DIST);
|
||||
};
|
||||
}
|
||||
|
||||
private static final EnumSet<Phase> YAY = EnumSet.of(Phase.AFTER);
|
||||
@@ -48,179 +174,13 @@ public final class RuntimeDistCleaner implements ILaunchPluginService {
|
||||
return NAY;
|
||||
|
||||
String internalName = classType.getInternalName();
|
||||
|
||||
// Only apply to MC classes when on a dedicated server in a production environment
|
||||
if (FMLEnvironment.production)
|
||||
return FMLEnvironment.dist.isDedicatedServer() && isMinecraftClass(internalName) ? YAY : NAY;
|
||||
|
||||
// For dev time it's a bit trickier so that we can throw exceptions when someone uses @OnlyIn in their mod code,
|
||||
// so that it's more obvious that the behaviour they're expecting won't work in production anymore
|
||||
if (internalName.startsWith("net/minecraftforge/"))
|
||||
return NAY;
|
||||
|
||||
// No Vanilla classes use @OnlyIn(Dist.SERVER), so we can skip them entirely when we're running on a client
|
||||
if (FMLEnvironment.dist.isClient() && isMinecraftClass(internalName))
|
||||
return NAY;
|
||||
|
||||
return YAY; // mod classes might have @OnlyIn, so parse them in dev to find out and throw if they do
|
||||
return YAY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int processClassWithFlags(final Phase phase, final ClassNode classNode, final Type classType, final String reason) {
|
||||
var changed = false;
|
||||
|
||||
var annotations = unpack(classNode.visibleAnnotations);
|
||||
var isNonMinecraftClass = !isMinecraftClass(classNode.name);
|
||||
|
||||
if (remove(annotations, DIST)) {
|
||||
LOGGER.error(DISTXFORM, "Attempted to load class {} for invalid dist {}", classNode.name, DIST);
|
||||
throw new RuntimeException("Attempted to load class " + classNode.name + " for invalid dist " + DIST);
|
||||
}
|
||||
|
||||
if (isNonMinecraftClass && !annotations.isEmpty())
|
||||
throw new UnsupportedOperationException("Mod class " + classNode.name + " is annotated with @OnlyIn, this is no longer supported as it slowed down startup times");
|
||||
|
||||
if (classNode.interfaces != null && !classNode.interfaces.isEmpty()) {
|
||||
for (var ann : annotations) {
|
||||
if (ann.intf == null || DIST.equals(ann.side))
|
||||
continue;
|
||||
|
||||
if (classNode.interfaces.remove(ann.intf)) {
|
||||
LOGGER.debug(DISTXFORM, "Removing Interface: {} implements {}", classNode.name, ann.intf);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
//Remove Class level @OnlyIn/@OnlyIns annotations, this is important if anyone gets ambitious and tries to reflect an annotation with _interface set.
|
||||
if (classNode.visibleAnnotations != null) {
|
||||
for (var itr = classNode.visibleAnnotations.iterator(); itr.hasNext(); ) {
|
||||
var ann = itr.next();
|
||||
if (ONLYIN.equals(ann.desc) || ONLYINS.equals(ann.desc)) {
|
||||
LOGGER.debug(DISTXFORM, "Removing Class Annotation: {} @{}", classNode.name, ann.desc);
|
||||
itr.remove();
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var itr = classNode.fields.iterator(); itr.hasNext(); ) {
|
||||
var field = itr.next();
|
||||
if (remove(unpack(field.visibleAnnotations), DIST)) {
|
||||
LOGGER.debug(DISTXFORM, "Removing field: {}.{}", classNode.name, field.name);
|
||||
itr.remove();
|
||||
changed = true;
|
||||
|
||||
if (isNonMinecraftClass)
|
||||
throw new UnsupportedOperationException("Field " + field.name + " in mod class " + classNode.name + " is annotated with @OnlyIn, this is no longer supported as it slowed down startup times");
|
||||
}
|
||||
}
|
||||
|
||||
var lambdaGatherer = new LambdaGatherer();
|
||||
for (var itr = classNode.methods.iterator(); itr.hasNext(); ) {
|
||||
var method = itr.next();
|
||||
if (remove(unpack(method.visibleAnnotations), DIST)) {
|
||||
LOGGER.debug(DISTXFORM, "Removing method: {}.{}{}", classNode.name, method.name, method.desc);
|
||||
itr.remove();
|
||||
lambdaGatherer.accept(method);
|
||||
changed = true;
|
||||
|
||||
if (isNonMinecraftClass)
|
||||
throw new UnsupportedOperationException("Method " + method.name + " in mod class " + classNode.name + " is annotated with @OnlyIn, this is no longer supported as it slowed down startup times");
|
||||
}
|
||||
}
|
||||
|
||||
// remove dynamic synthetic lambda methods that are inside of removed methods
|
||||
for (List<Handle> handles = lambdaGatherer.getDynamicLambdaHandles();
|
||||
!handles.isEmpty(); handles = lambdaGatherer.getDynamicLambdaHandles()) {
|
||||
lambdaGatherer = new LambdaGatherer();
|
||||
for (var itr = classNode.methods.iterator(); itr.hasNext(); ) {
|
||||
MethodNode method = itr.next();
|
||||
if ((method.access & Opcodes.ACC_SYNTHETIC) == 0)
|
||||
continue;
|
||||
|
||||
for (var handle : handles) {
|
||||
if (method.name.equals(handle.getName()) && method.desc.equals(handle.getDesc())) {
|
||||
LOGGER.debug(DISTXFORM, "Removing lambda method: {}.{}{}", classNode.name, method.name, method.desc);
|
||||
itr.remove();
|
||||
lambdaGatherer.accept(method);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return changed ? ComputeFlags.SIMPLE_REWRITE : ComputeFlags.NO_REWRITE;
|
||||
}
|
||||
|
||||
private record Target(String side, String intf) {
|
||||
static Target from(final AnnotationNode node) {
|
||||
var idx = node.values.indexOf("value");
|
||||
var value = (String[])node.values.get(idx + 1); // Enums are stored as [Type, Value]
|
||||
|
||||
idx = node.values.indexOf("_interface");
|
||||
if (idx == -1)
|
||||
return new Target(value[1], null);
|
||||
|
||||
var intf = (Type)node.values.get(idx + 1);
|
||||
return new Target(value[1], intf.getInternalName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpack any OnlyIn or OnlyIns annotations to Target objects.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static List<Target> unpack(final List<AnnotationNode> anns) {
|
||||
if (anns == null || anns.isEmpty())
|
||||
return Collections.emptyList();
|
||||
|
||||
var ret = new ArrayList<Target>();
|
||||
for (var node : anns) {
|
||||
if (ONLYIN.equals(node.desc))
|
||||
ret.add(Target.from(node));
|
||||
else if (ONLYINS.equals(node.desc)) {
|
||||
if (node.values == null || node.values.isEmpty())
|
||||
continue;
|
||||
|
||||
int idx = node.values.indexOf("value");
|
||||
if (idx == -1)
|
||||
continue;
|
||||
|
||||
var subNodes = (List<AnnotationNode>)node.values.get(idx + 1);
|
||||
if (subNodes != null) {
|
||||
for (var sub : subNodes)
|
||||
ret.add(Target.from(sub));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static boolean remove(final List<Target> targets, final String side) {
|
||||
for (var target : targets) {
|
||||
if (target.intf == null && !side.equals(target.side))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isMinecraftClass(final String internalName) {
|
||||
return internalName.startsWith("net/minecraft/") || internalName.startsWith("com/mojang/");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Consumer<Dist> getExtension() {
|
||||
return s -> {
|
||||
DIST = s.name();
|
||||
LOGGER.debug(DISTXFORM, "Configuring for Dist {}", DIST);
|
||||
};
|
||||
}
|
||||
|
||||
private static final class LambdaGatherer extends MethodVisitor {
|
||||
private static class LambdaGatherer extends MethodVisitor {
|
||||
private static final Handle META_FACTORY = new Handle(Opcodes.H_INVOKESTATIC,
|
||||
"java/lang/invoke/LambdaMetafactory", "metafactory",
|
||||
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;",
|
||||
@@ -232,21 +192,23 @@ public final class RuntimeDistCleaner implements ILaunchPluginService {
|
||||
}
|
||||
|
||||
public void accept(MethodNode method) {
|
||||
for (var insn : method.instructions) {
|
||||
if (insn.getType() == AbstractInsnNode.INVOKE_DYNAMIC_INSN)
|
||||
insn.accept(this);
|
||||
}
|
||||
Streams.stream(method.instructions.iterator()).
|
||||
filter(insnNode->insnNode.getType() == AbstractInsnNode.INVOKE_DYNAMIC_INSN).
|
||||
forEach(insnNode->insnNode.accept(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
|
||||
if (META_FACTORY.equals(bsm)) {
|
||||
var handle = (Handle)bsmArgs[1];
|
||||
dynamicLambdaHandles.add(handle);
|
||||
public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs)
|
||||
{
|
||||
if (META_FACTORY.equals(bsm))
|
||||
{
|
||||
Handle dynamicLambdaHandle = (Handle) bsmArgs[1];
|
||||
dynamicLambdaHandles.add(dynamicLambdaHandle);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Handle> getDynamicLambdaHandles() {
|
||||
public List<Handle> getDynamicLambdaHandles()
|
||||
{
|
||||
return dynamicLambdaHandles;
|
||||
}
|
||||
}
|
||||
|
||||
Loaded 100 of 3800 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user