mirror of
https://github.com/MinecraftForge/MinecraftForge.git
synced 2026-11-14 01:04:24 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c71ba862cf | ||
|
|
3c057d277b | ||
|
|
b99abff6ce | ||
|
|
558e7d754e | ||
|
|
d29524bac3 | ||
|
|
929064ee69 | ||
|
|
b4024a7884 | ||
|
|
07f6499143 | ||
|
|
236842f210 | ||
|
|
ef811aebe8 | ||
|
|
d61de63482 | ||
|
|
8368a1d20a | ||
|
|
8b8d3fdef7 | ||
|
|
f5d4fbbee5 | ||
|
|
6f4f60e213 | ||
|
|
01bfc33b28 | ||
|
|
17e1002e86 | ||
|
|
da1b8f4efa | ||
|
|
42bbe44a34 | ||
|
|
f095474adf | ||
|
|
1b5ca64f20 | ||
|
|
b518c804fb | ||
|
|
e50a0ffc6c | ||
|
|
7ea0a41fe2 | ||
|
|
f3d16621c0 | ||
|
|
aa55a6a831 | ||
|
|
3449217c65 | ||
|
|
41609a03d2 | ||
|
|
9b8e5c7109 | ||
|
|
f05ad3afd0 | ||
|
|
6a615f1a4b | ||
|
|
4376cd6b85 | ||
|
|
d17a111dc0 | ||
|
|
1b99b2cea4 | ||
|
|
4ef6695a30 | ||
|
|
0e6341a23e | ||
|
|
17ce723edf | ||
|
|
fc80e30289 | ||
|
|
ecd1491f96 | ||
|
|
8f7fbc76e1 | ||
|
|
ec405958c9 | ||
|
|
e2ff990601 | ||
|
|
8443262e39 | ||
|
|
8e09d47cd6 | ||
|
|
0404d2d776 | ||
|
|
aea804cb6e | ||
|
|
0bffcb83c5 | ||
|
|
9a022d1451 | ||
|
|
58aa3b276c | ||
|
|
b0ccf1a480 | ||
|
|
c1d703e395 | ||
|
|
728fd6ff24 | ||
|
|
0a885a77e7 | ||
|
|
15dbadaae6 | ||
|
|
4c1c3aafdb | ||
|
|
f06727a205 | ||
|
|
ef0e7bc17f | ||
|
|
ec7a098f81 | ||
|
|
ff25961a05 | ||
|
|
c999a2d4eb | ||
|
|
ce79df9716 | ||
|
|
eae9b9145d | ||
|
|
7f56986027 | ||
|
|
c6874bce67 | ||
|
|
aa11175f41 | ||
|
|
2730559ff3 | ||
|
|
e80c3402b7 | ||
|
|
837c259668 | ||
|
|
2e48910b4c | ||
|
|
70dd556db6 | ||
|
|
e44a558f37 | ||
|
|
1e154cbe3e | ||
|
|
d5c5df1b64 | ||
|
|
5e75de5eb7 | ||
|
|
ddb1e52e99 | ||
|
|
9e55363e2c | ||
|
|
219abfed22 | ||
|
|
6915753b5a | ||
|
|
1527738ddc | ||
|
|
18c806f1d0 | ||
|
|
47f7c68ed5 | ||
|
|
5e8942e0a7 | ||
|
|
a02f3f9841 | ||
|
|
c5e462f7d2 | ||
|
|
d1f79d6655 | ||
|
|
9d5e81b1a9 | ||
|
|
a1c275df46 | ||
|
|
4ef7fe8bd8 | ||
|
|
ce8838e721 | ||
|
|
b7e2593d48 | ||
|
|
33d177f02e | ||
|
|
5d2186df63 | ||
|
|
77af36b807 | ||
|
|
54eeca0aed | ||
|
|
3de0566425 | ||
|
|
a5ebf3b645 | ||
|
|
63d0496cc4 | ||
|
|
31f623e794 | ||
|
|
eb001a6382 | ||
|
|
504dcad849 | ||
|
|
81f7f029c3 | ||
|
|
207c1997c9 | ||
|
|
e348ae5b84 | ||
|
|
0f3b8ee830 | ||
|
|
b4b6e48a7a | ||
|
|
d50a3b5ab7 | ||
|
|
88904a1d09 | ||
|
|
b864faf0ef | ||
|
|
c97eabc0c4 | ||
|
|
6ae39c8597 | ||
|
|
dcafb4f633 | ||
|
|
1ea7ce9831 | ||
|
|
674f2efd54 | ||
|
|
02e82c672d | ||
|
|
b25f6e3a7c | ||
|
|
e69b07a54a | ||
|
|
79efe2ac36 | ||
|
|
464483db5b | ||
|
|
f49adb285f | ||
|
|
55862dfd1d | ||
|
|
124b9bd8d3 | ||
|
|
bae3915728 | ||
|
|
b3c2c62c7e | ||
|
|
68698d70c1 | ||
|
|
d55dee9394 | ||
|
|
2d114f2719 | ||
|
|
3e6583af01 | ||
|
|
762f6a7d78 | ||
|
|
3c5a534b7a | ||
|
|
bc62d1693a | ||
|
|
554ead8d4a | ||
|
|
ec5ac91389 | ||
|
|
f63813d929 | ||
|
|
5d4825ac4b | ||
|
|
3024ef7707 | ||
|
|
0d3b488728 | ||
|
|
a01012f20c | ||
|
|
d4e3ef4a22 | ||
|
|
d7293a78dc | ||
|
|
8e4e02e258 | ||
|
|
2f5a6fee48 | ||
|
|
0e74886f8d | ||
|
|
07977ba242 | ||
|
|
a894c244fe | ||
|
|
cf32a799a4 | ||
|
|
0fda1d9393 | ||
|
|
c7f0acbe38 | ||
|
|
9c06c20023 | ||
|
|
041084d1da | ||
|
|
e643d0a11f | ||
|
|
5efb8d3f12 | ||
|
|
70537542d1 | ||
|
|
bd4b059c2b | ||
|
|
e0d2c68c5b | ||
|
|
86d4bda32e | ||
|
|
98b90f26bc | ||
|
|
4e7c471fa3 | ||
|
|
ec6f9fc6e7 | ||
|
|
427fef1d4b | ||
|
|
83594b0b0a | ||
|
|
b76064e57a | ||
|
|
b2bf70c7f1 | ||
|
|
384286a453 | ||
|
|
d93b7db69a | ||
|
|
bdbde4bb0f | ||
|
|
e2a68ad903 | ||
|
|
46cfe50108 | ||
|
|
4b423a3087 | ||
|
|
7d0b07e858 | ||
|
|
40220600b0 | ||
|
|
0c7a0e927c | ||
|
|
60e8157b9a | ||
|
|
b8f5e4553b | ||
|
|
de587eb50a | ||
|
|
086bcb7148 | ||
|
|
c22589b5b3 | ||
|
|
e0fbab3c49 | ||
|
|
7cfed38194 | ||
|
|
b8105ca5a6 | ||
|
|
c3ee633897 | ||
|
|
23b41ce9b3 | ||
|
|
2c6156be25 | ||
|
|
eeb3eb3689 | ||
|
|
44a158a714 | ||
|
|
770298b1d5 | ||
|
|
7bd50903a6 | ||
|
|
64295d04ff | ||
|
|
f168ce4499 | ||
|
|
419c349a1f | ||
|
|
26d0f7282f | ||
|
|
93c72d2d92 | ||
|
|
f252871322 | ||
|
|
32c8597305 | ||
|
|
9714512c31 | ||
|
|
f106ff0c21 | ||
|
|
9c85a6bb64 | ||
|
|
dc594f196a | ||
|
|
db4d8a4729 | ||
|
|
0ca1d0ebd0 | ||
|
|
4995902bc0 | ||
|
|
28e55b2843 | ||
|
|
decaace84a | ||
|
|
72f98dc4a3 | ||
|
|
2951c1dac8 | ||
|
|
40a0a16db0 | ||
|
|
520680b037 | ||
|
|
2844bead6e | ||
|
|
d498ec4a45 | ||
|
|
e2d1ca7d0e | ||
|
|
a547327c61 | ||
|
|
e9a7ce9544 | ||
|
|
511be6415c | ||
|
|
4d85053a4d | ||
|
|
4fcf1877c1 | ||
|
|
41fd15fc57 | ||
|
|
8e0845d032 | ||
|
|
c04e533a61 | ||
|
|
54f4997f71 | ||
|
|
3272e995ab | ||
|
|
8edb551571 | ||
|
|
4bf0de4763 | ||
|
|
f6a54436b9 | ||
|
|
7a497cdbd6 | ||
|
|
06336a40d8 | ||
|
|
842c95cfb9 | ||
|
|
aa2f471e87 | ||
|
|
4209d1fc12 | ||
|
|
583f363b6b | ||
|
|
1c633b4624 | ||
|
|
2677511da6 | ||
|
|
387441c045 | ||
|
|
4a2db246e0 | ||
|
|
3d9e12a71d | ||
|
|
cdeb786877 | ||
|
|
ff7085b95b | ||
|
|
16dde4d551 | ||
|
|
60661350c6 | ||
|
|
7ee4a44c75 | ||
|
|
f22894cf70 | ||
|
|
a3fce36942 | ||
|
|
549dbe7df4 | ||
|
|
5505e472aa | ||
|
|
c2c7f9b656 | ||
|
|
fd8a0f9c8b | ||
|
|
0ac873e296 | ||
|
|
38b5b678cb | ||
|
|
9a2bc1e7c2 | ||
|
|
95b49bedbc | ||
|
|
7c36115e3d | ||
|
|
c037e6aca1 | ||
|
|
e8f1b7ed6d | ||
|
|
5da16e6719 | ||
|
|
f0a82d79a6 | ||
|
|
2b3a4137e1 | ||
|
|
640c206117 | ||
|
|
59a7bbdb57 | ||
|
|
be7ad14b4b | ||
|
|
c700b4ea9d | ||
|
|
dffa3dd4c9 | ||
|
|
4b934809c4 | ||
|
|
ddf76db9b3 | ||
|
|
64d6b4f76a | ||
|
|
58012bfb17 | ||
|
|
0cad6b14d9 | ||
|
|
8848294210 | ||
|
|
0e28336fc0 | ||
|
|
968fa9eeb5 | ||
|
|
43768ea328 | ||
|
|
0e829630da | ||
|
|
0467d71466 | ||
|
|
115eb5e4c8 | ||
|
|
a7345372fd | ||
|
|
bd714b2f76 | ||
|
|
7b782e5b05 |
No files matched your search
@@ -3,6 +3,7 @@
|
||||
*.patch text eol=lf
|
||||
*.java text eol=lf
|
||||
*.gradle text eol=crlf
|
||||
**/MANIFEST.MF text eol=crlf
|
||||
*.png binary
|
||||
*.gif binary
|
||||
*.exe binary
|
||||
|
||||
@@ -10,4 +10,4 @@ assignees: ''
|
||||
<!-- Please note, feature requests that are submitted as GitHub issues here are rarely implemented. -->
|
||||
<!-- If you'd like to see something in Forge, you should be ready to implement it as a PR. -->
|
||||
<!-- If you're not sure, open a Draft PR or discuss it in the Discord server. -->
|
||||
<!-- Discord: https://discord.gg/UvedJ9m -->
|
||||
<!-- Discord: https://discord..minecraftforge.net/ -->
|
||||
@@ -11,6 +11,7 @@ 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}
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
name: Publish
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ '1.21.11' ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
uses: MinecraftForge/SharedActions/.github/workflows/gradle.yml@main
|
||||
with:
|
||||
java: 21
|
||||
gradle_setup: './gradlew setup'
|
||||
gradle_tasks: 'build publish'
|
||||
artifact_name: 'forge'
|
||||
secrets:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
PROMOTE_ARTIFACT_WEBHOOK: ${{ secrets.PROMOTE_ARTIFACT_WEBHOOK }}
|
||||
PROMOTE_ARTIFACT_USERNAME: ${{ secrets.PROMOTE_ARTIFACT_USERNAME }}
|
||||
PROMOTE_ARTIFACT_PASSWORD: ${{ secrets.PROMOTE_ARTIFACT_PASSWORD }}
|
||||
MAVEN_USER: ${{ secrets.MAVEN_USER }}
|
||||
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
|
||||
SIGN_KEY_ALIAS: ${{ secrets.SIGN_KEY_ALIAS }}
|
||||
SIGN_KEY_PASSWORD: ${{ secrets.SIGN_KEY_PASSWORD }}
|
||||
SIGN_KEYSTORE_PASSWORD: ${{ secrets.SIGN_KEYSTORE_PASSWORD }}
|
||||
SIGN_KEYSTORE_DATA: ${{ secrets.SIGN_KEYSTORE_DATA }}
|
||||
@@ -7,7 +7,7 @@ jobs:
|
||||
name: Upload payload
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/upload-artifact@v3 # Payload artifact is consumed in Actionable
|
||||
- uses: actions/upload-artifact@v4 # 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@v3 # Payload artifact is consumed in Actionable
|
||||
- uses: actions/upload-artifact@v4 # Payload artifact is consumed in Actionable
|
||||
with:
|
||||
name: payload
|
||||
path: ${{ github.event_path }}
|
||||
+6
-12
@@ -26,7 +26,6 @@
|
||||
/projects/mcp/
|
||||
/projects/clean/
|
||||
/projects/forge/
|
||||
#/patches/*
|
||||
|
||||
#occupational hazards
|
||||
/projects/**/build/
|
||||
@@ -35,20 +34,15 @@
|
||||
/projects/**/*.launch
|
||||
/repo/
|
||||
src/*/generated/**/.cache/
|
||||
|
||||
# Generated by gradle every import
|
||||
**/src/main/resources/META-INF/MANIFEST.MF
|
||||
/fmlloader/src/main/resources/forge_version.json
|
||||
|
||||
#Patch rejects
|
||||
#/patches-/
|
||||
#*.patch.rej
|
||||
#/projects/*/rejects/
|
||||
|
||||
# FML changelog
|
||||
changelog.txt
|
||||
*.py
|
||||
*.7z
|
||||
/logs/
|
||||
|
||||
/ForgeRoot.ipr
|
||||
/ForgeRoot.iws
|
||||
|
||||
# APT stuff
|
||||
/*/.gitignore
|
||||
/*/.factorypath
|
||||
/*/.apt_generated/
|
||||
Vendored
-104
@@ -1,104 +0,0 @@
|
||||
<?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
-193
@@ -1,193 +0,0 @@
|
||||
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", "17", 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", "7.5.1", 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_GROUP", "net.minecraftforge", label = "Published group", description = "The maven coordinate group that has been published by this build. Can not be empty.", allowEmpty = false)
|
||||
//These are references and not actually keys
|
||||
password("env.CROWDIN_KEY", "credentialsJSON:a3102dbe-805d-4177-9f54-3d2c2eb08fd5", display = ParameterDisplay.HIDDEN)
|
||||
password("env.KEYSTORE_URL", "credentialsJSON:a7ae1c82-8058-4061-8d12-7f6bc2618d2e", display = ParameterDisplay.HIDDEN)
|
||||
password("env.KEYSTORE_PASSWORD", "credentialsJSON:d7b964e3-a1fd-47a8-b892-6f601fe47479", display = ParameterDisplay.HIDDEN)
|
||||
text("additional_publishing_gradle_parameters", "-PcrowdinKey=%env.CROWDIN_KEY% -PkeystoreKeyPass=%env.KEYSTORE_PASSWORD% -PkeystoreStorePass=%env.KEYSTORE_PASSWORD% -Pkeystore=%env.KEYSTORE_URL%", label = "Additional gradle parameters for publish", description = "Contains the additional gradle parameters used during publishing.", display = ParameterDisplay.HIDDEN, allowEmpty = true)
|
||||
|
||||
checkbox("should_execute_build", "false", label = "Should build", description = "Indicates if the build task should be executed.", display = ParameterDisplay.HIDDEN,
|
||||
checked = "true", unchecked = "false")
|
||||
checkbox("should_execute_test", "false", label = "Should build", description = "Indicates if the build task should be executed.", display = ParameterDisplay.HIDDEN,
|
||||
checked = "true", unchecked = "false")
|
||||
}
|
||||
|
||||
features {
|
||||
githubIssues {
|
||||
id = "MinecraftForge_MinecraftForge__IssueTracker"
|
||||
displayName = "MinecraftForge/MinecraftForge"
|
||||
repositoryURL = "https://github.com/MinecraftForge/MinecraftForge"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object Build : BuildType({
|
||||
templates(AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_BuildMainBranches"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"), AbsoluteId("MinecraftForge_BuildUsingGradle"), AbsoluteId("MinecraftForge_PublishProjectUsingGradle"), AbsoluteId("MinecraftForge_TriggersStaticFilesWebpageGenerator"))
|
||||
id("MinecraftForge_MinecraftForge__Build")
|
||||
name = "Build"
|
||||
description = "Builds and Publishes the main branches of the project."
|
||||
})
|
||||
|
||||
object BuildSecondaryBranches : BuildType({
|
||||
templates(AbsoluteId("MinecraftForge_ExcludesBuildingDefaultBranch"), AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_BuildMainBranches"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"), AbsoluteId("MinecraftForge_BuildUsingGradle"))
|
||||
id("MinecraftForge_MinecraftForge__BuildSecondaryBranches")
|
||||
name = "Build - Secondary Branches"
|
||||
description = "Builds and Publishes the secondary branches of the project."
|
||||
params {
|
||||
text("git_branch_spec", """
|
||||
+:refs/heads/(*)
|
||||
-:refs/heads/noci*
|
||||
""".trimIndent(), label = "The branch specification of the repository", description = "By default all main branches are build by the configuration. Modify this value to adapt the branches build.", display = ParameterDisplay.HIDDEN, allowEmpty = true)
|
||||
}
|
||||
vcs {
|
||||
branchFilter = """
|
||||
+:*
|
||||
-:1.*
|
||||
-:<default>
|
||||
""".trimIndent()
|
||||
}
|
||||
})
|
||||
|
||||
object PullRequests : BuildType({
|
||||
templates(AbsoluteId("MinecraftForge_BuildPullRequests"), AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"), AbsoluteId("MinecraftForge_BuildUsingGradle"))
|
||||
id("MinecraftForge_MinecraftForge__PullRequests")
|
||||
name = "Pull Requests"
|
||||
description = "Builds pull requests for the project"
|
||||
|
||||
params {
|
||||
checkbox("should_execute_build", "true", label = "Should build", description = "Indicates if the build task should be executed.", display = ParameterDisplay.HIDDEN,
|
||||
checked = "true", unchecked = "false")
|
||||
text(
|
||||
"gradle_build_task",
|
||||
"assemble",
|
||||
label = "Gradle build task to execute during build",
|
||||
description = "Determines the build task that is executed to build the project.",
|
||||
display = ParameterDisplay.HIDDEN,
|
||||
allowEmpty = false
|
||||
)
|
||||
}
|
||||
|
||||
vcs {
|
||||
branchFilter = """
|
||||
+:*
|
||||
-:1.*
|
||||
-:<default>
|
||||
""".trimIndent()
|
||||
}
|
||||
})
|
||||
|
||||
object PullRequestChecks : BuildType({
|
||||
templates(AbsoluteId("MinecraftForge_BuildPullRequests"), AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"))
|
||||
id("MinecraftForge_MinecraftForge__PullRequestChecks")
|
||||
name = "Pull Requests (Checks)"
|
||||
description = "Checks pull requests for the project"
|
||||
|
||||
steps {
|
||||
gradle {
|
||||
name = "Check"
|
||||
id = "RUNNER_10_Check"
|
||||
|
||||
tasks = "checkAll"
|
||||
gradleParams = "--continue %gradle_custom_args%"
|
||||
enableStacktrace = true
|
||||
dockerImage = "%docker_gradle_image%"
|
||||
dockerRunParameters = """
|
||||
-v "/opt/cache/agent/gradle:/home/gradle/.gradle"
|
||||
-v "/opt/cache/shared/gradle:/home/gradle/rocache:ro"
|
||||
--network=host
|
||||
-u 1000:1000
|
||||
%docker_additional_args%
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
|
||||
vcs {
|
||||
branchFilter = """
|
||||
+:*
|
||||
-:1.*
|
||||
-:<default>
|
||||
""".trimIndent()
|
||||
}
|
||||
})
|
||||
|
||||
object PullRequestCompatibility : BuildType({
|
||||
templates(AbsoluteId("MinecraftForge_BuildPullRequests"), AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"))
|
||||
id("MinecraftForge_MinecraftForge__PullRequestCompatibility")
|
||||
name = "Pull Requests (Compatibility)"
|
||||
description = "Validates binary compatibility for pull requests made to the project"
|
||||
|
||||
steps {
|
||||
gradle {
|
||||
name = "Validate"
|
||||
id = "RUNNER_10_Compatibility"
|
||||
|
||||
tasks = "checkJarCompatibility"
|
||||
gradleParams = "--continue %gradle_custom_args%"
|
||||
enableStacktrace = true
|
||||
dockerImage = "%docker_gradle_image%"
|
||||
dockerRunParameters = """
|
||||
-v "/opt/cache/agent/gradle:/home/gradle/.gradle"
|
||||
-v "/opt/cache/shared/gradle:/home/gradle/rocache:ro"
|
||||
--network=host
|
||||
-u 1000:1000
|
||||
%docker_additional_args%
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
|
||||
vcs {
|
||||
branchFilter = """
|
||||
+:*
|
||||
-:1.*
|
||||
-:<default>
|
||||
""".trimIndent()
|
||||
}
|
||||
})
|
||||
-65
@@ -1,65 +0,0 @@
|
||||
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 its 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 stabilized 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.
|
||||
https://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.
|
||||
https://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.
|
||||
|
||||
+4
-5
@@ -4,11 +4,11 @@ import net.minecraftforge.gradleutils.PomUtils
|
||||
plugins {
|
||||
id 'net.minecraftforge.licenser' version '1.0.1'
|
||||
id 'com.github.ben-manes.versions' version '0.46.0'
|
||||
id 'net.minecraftforge.gradleutils' version '[2.3.0,2.4.0)'
|
||||
id 'net.minecraftforge.gradleutils' version '[2.6.4,2.7)'
|
||||
id 'eclipse'
|
||||
id 'de.undercouch.download' version '5.4.0'
|
||||
id 'net.minecraftforge.gradle.patcher' version '[6.0.16,6.2)' apply false
|
||||
id 'net.minecraftforge.gradle.mcp' version '[6.0.16,6.2)' apply false
|
||||
id 'net.minecraftforge.gradle.patcher' version '[6.0.46,6.2)' apply false
|
||||
id 'net.minecraftforge.gradle.mcp' version '[6.0.46,6.2)' apply false
|
||||
id 'net.minecraftforge.gradlejarsigner' version '1.0.4'
|
||||
id 'org.barfuin.gradle.taskinfo' version '2.1.0'
|
||||
}
|
||||
@@ -16,8 +16,7 @@ plugins {
|
||||
Util.init() //Init all our extension methods!
|
||||
|
||||
ext {
|
||||
GIT_INFO = gradleutils.gitInfo
|
||||
VERSION = gradleutils.getFilteredMCTagOffsetBranchVersion(true, '[0-9]', MC_VERSION)
|
||||
VERSION = gitversion.getMCTagOffsetBranch(MC_VERSION)
|
||||
FORGE_VERSION = VERSION.substring(MC_VERSION.length() + 1)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,10 +4,11 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.ow2.asm:asm:9.6'
|
||||
implementation 'org.ow2.asm:asm-tree:9.6'
|
||||
implementation 'net.minecraftforge:srgutils:0.5.+'
|
||||
implementation 'org.ow2.asm:asm:9.8'
|
||||
implementation 'org.ow2.asm:asm-tree:9.8'
|
||||
implementation 'net.minecraftforge:srgutils:0.5.10'
|
||||
implementation 'net.minecraftforge:JarJarMetadata:0.3.27'
|
||||
implementation 'commons-io:commons-io:2.13.0'
|
||||
implementation 'com.google.code.gson:gson:2.10.1'
|
||||
implementation 'org.eclipse.jgit:org.eclipse.jgit:6.5.0.202303070854-r'
|
||||
implementation 'org.eclipse.jgit:org.eclipse.jgit:6.7.0.202309050840-r'
|
||||
}
|
||||
@@ -10,15 +10,19 @@ import java.util.zip.ZipFile
|
||||
|
||||
abstract class BundleList extends DefaultTask {
|
||||
@InputFiles abstract ConfigurableFileCollection getConfig()
|
||||
@InputFiles abstract ConfigurableFileCollection getConfigExtra()
|
||||
@InputFile abstract RegularFileProperty getServerBundle()
|
||||
@OutputFile abstract RegularFileProperty getOutput()
|
||||
|
||||
BundleList() {
|
||||
config.setFrom(project.configurations.installer)
|
||||
configExtra.setFrom(project.configurations.installerextra)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +36,13 @@ abstract class BundleList extends DefaultTask {
|
||||
entries.put("$info.art.group:$info.art.name", "$dep.file.sha256\t$info.name\t$info.path")
|
||||
}
|
||||
|
||||
resolved = project.configurations.installerextra.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
|
||||
@@ -51,6 +62,10 @@ abstract class BundleList extends DefaultTask {
|
||||
def (group, name, other) = artifact.split(':', 3)
|
||||
//println("Group: $group Name: $name")
|
||||
def key = "$group:$name"
|
||||
if (other.indexOf(':') != -1){
|
||||
key += ':' + other.split(':', 2)[1]
|
||||
//println("\tKey: $key")
|
||||
}
|
||||
if (!entries.containsKey(key))
|
||||
entries.put(key, line)
|
||||
}
|
||||
|
||||
+19
-3
@@ -3,6 +3,7 @@ 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
|
||||
|
||||
@@ -12,13 +13,19 @@ abstract class BytecodePredicateFinder extends BytecodeFinder {
|
||||
@Internal
|
||||
abstract Property<Closure<Boolean>> getPredicate()
|
||||
|
||||
private final Set<String> matches = new HashSet<>()
|
||||
private final Map<String, List<String>> matches = new TreeMap<>(Comparator.naturalOrder())
|
||||
|
||||
@Override
|
||||
protected process(ClassNode parent, MethodNode node) {
|
||||
for (final current : node.instructions) {
|
||||
if (predicate.get().call(parent, node, current)) {
|
||||
matches.add(parent.name)
|
||||
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)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -27,6 +34,15 @@ abstract class BytecodePredicateFinder extends BytecodeFinder {
|
||||
@Internal
|
||||
@Override
|
||||
protected Object getData() {
|
||||
return matches.toSorted(Comparator.<String>naturalOrder()) ?: {throw new RuntimeException('Failed to find any targets, please ensure that method names and descriptors are correct.')}()
|
||||
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.') }()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,415 @@
|
||||
/*
|
||||
* 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 + ']';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ abstract class LauncherJson extends DefaultTask {
|
||||
dependsOn(project.tasks.universalJar)
|
||||
input.from(project.tasks.universalJar.archiveFile)
|
||||
input.from(project.configurations.installer)
|
||||
input.from(project.configurations.installerextra)
|
||||
configure {
|
||||
def mc = project.rootProject.ext.MC_VERSION
|
||||
def forge = project.rootProject.ext.FORGE_VERSION
|
||||
@@ -88,6 +89,7 @@ abstract class LauncherJson extends DefaultTask {
|
||||
]
|
||||
])
|
||||
|
||||
json.libraries.addAll(getArtifacts(project.configurations.installerextra).values())
|
||||
json.libraries.addAll(getArtifacts(project.configurations.installer).values())
|
||||
Files.writeString(output.get().asFile.toPath(), new JsonBuilder(json).toPrettyString())
|
||||
}
|
||||
|
||||
@@ -289,6 +289,7 @@ 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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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", false)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,4 +61,13 @@ eclipse.classpath.file.whenMerged {
|
||||
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'
|
||||
}
|
||||
+191
-161
@@ -12,9 +12,9 @@ import net.minecraftforge.gradle.common.tasks.ApplyBinPatches
|
||||
import net.minecraftforge.gradle.common.tasks.CheckJarCompatibility
|
||||
import net.minecraftforge.gradle.common.tasks.DownloadMavenArtifact
|
||||
import net.minecraftforge.gradle.common.tasks.ExtractInheritance
|
||||
import net.minecraftforge.gradle.patcher.tasks.FilterNewJar
|
||||
import net.minecraftforge.gradle.patcher.tasks.GeneratePatches
|
||||
import net.minecraftforge.gradle.userdev.tasks.RenameJar
|
||||
import net.minecraftforge.gradleutils.PomUtils
|
||||
import org.apache.tools.ant.filters.ReplaceTokens
|
||||
import org.objectweb.asm.Opcodes
|
||||
|
||||
@@ -46,6 +46,8 @@ jarSigner.autoDetect('forge')
|
||||
|
||||
applyPatches {
|
||||
level 'WARNING'
|
||||
failOnError = UPDATING != 'true'
|
||||
rejects = rootProject.layout.projectDirectory.dir('rejects').asFile
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
@@ -69,7 +71,7 @@ sourceSets {
|
||||
}
|
||||
}
|
||||
|
||||
final String SPEC_VERSION = gradleutils.gitInfo.tag
|
||||
final String SPEC_VERSION = gitversion.info.tag
|
||||
|
||||
// The new versioning sceme is <MCVersion>-<ForgeMC>.<RB>.<CommitsSinceRB>
|
||||
// ForgeMC is a unique identifier for every MC version we have supported.
|
||||
@@ -77,7 +79,6 @@ final String SPEC_VERSION = gradleutils.gitInfo.tag
|
||||
final def MCP_ARTIFACT = project(':mcp').mcp.config.get()
|
||||
|
||||
final List<File> EXTRA_TXTS = [
|
||||
rootProject.file('CREDITS.txt'),
|
||||
rootProject.file('LICENSE.txt'),
|
||||
rootProject.tasks.createChangelog.outputFile
|
||||
]
|
||||
@@ -88,15 +89,20 @@ ext {
|
||||
final String MAVEN_PATH = ext.MAVEN_PATH
|
||||
|
||||
final String BINPATCH_TOOL = 'net.minecraftforge:binarypatcher:1.2.0:fatjar'
|
||||
final String INSTALLER_TOOLS = 'net.minecraftforge:installertools:1.4.0'
|
||||
final String INSTALLER_TOOLS = 'net.minecraftforge:installertools:1.4.3'
|
||||
final String FART = 'net.minecraftforge:ForgeAutoRenamingTool:1.0.6'
|
||||
final String S2S_TOOL = 'net.minecraftforge:Srg2Source:8.2.0:fatjar'
|
||||
|
||||
configurations {
|
||||
// Don't pull all libraries, if we're missing something, add it to the installer list so the installer knows to download it.
|
||||
bootstrap { transitive = false }
|
||||
installer {
|
||||
bootstrap { transitive = false }
|
||||
installer {
|
||||
extendsFrom(bootstrap)
|
||||
transitive = false
|
||||
transitive = false
|
||||
}
|
||||
installerextra {
|
||||
extendsFrom(bootstrap)
|
||||
transitive = false
|
||||
}
|
||||
api.extendsFrom(installer)
|
||||
}
|
||||
@@ -106,27 +112,30 @@ dependencies {
|
||||
// And this is only needed because custom file systems are REQUIRED to be on the boot classloader.
|
||||
// This has ASM/BootStrap/Unsafe all because I haven't gotten around to moving UnionFileSystem out to its own project.
|
||||
bootstrap(libs.jarjar.fs) // JarInJar file system
|
||||
bootstrap(libs.roimfs) // JarInJar File System - FileSystems need to be on boot loader.
|
||||
bootstrap(libs.bundles.jimfs) // In memory file system used for ForgeDev launches
|
||||
bootstrap(libs.securemodules) // Has Union file system in it
|
||||
bootstrap(libs.unsafe) // Needed by securemodules
|
||||
bootstrap(libs.bundles.asm) // Needed by securemodules
|
||||
|
||||
|
||||
implementation(libs.jopt.simple)
|
||||
|
||||
installer(libs.bootstrap)
|
||||
installer(libs.bootstrap.api) // Needed by securemodules
|
||||
installer(libs.accesstransformers)
|
||||
installer(libs.antlr.runtime) // Dep of AccessTransformer
|
||||
installer(libs.eventbus)
|
||||
installer(libs.typetools) // Dep of EventBus
|
||||
installer(libs.jspecify) // Dep of EventBus
|
||||
installer(libs.forgespi)
|
||||
installer(libs.coremods)
|
||||
installer(libs.nashorn) // Dep of Coremods
|
||||
installer(libs.coremods.api)
|
||||
installer(libs.modlauncher)
|
||||
installer(libs.mergetool.api)
|
||||
installer(libs.bundles.night.config)
|
||||
installer(libs.maven.artifact)
|
||||
installer(libs.bundles.terminalconsoleappender)
|
||||
installer(libs.mixin)
|
||||
installer(libs.mixinextras.forge)
|
||||
installer(libs.bundles.jarjar)
|
||||
installer(libs.roimfs)
|
||||
|
||||
installer(project(':fmlcore'))
|
||||
installer(project(':fmlloader'))
|
||||
@@ -134,9 +143,24 @@ dependencies {
|
||||
installer(project(':javafmllanguage'))
|
||||
installer(project(':lowcodelanguage'))
|
||||
installer(project(':mclanguage'))
|
||||
installer(project(':forge-transformers'))
|
||||
|
||||
installerextra("${MCP_ARTIFACT.group}:${MCP_ARTIFACT.name}:${MCP_ARTIFACT.version}:srg2off")
|
||||
|
||||
runtimeOnly(libs.bootstrap)
|
||||
runtimeOnly(libs.bootstrap.dev)
|
||||
|
||||
annotationProcessor(libs.eventbus.validator)
|
||||
}
|
||||
|
||||
tasks.named('extractRangeMap').configure {
|
||||
tool = S2S_TOOL
|
||||
}
|
||||
tasks.named('applyRangeMap').configure {
|
||||
tool = S2S_TOOL
|
||||
}
|
||||
tasks.named('applyRangeMapBase').configure {
|
||||
tool = S2S_TOOL
|
||||
}
|
||||
|
||||
patcher {
|
||||
@@ -152,7 +176,7 @@ patcher {
|
||||
|
||||
runs {
|
||||
forge_client {
|
||||
property 'eventbus.checkTypesOnDispatch', 'true'
|
||||
property 'eventbus.api.strictRuntimeChecks', 'true'
|
||||
property 'org.lwjgl.system.SharedLibraryExtractDirectory', 'lwjgl_dll'
|
||||
|
||||
args '--launchTarget', 'forge_dev_client',
|
||||
@@ -167,6 +191,7 @@ patcher {
|
||||
|
||||
forge_client_test {
|
||||
parent runs.forge_client
|
||||
source sourceSets.test
|
||||
}
|
||||
|
||||
forge_server {
|
||||
@@ -175,14 +200,17 @@ patcher {
|
||||
|
||||
forge_server_test {
|
||||
parent runs.forge_server
|
||||
source sourceSets.test
|
||||
}
|
||||
|
||||
forge_server_gametest {
|
||||
args '--launchTarget', 'forge_dev_server_gametest'
|
||||
args '--uniqueWorld' // Unique world is used so that the world regenerates, as well as the world config isn't influenced by other runs
|
||||
}
|
||||
|
||||
forge_server_gametest_test {
|
||||
parent runs.forge_server_gametest
|
||||
source sourceSets.test
|
||||
}
|
||||
|
||||
forge_data {
|
||||
@@ -191,13 +219,13 @@ patcher {
|
||||
'--all',
|
||||
'--validate',
|
||||
'--output', rootProject.file('src/main/generated/'),
|
||||
'--validate',
|
||||
'--existing', sourceSets.main.resources.srcDirs[0],
|
||||
'--assetsDir', downloadAssets.output,
|
||||
'--assetIndex', "{asset_index}"
|
||||
}
|
||||
|
||||
forge_data_test {
|
||||
source sourceSets.test
|
||||
args '--launchTarget', 'forge_dev_data',
|
||||
'--mod', '.+',
|
||||
'--all',
|
||||
@@ -209,6 +237,29 @@ patcher {
|
||||
'--assetIndex', "{asset_index}"
|
||||
}
|
||||
|
||||
forge_client_data {
|
||||
args '--launchTarget', 'forge_dev_client_data',
|
||||
'--mod', 'forge',
|
||||
'--all',
|
||||
'--output', rootProject.file('src/main/generated/'),
|
||||
'--existing', sourceSets.main.resources.srcDirs[0],
|
||||
'--assetsDir', downloadAssets.output,
|
||||
'--assetIndex', "{asset_index}"
|
||||
}
|
||||
|
||||
forge_client_data_test {
|
||||
source sourceSets.test
|
||||
args '--launchTarget', 'forge_dev',
|
||||
'--launchEntry', 'minecraft/net.minecraft.client.data.Main',
|
||||
'--launchData',
|
||||
'--mod', '.+',
|
||||
'--all',
|
||||
'--output', rootProject.file('src/test/generated/'),
|
||||
'--existing', sourceSets.main.resources.srcDirs[0],
|
||||
'--existing', sourceSets.test.resources.srcDirs[0],
|
||||
'--assetsDir', downloadAssets.output,
|
||||
'--assetIndex', "{asset_index}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,11 +301,16 @@ tasks.register('downloadCrowdin', Download) {
|
||||
tasks.userdevConfig.configure {
|
||||
configurations.installer.allDependencies.forEach {
|
||||
def dep = it.toString()
|
||||
if (it instanceof ProjectDependency)
|
||||
if (it instanceof ProjectDependency)
|
||||
dep = "net.minecraftforge:$it.dependencyProject.name:$it.dependencyProject.version"
|
||||
libraries.add(dep)
|
||||
}
|
||||
|
||||
|
||||
// TODO [ForgeDev][UserDev] Split API/Runtime elements in userdev config
|
||||
// so we don't gotta keep making exceptions for MixinExtras
|
||||
addCompileDependency(libs.mixinextras.common)
|
||||
addAnnotationProcessorDependency(libs.mixinextras.common)
|
||||
|
||||
inject = '' // We don't have a userdev sourceset anymore. Empty as a gradle workaround...
|
||||
runs {
|
||||
client {
|
||||
@@ -266,6 +322,13 @@ tasks.userdevConfig.configure {
|
||||
args '--assetsDir', '{assets_root}'
|
||||
}
|
||||
|
||||
clientData {
|
||||
environment 'MCP_MAPPINGS', '{mcp_mappings}'
|
||||
args '--launchTarget', "forge_userdev_client_data"
|
||||
args '--assetIndex', '{asset_index}'
|
||||
args '--assetsDir', '{assets_root}'
|
||||
}
|
||||
|
||||
server {
|
||||
environment 'MCP_MAPPINGS', '{mcp_mappings}'
|
||||
property 'forge.enableGameTest', 'true'
|
||||
@@ -362,33 +425,15 @@ tasks.register('extractInheritance', ExtractInheritance) {
|
||||
Files.readAllLines(rf.asFile.toPath()).stream().map(File::new).toList()
|
||||
}
|
||||
}
|
||||
tasks.register("findFieldInstanceChecks", FieldCompareFinder) {
|
||||
jar = createJoinedSRG.output
|
||||
output = rootProject.file('src/main/resources/coremods/field_to_instanceof.json')
|
||||
fields {
|
||||
/* 1.17 changed most things to use Tags. So this isn't nessasary anymore so just make an empty list
|
||||
bows {
|
||||
cls = 'net/minecraft/world/item/Items'
|
||||
name = 'f_42411_'
|
||||
replacement = 'net/minecraft/world/item/BowItem'
|
||||
}
|
||||
crossbows {
|
||||
cls = 'net/minecraft/world/item/Items'
|
||||
name = 'f_42717_'
|
||||
replacement = 'net/minecraft/world/item/CrossbowItem'
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
tasks.register("findFinalizeSpawnTargets", BytecodePredicateFinder) {
|
||||
jar = createJoinedSRG.output
|
||||
output = rootProject.file('src/main/resources/coremods/finalize_spawn_targets.json')
|
||||
jar = createClientOfficial.output
|
||||
output = rootProject.file('forge-transformers/src/main/resources/coremods/finalize_spawn_targets.json')
|
||||
predicate = {
|
||||
parent, node, insn ->
|
||||
'net/minecraft/world/level/BaseSpawner' != parent.name // Ignore this class as we special case it.
|
||||
&& insn.getOpcode() === Opcodes.INVOKEVIRTUAL
|
||||
&& insn.name == 'm_6518_'
|
||||
&& insn.desc == '(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/MobSpawnType;Lnet/minecraft/world/entity/SpawnGroupData;Lnet/minecraft/nbt/CompoundTag;)Lnet/minecraft/world/entity/SpawnGroupData;'
|
||||
return 'net/minecraft/world/level/BaseSpawner' != parent.name // Ignore this class as we special case it.
|
||||
&& insn.getOpcode().equals(Opcodes.INVOKEVIRTUAL)
|
||||
&& insn.name == 'finalizeSpawn'
|
||||
&& insn.desc == '(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/EntitySpawnReason;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;';
|
||||
}
|
||||
}
|
||||
tasks.register('validateDeprecations', ValidateDeprecations) {
|
||||
@@ -406,16 +451,6 @@ tasks.register("downloadServerShim", DownloadMavenArtifact) {
|
||||
artifact = libs.bootstrap.shim.get().toString()
|
||||
changing = true
|
||||
}
|
||||
tasks.register("downloadMCPConfig", DownloadMavenArtifact) {
|
||||
artifact = MCP_ARTIFACT
|
||||
}
|
||||
tasks.register('extractMCPConfigMappings', net.minecraftforge.gradle.common.tasks.JarExec) {
|
||||
ext.output = file('build/mcp_mappings.tsrg')
|
||||
tool = INSTALLER_TOOLS + ':fatjar'
|
||||
args = ['--task', 'MCP_DATA', '--key', 'mappings', '--input', downloadMCPConfig.output.get().asFile.absolutePath, '--output', output.absolutePath]
|
||||
outputs.file(output)
|
||||
inputs.file(downloadMCPConfig.output)
|
||||
}
|
||||
tasks.register('downloadClientMappings', net.minecraftforge.gradle.common.tasks.JarExec) {
|
||||
ext.output = file('build/client_mappings.tsrg')
|
||||
tool = INSTALLER_TOOLS + ':fatjar'
|
||||
@@ -428,43 +463,34 @@ tasks.register('downloadServerMappings', net.minecraftforge.gradle.common.tasks.
|
||||
args = ['--task', 'DOWNLOAD_MOJMAPS', '--sanitize', '--version', MC_VERSION, '--side', 'server', '--output', output.absolutePath]
|
||||
outputs.file(output)
|
||||
}
|
||||
tasks.register('mergeClientMappings', net.minecraftforge.gradle.common.tasks.JarExec) {
|
||||
ext.output = file('build/client_mappings_merged.tsrg')
|
||||
tool = INSTALLER_TOOLS + ':fatjar'
|
||||
args = ['--task', 'MERGE_MAPPING', '--classes', '--reverse-right', '--left', extractMCPConfigMappings.output.absolutePath, '--right', downloadClientMappings.output.absolutePath, '--output', output.absolutePath]
|
||||
outputs.file(output)
|
||||
}
|
||||
tasks.register('mergeServerMappings', net.minecraftforge.gradle.common.tasks.JarExec) {
|
||||
ext.output = file('build/server_mappings_merged.tsrg')
|
||||
tool = INSTALLER_TOOLS + ':fatjar'
|
||||
args = ['--task', 'MERGE_MAPPING', '--classes', '--reverse-right', '--left', extractMCPConfigMappings.output.absolutePath, '--right', downloadServerMappings.output.absolutePath, '--output', output.absolutePath]
|
||||
outputs.file(output)
|
||||
}
|
||||
tasks.register("createJoinedSRG", DownloadMavenArtifact) {
|
||||
artifact = "net.minecraft:joined:${MC_VERSION}-${MCP_VERSION}:srg"
|
||||
}
|
||||
tasks.register("createClientSRG", RenameJar) {
|
||||
tasks.register("createClientOfficial", RenameJar) {
|
||||
tool = FART + ':all'
|
||||
args = ['--input', '{input}', '--output', '{output}', '--names', '{mappings}', '--ann-fix', '--ids-fix', '--src-fix', '--record-fix', '--strip-sigs']
|
||||
mappings = createMcp2Srg.srg
|
||||
dependsOn(downloadClientRaw)
|
||||
args = ['--input', '{input}', '--output', '{output}', '--names', '{mappings}', '--ann-fix', '--ids-fix', '--src-fix', '--record-fix', '--strip-sigs', '--reverse']
|
||||
mappings = downloadClientMappings.output
|
||||
dependsOn(downloadClientRaw, downloadClientMappings)
|
||||
input = downloadClientRaw.dest
|
||||
output = file("build/createClientSRG/output.jar")
|
||||
output = file("build/createClientOfficial/output.jar")
|
||||
}
|
||||
tasks.register("createServerSRG", RenameJar) {
|
||||
tasks.register("createServerOfficial", RenameJar) {
|
||||
tool = FART + ':all'
|
||||
args = ['--input', '{input}', '--output', '{output}', '--names', '{mappings}', '--ann-fix', '--ids-fix', '--src-fix', '--record-fix', '--strip-sigs']
|
||||
mappings = createMcp2Srg.srg
|
||||
args = ['--input', '{input}', '--output', '{output}', '--names', '{mappings}', '--ann-fix', '--ids-fix', '--src-fix', '--record-fix', '--strip-sigs', '--reverse']
|
||||
mappings = downloadServerMappings.output
|
||||
dependsOn(downloadServerMappings)
|
||||
input = extractServer.output
|
||||
output = file("build/createServerSRG/output.jar")
|
||||
output = file("build/createServerOfficial/output.jar")
|
||||
}
|
||||
tasks.named('genClientBinPatches').configure {
|
||||
tool = BINPATCH_TOOL
|
||||
cleanJar = createClientSRG.output
|
||||
cleanJar = createClientOfficial.output
|
||||
dirtyJar = jar.archiveFile
|
||||
}
|
||||
tasks.named('genServerBinPatches').configure {
|
||||
tool = BINPATCH_TOOL
|
||||
cleanJar = createServerSRG.output
|
||||
cleanJar = createServerOfficial.output
|
||||
dirtyJar = jar.archiveFile
|
||||
}
|
||||
tasks.named('genJoinedBinPatches').configure {
|
||||
tool = BINPATCH_TOOL
|
||||
@@ -472,13 +498,13 @@ tasks.named('genJoinedBinPatches').configure {
|
||||
}
|
||||
tasks.register('applyClientBinPatches', ApplyBinPatches) {
|
||||
tool = BINPATCH_TOOL
|
||||
clean = createClientSRG.output
|
||||
clean = createClientOfficial.output
|
||||
patch = genClientBinPatches.output
|
||||
args.addAll(['--data', '--unpatched'])
|
||||
}
|
||||
tasks.register('applyServerBinPatches', ApplyBinPatches) {
|
||||
tool = BINPATCH_TOOL
|
||||
clean = createServerSRG.output
|
||||
clean = createServerOfficial.output
|
||||
patch = genServerBinPatches.output
|
||||
args.addAll(['--data', '--unpatched'])
|
||||
}
|
||||
@@ -494,10 +520,10 @@ tasks.register('createServerShimClasspath', BundleList) {
|
||||
tasks.register('createServerShimConfig') {
|
||||
ext.output = file('build/libs/bootstrap-shim.properties')
|
||||
doLast {
|
||||
def cfg = new CleanProperties()
|
||||
cfg."Main-Class" = 'net.minecraftforge.bootstrap.ForgeBootstrap'
|
||||
cfg."Java-Version" = '17'
|
||||
cfg."Arguments" = "--launchTarget forge_server"
|
||||
var cfg = new CleanProperties()
|
||||
cfg['Main-Class'] = 'net.minecraftforge.bootstrap.ForgeBootstrap'
|
||||
cfg['Java-Version'] = '21'
|
||||
cfg['Arguments'] = '--launchTarget forge_server'
|
||||
cfg.store(output)
|
||||
}
|
||||
}
|
||||
@@ -512,7 +538,7 @@ tasks.register('serverShimJar', Jar) {
|
||||
from {
|
||||
zipTree(downloadServerShim.output).find { it.name == 'MANIFEST.MF' }
|
||||
}
|
||||
|
||||
|
||||
attributes('Class-Path': configurations.bootstrap.resolvedConfiguration.resolvedArtifacts.collect { "libraries/${Util.getMavenInfoFromDep(it).path}" }.join(' '))
|
||||
}
|
||||
archiveClassifier = 'shim'
|
||||
@@ -523,7 +549,7 @@ tasks.register('checkAll') {
|
||||
group = 'checks'
|
||||
}
|
||||
tasks.register('checkAllAndFix') {
|
||||
dependsOn 'findFinalizeSpawnTargets', 'findFieldInstanceChecks', 'checkLicenses'
|
||||
dependsOn 'findFinalizeSpawnTargets', 'checkLicenses'
|
||||
group = 'checks'
|
||||
}
|
||||
|
||||
@@ -605,7 +631,7 @@ tasks.register('checkJarCompatibility', CheckJarCompatibility) {
|
||||
baseLibraries.from(project.tasks.named('createJoinedSRG').flatMap { it.output })
|
||||
|
||||
inputJar = project.tasks.named('reobfJar').flatMap { it.output }
|
||||
|
||||
|
||||
commonLibraries.from(project.configurations.minecraftImplementation)
|
||||
commonLibraries.from(project.configurations.installer)
|
||||
}
|
||||
@@ -626,26 +652,20 @@ tasks.register('launcherJson', LauncherJson).configure {
|
||||
|
||||
tasks.register('installerJson', InstallerJson) {
|
||||
icon = rootProject.file('icon.ico')
|
||||
|
||||
|
||||
// Json to install into the client's launcher
|
||||
dependsOn(launcherJson)
|
||||
input.from(launcherJson.output)
|
||||
|
||||
// Extract MCP Mappings
|
||||
dependsOn(extractMCPConfigMappings)
|
||||
input.from(extractMCPConfigMappings.output)
|
||||
|
||||
// Download Official Mappings
|
||||
dependsOn(downloadClientMappings, downloadServerMappings)
|
||||
input.from(downloadClientMappings.output, downloadServerMappings.output)
|
||||
// Merge MCP and Official Mappings
|
||||
dependsOn(mergeClientMappings, mergeServerMappings)
|
||||
input.from(mergeClientMappings.output, mergeServerMappings.output)
|
||||
// Get 'base' MC jar, Client is straight download, server is extracted from the bundle
|
||||
dependsOn(downloadClientRaw, extractServer)
|
||||
input.from(downloadClientRaw.dest, extractServer.output)
|
||||
// Rename MC Jar
|
||||
dependsOn(createClientSRG, createServerSRG)
|
||||
input.from(createClientSRG.output, createServerSRG.output)
|
||||
dependsOn(createClientOfficial, createServerOfficial)
|
||||
input.from(createClientOfficial.output, createServerOfficial.output)
|
||||
// Apply Binary patches to vanilla jar
|
||||
dependsOn(applyClientBinPatches, applyServerBinPatches)
|
||||
input.from(applyClientBinPatches.output, applyServerBinPatches.output, genClientBinPatches.toolJar)
|
||||
@@ -663,30 +683,14 @@ tasks.register('installerJson', InstallerJson) {
|
||||
minecraft: MC_VERSION,
|
||||
serverJarPath: '{LIBRARY_DIR}/net/minecraft/server/{MINECRAFT_VERSION}/server-{MINECRAFT_VERSION}-bundled.jar',
|
||||
data: [
|
||||
MAPPINGS: [
|
||||
client: "[${MCP_ARTIFACT.group}:${MCP_ARTIFACT.name}:${MCP_ARTIFACT.version}:mappings@tsrg]",
|
||||
server: "[${MCP_ARTIFACT.group}:${MCP_ARTIFACT.name}:${MCP_ARTIFACT.version}:mappings@tsrg]"
|
||||
],
|
||||
MAPPINGS_SHA: [
|
||||
client: "'${extractMCPConfigMappings.output.sha1}'",
|
||||
server: "'${extractMCPConfigMappings.output.sha1}'"
|
||||
],
|
||||
MOJMAPS: [
|
||||
client: "[net.minecraft:client:${MC_VERSION}-${MCP_VERSION}:mappings@tsrg]",
|
||||
server: "[net.minecraft:server:${MC_VERSION}-${MCP_VERSION}:mappings@tsrg]"
|
||||
client: "[net.minecraft:client:${MC_VERSION}:mappings@tsrg]",
|
||||
server: "[net.minecraft:server:${MC_VERSION}:mappings@tsrg]"
|
||||
],
|
||||
MOJMAPS_SHA: [
|
||||
client: "'${downloadClientMappings.output.sha1}'",
|
||||
server: "'${downloadServerMappings.output.sha1}'"
|
||||
],
|
||||
MERGED_MAPPINGS: [
|
||||
client: "[${MCP_ARTIFACT.group}:${MCP_ARTIFACT.name}:${MCP_ARTIFACT.version}:mappings-merged@tsrg]",
|
||||
server: "[${MCP_ARTIFACT.group}:${MCP_ARTIFACT.name}:${MCP_ARTIFACT.version}:mappings-merged@tsrg]"
|
||||
],
|
||||
MERGED_MAPPINGS_SHA: [
|
||||
client: "'${mergeClientMappings.output.sha1}'",
|
||||
server: "'${mergeServerMappings.output.sha1}'"
|
||||
],
|
||||
MC_UNPACKED: [
|
||||
client: "[net.minecraft:client:${MC_VERSION}]",
|
||||
server: "[net.minecraft:server:${MC_VERSION}:unpacked]"
|
||||
@@ -695,13 +699,13 @@ tasks.register('installerJson', InstallerJson) {
|
||||
client: "'${downloadClientRaw.dest.sha1}'",
|
||||
server: "'${extractServer.output.get().asFile.sha1}'"
|
||||
],
|
||||
MC_SRG: [
|
||||
client: "[net.minecraft:client:${MC_VERSION}-${MCP_VERSION}:srg]",
|
||||
server: "[net.minecraft:server:${MC_VERSION}-${MCP_VERSION}:srg]"
|
||||
MC_OFF: [
|
||||
client: "[net.minecraft:client:${MC_VERSION}:official]",
|
||||
server: "[net.minecraft:server:${MC_VERSION}:official]"
|
||||
],
|
||||
MC_SRG_SHA: [
|
||||
client: "'${createClientSRG.output.get().asFile.sha1}'",
|
||||
server: "'${createServerSRG.output.get().asFile.sha1}'"
|
||||
MC_OFF_SHA: [
|
||||
client: "'${createClientOfficial.output.get().asFile.sha1}'",
|
||||
server: "'${createServerOfficial.output.get().asFile.sha1}'"
|
||||
],
|
||||
BINPATCH: [
|
||||
client: '/data/client.lzma',
|
||||
@@ -768,18 +772,6 @@ tasks.register('installerJson', InstallerJson) {
|
||||
outputs: [
|
||||
'{MC_UNPACKED}': '{MC_UNPACKED_SHA}'
|
||||
]
|
||||
], [
|
||||
jar: INSTALLER_TOOLS,
|
||||
classpath: INSTALLER_TOOLS_CLASSPATH,
|
||||
args: [
|
||||
'--task', 'MCP_DATA',
|
||||
'--input', "[${MCP_ARTIFACT.descriptor}]",
|
||||
'--output', '{MAPPINGS}',
|
||||
'--key', 'mappings'
|
||||
],
|
||||
outputs: [
|
||||
'{MAPPINGS}': '{MAPPINGS_SHA}'
|
||||
]
|
||||
], [
|
||||
jar: INSTALLER_TOOLS,
|
||||
classpath: INSTALLER_TOOLS_CLASSPATH,
|
||||
@@ -793,31 +785,18 @@ tasks.register('installerJson', InstallerJson) {
|
||||
outputs: [
|
||||
'{MOJMAPS}': '{MOJMAPS_SHA}'
|
||||
]
|
||||
], [
|
||||
jar: INSTALLER_TOOLS,
|
||||
classpath: INSTALLER_TOOLS_CLASSPATH,
|
||||
args: [
|
||||
'--task', 'MERGE_MAPPING',
|
||||
'--left', '{MAPPINGS}',
|
||||
'--right', '{MOJMAPS}',
|
||||
'--output', '{MERGED_MAPPINGS}',
|
||||
'--classes', '--reverse-right'
|
||||
],
|
||||
outputs: [
|
||||
'{MERGED_MAPPINGS}': '{MERGED_MAPPINGS_SHA}'
|
||||
]
|
||||
], [
|
||||
sides: ['server'],
|
||||
jar: FART,
|
||||
classpath: getClasspath(project, libs, FART),
|
||||
args: [
|
||||
'--input', '{MC_UNPACKED}',
|
||||
'--output', '{MC_SRG}',
|
||||
'--names', '{MERGED_MAPPINGS}',
|
||||
'--ann-fix', '--ids-fix', '--src-fix', '--record-fix', '--strip-sigs'
|
||||
'--output', '{MC_OFF}',
|
||||
'--names', '{MOJMAPS}',
|
||||
'--ann-fix', '--ids-fix', '--src-fix', '--record-fix', '--strip-sigs', '--reverse'
|
||||
],
|
||||
outputs: [
|
||||
'{MC_SRG}': '{MC_SRG_SHA}'
|
||||
'{MC_OFF}': '{MC_OFF_SHA}'
|
||||
]
|
||||
], [
|
||||
sides: ['client'],
|
||||
@@ -825,18 +804,18 @@ tasks.register('installerJson', InstallerJson) {
|
||||
classpath: getClasspath(project, libs, FART),
|
||||
args: [
|
||||
'--input', '{MINECRAFT_JAR}',
|
||||
'--output', '{MC_SRG}',
|
||||
'--names', '{MERGED_MAPPINGS}',
|
||||
'--ann-fix', '--ids-fix', '--src-fix', '--record-fix', '--strip-sigs'
|
||||
'--output', '{MC_OFF}',
|
||||
'--names', '{MOJMAPS}',
|
||||
'--ann-fix', '--ids-fix', '--src-fix', '--record-fix', '--strip-sigs', '--reverse'
|
||||
],
|
||||
outputs: [
|
||||
'{MC_SRG}': '{MC_SRG_SHA}'
|
||||
'{MC_OFF}': '{MC_OFF_SHA}'
|
||||
]
|
||||
], [
|
||||
jar: BINPATCH_TOOL.rsplit(':', 1)[0], // remove :fatjar
|
||||
classpath: getClasspath(project, libs, BINPATCH_TOOL.rsplit(':', 1)[0]),
|
||||
args: [
|
||||
'--clean', '{MC_SRG}',
|
||||
'--clean', '{MC_OFF}',
|
||||
'--output', '{PATCHED}',
|
||||
'--apply', '{BINPATCH}',
|
||||
'--data', '--unpatched'
|
||||
@@ -851,6 +830,27 @@ tasks.register('installerJson', InstallerJson) {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register('officialClassesOnly', Zip).configure {
|
||||
dependsOn(jar)
|
||||
destinationDirectory = file('build/libs')
|
||||
archiveClassifier = 'official-classes'
|
||||
from zipTree(jar.archiveFile).matching {
|
||||
include '**/*.class'
|
||||
exclude 'mcp/**'
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named('filterJarNew').configure {
|
||||
dependsOn('officialClassesOnly')
|
||||
input = officialClassesOnly.archiveFile
|
||||
}
|
||||
|
||||
tasks.register('filterJarNewSRG', FilterNewJar).configure {
|
||||
input = reobfJar.output
|
||||
srg = filterJarNew.srg
|
||||
blacklist = filterJarNew.blacklist
|
||||
}
|
||||
|
||||
tasks.named('universalJar').configure {
|
||||
dependsOn downloadCrowdin
|
||||
from zipTree(downloadCrowdin.dest).matching {
|
||||
@@ -862,6 +862,9 @@ tasks.named('universalJar').configure {
|
||||
exclude '.cache'
|
||||
|
||||
manifest {
|
||||
attributes([
|
||||
'Automatic-Module-Name': 'net.minecraftforge.forge'
|
||||
] as LinkedHashMap)
|
||||
attributes([
|
||||
'Specification-Title': 'Forge',
|
||||
'Specification-Vendor': 'Forge Development LLC',
|
||||
@@ -882,6 +885,20 @@ tasks.named('universalJar').configure {
|
||||
jarSigner.sign(it)
|
||||
}
|
||||
|
||||
tasks.register('universalJarSrg', Jar).configure {
|
||||
from zipTree(filterJarNewSRG.output)
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
from zipTree(universalJar.archiveFile)
|
||||
destinationDirectory = file('build/libs')
|
||||
archiveClassifier = 'universal-srg'
|
||||
manifest.from(universalJar.manifest)
|
||||
jarSigner.sign(it)
|
||||
}
|
||||
|
||||
tasks.named('userdevConfig').configure {
|
||||
universal = "$project.group:$project.name:$project.version:universal-srg@jar"
|
||||
}
|
||||
|
||||
tasks.register('installerJar', InstallerJar) {
|
||||
fat = !System.env.TEAMCITY_VERSION
|
||||
//offline = true
|
||||
@@ -921,23 +938,31 @@ tasks.register('installerJar', InstallerJar) {
|
||||
jarSigner.sign(it)
|
||||
}
|
||||
|
||||
final mdkGradleWrapper = tasks.register('mdkGradleWrapper', Wrapper) {
|
||||
gradleVersion = '9.3.1'
|
||||
}
|
||||
|
||||
tasks.register('mdkZip', Zip) {
|
||||
dependsOn mdkGradleWrapper
|
||||
|
||||
archiveBaseName = project.name
|
||||
archiveClassifier = 'mdk'
|
||||
archiveVersion = project.version
|
||||
destinationDirectory = file('build/libs')
|
||||
|
||||
from rootProject.file('gradlew')
|
||||
from rootProject.file('gradlew.bat')
|
||||
from mdkGradleWrapper.map(Wrapper.&getScriptFile)
|
||||
from mdkGradleWrapper.map(Wrapper.&getBatchScript)
|
||||
from(EXTRA_TXTS)
|
||||
from(rootProject.file('gradle/')){
|
||||
into('gradle/')
|
||||
into('gradle/wrapper/') {
|
||||
from mdkGradleWrapper.map(Wrapper.&getJarFile)
|
||||
from mdkGradleWrapper.map(Wrapper.&getPropertiesFile)
|
||||
}
|
||||
from(rootProject.file('mdk/')){
|
||||
rootProject.file('mdk/gitignore.txt').eachLine{
|
||||
rootProject.file('mdk/gitignore.txt').eachLine {
|
||||
if (!it.trim().isEmpty() && !it.trim().startsWith('#'))
|
||||
exclude it
|
||||
}
|
||||
|
||||
filter(ReplaceTokens, tokens: [
|
||||
FORGE_VERSION: FORGE_VERSION,
|
||||
FORGE_GROUP: project.group,
|
||||
@@ -946,11 +971,15 @@ tasks.register('mdkZip', Zip) {
|
||||
MAPPING_CHANNEL: MAPPING_CHANNEL,
|
||||
MAPPING_VERSION: MAPPING_VERSION,
|
||||
FORGE_SPEC_VERSION: SPEC_VERSION.split("\\.")[0],
|
||||
MC_NEXT_VERSION: MC_NEXT_VERSION
|
||||
MC_NEXT_VERSION: MC_NEXT_VERSION,
|
||||
EVENTBUS_VERSION: libs.versions.eventbus.get()
|
||||
])
|
||||
rename 'gitignore\\.txt', '.gitignore'
|
||||
rename 'gitattributes\\.txt', '.gitattributes'
|
||||
}
|
||||
from(rootProject.file('src/test/java/com/example/examplemod/')) {
|
||||
into('src/main/java/com/example/examplemod/')
|
||||
}
|
||||
from(rootProject.file('src/test/generated/mdk_datagen/')) {
|
||||
into('src/main/resources/')
|
||||
exclude '**/.cache/'
|
||||
@@ -990,7 +1019,7 @@ if (project.hasProperty('UPDATE_MAPPINGS')) {
|
||||
|
||||
tasks.named('javadoc', Javadoc).configure {
|
||||
description 'Generates the combined javadocs for the FML projects and the main Forge project'
|
||||
var includedProjects = [ ':fmlcore', ':fmlloader', ':javafmllanguage', ':mclanguage' ]
|
||||
var includedProjects = [ ':fmlcore', ':fmlloader', ':javafmllanguage', ':mclanguage', ':forge-transformers' ]
|
||||
source includedProjects.collect { project(it).sourceSets.main.allJava }
|
||||
classpath = classpath + files(includedProjects.collect { project(it).sourceSets.main.compileClasspath })
|
||||
|
||||
@@ -1068,6 +1097,7 @@ tasks.named('javadoc', Javadoc).configure {
|
||||
publishing {
|
||||
publications.register('mavenJava', MavenPublication).configure {
|
||||
artifact universalJar
|
||||
artifact universalJarSrg
|
||||
artifact installerJar
|
||||
artifact mdkZip
|
||||
artifact userdevJar
|
||||
@@ -1079,16 +1109,16 @@ publishing {
|
||||
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
|
||||
gradleutils.pom.setGitHubDetails(pom, 'MinecraftForge')
|
||||
license gradleutils.pom.Licenses.LGPLv2_1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
repositories {
|
||||
maven gradleutils.publishingForgeMaven
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we run bin compat checking during local testing.
|
||||
if (!System.env.MAVEN_USER || !System.env.MAVEN_PASSWORD)
|
||||
if ((!System.env.MAVEN_USER || !System.env.MAVEN_PASSWORD) && CHECK_COMPATIBILITY == "true")
|
||||
tasks.named('publish').configure { dependsOn(':forge:checkJarCompatibility') }
|
||||
@@ -38,12 +38,17 @@ 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
|
||||
|
||||
+8
-2
@@ -2,8 +2,8 @@
|
||||
|
||||
MinecraftForge
|
||||
=============
|
||||
[][Download]
|
||||
[][Download]
|
||||
[][Download] [][Discord] [][Patreon]
|
||||
|
||||
Forge is a free, open-source modding API all of your favourite mods use!
|
||||
@@ -70,6 +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
|
||||
|
||||
@@ -93,3 +98,4 @@ Please ensure you have a valid email address associated with your GitHub account
|
||||
[Pull-Requests]: https://docs.minecraftforge.net/en/latest/forgedev/#making-changes-and-pull-requests
|
||||
|
||||
[Patreon]: https://www.patreon.com/LexManos
|
||||
[YourKit Java Profiler]: https://www.yourkit.com/java/profiler/
|
||||
@@ -0,0 +1,24 @@
|
||||
#!/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
|
||||
@@ -10,9 +10,10 @@ plugins {
|
||||
apply from: rootProject.file('build_shared.gradle')
|
||||
|
||||
dependencies {
|
||||
compileOnly(libs.nulls)
|
||||
compileOnly(libs.jetbrains.annotations)
|
||||
|
||||
api(libs.eventbus)
|
||||
annotationProcessor(libs.eventbus.validator)
|
||||
|
||||
implementation(project(':fmlloader'))
|
||||
implementation(libs.commons.io)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import net.minecraftforge.eventbus.api.bus.BusGroup;
|
||||
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<IEventBus> getForgeBus() {
|
||||
public static Supplier<BusGroup> getForgeBus() {
|
||||
return PROVIDER.getForgeBusSupplier();
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,9 @@ public class DeferredWorkQueue
|
||||
LOGGER.debug(LOADING, "Dispatching synchronous work for work queue {}: {} jobs", modLoadingStage, tasks.size());
|
||||
RuntimeException aggregate = new RuntimeException();
|
||||
Stopwatch timer = Stopwatch.createStarted();
|
||||
tasks.forEach(t -> makeRunnable(t, Runnable::run, aggregate));
|
||||
for (TaskInfo t : tasks) {
|
||||
makeRunnable(t, Runnable::run, aggregate);
|
||||
}
|
||||
timer.stop();
|
||||
if (aggregate.getSuppressed().length > 0) {
|
||||
LOGGER.fatal(
|
||||
@@ -114,7 +116,7 @@ public class DeferredWorkQueue
|
||||
return future;
|
||||
}
|
||||
|
||||
private static class TaskInfo {
|
||||
private static final class TaskInfo {
|
||||
private final ModContainer owner;
|
||||
private Runnable task;
|
||||
private CompletableFuture<?> future;
|
||||
|
||||
@@ -25,7 +25,18 @@ import java.util.function.Supplier;
|
||||
* <li>When you want to call one thing on one side, another thing on the other and return a result {@link #safeRunForDist(Supplier, Supplier)}</li>
|
||||
* <li>When you want to run something on one side {@link #safeRunWhenOn(Dist, Supplier)}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @deprecated Historically, this allowed for safe execution of code on the correct side by exploiting a low-level
|
||||
* detail in Java 8, but in newer versions a simple if check is enough.
|
||||
* <br>
|
||||
* For example:
|
||||
* <pre>{@code
|
||||
* if (FMLEnvironment.dist.isClient()) {
|
||||
* // client code here
|
||||
* }
|
||||
* }</pre>
|
||||
*/
|
||||
@Deprecated(since = "1.21.5", forRemoval = true)
|
||||
public final class DistExecutor
|
||||
{
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import net.minecraftforge.eventbus.api.bus.BusGroup;
|
||||
import net.minecraftforge.fml.config.IConfigEvent;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public interface IBindingsProvider {
|
||||
Supplier<IEventBus> getForgeBusSupplier();
|
||||
Supplier<BusGroup> getForgeBusSupplier();
|
||||
Supplier<I18NParser> getMessageParser();
|
||||
Supplier<IConfigEvent.ConfigConfig> getConfigConfiguration();
|
||||
}
|
||||
@@ -5,8 +5,6 @@
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.fml.loading.progress.ProgressMeter;
|
||||
|
||||
import java.util.Optional;
|
||||
@@ -61,27 +59,27 @@ public interface IModLoadingState {
|
||||
/**
|
||||
* Builds the transition task for this state with a blank pre-sync and post-sync task.
|
||||
*
|
||||
* @param <T> a type of event fired on the mod-specific event bus
|
||||
* @param syncExecutor a synchronous executor
|
||||
* @param parallelExecutor a parallel executor
|
||||
* @param progressBar a progress meter for tracking progress
|
||||
* @return a transition task for this state
|
||||
* @see #buildTransition(Executor, Executor, ProgressMeter, Function, Function)
|
||||
*/
|
||||
default <T extends Event & IModBusEvent>
|
||||
Optional<CompletableFuture<Void>> buildTransition(final Executor syncExecutor,
|
||||
final Executor parallelExecutor,
|
||||
final ProgressMeter progressBar) {
|
||||
default Optional<CompletableFuture<Void>> buildTransition(
|
||||
final Executor syncExecutor,
|
||||
final Executor parallelExecutor,
|
||||
final ProgressMeter progressBar
|
||||
) {
|
||||
return buildTransition(syncExecutor, parallelExecutor, progressBar,
|
||||
e -> CompletableFuture.runAsync(() -> {}, e),
|
||||
e -> CompletableFuture.runAsync(() -> {}, e));
|
||||
e -> CompletableFuture.completedFuture(null),
|
||||
e -> CompletableFuture.completedFuture(null)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -89,7 +87,6 @@ public interface IModLoadingState {
|
||||
* @param postSyncTask a function which returns a task to run after event post-dispatch hook
|
||||
* @return a transition task for this state
|
||||
*/
|
||||
<T extends Event & IModBusEvent>
|
||||
Optional<CompletableFuture<Void>> buildTransition(final Executor syncExecutor,
|
||||
final Executor parallelExecutor,
|
||||
final ProgressMeter progressBar,
|
||||
|
||||
@@ -5,12 +5,9 @@
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.fml.loading.progress.ProgressMeter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.BiFunction;
|
||||
@@ -18,103 +15,67 @@ import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface IModStateTransition {
|
||||
/** Magic value to allow me to optimize the futures list by ignoring the default value without making old methods nullable. */
|
||||
BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> NULL_HOOK = (e, g) -> CompletableFuture.completedFuture(null);
|
||||
|
||||
static IModStateTransition buildNoopTransition() {
|
||||
return new NoopTransition();
|
||||
return ModStateTransitionHelper.NOOP;
|
||||
}
|
||||
|
||||
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 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 BiFunction<ModLoadingStage, Throwable, ModLoadingStage> nextModLoadingStage() {
|
||||
return ModLoadingStage::nextState;
|
||||
}
|
||||
|
||||
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;
|
||||
/**
|
||||
* This used to allow you to fire multiple events during the transition. However, in doing so it would cause issues with the default
|
||||
* ModContainer's event handlers causing issues such as mod classes being constructed multiple times.
|
||||
*/
|
||||
@Deprecated(since = "1.21.3", forRemoval = true)
|
||||
default Supplier<Stream<EventGenerator<?>>> eventFunctionStream() {
|
||||
return () -> Stream.ofNullable(eventFunction());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
default <T extends IModBusEvent> EventGenerator<T> eventFunction() {
|
||||
return null;
|
||||
}
|
||||
|
||||
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 Event & IModBusEvent> extends Function<ModContainer, T> {
|
||||
static <FN extends Event & IModBusEvent> EventGenerator<FN> fromFunction(Function<ModContainer, FN> fn) {
|
||||
/**
|
||||
* I think this was meant as a way to do some things for each mod container beforge/after the transition had been sent to the container.
|
||||
* However, the Future returned by this was never linked to the main transition future in any way. Which means that it was run
|
||||
* in parallel and couldn't guarantee the state of the ModContainer.
|
||||
* <p>
|
||||
* Plus all existing code that I could find returned a completedFuture, so I don't think anyone ever used this.
|
||||
* <p>
|
||||
* If I were to add this back it would be a CompletableFuture wrap(ModContainer, CompletableFuture)
|
||||
* <p>
|
||||
* Added magic value NULL_HOOK to allow me to optimize the futures list by ignoring the default value without making this method nullable.
|
||||
*/
|
||||
@Deprecated(since = "1.21.3", forRemoval = true)
|
||||
default BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> preDispatchHook() { return NULL_HOOK; }
|
||||
@Deprecated(since = "1.21.3", forRemoval = true)
|
||||
default BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> postDispatchHook() { return NULL_HOOK; }
|
||||
|
||||
interface EventGenerator<T extends IModBusEvent> extends Function<ModContainer, T> {
|
||||
static <FN extends IModBusEvent> EventGenerator<FN> fromFunction(Function<ModContainer, FN> fn) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,7 @@ public class InterModComms
|
||||
}
|
||||
}
|
||||
|
||||
private static ConcurrentMap<String, ConcurrentLinkedQueue<IMCMessage>> containerQueues = new ConcurrentHashMap<>();
|
||||
private static final ConcurrentMap<String, ConcurrentLinkedQueue<IMCMessage>> containerQueues = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Send IMC to remote. Sender will default to the active modcontainer, or minecraft if not.
|
||||
@@ -115,16 +115,14 @@ public class InterModComms
|
||||
return getMessages(modId, s->Boolean.TRUE);
|
||||
}
|
||||
|
||||
private static class QueueFilteringSpliterator implements Spliterator<IMCMessage>
|
||||
{
|
||||
private final ConcurrentLinkedQueue<IMCMessage> queue;
|
||||
private final Predicate<String> methodFilter;
|
||||
private final Iterator<IMCMessage> iterator;
|
||||
private record QueueFilteringSpliterator(
|
||||
ConcurrentLinkedQueue<IMCMessage> queue,
|
||||
Predicate<String> methodFilter,
|
||||
Iterator<IMCMessage> iterator
|
||||
) implements Spliterator<IMCMessage> {
|
||||
|
||||
public QueueFilteringSpliterator(final ConcurrentLinkedQueue<IMCMessage> queue, final Predicate<String> methodFilter) {
|
||||
this.queue = queue;
|
||||
this.iterator = queue.iterator();
|
||||
this.methodFilter = methodFilter;
|
||||
this(queue, methodFilter, queue.iterator());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -16,8 +16,7 @@ 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.
|
||||
@@ -27,6 +26,7 @@ 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,16 +41,14 @@ 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,22 +5,19 @@
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.eventbus.api.bus.BusGroup;
|
||||
import net.minecraftforge.fml.config.IConfigEvent;
|
||||
import net.minecraftforge.fml.config.ModConfig;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.fml.loading.progress.ProgressMeter;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
@@ -33,26 +30,24 @@ 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 HashMap<>();
|
||||
protected final Map<ModLoadingStage, Runnable> activityMap = new EnumMap<>(ModLoadingStage.class);
|
||||
protected final Map<Class<? extends IExtensionPoint<?>>, Supplier<?>> extensionPoints = new IdentityHashMap<>();
|
||||
protected final EnumMap<ModConfig.Type, ModConfig> configs = new EnumMap<>(ModConfig.Type.class);
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
final Set<ModContainer> dependencies = new HashSet<>();
|
||||
/**
|
||||
* If you want to handle the event, override {@link #dispatchConfigEvent(IConfigEvent)}
|
||||
*/
|
||||
@Deprecated(since = "1.21.3", forRemoval = true)
|
||||
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;
|
||||
@@ -82,58 +77,35 @@ 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 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()
|
||||
{
|
||||
public IModInfo getModInfo() {
|
||||
return modInfo;
|
||||
}
|
||||
|
||||
@@ -142,8 +114,7 @@ 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);
|
||||
}
|
||||
|
||||
@@ -168,7 +139,10 @@ public abstract class ModContainer
|
||||
}
|
||||
|
||||
public void dispatchConfigEvent(IConfigEvent event) {
|
||||
configHandler.ifPresent(configHandler->configHandler.accept(event));
|
||||
var handler = configHandler.orElse(null);
|
||||
if (handler != null) {
|
||||
handler.accept(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,9 +158,17 @@ 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 Event & IModBusEvent> void acceptEvent(T e) {}
|
||||
protected <T extends IModBusEvent> void acceptEvent(T e) {}
|
||||
}
|
||||
@@ -5,9 +5,6 @@
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.fml.loading.progress.ProgressMeter;
|
||||
import net.minecraftforge.forgespi.language.IModFileInfo;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import net.minecraftforge.forgespi.language.ModFileScanData;
|
||||
@@ -15,27 +12,15 @@ 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.Collections;
|
||||
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;
|
||||
@@ -45,27 +30,25 @@ import java.util.stream.Stream;
|
||||
* Master list of all mods - game-side version. This is classloaded in the game scope and
|
||||
* can dispatch game level events as a result.
|
||||
*/
|
||||
public class ModList
|
||||
{
|
||||
//private static final Logger LOGGER = LogManager.getLogger();
|
||||
public class ModList {
|
||||
private static ModList INSTANCE;
|
||||
private final List<IModFileInfo> modFiles;
|
||||
private final List<IModInfo> sortedList;
|
||||
private final Map<String, ModFileInfo> fileById;
|
||||
private final Map<String, IModFileInfo> fileById;
|
||||
private List<ModContainer> mods;
|
||||
private Map<String, ModContainer> indexedMods;
|
||||
private List<ModFileScanData> modFileScanData;
|
||||
private List<ModContainer> sortedContainers;
|
||||
|
||||
private ModList(final List<ModFile> modFiles, final List<ModInfo> sortedList)
|
||||
{
|
||||
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));
|
||||
private ModList(final List<ModFile> modFiles, final List<ModInfo> sortedList) {
|
||||
var modFileInfos = this.modFiles = modFiles.stream().map(ModFile::getModFileInfo).toList();
|
||||
this.sortedList = sortedList.stream().map(IModInfo.class::cast).toList();
|
||||
var byId = new HashMap<String, IModFileInfo>();
|
||||
for (var file : modFileInfos) {
|
||||
for (var mod : file.getMods())
|
||||
byId.put(mod.getModId(), mod.getOwningFile());
|
||||
}
|
||||
this.fileById = Map.copyOf(byId);
|
||||
CrashReportCallables.registerCrashCallable("Mod List", this::crashReport);
|
||||
}
|
||||
|
||||
@@ -74,19 +57,20 @@ public class ModList
|
||||
}
|
||||
|
||||
private String fileToLine(IModFile mf) {
|
||||
var mainMod = mf.getModInfos().getFirst();
|
||||
return String.format(Locale.ENGLISH, "%-50.50s|%-30.30s|%-30.30s|%-20.20s|%-10.10s|Manifest: %s", mf.getFileName(),
|
||||
mf.getModInfos().get(0).getDisplayName(),
|
||||
mf.getModInfos().get(0).getModId(),
|
||||
mf.getModInfos().get(0).getVersion(),
|
||||
getModContainerState(mf.getModInfos().get(0).getModId()),
|
||||
mainMod.getDisplayName(),
|
||||
mainMod.getModId(),
|
||||
mainMod.getVersion(),
|
||||
getModContainerState(mainMod.getModId()),
|
||||
((ModFileInfo)mf.getModFileInfo()).getCodeSigningFingerprint().orElse("NOSIGNATURE"));
|
||||
}
|
||||
|
||||
private String crashReport() {
|
||||
return "\n"+applyForEachModFile(this::fileToLine).collect(Collectors.joining("\n\t\t", "\t\t", ""));
|
||||
return "\n" + applyForEachModFile(this::fileToLine).collect(Collectors.joining("\n\t\t", "\t\t", ""));
|
||||
}
|
||||
|
||||
public static ModList of(List<ModFile> modFiles, List<ModInfo> sortedList)
|
||||
{
|
||||
public static ModList of(List<ModFile> modFiles, List<ModInfo> sortedList) {
|
||||
INSTANCE = new ModList(modFiles, sortedList);
|
||||
return INSTANCE;
|
||||
}
|
||||
@@ -95,123 +79,59 @@ public class ModList
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public List<IModFileInfo> getModFiles()
|
||||
{
|
||||
public List<IModFileInfo> getModFiles() {
|
||||
return modFiles;
|
||||
}
|
||||
|
||||
public IModFileInfo getModFileById(String modid)
|
||||
{
|
||||
public IModFileInfo getModFileById(String modid) {
|
||||
return this.fileById.get(modid);
|
||||
}
|
||||
|
||||
<T extends Event & IModBusEvent> Function<Executor, CompletableFuture<Void>> futureVisitor(
|
||||
final IModStateTransition.EventGenerator<T> eventGenerator,
|
||||
final ProgressMeter progressBar,
|
||||
final BiFunction<ModLoadingStage, Throwable, ModLoadingStage> stateChange) {
|
||||
return executor -> gather(
|
||||
this.mods.stream()
|
||||
.map(mod -> ModContainer.buildTransitionHandler(mod, eventGenerator, progressBar, stateChange, executor))
|
||||
.toList()
|
||||
).thenComposeAsync(ModList::completableFutureFromExceptionList, executor);
|
||||
}
|
||||
static CompletionStage<Void> completableFutureFromExceptionList(List<? extends Map.Entry<?, Throwable>> t) {
|
||||
if (t.stream().noneMatch(e->e.getValue()!=null)) {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
} else {
|
||||
final List<Throwable> throwables = t.stream().filter(e -> e.getValue() != null).map(Map.Entry::getValue).toList();
|
||||
CompletableFuture<Void> cf = new CompletableFuture<>();
|
||||
final RuntimeException accumulator = new RuntimeException();
|
||||
cf.completeExceptionally(accumulator);
|
||||
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)
|
||||
{
|
||||
void setLoadedMods(final List<ModContainer> modContainers) {
|
||||
this.mods = modContainers;
|
||||
this.sortedContainers = modContainers.stream().sorted(Comparator.comparingInt(c->sortedList.indexOf(c.getModInfo()))).toList();
|
||||
this.sortedContainers = modContainers.stream().sorted(Comparator.comparingInt(c -> sortedList.indexOf(c.getModInfo()))).toList();
|
||||
this.indexedMods = modContainers.stream().collect(Collectors.toMap(ModContainer::getModId, Function.identity()));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Optional<T> getModObjectById(String modId)
|
||||
{
|
||||
public <T> Optional<T> getModObjectById(String modId) {
|
||||
return getModContainerById(modId).map(ModContainer::getMod).map(o -> (T) o);
|
||||
}
|
||||
|
||||
public Optional<? extends ModContainer> getModContainerById(String modId)
|
||||
{
|
||||
public Optional<? extends ModContainer> getModContainerById(String modId) {
|
||||
return Optional.ofNullable(this.indexedMods.get(modId));
|
||||
}
|
||||
|
||||
public Optional<? extends ModContainer> getModContainerByObject(Object obj)
|
||||
{
|
||||
public Optional<? extends ModContainer> getModContainerByObject(Object obj) {
|
||||
return mods.stream().filter(mc -> mc.getMod() == obj).findFirst();
|
||||
}
|
||||
|
||||
public List<IModInfo> getMods()
|
||||
{
|
||||
public List<IModInfo> getMods() {
|
||||
return this.sortedList;
|
||||
}
|
||||
|
||||
public boolean isLoaded(String modTarget)
|
||||
{
|
||||
public boolean isLoaded(String modTarget) {
|
||||
return this.indexedMods.containsKey(modTarget);
|
||||
}
|
||||
|
||||
public int size()
|
||||
{
|
||||
public int size() {
|
||||
return mods.size();
|
||||
}
|
||||
|
||||
public List<ModFileScanData> getAllScanData()
|
||||
{
|
||||
if (modFileScanData == null)
|
||||
{
|
||||
public List<ModFileScanData> getAllScanData() {
|
||||
if (modFileScanData == null) {
|
||||
modFileScanData = this.sortedList.stream().
|
||||
map(IModInfo::getOwningFile).
|
||||
filter(Objects::nonNull).
|
||||
map(IModFileInfo::getFile).
|
||||
distinct().
|
||||
map(IModFile::getScanResult).
|
||||
collect(Collectors.toList());
|
||||
toList();
|
||||
}
|
||||
return modFileScanData;
|
||||
|
||||
}
|
||||
|
||||
public void forEachModFile(Consumer<IModFile> fileConsumer)
|
||||
{
|
||||
public void forEachModFile(Consumer<IModFile> fileConsumer) {
|
||||
modFiles.stream().map(IModFileInfo::getFile).forEach(fileConsumer);
|
||||
}
|
||||
|
||||
@@ -227,13 +147,11 @@ public class ModList
|
||||
this.sortedContainers.forEach(containerConsumer);
|
||||
}
|
||||
|
||||
public List<ModContainer> getLoadedMods() {
|
||||
return this.sortedContainers;
|
||||
}
|
||||
|
||||
public <T> Stream<T> applyForEachModContainer(Function<ModContainer, T> function) {
|
||||
return indexedMods.values().stream().map(function);
|
||||
}
|
||||
|
||||
private static class UncaughtModLoadingException extends ModLoadingException {
|
||||
public UncaughtModLoadingException(ModLoadingStage stage, Throwable originalException) {
|
||||
super(null, stage, "fml.modloading.uncaughterror", originalException);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,7 @@
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Streams;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.eventbus.api.bus.BusGroup;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.fml.loading.FMLEnvironment;
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
@@ -33,8 +32,6 @@ import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static net.minecraftforge.fml.Logging.CORE;
|
||||
import static net.minecraftforge.fml.Logging.LOADING;
|
||||
|
||||
@@ -74,60 +71,68 @@ import static net.minecraftforge.fml.Logging.LOADING;
|
||||
* and completes the mod loading sequence.</dd>
|
||||
* </dl>
|
||||
*/
|
||||
public class ModLoader
|
||||
{
|
||||
public class ModLoader {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
private static ModLoader INSTANCE;
|
||||
private final LoadingModList loadingModList;
|
||||
|
||||
private final List<ModLoadingException> loadingExceptions;
|
||||
private final Set<IModInfo> erroredModInfos;
|
||||
private final List<ModLoadingWarning> loadingWarnings;
|
||||
private final ModStateManager stateManager;
|
||||
private boolean loadingStateValid;
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
private final Optional<Consumer<String>> statusConsumer = StartupNotificationManager.modLoaderConsumer();
|
||||
private static boolean loadingStateValid;
|
||||
private final Consumer<String> statusConsumer = StartupNotificationManager.modLoaderConsumer().orElse(msg -> {});
|
||||
private final Set<IModLoadingState> completedStates = new HashSet<>();
|
||||
private ModList modList;
|
||||
|
||||
private ModLoader()
|
||||
{
|
||||
INSTANCE = this;
|
||||
this.loadingModList = FMLLoader.getLoadingModList();
|
||||
this.loadingExceptions = this.loadingModList.getErrors().stream()
|
||||
private ModLoader() {
|
||||
var loadingModList = this.loadingModList = FMLLoader.getLoadingModList();
|
||||
var loadingExceptions = this.loadingExceptions = loadingModList.getErrors().stream()
|
||||
.flatMap(ModLoadingException::fromEarlyException)
|
||||
.collect(Collectors.toList());
|
||||
this.loadingWarnings = this.loadingModList.getBrokenFiles().stream()
|
||||
this.loadingWarnings = loadingModList.getBrokenFiles().stream()
|
||||
.map(file -> new ModLoadingWarning(null, ModLoadingStage.VALIDATE, InvalidModIdentifier.identifyJarProblem(file.getFilePath()).orElse("fml.modloading.brokenfile"), file.getFileName()))
|
||||
.collect(Collectors.toList());
|
||||
this.loadingModList.getModFiles().stream()
|
||||
if (loadingExceptions.isEmpty()) {
|
||||
this.erroredModInfos = Collections.emptySet();
|
||||
} else {
|
||||
this.erroredModInfos = Collections.newSetFromMap(new IdentityHashMap<>());
|
||||
this.erroredModInfos.addAll(loadingExceptions.stream().map(ModLoadingException::getModInfo).toList());
|
||||
}
|
||||
loadingModList.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
|
||||
.filter(modFileInfo -> modFileInfo.getMods().stream().noneMatch(this.erroredModInfos::contains)) //Ignore files where any other mod already encountered an error
|
||||
.map(modFileInfo -> new ModLoadingException(null, ModLoadingStage.VALIDATE, "fml.modloading.missinglicense", null, modFileInfo.getFile()))
|
||||
.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);
|
||||
CrashReportCallables.registerCrashCallable("ModLauncher services", ModLoader::computeModLauncherServiceList);
|
||||
CrashReportCallables.registerCrashCallable("FML Language Providers", ModLoader::computeLanguageList);
|
||||
}
|
||||
|
||||
private String computeLanguageList() {
|
||||
return "\n"+FMLLoader.getLanguageLoadingProvider().applyForEach(lp->lp.name() +"@"+ lp.getClass().getPackage().getImplementationVersion()).collect(Collectors.joining("\n\t\t", "\t\t", ""));
|
||||
private static 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() {
|
||||
private static String computeModLauncherServiceList() {
|
||||
final List<Map<String, String>> mods = FMLLoader.modLauncherModList();
|
||||
return "\n"+mods.stream().map(mod->mod.getOrDefault("file","nofile")+
|
||||
" "+mod.getOrDefault("name", "missing")+
|
||||
" "+mod.getOrDefault("type","NOTYPE")+
|
||||
" "+mod.getOrDefault("description", "")).
|
||||
return "\n" + mods.stream().map(mod -> mod.getOrDefault("file","nofile") +
|
||||
' ' + mod.getOrDefault("name", "missing") +
|
||||
' ' + mod.getOrDefault("type","NOTYPE") +
|
||||
' ' + mod.getOrDefault("description", "")).
|
||||
collect(Collectors.joining("\n\t\t","\t\t",""));
|
||||
}
|
||||
|
||||
public static ModLoader get()
|
||||
{
|
||||
return INSTANCE == null ? INSTANCE = new ModLoader() : INSTANCE;
|
||||
public static ModLoader get() {
|
||||
return LazyInit.INSTANCE;
|
||||
}
|
||||
|
||||
private static final class LazyInit {
|
||||
private static final ModLoader INSTANCE = new ModLoader();
|
||||
private LazyInit() {}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -144,21 +149,23 @@ public class ModLoader
|
||||
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"));
|
||||
LOGGER.fatal(CORE, "Error during pre-loading phase", loadingExceptions.getFirst());
|
||||
statusConsumer.accept("ERROR DURING MOD LOADING");
|
||||
modList.setLoadedMods(Collections.emptyList());
|
||||
loadingStateValid = false;
|
||||
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();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if (!failedBounds.isEmpty()) {
|
||||
LOGGER.fatal(CORE, "Failed to validate feature bounds for mods: {}", failedBounds);
|
||||
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
|
||||
statusConsumer.accept("ERROR DURING MOD LOADING");
|
||||
modList.setLoadedMods(Collections.emptyList());
|
||||
loadingStateValid = false;
|
||||
throw new LoadingFailedException(failedBounds.stream()
|
||||
@@ -166,38 +173,79 @@ public class ModLoader
|
||||
.toList());
|
||||
}
|
||||
|
||||
final List<ModContainer> modContainers = loadingModList.getModFiles().stream()
|
||||
.map(ModFileInfo::getFile)
|
||||
.map(this::buildMods)
|
||||
.<ModContainer>mapMulti(Iterable::forEach)
|
||||
.toList();
|
||||
final var modContainers = new HashMap<String, ModContainer>();
|
||||
for (var file : loadingModList.getModFiles()) {
|
||||
var containers = this.buildMods(file.getFile());
|
||||
for (var container : containers)
|
||||
modContainers.put(container.getModId(), container);
|
||||
}
|
||||
|
||||
if (!loadingExceptions.isEmpty()) {
|
||||
LOGGER.fatal(CORE, "Failed to initialize mod containers", loadingExceptions.get(0));
|
||||
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
|
||||
LOGGER.fatal(CORE, "Failed to initialize mod containers", loadingExceptions.getFirst());
|
||||
statusConsumer.accept("ERROR DURING MOD LOADING");
|
||||
modList.setLoadedMods(Collections.emptyList());
|
||||
loadingStateValid = false;
|
||||
throw new LoadingFailedException(loadingExceptions);
|
||||
}
|
||||
modList.setLoadedMods(modContainers);
|
||||
|
||||
// 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());
|
||||
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));
|
||||
var progress = StartupMessageManager.addProgressBar("Mod Gather", stateList.stream().mapToInt(mls -> mls.size().applyAsInt(this.modList)).sum());
|
||||
for (IModLoadingState mls : stateList) {
|
||||
dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress);
|
||||
}
|
||||
progress.complete();
|
||||
}
|
||||
|
||||
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));
|
||||
for (IModLoadingState mls : stateList) {
|
||||
dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress);
|
||||
}
|
||||
progress.complete();
|
||||
}
|
||||
|
||||
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())));
|
||||
for (IModLoadingState mls : stateList) {
|
||||
dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress);
|
||||
}
|
||||
|
||||
var modBusGroups = Set.of(
|
||||
modList.getLoadedMods().stream()
|
||||
.map(ModContainer::getModBusGroup)
|
||||
.filter(Objects::nonNull)
|
||||
.toArray(BusGroup[]::new)
|
||||
);
|
||||
for (var modBusGroup : modBusGroups) {
|
||||
parallelExecutor.execute(modBusGroup::trim);
|
||||
}
|
||||
parallelExecutor.execute(BusGroup.DEFAULT::trim);
|
||||
|
||||
statusConsumer.accept("Mod loading complete - %d mods loaded".formatted(this.modList.size()));
|
||||
progress.complete();
|
||||
}
|
||||
|
||||
@@ -206,25 +254,32 @@ public class ModLoader
|
||||
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);
|
||||
state.inlineRunnable().ifPresent(a->this.handleInlineTransition(a, state, syncExecutor, ticker));
|
||||
state.buildTransition(syncExecutor, parallelExecutor, progressBar).ifPresent(t->waitForTransition(state, syncExecutor, ticker, t));
|
||||
|
||||
var inlineRunnable = state.inlineRunnable().orElse(null);
|
||||
if (inlineRunnable != null) handleInlineTransition(inlineRunnable, state, syncExecutor, ticker);
|
||||
|
||||
var transition = state.buildTransition(syncExecutor, parallelExecutor, progressBar).orElse(null);
|
||||
if (transition != null) waitForTransition(state, syncExecutor, ticker, transition);
|
||||
|
||||
completedStates.add(state);
|
||||
}
|
||||
|
||||
private void handleInlineTransition(final Consumer<ModList> transition, final IModLoadingState state, final ModWorkManager.DrivenExecutor syncExecutor, final Runnable ticker) {
|
||||
var pb = StartupMessageManager.addProgressBar("State transition " +state.name()+" running", 0);
|
||||
var pb = StartupMessageManager.addProgressBar("State transition " + state.name() + " running", 0);
|
||||
syncExecutor.drive(ticker);
|
||||
transition.accept(this.modList);
|
||||
syncExecutor.drive(ticker);
|
||||
pb.complete();
|
||||
syncExecutor.drive(ticker);
|
||||
}
|
||||
|
||||
private void waitForTransition(final IModLoadingState state, final ModWorkManager.DrivenExecutor syncExecutor, final Runnable ticker, final CompletableFuture<Void> transition) {
|
||||
while (!transition.isDone()) {
|
||||
syncExecutor.drive(ticker);
|
||||
}
|
||||
|
||||
try {
|
||||
transition.join();
|
||||
} catch (CompletionException e) {
|
||||
@@ -234,36 +289,38 @@ public class ModLoader
|
||||
.anyMatch(obj -> !(obj instanceof ModLoadingException));
|
||||
if (hasNotModLoadingEx) {
|
||||
LOGGER.fatal("Encountered non-modloading exceptions!", e);
|
||||
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
|
||||
statusConsumer.accept("ERROR DURING MOD LOADING");
|
||||
throw e;
|
||||
}
|
||||
|
||||
final List<ModLoadingException> modLoadingExceptions = Arrays.stream(t.getSuppressed())
|
||||
.filter(ModLoadingException.class::isInstance)
|
||||
.map(ModLoadingException.class::cast)
|
||||
.collect(Collectors.toList());
|
||||
.toList();
|
||||
LOGGER.fatal(LOADING,"Failed to complete lifecycle event {}, {} errors found", state.name(), modLoadingExceptions.size());
|
||||
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
|
||||
statusConsumer.accept("ERROR DURING MOD LOADING");
|
||||
throw new LoadingFailedException(modLoadingExceptions);
|
||||
}
|
||||
}
|
||||
|
||||
private List<ModContainer> buildMods(final IModFile modFile)
|
||||
{
|
||||
final Map<String, IModInfo> modInfoMap = modFile.getModFileInfo().getMods().stream().collect(Collectors.toMap(IModInfo::getModId, Function.identity()));
|
||||
private List<ModContainer> buildMods(final IModFile modFile) {
|
||||
final Map<String, IModInfo> modInfoMap = new HashMap<>();
|
||||
for (var mod : modFile.getModFileInfo().getMods())
|
||||
modInfoMap.put(mod.getModId(), mod);
|
||||
|
||||
LOGGER.trace(LOADING, "ModContainer is {}", ModContainer.class.getClassLoader());
|
||||
final List<ModContainer> containers = modFile.getScanResult().getTargets()
|
||||
.entrySet()
|
||||
.stream()
|
||||
.map(e -> buildModContainerFromTOML(modFile, modInfoMap, e))
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
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);
|
||||
}
|
||||
|
||||
if (containers.size() != modInfoMap.size()) {
|
||||
var modIds = modInfoMap.values().stream().map(IModInfo::getModId).sorted().toList();
|
||||
var containerIds = containers.stream().map(c -> c != null ? c.getModId() : "(null)").sorted().toList();
|
||||
|
||||
LOGGER.fatal(LOADING,"File {} constructed {} mods: {}, but had {} mods specified: {}",
|
||||
LOGGER.fatal(LOADING, "File {} constructed {} mods: {}, but had {} mods specified: {}",
|
||||
modFile.getFilePath(),
|
||||
containers.size(), containerIds,
|
||||
modInfoMap.size(), modIds);
|
||||
@@ -278,17 +335,20 @@ public class ModLoader
|
||||
|
||||
loadingExceptions.add(new ModLoadingException(null, ModLoadingStage.CONSTRUCT, "fml.modloading.missingclasses", null, modFile.getFilePath()));
|
||||
}
|
||||
|
||||
// remove errored mod containers
|
||||
return containers.stream().filter(mc -> mc.modLoadingStage != ModLoadingStage.ERROR).toList();
|
||||
}
|
||||
|
||||
private ModContainer buildModContainerFromTOML(final IModFile modFile, final Map<String, IModInfo> modInfoMap, final Map.Entry<String, ? extends IModLanguageProvider.IModLanguageLoader> idToProviderEntry) {
|
||||
private ModContainer buildModContainerFromTOML(final IModFile modFile, final Map<String, IModInfo> modInfoMap,
|
||||
final String modId, final IModLanguageProvider.IModLanguageLoader languageLoader
|
||||
) {
|
||||
try {
|
||||
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));
|
||||
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);
|
||||
|
||||
return languageLoader.loadMod(info, modFile.getScanResult(), FMLLoader.getGameLayer());
|
||||
} catch (ModLoadingException mle) {
|
||||
// exceptions are caught and added to the error list for later handling
|
||||
@@ -303,59 +363,73 @@ public 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 get().loadingStateValid;
|
||||
return loadingStateValid;
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link #hasCompletedState(IModLoadingState)} instead */
|
||||
@Deprecated(forRemoval = true)
|
||||
public boolean hasCompletedState(final String stateName) {
|
||||
IModLoadingState state = stateManager.findState(stateName);
|
||||
return completedStates.contains(state);
|
||||
}
|
||||
|
||||
public <T extends Event & IModBusEvent> void runEventGenerator(Function<ModContainer, T> generator) {
|
||||
public boolean hasCompletedState(IModLoadingState state) {
|
||||
return completedStates.contains(state);
|
||||
}
|
||||
|
||||
public static <T extends IModBusEvent> void runEventGenerator(Function<ModContainer, T> generator) {
|
||||
if (!loadingStateValid) {
|
||||
LOGGER.error("Cowardly refusing to send event generator to a broken mod state");
|
||||
return;
|
||||
}
|
||||
ModList.get().forEachModInOrder(mc -> mc.acceptEvent(generator.apply(mc)));
|
||||
for (var mod : ModList.get().getLoadedMods())
|
||||
mod.acceptEvent(generator.apply(mod));
|
||||
}
|
||||
|
||||
public <T extends Event & IModBusEvent> void postEvent(T e) {
|
||||
public static <T extends IModBusEvent> void postEvent(T e) {
|
||||
if (!loadingStateValid) {
|
||||
LOGGER.error("Cowardly refusing to send event {} to a broken mod state", e.getClass().getName());
|
||||
return;
|
||||
}
|
||||
ModList.get().forEachModInOrder(mc -> mc.acceptEvent(e));
|
||||
for (var mod : ModList.get().getLoadedMods())
|
||||
mod.acceptEvent(e);
|
||||
}
|
||||
public <T extends Event & IModBusEvent> T postEventWithReturn(T e) {
|
||||
|
||||
public static <T extends IModBusEvent> T postEventWithReturn(T e) {
|
||||
if (!loadingStateValid) {
|
||||
LOGGER.error("Cowardly refusing to send event {} to a broken mod state", e.getClass().getName());
|
||||
return e;
|
||||
}
|
||||
ModList.get().forEachModInOrder(mc -> mc.acceptEvent(e));
|
||||
for (var mod : ModList.get().getLoadedMods())
|
||||
mod.acceptEvent(e);
|
||||
return e;
|
||||
}
|
||||
public <T extends Event & IModBusEvent> void postEventWrapContainerInModOrder(T event) {
|
||||
postEventWithWrapInModOrder(event, (mc, e) -> ModLoadingContext.get().setActiveContainer(mc), (mc, e) -> ModLoadingContext.get().setActiveContainer(null));
|
||||
|
||||
@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 postEventWithWrapInModOrder(T e, BiConsumer<ModContainer, T> pre, BiConsumer<ModContainer, T> post) {
|
||||
|
||||
public static <T extends 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;
|
||||
}
|
||||
ModList.get().forEachModInOrder(mc -> {
|
||||
pre.accept(mc, e);
|
||||
mc.acceptEvent(e);
|
||||
post.accept(mc, e);
|
||||
});
|
||||
for (var mod : ModList.get().getLoadedMods()) {
|
||||
pre.accept(mod, e);
|
||||
mod.acceptEvent(e);
|
||||
post.accept(mod, e);
|
||||
}
|
||||
}
|
||||
|
||||
public List<ModLoadingWarning> getWarnings()
|
||||
{
|
||||
public List<ModLoadingWarning> getWarnings() {
|
||||
return ImmutableList.copyOf(this.loadingWarnings);
|
||||
}
|
||||
|
||||
public void addWarning(ModLoadingWarning warning)
|
||||
{
|
||||
public void addWarning(ModLoadingWarning warning) {
|
||||
this.loadingWarnings.add(warning);
|
||||
}
|
||||
|
||||
@@ -365,7 +439,7 @@ public class ModLoader
|
||||
return runningDataGen;
|
||||
}
|
||||
|
||||
private static class ErroredModContainer extends ModContainer {
|
||||
private static final class ErroredModContainer extends ModContainer {
|
||||
public ErroredModContainer() {
|
||||
super();
|
||||
}
|
||||
|
||||
@@ -9,49 +9,58 @@ 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.
|
||||
* @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(forRemoval = true, since = "1.21.1")
|
||||
@Deprecated(forRemoval = true, since="1.21.1")
|
||||
public static ModLoadingContext get() {
|
||||
return context.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Going to be moved to ForgeHooks for internal use.
|
||||
* @deprecated Going to be moved to ForgeHooks for Internal use.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "1.21.1")
|
||||
@Deprecated(forRemoval = true, since="1.21.1")
|
||||
public void setActiveContainer(final ModContainer container) {
|
||||
this.activeContainer = container;
|
||||
this.languageExtension = container == null ? null : container.contextExtension.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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")
|
||||
/**
|
||||
* @deprecated Going to be moved to ForgeHooks for Internal use.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since="1.21.1")
|
||||
public String getActiveNamespace() {
|
||||
return activeContainer == null ? "minecraft" : activeContainer.getNamespace();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@link ModLoadingContext#getActiveContainer()} by default.
|
||||
*/
|
||||
public ModContainer getContainer() {
|
||||
return getActiveContainer();
|
||||
}
|
||||
|
||||
@@ -5,10 +5,9 @@
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.fml.loading.progress.ProgressMeter;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -26,20 +25,27 @@ import java.util.function.ToIntFunction;
|
||||
* @param inlineRunnable an optional runnable, which runs before starting the transition from this state to the next
|
||||
* @param transition optional state transition information
|
||||
*/
|
||||
public record ModLoadingState(String name, String previous,
|
||||
Function<ModList, String> message,
|
||||
ToIntFunction<ModList> size,
|
||||
ModLoadingPhase phase,
|
||||
Optional<Consumer<ModList>> inlineRunnable,
|
||||
Optional<IModStateTransition> transition) implements IModLoadingState {
|
||||
public record ModLoadingState(
|
||||
String name,
|
||||
String previous,
|
||||
Function<ModList, String> message,
|
||||
ToIntFunction<ModList> size,
|
||||
ModLoadingPhase phase,
|
||||
Optional<Consumer<ModList>> inlineRunnable,
|
||||
Optional<IModStateTransition> transition
|
||||
) implements IModLoadingState {
|
||||
@Override
|
||||
public <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));
|
||||
public Optional<CompletableFuture<Void>> buildTransition(
|
||||
final Executor syncExecutor,
|
||||
final Executor parallelExecutor,
|
||||
final ProgressMeter progressBar,
|
||||
final Function<Executor, CompletableFuture<Void>> preSyncTask,
|
||||
final Function<Executor, CompletableFuture<Void>> postSyncTask
|
||||
) {
|
||||
var transition = this.transition.orElse(null);
|
||||
return transition == null
|
||||
? Optional.empty()
|
||||
: Optional.ofNullable(transition.build(name, syncExecutor, parallelExecutor, progressBar, preSyncTask, postSyncTask));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,9 +55,12 @@ public record ModLoadingState(String name, String previous,
|
||||
* @param name the name of the state
|
||||
* @param previous the name of the immediately previous state to this state
|
||||
* @param phase the mod loading phase the state belongs to
|
||||
*
|
||||
* @deprecated Use the builder
|
||||
*/
|
||||
@Deprecated(since = "1.21.3", forRemoval = true)
|
||||
public static ModLoadingState empty(final String name, final String previous, final ModLoadingPhase phase) {
|
||||
return new ModLoadingState(name, previous, ml -> "", f->0, phase, Optional.empty(), Optional.empty());
|
||||
return new ModLoadingState(name, previous, ml -> "", ml -> 0, phase, Optional.empty(), Optional.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,11 +71,13 @@ public record ModLoadingState(String name, String previous,
|
||||
* @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
|
||||
* @return a mod loading state with state transition information and a default message
|
||||
*
|
||||
* @deprecated Use the builder
|
||||
*/
|
||||
@Deprecated(since = "1.21.3", forRemoval = true)
|
||||
public static ModLoadingState withTransition(final String name, final String previous, final ModLoadingPhase phase,
|
||||
final IModStateTransition transition) {
|
||||
return new ModLoadingState(name, previous, ml -> "Processing transition " + name, ModList::size, phase, Optional.empty(), Optional.of(transition));
|
||||
return of(name, phase).after(previous).withTransition(transition);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,11 +89,14 @@ public record ModLoadingState(String name, String previous,
|
||||
* @param phase the mod loading phase the state belongs to
|
||||
* @param transition the state transition information
|
||||
* @return a mod loading state with state transition information and a custom message
|
||||
*
|
||||
* @deprecated Use the builder
|
||||
*/
|
||||
@Deprecated(since = "1.21.3", forRemoval = true)
|
||||
public static ModLoadingState withTransition(final String name, final String previous,
|
||||
final Function<ModList, String> message, final ModLoadingPhase phase,
|
||||
final IModStateTransition transition) {
|
||||
return new ModLoadingState(name, previous, message, ModList::size, phase, Optional.empty(), Optional.of(transition));
|
||||
return of(name, phase).after(previous).message(message).withTransition(transition);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,9 +108,75 @@ public record ModLoadingState(String name, String previous,
|
||||
* @param phase the mod loading phase the state belongs to
|
||||
* @param inline an optional runnable, which runs before starting the transition from this state to the next
|
||||
* @return a mod loading state with an inline runnable and default message
|
||||
*
|
||||
* @deprecated Use the builder
|
||||
*/
|
||||
@Deprecated(since = "1.21.3", forRemoval = true)
|
||||
public static ModLoadingState withInline(final String name, final String previous, final ModLoadingPhase phase,
|
||||
final Consumer<ModList> inline) {
|
||||
return new ModLoadingState(name, previous, ml -> "Processing work " + name, ml->0, phase, Optional.of(inline), Optional.empty());
|
||||
return of(name, phase).after(previous).withInline(inline);
|
||||
}
|
||||
|
||||
public static Builder of(final String name, final ModLoadingPhase phase) {
|
||||
return new Builder(name, phase);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof ModLoadingState that
|
||||
&& this.phase == that.phase
|
||||
&& this.name.equals(that.name)
|
||||
&& Objects.equals(this.previous, that.previous);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = phase.hashCode();
|
||||
result = 31 * result + name.hashCode();
|
||||
result = 31 * result + Objects.hashCode(previous);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private final String name;
|
||||
private final ModLoadingPhase phase;
|
||||
private String after;
|
||||
private Function<ModList, String> message = null;
|
||||
private ToIntFunction<ModList> size = null;
|
||||
|
||||
private Builder(final String name, final ModLoadingPhase phase) {
|
||||
this.name = name;
|
||||
this.phase = phase;
|
||||
}
|
||||
|
||||
public Builder after(final ModLoadingState value) { return after(value.name()); }
|
||||
public Builder after(final String value) {
|
||||
this.after = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder message(final String value) { return message(ml -> value); }
|
||||
public Builder message(final Function<ModList, String> value) {
|
||||
this.message = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder size(final int value) { return size(ml -> value); }
|
||||
public Builder size(final ToIntFunction<ModList> value) {
|
||||
this.size = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ModLoadingState empty() {
|
||||
return new ModLoadingState(name, after, message != null ? message : ml -> "", size != null ? size : ml -> 0, phase, Optional.empty(), Optional.empty());
|
||||
}
|
||||
|
||||
public ModLoadingState withTransition(final IModStateTransition transition) {
|
||||
return new ModLoadingState(name, after, message != null ? message : ml -> "Processing transition " + name, size != null ? size : ModList::size, phase, Optional.empty(), Optional.of(transition));
|
||||
}
|
||||
|
||||
public ModLoadingState withInline(final Consumer<ModList> inline) {
|
||||
return new ModLoadingState(name, after, message != null ? message : ml -> "Processing work " + name, size != null ? size : ml -> 0, phase, Optional.of(inline), Optional.empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,15 +16,13 @@ import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
public class ModStateManager {
|
||||
static ModStateManager INSTANCE;
|
||||
private final EnumMap<ModLoadingPhase, List<IModLoadingState>> stateMap;
|
||||
|
||||
public ModStateManager() {
|
||||
INSTANCE = this;
|
||||
final var sp = ServiceLoader.load(FMLLoader.getGameLayer(), IModStateProvider.class);
|
||||
this.stateMap = ServiceLoaderUtils.streamWithErrorHandling(sp, sce->{})
|
||||
.map(IModStateProvider::getAllStates)
|
||||
.<IModLoadingState>mapMulti(Iterable::forEach)
|
||||
.flatMap(List::stream)
|
||||
.collect(Collectors.groupingBy(IModLoadingState::phase, ()->new EnumMap<>(ModLoadingPhase.class), Collectors.toUnmodifiableList()));
|
||||
}
|
||||
|
||||
@@ -36,16 +34,20 @@ public class ModStateManager {
|
||||
var dummy = ModLoadingState.empty("", "", phase);
|
||||
nodes.forEach(graph::addNode);
|
||||
graph.addNode(dummy);
|
||||
nodes.forEach(n->graph.putEdge(lookup.getOrDefault(n.previous(), dummy), n));
|
||||
for (IModLoadingState node : nodes) {
|
||||
graph.putEdge(lookup.getOrDefault(node.previous(), dummy), node);
|
||||
}
|
||||
return TopologicalSort.topologicalSort(graph, Comparator.comparingInt(nodes::indexOf)).stream().filter(st->st!=dummy).toList();
|
||||
}
|
||||
|
||||
public IModLoadingState findState(final String stateName) {
|
||||
return stateMap.values()
|
||||
var result = stateMap.values()
|
||||
.stream()
|
||||
.flatMap(Collection::stream)
|
||||
.flatMap(List::stream)
|
||||
.filter(mls -> mls.name().equals(stateName))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalArgumentException("Unknown IModLoadingState: " + stateName));
|
||||
.orElse(null);
|
||||
if (result != null) return result;
|
||||
else throw new IllegalArgumentException("Unknown IModLoadingState: " + stateName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import net.minecraftforge.fml.IModStateTransition.EventGenerator;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.fml.loading.progress.ProgressMeter;
|
||||
|
||||
@ApiStatus.Internal
|
||||
class ModStateTransitionHelper {
|
||||
static final IModStateTransition NOOP = new NoopTransition();
|
||||
record NoopTransition() implements IModStateTransition {
|
||||
@Override
|
||||
public ThreadSelector threadSelector() {
|
||||
return ThreadSelector.SYNC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BiFunction<Executor, CompletableFuture<Void>, CompletableFuture<Void>> finalActivityGenerator() {
|
||||
return (e, t) -> t.thenApplyAsync(Function.identity(), e);
|
||||
}
|
||||
}
|
||||
|
||||
static <V> CompletionStage<Void> completableFutureFromExceptionList(List<FutureResult<V>> t) {
|
||||
if (t.stream().noneMatch(e -> e.exception() != null)) {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
} else {
|
||||
final var throwables = t.stream().map(FutureResult::exception).filter(Objects::nonNull).toList();
|
||||
CompletableFuture<Void> cf = new CompletableFuture<>();
|
||||
final RuntimeException accumulator = new RuntimeException();
|
||||
cf.completeExceptionally(accumulator);
|
||||
for (Throwable exception : throwables) {
|
||||
if (exception instanceof CompletionException) {
|
||||
exception = exception.getCause();
|
||||
}
|
||||
if (exception.getSuppressed().length != 0) {
|
||||
for (Throwable throwable : exception.getSuppressed()) {
|
||||
accumulator.addSuppressed(throwable);
|
||||
}
|
||||
} else {
|
||||
accumulator.addSuppressed(exception);
|
||||
}
|
||||
}
|
||||
return cf;
|
||||
}
|
||||
}
|
||||
|
||||
record FutureResult<V>(V value, Throwable exception){}
|
||||
|
||||
static <V> CompletableFuture<List<FutureResult<V>>> gather(Collection<? extends CompletableFuture<? extends V>> futures) {
|
||||
var list = new ArrayList<FutureResult<V>>(futures.size());
|
||||
var results = new CompletableFuture[futures.size()];
|
||||
|
||||
for (var future : futures) {
|
||||
int i = list.size();
|
||||
list.add(null);
|
||||
@SuppressWarnings("unchecked") // Bypass a weird javac error with generic by forcing it to be the exact type we want.
|
||||
var raw = ((CompletableFuture<V>)future);
|
||||
results[i] = raw.whenComplete((result, exception) -> list.set(i, new FutureResult<>(result, exception)));
|
||||
}
|
||||
|
||||
return CompletableFuture.allOf(results).handle((r, th)->null).thenApply(res -> list);
|
||||
}
|
||||
|
||||
private static <T extends IModBusEvent> void addCompletableFutureTaskForModDispatch(
|
||||
final IModStateTransition transition,
|
||||
final Executor executor,
|
||||
final List<CompletableFuture<Void>> completableFutures,
|
||||
final ProgressMeter progressBar,
|
||||
final EventGenerator<T> eventGenerator,
|
||||
final BiFunction<ModLoadingStage, Throwable, ModLoadingStage> nextState
|
||||
) {
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
var preDispatchHook = getHook(transition.preDispatchHook(), executor, eventGenerator);
|
||||
if (preDispatchHook != null)
|
||||
completableFutures.add(preDispatchHook);
|
||||
|
||||
var modFutures = new LinkedHashMap<String, CompletableFuture<Void>>();
|
||||
for (var mod : ModList.get().getLoadedMods()) {
|
||||
|
||||
CompletableFuture<Void> parent = null;
|
||||
if (mod.dependencies.isEmpty()) {
|
||||
parent = CompletableFuture.allOf();
|
||||
} else {
|
||||
var deps = new CompletableFuture[mod.dependencies.size()];
|
||||
int idx = 0;
|
||||
for (var depContainer : mod.dependencies) {
|
||||
var future = modFutures.get(depContainer.getModId());
|
||||
if (future == null)
|
||||
throw new IllegalStateException("Could not find dependency future " + depContainer.getModId() + " for " + mod.getModId());
|
||||
deps[idx++] = future;
|
||||
}
|
||||
parent = CompletableFuture.allOf(deps);
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
var dispatch = parent
|
||||
.thenRunAsync(() -> {
|
||||
ModLoadingContext.get().setActiveContainer(mod);
|
||||
var handler = mod.activityMap.get(mod.modLoadingStage);
|
||||
if (handler != null)
|
||||
handler.run();
|
||||
mod.acceptEvent(eventGenerator.apply(mod));
|
||||
}, executor)
|
||||
.whenComplete((mc, exception) -> {
|
||||
mod.modLoadingStage = nextState.apply(mod.modLoadingStage, exception);
|
||||
progressBar.increment();
|
||||
ModLoadingContext.get().setActiveContainer(null);
|
||||
});
|
||||
|
||||
modFutures.put(mod.getModId(), dispatch);
|
||||
}
|
||||
|
||||
var dispatch = gather(modFutures.values()).thenComposeAsync(ModStateTransitionHelper::completableFutureFromExceptionList, executor);
|
||||
completableFutures.add(dispatch);
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
var postDispatchHook = getHook(transition.preDispatchHook(), executor, eventGenerator);
|
||||
if (postDispatchHook != null)
|
||||
completableFutures.add(postDispatchHook);
|
||||
}
|
||||
|
||||
private static <T extends IModBusEvent> CompletableFuture<Void> getHook(BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> hook, Executor executor, EventGenerator<T> eventGenerator) {
|
||||
if (hook == null || hook == IModStateTransition.NULL_HOOK) return null;
|
||||
@SuppressWarnings("unchecked")
|
||||
var hookTyped = (BiFunction<Executor, EventGenerator<T>, CompletableFuture<Void>>)hook;
|
||||
return hookTyped.apply(executor, eventGenerator);
|
||||
}
|
||||
|
||||
static <T extends IModBusEvent> CompletableFuture<Void> build(
|
||||
final IModStateTransition transition,
|
||||
final String name,
|
||||
final Executor syncExecutor,
|
||||
final Executor parallelExecutor,
|
||||
final ProgressMeter progressBar,
|
||||
final Function<Executor, CompletableFuture<Void>> preSyncTask,
|
||||
final Function<Executor, CompletableFuture<Void>> postSyncTask
|
||||
) {
|
||||
List<CompletableFuture<Void>> futures = new ArrayList<>();
|
||||
final var executor = transition.threadSelector().apply(syncExecutor, parallelExecutor);
|
||||
|
||||
@SuppressWarnings({ "removal", "unchecked" })
|
||||
var events = transition.eventFunctionStream().get().map(f -> (EventGenerator<T>)f).toList();
|
||||
for (int x = 0; x < events.size(); x++) {
|
||||
var gen = events.get(x);
|
||||
BiFunction<ModLoadingStage, Throwable, ModLoadingStage> state = x == events.size() - 1
|
||||
? transition.nextModLoadingStage()
|
||||
: ModLoadingStage::currentState;
|
||||
addCompletableFutureTaskForModDispatch(transition, executor, futures, progressBar, gen, state);
|
||||
}
|
||||
|
||||
final CompletableFuture<Void> preSyncTaskCF = preSyncTask.apply(syncExecutor);
|
||||
final CompletableFuture<Void> eventDispatchCF = gather(futures).thenCompose(ModStateTransitionHelper::completableFutureFromExceptionList);
|
||||
final CompletableFuture<Void> postEventDispatchCF = preSyncTaskCF
|
||||
.thenApplyAsync(n -> {
|
||||
progressBar.label(progressBar.name() + ": dispatching " + name);
|
||||
return null;
|
||||
}, parallelExecutor)
|
||||
.thenComposeAsync(n -> eventDispatchCF, parallelExecutor)
|
||||
.thenApply(r -> {
|
||||
postSyncTask.apply(syncExecutor);
|
||||
return null;
|
||||
});
|
||||
return transition.finalActivityGenerator().apply(syncExecutor, postEventDispatchCF);
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import static net.minecraftforge.fml.Logging.LOADING;
|
||||
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();
|
||||
@@ -33,8 +34,11 @@ public class ModWorkManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
private static class SyncExecutor implements DrivenExecutor {
|
||||
private ConcurrentLinkedDeque<Runnable> tasks = new ConcurrentLinkedDeque<>();
|
||||
|
||||
private record SyncExecutor(ConcurrentLinkedDeque<Runnable> tasks) implements DrivenExecutor {
|
||||
public SyncExecutor() {
|
||||
this(new ConcurrentLinkedDeque<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean driveOne() {
|
||||
@@ -56,13 +60,7 @@ public class ModWorkManager {
|
||||
}
|
||||
}
|
||||
|
||||
private static class WrappingExecutor implements DrivenExecutor {
|
||||
private final Executor wrapped;
|
||||
|
||||
public WrappingExecutor(final Executor executor) {
|
||||
this.wrapped = executor;
|
||||
}
|
||||
|
||||
private record WrappingExecutor(Executor wrapped) implements DrivenExecutor {
|
||||
@Override
|
||||
public boolean selfDriven() {
|
||||
return true;
|
||||
@@ -79,11 +77,9 @@ public class ModWorkManager {
|
||||
}
|
||||
}
|
||||
|
||||
private static SyncExecutor syncExecutor;
|
||||
private static final SyncExecutor syncExecutor = new SyncExecutor();
|
||||
|
||||
public static DrivenExecutor syncExecutor() {
|
||||
if (syncExecutor == null)
|
||||
syncExecutor = new SyncExecutor();
|
||||
return syncExecutor;
|
||||
}
|
||||
|
||||
@@ -91,14 +87,8 @@ public class ModWorkManager {
|
||||
return new WrappingExecutor(executor);
|
||||
}
|
||||
|
||||
private static ForkJoinPool parallelThreadPool;
|
||||
public static Executor parallelExecutor() {
|
||||
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;
|
||||
return LazyInit.PARALLEL_EXECUTOR;
|
||||
}
|
||||
|
||||
private static ForkJoinWorkerThread newForkJoinWorkerThread(ForkJoinPool pool) {
|
||||
@@ -109,4 +99,14 @@ public 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 OptionalMod<?> EMPTY = new OptionalMod<>(true);
|
||||
private static final OptionalMod<?> EMPTY = new OptionalMod<>(true);
|
||||
private static <T> OptionalMod<T> empty() {
|
||||
@SuppressWarnings("unchecked")
|
||||
OptionalMod<T> t = (OptionalMod<T>) EMPTY;
|
||||
@@ -213,8 +213,8 @@ public final class OptionalMod<T>
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj) return true;
|
||||
if (obj instanceof OptionalMod) {
|
||||
return Objects.equals(((OptionalMod)obj).modId, modId);
|
||||
if (obj instanceof OptionalMod<?> optionalMod) {
|
||||
return Objects.equals(optionalMod.modId, modId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ 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;
|
||||
@@ -25,9 +26,66 @@ 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());
|
||||
@@ -37,7 +95,7 @@ public class ConfigFileTypeHandler {
|
||||
onFileNotFound((newfile, configFormat)-> setupConfigFile(c, newfile, configFormat)).
|
||||
writingMode(WritingMode.REPLACE).
|
||||
build();
|
||||
LOGGER.debug(CONFIG, "Built TOML config for {}", configPath.toString());
|
||||
LOGGER.debug(CONFIG, "Built TOML config for {}", configPath);
|
||||
try
|
||||
{
|
||||
configData.load();
|
||||
@@ -46,13 +104,9 @@ public class ConfigFileTypeHandler {
|
||||
{
|
||||
throw new ConfigLoadingException(c, ex);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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);
|
||||
return configData;
|
||||
};
|
||||
}
|
||||
@@ -60,14 +114,16 @@ public class ConfigFileTypeHandler {
|
||||
public void unload(Path configBasePath, ModConfig config) {
|
||||
Path configPath = configBasePath.resolve(config.getFileName());
|
||||
try {
|
||||
FileWatcher.defaultInstance().removeWatch(configBasePath.resolve(config.getFileName()));
|
||||
this.getWatcher().removeWatch(configPath);
|
||||
} catch (RuntimeException e) {
|
||||
LOGGER.error("Failed to remove config {} from tracker!", configPath.toString(), e);
|
||||
LOGGER.error("Failed to remove config {} from tracker!", configPath, e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean setupConfigFile(final ModConfig modConfig, final Path file, final ConfigFormat<?> conf) throws IOException {
|
||||
Files.createDirectories(file.getParent());
|
||||
private static boolean setupConfigFile(final ModConfig modConfig, final Path file, final ConfigFormat<?> conf) throws IOException {
|
||||
if (!Files.isDirectory(file.getParent())) {
|
||||
Files.createDirectories(file.getParent());
|
||||
}
|
||||
Path p = defaultConfigPath.resolve(modConfig.getFileName());
|
||||
if (Files.exists(p)) {
|
||||
LOGGER.info(CONFIG, "Loading default config file from path {}", p);
|
||||
@@ -111,17 +167,16 @@ public class ConfigFileTypeHandler {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ConfigFileTypeHandler[" + (type != null ? type : "UNKNOWN") + "]";
|
||||
}
|
||||
|
||||
private record ConfigWatcher(
|
||||
ModConfig modConfig,
|
||||
CommentedFileConfig commentedFileConfig,
|
||||
ClassLoader realClassLoader
|
||||
) implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
// Force the regular classloader onto the special thread
|
||||
|
||||
@@ -8,6 +8,9 @@ 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.api.distmarker.Dist;
|
||||
import net.minecraftforge.fml.DistExecutor;
|
||||
import net.minecraftforge.fml.loading.FMLPaths;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.Marker;
|
||||
import org.slf4j.MarkerFactory;
|
||||
@@ -47,15 +50,32 @@ public class ConfigTracker {
|
||||
|
||||
public void loadConfigs(ModConfig.Type type, Path configBasePath) {
|
||||
LOGGER.debug(CONFIG, "Loading configs type {}", type);
|
||||
this.configSets.get(type).forEach(config -> openConfig(config, configBasePath));
|
||||
for (ModConfig config : this.configSets.get(type)) {
|
||||
openConfig(config, configBasePath);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: [FML] This is only called for the server (outside of forceUnload)
|
||||
// rethink config implementation for eventual FML rewrite
|
||||
public void unloadConfigs(ModConfig.Type type, Path configBasePath) {
|
||||
LOGGER.debug(CONFIG, "Unloading configs type {}", type);
|
||||
this.configSets.get(type).forEach(config -> closeConfig(config, configBasePath));
|
||||
for (ModConfig config : this.configSets.get(type)) {
|
||||
closeConfig(config, configBasePath);
|
||||
}
|
||||
ConfigFileTypeHandler.get(type).stopWatcher();
|
||||
}
|
||||
|
||||
private void openConfig(final ModConfig config, final Path configBasePath) {
|
||||
// If there is a better way to do this, please tell me. Because this is FUCKED
|
||||
public void forceUnload() {
|
||||
// This is how ModStateProvider handles loading configs. So we're doing the same but with unloadConfigs instead
|
||||
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> this.unloadConfigs(ModConfig.Type.CLIENT, FMLPaths.CONFIGDIR.get()));
|
||||
this.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) {
|
||||
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);
|
||||
@@ -63,7 +83,7 @@ public class ConfigTracker {
|
||||
config.save();
|
||||
}
|
||||
|
||||
private void closeConfig(final ModConfig config, final Path configBasePath) {
|
||||
private static 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
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
package net.minecraftforge.fml.config;
|
||||
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.eventbus.api.event.InheritableEvent;
|
||||
import net.minecraftforge.fml.Bindings;
|
||||
|
||||
import java.util.function.Function;
|
||||
@@ -29,7 +29,7 @@ public interface IConfigEvent {
|
||||
ModConfig getConfig();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
default <T extends Event & IConfigEvent> T self() {
|
||||
default <T extends InheritableEvent & IConfigEvent> T self() {
|
||||
return (T) this;
|
||||
}
|
||||
}
|
||||
@@ -24,14 +24,13 @@ 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.TOML;
|
||||
this.configHandler = ConfigFileTypeHandler.get(type);
|
||||
ConfigTracker.INSTANCE.trackConfig(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,33 @@
|
||||
|
||||
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 events dispatched on the ModLifecycle event bus instead of the primary event bus
|
||||
* 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.
|
||||
*/
|
||||
public interface IModBusEvent {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,11 @@ package net.minecraftforge.fml.util.thread;
|
||||
|
||||
import net.minecraftforge.fml.LogicalSide;
|
||||
|
||||
public class EffectiveSide
|
||||
{
|
||||
/**
|
||||
* 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 static LogicalSide get() {
|
||||
final ThreadGroup group = Thread.currentThread().getThreadGroup();
|
||||
return group instanceof SidedThreadGroup ? ((SidedThreadGroup) group).getSide() : LogicalSide.CLIENT;
|
||||
|
||||
@@ -7,11 +7,13 @@ 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);
|
||||
|
||||
private SidedThreadGroups() {
|
||||
public static SidedThreadGroup get(boolean client) {
|
||||
return client ? CLIENT : SERVER;
|
||||
}
|
||||
|
||||
private SidedThreadGroups() { }
|
||||
}
|
||||
@@ -28,7 +28,7 @@ java {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(libs.nulls)
|
||||
compileOnly(libs.jetbrains.annotations)
|
||||
implementation(project(':fmlloader'))
|
||||
implementation(project(':fmlcore'))
|
||||
implementation(libs.bundles.lwjgl)
|
||||
|
||||
+38
-33
@@ -22,9 +22,7 @@ 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;
|
||||
@@ -87,11 +85,17 @@ 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 ScheduledExecutorService renderScheduler;
|
||||
private static final ScheduledExecutorService renderScheduler = Executors.newSingleThreadScheduledExecutor(r -> {
|
||||
final var thread = Executors.defaultThreadFactory().newThread(r);
|
||||
thread.setName("EarlyDisplay");
|
||||
thread.setDaemon(true);
|
||||
return thread;
|
||||
});
|
||||
private int fbWidth;
|
||||
private int fbHeight;
|
||||
private int fbScale;
|
||||
@@ -150,7 +154,7 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
}
|
||||
this.maximized = parsed.has(maximizedopt) || FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_MAXIMIZED);
|
||||
|
||||
StartupNotificationManager.modLoaderConsumer().ifPresent(c->c.accept("Forge loading " + forgeVersion));
|
||||
StartupNotificationManager.modLoaderConsumer().ifPresent(c->c.accept("Forge loading " + FMLLoader.versionInfo().forgeVersion()));
|
||||
performanceInfo = new PerformanceInfo();
|
||||
return start(mcVersion, forgeVersion);
|
||||
}
|
||||
@@ -239,7 +243,7 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
|
||||
var date = Calendar.getInstance();
|
||||
if (FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_SQUIR) || (date.get(Calendar.MONTH) == Calendar.APRIL && date.get(Calendar.DAY_OF_MONTH) == 1))
|
||||
this.elements.add(0, RenderElement.squir());
|
||||
this.elements.addFirst(RenderElement.squir());
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
@@ -259,7 +263,11 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
this.elements.removeIf(element -> !element.render(context, framecount));
|
||||
for (var itr = this.elements.iterator(); itr.hasNext(); ) {
|
||||
var element = itr.next();
|
||||
if (!element.render(context, framecount))
|
||||
itr.remove();
|
||||
}
|
||||
if (animationTimerTrigger.compareAndSet(true, false)) // we only increment the framecount on a periodic basis
|
||||
framecount++;
|
||||
}
|
||||
@@ -284,12 +292,6 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
* Start the window and Render Thread; we're ready to go.
|
||||
*/
|
||||
public Runnable start(@Nullable String mcVersion, final String forgeVersion) {
|
||||
renderScheduler = Executors.newSingleThreadScheduledExecutor(r -> {
|
||||
final var thread = Executors.defaultThreadFactory().newThread(r);
|
||||
thread.setName("EarlyDisplay");
|
||||
thread.setDaemon(true);
|
||||
return thread;
|
||||
});
|
||||
initWindow(mcVersion);
|
||||
this.initializationFuture = renderScheduler.schedule(() -> initRender(mcVersion, forgeVersion), 1, TimeUnit.MILLISECONDS);
|
||||
return this::periodicTick;
|
||||
@@ -301,14 +303,7 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
return this.glVersion;
|
||||
}
|
||||
|
||||
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 = "";
|
||||
}
|
||||
|
||||
private static void crashElegantly(String errorDetails) {
|
||||
StringBuilder msgBuilder = new StringBuilder(2000);
|
||||
msgBuilder.append("Failed to initialize graphics window with current settings.\n");
|
||||
msgBuilder.append("\n\n");
|
||||
@@ -407,13 +402,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
|
||||
@@ -497,10 +492,6 @@ 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;
|
||||
@@ -520,7 +511,7 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
this.winY = y;
|
||||
}
|
||||
}
|
||||
private void handleLastGLFWError(BiConsumer<Integer, String> handler) {
|
||||
private static void handleLastGLFWError(BiConsumer<Integer, String> handler) {
|
||||
try (MemoryStack memorystack = MemoryStack.stackPush()) {
|
||||
PointerBuffer pointerbuffer = memorystack.mallocPointer(1);
|
||||
int error = glfwGetError(pointerbuffer);
|
||||
@@ -612,10 +603,25 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
|
||||
@Override
|
||||
public void updateModuleReads(final ModuleLayer layer) {
|
||||
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.getDeclaredMethods()).filter(m-> Modifier.isStatic(m.getModifiers()) && m.getName().equals("newInstance")).findFirst().orElseThrow();
|
||||
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());
|
||||
}
|
||||
|
||||
public int getFramebufferTextureId() {
|
||||
@@ -633,8 +639,7 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
}
|
||||
|
||||
public void addMojangTexture(final int textureId) {
|
||||
this.elements.add(0, RenderElement.mojang(textureId, framecount));
|
||||
// this.elements.get(0).retire(framecount + 1);
|
||||
this.elements.addFirst(RenderElement.mojang(textureId, framecount));
|
||||
}
|
||||
|
||||
public void close() {
|
||||
|
||||
+30
-39
@@ -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,6 +38,9 @@ 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> {}
|
||||
|
||||
@@ -56,11 +59,18 @@ 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;
|
||||
@@ -75,7 +85,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 = clamp((4000.0f - (float) pair.age() - ( i - 4 ) * 1000.0f) / 5000.0f, 0.0f, 1.0f);
|
||||
final float fade = Math.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;
|
||||
@@ -84,19 +94,9 @@ 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 {
|
||||
var progress = frame % 100;
|
||||
return new float[]{clamp((progress - 2) / 100f, 0f, 1f), clamp((progress + 2) / 100f, 0f, 1f)};
|
||||
int progress = frame % 100;
|
||||
return new float[] { Math.clamp((progress - 2) / 100f, 0f, 1f), Math.clamp((progress + 2) / 100f, 0f, 1f) };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,8 +249,8 @@ public class RenderElement {
|
||||
};
|
||||
}
|
||||
|
||||
private static Initializer initializeText(SimpleFont font, TextGenerator textGenerator) {
|
||||
return () -> (bb, context, frame) -> renderText(font, textGenerator, bb, context);
|
||||
private static Renderer 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,14 +264,13 @@ 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 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);
|
||||
};
|
||||
|
||||
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);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -281,21 +280,13 @@ public class RenderElement {
|
||||
bb.draw();
|
||||
}
|
||||
|
||||
|
||||
@Deprecated(since = "1.21.5", forRemoval = true)
|
||||
public static float clamp(float num, float min, float max) {
|
||||
if (num < min) {
|
||||
return min;
|
||||
} else {
|
||||
return Math.min(num, max);
|
||||
}
|
||||
return Math.clamp(num, min, max);
|
||||
}
|
||||
|
||||
public static int clamp(int num, int min, int max) {
|
||||
if (num < min) {
|
||||
return min;
|
||||
} else {
|
||||
return Math.min(num, max);
|
||||
}
|
||||
return Math.min(max, Math.max(num, min));
|
||||
}
|
||||
|
||||
public static int hsvToRGB(float hue, float saturation, float value) {
|
||||
|
||||
@@ -11,8 +11,6 @@ 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;
|
||||
|
||||
@@ -24,8 +22,8 @@ public class SimpleFont {
|
||||
private final int textureNumber;
|
||||
private final int lineSpacing;
|
||||
private final int descent;
|
||||
private final int GLYPH_COUNT = 127-32;
|
||||
private Glyph[] glyphs;
|
||||
private static final int GLYPH_COUNT = 127 - 32;
|
||||
private final Glyph[] glyphs;
|
||||
|
||||
private record Glyph(char c, int charwidth, int[] pos, float[] uv) {
|
||||
Pos loadQuad(Pos pos, int colour, SimpleBufferBuilder bb) {
|
||||
@@ -122,7 +120,7 @@ public class SimpleFont {
|
||||
case '\n', '\t' -> 0;
|
||||
case ' ' -> glyphs[0].charwidth();
|
||||
default -> {
|
||||
if (c - 32 < this.GLYPH_COUNT && c > 32) {
|
||||
if (c - 32 < GLYPH_COUNT && c > 32) {
|
||||
yield this.glyphs[c - 32].charwidth();
|
||||
} else {
|
||||
yield 0;
|
||||
@@ -153,7 +151,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 < font.GLYPH_COUNT && c > 32) {
|
||||
if (c-32 < GLYPH_COUNT && c > 32) {
|
||||
pos = font.glyphs[c - 32].loadQuad(pos, colour(), bb);
|
||||
}
|
||||
yield pos;
|
||||
|
||||
+47
-7
@@ -5,6 +5,7 @@ plugins {
|
||||
id 'maven-publish'
|
||||
id 'net.minecraftforge.licenser'
|
||||
id 'net.minecraftforge.gradleutils'
|
||||
alias(libs.plugins.apt)
|
||||
}
|
||||
|
||||
apply from: rootProject.file('build_shared.gradle')
|
||||
@@ -12,7 +13,7 @@ apply from: rootProject.file('build_shared.gradle')
|
||||
configurations.forEach{ it.transitive = false }
|
||||
|
||||
dependencies {
|
||||
compileOnly(libs.nulls)
|
||||
compileOnly(libs.jetbrains.annotations)
|
||||
|
||||
api(libs.bundles.asm) // Needed by all the black magic
|
||||
api(libs.forgespi)
|
||||
@@ -25,7 +26,6 @@ dependencies {
|
||||
api(libs.apache.commons)
|
||||
api(libs.bundles.night.config)
|
||||
api(libs.modlauncher)
|
||||
api(libs.coremods)
|
||||
api(libs.mojang.logging)
|
||||
api(libs.jarjar.selector)
|
||||
api(libs.jarjar.meta)
|
||||
@@ -35,11 +35,13 @@ dependencies {
|
||||
implementation(libs.accesstransformers)
|
||||
implementation(libs.terminalconsoleappender)
|
||||
implementation(libs.jimfs)
|
||||
implementation(libs.roimfs)
|
||||
|
||||
// Needed because we have a custom log4j plugin, and they removed package scanning and require a data file to be generated
|
||||
implementation(libs.log4j.core)
|
||||
annotationProcessor(libs.log4j.core)
|
||||
annotationProcessor(libs.bundles.log4j)
|
||||
|
||||
testCompileOnly(libs.nulls)
|
||||
testCompileOnly(libs.jetbrains.annotations)
|
||||
testRuntimeOnly(libs.bootstrap)
|
||||
}
|
||||
|
||||
@@ -85,7 +87,7 @@ publishing {
|
||||
license PomUtils.Licenses.LGPLv2_1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
repositories {
|
||||
maven gradleutils.publishingForgeMaven
|
||||
}
|
||||
@@ -101,6 +103,44 @@ tasks.register('writeForgeVersionJson') {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named('generateResources').configure {
|
||||
dependsOn('writeForgeVersionJson')
|
||||
tasks.register('jarJarOptionsJson', net.minecraftforge.forge.tasks.JarJarMetadataOptions) {
|
||||
metadataFile = project.file('src/main/resources/jarjar_options.json')
|
||||
// This is resolved too early by cpw.mods.modlauncher.TransformationServicesHandler.discoverServices(DiscoveryData) But eventually...
|
||||
//add(libs.mixin, 'org/spongepowered/asm/mixin/Mixin.class')
|
||||
add(libs.mixinextras.forge) {
|
||||
containedJarMetadata {
|
||||
path = ''
|
||||
}
|
||||
resource = 'com/llamalad7/mixinextras/platform/forge/MixinExtrasConfigPlugin.class'
|
||||
layer = 'GAMELIBRARY'
|
||||
id = 'mixinextras'
|
||||
nested = true
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named('generateResources').configure {
|
||||
dependsOn('eclipseJdt')
|
||||
dependsOn('eclipseJdtApt')
|
||||
dependsOn('eclipseFactorypath')
|
||||
dependsOn('writeForgeVersionJson')
|
||||
dependsOn('jarJarOptionsJson')
|
||||
}
|
||||
|
||||
tasks.named('sourcesJar') {
|
||||
dependsOn('jarJarOptionsJson')
|
||||
}
|
||||
|
||||
eclipse {
|
||||
classpath {
|
||||
// We need to set the default output directory for the log4j annotation processor
|
||||
// Ideally we'd just set the value, but the eclipse plugin deduplicates the output directories
|
||||
// So we have to do a hacky whenMerged
|
||||
//defaultOutputDir = file('bin/main')
|
||||
file.whenMerged {
|
||||
entries.each { entry ->
|
||||
if (entry.kind == 'output' && entry.hasProperty('path'))
|
||||
entry.path = 'bin/main'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
-34
@@ -34,11 +34,10 @@ import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
|
||||
* }</code>
|
||||
* </pre>
|
||||
*/
|
||||
public class CapabilityTokenSubclass implements ILaunchPluginService {
|
||||
|
||||
public final 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() {
|
||||
@@ -49,56 +48,48 @@ public 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 YAY;
|
||||
return internalName.contains("$") ? YAY : NAY; // Anonymous subclasses only
|
||||
}
|
||||
|
||||
@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))
|
||||
{
|
||||
Holder cls = new Holder();
|
||||
} else if (CAP_INJECT.equals(classNode.superName)) {
|
||||
var cls = new Object() { String value = null; };
|
||||
|
||||
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;
|
||||
@@ -113,15 +104,8 @@ public 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
@@ -1,131 +0,0 @@
|
||||
/*
|
||||
* 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"))
|
||||
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));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private String getValue(List<AnnotationNode> lst)
|
||||
{
|
||||
AnnotationNode ann = lst.stream().filter(n -> n.desc.equals(OBJECT_HOLDER)).findFirst().get();
|
||||
if (ann.values != null)
|
||||
{
|
||||
for (int x = 0; x < ann.values.size() - 1; x += 2) {
|
||||
if (ann.values.get(x).equals("value")) {
|
||||
return (String)ann.values.get(x + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int processClassWithFlags(final Phase phase, final ClassNode classNode, final Type classType, final String reason)
|
||||
{
|
||||
final AtomicBoolean changes = new AtomicBoolean();
|
||||
//Must be public static finals, and non-array objects
|
||||
|
||||
//Fix Annotated Fields before injecting from class level
|
||||
classNode.fields.stream().filter(f -> ((f.access & PUBLIC_STATIC_FINAL_FLAGS) == PUBLIC_STATIC_FINAL_FLAGS) && f.desc.startsWith("L") && hasHolder(f.visibleAnnotations)).forEach(f ->
|
||||
{
|
||||
int prev = f.access;
|
||||
f.access &= ~Opcodes.ACC_FINAL; //Strip final
|
||||
f.access |= Opcodes.ACC_SYNTHETIC; //Add Synthetic so we can check in runtime. ? Good idea?
|
||||
changes.compareAndSet(false, prev != f.access);
|
||||
});
|
||||
|
||||
if (VANILLA_OBJECT_HOLDERS.containsKey(classType.getClassName())) //Class level, de-finalize all fields and add @ObjectHolder to them!
|
||||
{
|
||||
classNode.fields.stream().filter(f -> ((f.access & PUBLIC_STATIC_FINAL_FLAGS) == PUBLIC_STATIC_FINAL_FLAGS) && f.desc.startsWith("L")).forEach(f ->
|
||||
{
|
||||
int prev = f.access;
|
||||
f.access &= ~Opcodes.ACC_FINAL;
|
||||
f.access |= Opcodes.ACC_SYNTHETIC;
|
||||
/*if (!hasHolder(f.visibleAnnotations)) //Add field level annotation, doesn't do anything until after we figure out how ASMDataTable is gatherered
|
||||
{
|
||||
if (value == null)
|
||||
f.visitAnnotation(OBJECT_HOLDER, true);
|
||||
else
|
||||
f.visitAnnotation(OBJECT_HOLDER, true).visit("value", value + ":" + f.name.toLowerCase());
|
||||
}*/
|
||||
changes.compareAndSet(false, prev != f.access);
|
||||
});
|
||||
}
|
||||
return changes.get() ? ComputeFlags.SIMPLE_REWRITE : ComputeFlags.NO_REWRITE;
|
||||
}
|
||||
|
||||
private record VanillaObjectHolderData(String holderClass, String registryName, String registryType) {}
|
||||
}
|
||||
@@ -7,9 +7,9 @@ package net.minecraftforge.fml.common.asm;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraftforge.fml.loading.LoadingModList;
|
||||
import org.objectweb.asm.Label;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
@@ -47,16 +47,21 @@ public class RuntimeEnumExtender implements ILaunchPluginService {
|
||||
private static final EnumSet<Phase> YAY = EnumSet.of(Phase.AFTER);
|
||||
private static final EnumSet<Phase> NAY = EnumSet.noneOf(Phase.class);
|
||||
|
||||
private static final class LazyInit {
|
||||
private LazyInit() {}
|
||||
private static final boolean ANY_MODS = LoadingModList.get().getMods().size() > 2; // 2: forge, minecraft
|
||||
}
|
||||
|
||||
@Override
|
||||
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty) {
|
||||
if (isEmpty)
|
||||
if (isEmpty || !LazyInit.ANY_MODS)
|
||||
return NAY;
|
||||
|
||||
String internalName = classType.getInternalName();
|
||||
if (internalName.startsWith("net/minecraftforge/") || internalName.startsWith("com/mojang/"))
|
||||
return NAY;
|
||||
if (internalName.startsWith("net/minecraft/client/model") || internalName.startsWith("net/minecraft/world"))
|
||||
return YAY;
|
||||
|
||||
return YAY;
|
||||
return NAY;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -6,14 +6,13 @@
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
public class BackgroundWaiter {
|
||||
private static final ExecutorService runner = Executors.newSingleThreadExecutor();
|
||||
|
||||
public static void runAndTick(Runnable r, Runnable tick) {
|
||||
var runner = Executors.newSingleThreadExecutor();
|
||||
ImmediateWindowHandler.updateProgress("Loading bootstrap resources");
|
||||
final Future<?> work = runner.submit(r);
|
||||
do {
|
||||
|
||||
@@ -39,7 +39,6 @@ public class FMLConfig
|
||||
EARLY_WINDOW_SQUIR("earlyWindowSquir", Boolean.FALSE, "Squir?"),
|
||||
EARLY_WINDOW_SHOW_CPU("earlyWindowShowCPU", Boolean.FALSE, "Whether to show CPU usage stats in early window"),
|
||||
EARLY_WINDOW_LOG_HELP_MSG("earlyWindowLogHelpMessage", Boolean.TRUE, "Whether to log a help message on first attempt, to aid troubleshooting. This setting should automatically disable itself after a successful launch"),
|
||||
DETECT_NON_FORGE_MODS_TOML("detectNonForgeModsToml", Boolean.TRUE, "Attempt to detect non-Forge mods from their mods.toml to show a friendlier error message. This is a best-effort feature - disable if it causes issues."),
|
||||
;
|
||||
|
||||
private final String entry;
|
||||
|
||||
@@ -16,12 +16,9 @@ import net.minecraftforge.accesstransformer.service.AccessTransformerService;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.fml.loading.targets.CommonLaunchHandler;
|
||||
import net.minecraftforge.forgespi.Environment;
|
||||
import net.minecraftforge.forgespi.coremod.ICoreModProvider;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -36,8 +33,6 @@ import static net.minecraftforge.fml.loading.LogMarkers.SCAN;
|
||||
public class FMLLoader {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static AccessTransformerService accessTransformer;
|
||||
private static ModDiscoverer modDiscoverer;
|
||||
private static ICoreModProvider coreModProvider;
|
||||
private static LanguageLoadingProvider languageLoadingProvider;
|
||||
private static Dist dist;
|
||||
private static String naming;
|
||||
@@ -57,15 +52,9 @@ public class FMLLoader {
|
||||
LOGGER.debug(CORE, "Detected version data : {}", versionInfo);
|
||||
LOGGER.debug(CORE, "FML {} loading", LauncherVersion.getVersion());
|
||||
|
||||
// Allows us to communicate properties with other services through ModLauncher
|
||||
setupBlackboardKeys();
|
||||
|
||||
checkPackage(ITransformationService.class, "4.0", "ModLauncher");
|
||||
accessTransformer = getPlugin(env, "accesstransformer", "1.0", "AccessTransformer");
|
||||
/*eventBus =*/ getPlugin(env, "eventbus", "1.0", "EventBus");
|
||||
runtimeDistCleaner = getPlugin(env, "runtimedistcleaner", "1.0", "RuntimeDistCleaner");
|
||||
coreModProvider = getSingleService(ICoreModProvider.class, "CoreMod");
|
||||
LOGGER.debug(CORE,"FML found CoreMod version : {}", JarVersionLookupHandler.getInfo(coreModProvider.getClass()).impl().version().orElse("MISSING"));
|
||||
checkPackage(Environment.class, "2.0", "ForgeSPI");
|
||||
|
||||
try {
|
||||
@@ -77,14 +66,15 @@ public class FMLLoader {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> T getPlugin(IEnvironment env, String id, String version, String name) throws IncompatibleEnvironmentException {
|
||||
@SuppressWarnings("unchecked")
|
||||
var plugin = (T)env.findLaunchPlugin(id).orElseThrow(() -> {
|
||||
var plugin = env.findLaunchPlugin(id).orElse(null);
|
||||
if (plugin == null) {
|
||||
LOGGER.error(CORE, "{} library is missing, we need this to run", name);
|
||||
return new IncompatibleEnvironmentException("Missing " + name + ", cannot run");
|
||||
});
|
||||
throw new IncompatibleEnvironmentException("Missing " + name + ", cannot run");
|
||||
}
|
||||
checkPackage(plugin.getClass(), version, name);
|
||||
return plugin;
|
||||
return (T) plugin;
|
||||
}
|
||||
|
||||
private static void checkPackage(Class<?> cls, String version, String name) throws IncompatibleEnvironmentException {
|
||||
@@ -102,6 +92,7 @@ public class FMLLoader {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static <T> T getSingleService(Class<T> clazz, String name) throws IncompatibleEnvironmentException {
|
||||
var providers = new ArrayList<T>();
|
||||
for (var itr = ServiceLoader.load(FMLLoader.class.getModule().getLayer(), clazz).iterator(); itr.hasNext(); ) {
|
||||
@@ -123,12 +114,6 @@ public class FMLLoader {
|
||||
return providers.get(0);
|
||||
}
|
||||
|
||||
private static void setupBlackboardKeys() {
|
||||
LOGGER.debug(CORE, "Requesting CoreMods to not apply the fix for ASMAPI.findFirstInstructionBefore by default");
|
||||
var blackboardKey = TypesafeMap.Key.getOrCreate(Launcher.INSTANCE.blackboard(), "coremods.use_old_findFirstInstructionBefore", Boolean.class);
|
||||
Launcher.INSTANCE.blackboard().<Boolean>computeIfAbsent(blackboardKey, k -> true);
|
||||
}
|
||||
|
||||
static void setupLaunchHandler(final IEnvironment environment, final Map<String, Object> arguments) {
|
||||
final String launchTarget = environment.getProperty(IEnvironment.Keys.LAUNCHTARGET.get()).orElse("MISSING");
|
||||
arguments.put("launchTarget", launchTarget);
|
||||
@@ -146,21 +131,20 @@ public class FMLLoader {
|
||||
}
|
||||
commonLaunchHandler = (CommonLaunchHandler)launchHandler.get();
|
||||
launchHandlerName = launchHandler.get().name();
|
||||
gamePath = environment.getProperty(IEnvironment.Keys.GAMEDIR.get()).orElse(Paths.get(".").toAbsolutePath());
|
||||
gamePath = environment.getProperty(IEnvironment.Keys.GAMEDIR.get()).orElse(Path.of(".").toAbsolutePath());
|
||||
|
||||
naming = commonLaunchHandler.getNaming();
|
||||
dist = commonLaunchHandler.getDist();
|
||||
production = commonLaunchHandler.isProduction();
|
||||
|
||||
accessTransformer.getExtension().accept(Pair.of(naming, "srg"));
|
||||
accessTransformer.getExtension().accept(Map.entry(naming, "srg"));
|
||||
|
||||
runtimeDistCleaner.getExtension().accept(dist);
|
||||
}
|
||||
|
||||
public static List<ITransformationService.Resource> beginModScan(final Map<String,?> arguments) {
|
||||
LOGGER.debug(SCAN,"Scanning for Mod Locators");
|
||||
modDiscoverer = new ModDiscoverer(arguments);
|
||||
modValidator = modDiscoverer.discoverMods();
|
||||
LOGGER.debug(SCAN, "Scanning for Mod Locators");
|
||||
modValidator = ModDiscoverer.discoverMods(arguments);
|
||||
var pluginResources = modValidator.getPluginResources();
|
||||
return List.of(pluginResources);
|
||||
}
|
||||
@@ -173,24 +157,16 @@ public class FMLLoader {
|
||||
return List.of(modValidator.getModResources());
|
||||
}
|
||||
|
||||
public static ICoreModProvider getCoreModProvider() {
|
||||
return coreModProvider;
|
||||
}
|
||||
|
||||
public static LanguageLoadingProvider getLanguageLoadingProvider() {
|
||||
return languageLoadingProvider;
|
||||
}
|
||||
|
||||
static ModDiscoverer getModDiscoverer() {
|
||||
return modDiscoverer;
|
||||
}
|
||||
|
||||
public static CommonLaunchHandler getLaunchHandler() {
|
||||
return commonLaunchHandler;
|
||||
}
|
||||
|
||||
public static void addAccessTransformer(Path atPath, ModFile modName) {
|
||||
LOGGER.debug(SCAN, "Adding Access Transformer in {}", modName.getFilePath());
|
||||
LOGGER.debug(SCAN, "Adding Access Transformer {} in {}", atPath, modName.getFilePath());
|
||||
accessTransformer.offerResource(atPath, modName.getFileName());
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ package net.minecraftforge.fml.loading;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import cpw.mods.modlauncher.api.*;
|
||||
import joptsimple.OptionSpecBuilder;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
|
||||
import net.minecraftforge.forgespi.Environment;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -19,12 +18,11 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
|
||||
|
||||
public class FMLServiceProvider implements ITransformationService {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private Map<String, Object> arguments;
|
||||
private final Map<String, Object> arguments = new HashMap<>();
|
||||
|
||||
public FMLServiceProvider() {
|
||||
var markers = System.getProperty("forge.logging.markers", "").split(",");
|
||||
@@ -45,7 +43,6 @@ public class FMLServiceProvider implements ITransformationService {
|
||||
FMLConfig.load();
|
||||
LOGGER.debug(CORE, "Preparing ModFile");
|
||||
environment.computePropertyIfAbsent(Environment.Keys.MODFILEFACTORY.get(), k->ModFile::new);
|
||||
arguments = new HashMap<>();
|
||||
LOGGER.debug(CORE, "Preparing launch handler");
|
||||
FMLLoader.setupLaunchHandler(environment, arguments);
|
||||
FMLEnvironment.setupInteropEnvironment(environment);
|
||||
@@ -68,18 +65,9 @@ public class FMLServiceProvider implements ITransformationService {
|
||||
FMLLoader.onInitialLoad(environment, otherServices);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void arguments(BiFunction<String, String, OptionSpecBuilder> argumentBuilder) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void argumentValues(OptionResult option) {
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public @NotNull List<ITransformer> transformers() {
|
||||
LOGGER.debug(CORE, "Loading coremod transformers");
|
||||
return new ArrayList<>(FMLLoader.getCoreModProvider().getCoreModTransformers());
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
@@ -140,7 +140,7 @@ public class ImmediateWindowHandler {
|
||||
|
||||
@Override
|
||||
public void updateModuleReads(final ModuleLayer layer) {
|
||||
var fm = layer.findModule("forge");
|
||||
var fm = layer.findModule("net.minecraftforge.forge");
|
||||
if (fm.isPresent()) {
|
||||
getClass().getModule().addReads(fm.get());
|
||||
var clz = fm.map(l -> Class.forName(l, "net.minecraftforge.client.loading.NoVizFallback")).orElseThrow();
|
||||
|
||||
@@ -5,31 +5,31 @@
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
|
||||
import net.minecraftforge.fml.loading.EarlyLoadingException.ExceptionData;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.BackgroundScanHandler;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
|
||||
import net.minecraftforge.forgespi.locating.IModFile;
|
||||
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
|
||||
/**
|
||||
* Master list of all mods <em>in the loading context. This class cannot refer outside the
|
||||
* loading package</em>
|
||||
*/
|
||||
public class LoadingModList
|
||||
{
|
||||
public class LoadingModList {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static LoadingModList INSTANCE;
|
||||
private final List<ModFileInfo> modFiles;
|
||||
private final List<ModInfo> sortedList;
|
||||
@@ -37,15 +37,12 @@ public class LoadingModList
|
||||
private final List<EarlyLoadingException> preLoadErrors;
|
||||
private List<IModFile> brokenFiles;
|
||||
|
||||
private LoadingModList(final List<ModFile> modFiles, final List<ModInfo> sortedList)
|
||||
{
|
||||
private LoadingModList(final List<ModFile> modFiles, final List<ModInfo> sortedList) {
|
||||
this.modFiles = modFiles.stream()
|
||||
.map(ModFile::getModFileInfo)
|
||||
.map(ModFileInfo.class::cast)
|
||||
.collect(Collectors.toList());
|
||||
this.sortedList = sortedList.stream()
|
||||
.map(ModInfo.class::cast)
|
||||
.collect(Collectors.toList());
|
||||
.toList();
|
||||
this.sortedList = sortedList.stream().toList();
|
||||
this.fileById = this.modFiles.stream()
|
||||
.map(ModFileInfo::getMods)
|
||||
.flatMap(Collection::stream)
|
||||
@@ -54,50 +51,48 @@ public class LoadingModList
|
||||
this.preLoadErrors = new ArrayList<>();
|
||||
}
|
||||
|
||||
public static LoadingModList of(List<ModFile> modFiles, List<ModInfo> sortedList, final EarlyLoadingException earlyLoadingException)
|
||||
{
|
||||
public static LoadingModList of(List<ModFile> modFiles, List<ModInfo> sortedList, final EarlyLoadingException earlyLoadingException) {
|
||||
INSTANCE = new LoadingModList(modFiles, sortedList);
|
||||
if (earlyLoadingException != null)
|
||||
{
|
||||
INSTANCE.preLoadErrors.add(earlyLoadingException);
|
||||
}
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public static LoadingModList get() {
|
||||
return INSTANCE;
|
||||
}
|
||||
public void addCoreMods()
|
||||
{
|
||||
modFiles.stream()
|
||||
.map(ModFileInfo::getFile)
|
||||
.map(ModFile::getCoreMods)
|
||||
.flatMap(List::stream)
|
||||
.forEach(FMLLoader.getCoreModProvider()::addCoreMod);
|
||||
|
||||
public void addAccessTransformers() {
|
||||
var errors = new ArrayList<ExceptionData>();
|
||||
|
||||
for (ModFileInfo modFile : modFiles) {
|
||||
ModFile mod = modFile.getFile();
|
||||
for (var at : mod.getAccessTransformers()) {
|
||||
if (!Files.exists(at)) {
|
||||
var message = "Invalid mod file: " + modFile.getFile().getFileName() + ". Missing Access Transformer: " + at;
|
||||
errors.add(new ExceptionData(message));
|
||||
LOGGER.error(message);
|
||||
} else
|
||||
FMLLoader.addAccessTransformer(at, mod);
|
||||
}
|
||||
}
|
||||
|
||||
if (!errors.isEmpty())
|
||||
preLoadErrors.add(new EarlyLoadingException("Invalid Access Transformers", null, errors));
|
||||
}
|
||||
|
||||
public void addAccessTransformers()
|
||||
{
|
||||
modFiles.stream()
|
||||
.map(ModFileInfo::getFile)
|
||||
.forEach(mod -> mod.getAccessTransformer().ifPresent(path -> FMLLoader.addAccessTransformer(path, mod)));
|
||||
}
|
||||
|
||||
public void addForScanning(BackgroundScanHandler backgroundScanHandler)
|
||||
{
|
||||
public void addForScanning(BackgroundScanHandler backgroundScanHandler) {
|
||||
backgroundScanHandler.setLoadingModList(this);
|
||||
modFiles.stream()
|
||||
.map(ModFileInfo::getFile)
|
||||
.forEach(backgroundScanHandler::submitForScanning);
|
||||
}
|
||||
|
||||
public List<ModFileInfo> getModFiles()
|
||||
{
|
||||
public List<ModFileInfo> getModFiles() {
|
||||
return modFiles;
|
||||
}
|
||||
|
||||
public Path findResource(final String className)
|
||||
{
|
||||
public Path findResource(final String className) {
|
||||
for (ModFileInfo mf : modFiles) {
|
||||
final Path resource = mf.getFile().findResource(className);
|
||||
if (Files.exists(resource)) return resource;
|
||||
@@ -105,55 +100,11 @@ public class LoadingModList
|
||||
return null;
|
||||
}
|
||||
|
||||
public Enumeration<URL> findAllURLsForResource(final String resName) {
|
||||
final String resourceName;
|
||||
// strip a leading slash
|
||||
if (resName.startsWith("/")) {
|
||||
resourceName = resName.substring(1);
|
||||
} else {
|
||||
resourceName = resName;
|
||||
}
|
||||
return new Enumeration<URL>() {
|
||||
private final Iterator<ModFileInfo> modFileIterator = modFiles.iterator();
|
||||
private URL next;
|
||||
@Override
|
||||
public boolean hasMoreElements() {
|
||||
if (next!=null) return true;
|
||||
next = findNextURL();
|
||||
return next != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL nextElement() {
|
||||
if (next == null) {
|
||||
next = findNextURL();
|
||||
if (next == null) throw new NoSuchElementException();
|
||||
}
|
||||
URL result = next;
|
||||
next = null;
|
||||
return result;
|
||||
}
|
||||
|
||||
private URL findNextURL() {
|
||||
while (modFileIterator.hasNext()) {
|
||||
final ModFileInfo next = modFileIterator.next();
|
||||
final Path resource = next.getFile().findResource(resourceName);
|
||||
if (Files.exists(resource)) {
|
||||
return LamdbaExceptionUtils.uncheck(()->new URL("modjar://" + next.getMods().get(0).getModId() + "/" + resourceName));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public ModFileInfo getModFileById(String modid)
|
||||
{
|
||||
public ModFileInfo getModFileById(String modid) {
|
||||
return this.fileById.get(modid);
|
||||
}
|
||||
|
||||
public List<ModInfo> getMods()
|
||||
{
|
||||
public List<ModInfo> getMods() {
|
||||
return this.sortedList;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ public class MCPNamingService implements INameMappingService {
|
||||
|
||||
@Override
|
||||
public Map.Entry<String, String> understanding() {
|
||||
return Pair.of("srg", "mcp");
|
||||
return Map.entry("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 = ClassLoader.getSystemResource(mappingFileName); //We EXPLICITLY go through the SystemClassLoader here because this is dev-time only. And will be on the root classpath.
|
||||
URL path = MCPNamingService.class.getClassLoader().getResource(mappingFileName);
|
||||
if (path == null)
|
||||
return;
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ public class MavenCoordinateResolver {
|
||||
(!extension.isEmpty() ? "." + extension : ".jar");
|
||||
|
||||
String[] groups = groupId.split("\\.");
|
||||
Path result = Paths.get(groups[0]);
|
||||
Path result = Path.of(groups[0]);
|
||||
for (int i = 1; i < groups.length; i++) {
|
||||
result = result.resolve(groups[i]);
|
||||
}
|
||||
|
||||
+13
@@ -7,8 +7,10 @@ package net.minecraftforge.fml.loading;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import cpw.mods.jarhandling.SecureJar;
|
||||
import cpw.mods.modlauncher.Launcher;
|
||||
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
|
||||
import cpw.mods.modlauncher.api.NamedPath;
|
||||
import cpw.mods.modlauncher.api.TypesafeMap;
|
||||
import cpw.mods.modlauncher.serviceapi.ITransformerDiscoveryService;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
@@ -40,6 +42,17 @@ public class ModDirTransformerDiscoverer implements ITransformerDiscoveryService
|
||||
|
||||
@Override
|
||||
public void earlyInitialization(final String launchTarget, final String[] arguments) {
|
||||
boolean isMixinEnabledInDev = false;
|
||||
for (String arg : arguments) {
|
||||
if (arg.startsWith("-mixin") || arg.startsWith("--mixin")) {
|
||||
isMixinEnabledInDev = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Launcher.INSTANCE.blackboard().putIfAbsent(
|
||||
TypesafeMap.Key.getOrCreate(Launcher.INSTANCE.blackboard(), "isMixinEnabledInDev", Boolean.class),
|
||||
isMixinEnabledInDev
|
||||
);
|
||||
ImmediateWindowHandler.load(launchTarget, arguments);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ package net.minecraftforge.fml.loading;
|
||||
import com.google.common.graph.GraphBuilder;
|
||||
import com.google.common.graph.MutableGraph;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import net.minecraftforge.forgespi.language.IModInfo.ModVersion;
|
||||
import net.minecraftforge.fml.loading.EarlyLoadingException.ExceptionData;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
|
||||
@@ -20,229 +20,250 @@ import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
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.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.util.stream.Collectors.*;
|
||||
import static net.minecraftforge.fml.loading.LogMarkers.LOADING;
|
||||
|
||||
public class ModSorter
|
||||
{
|
||||
public class ModSorter {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private final UniqueModListBuilder uniqueModListBuilder;
|
||||
private List<ModFile> modFiles;
|
||||
private List<ModInfo> sortedList;
|
||||
private Map<String, IModInfo> modIdNameLookup;
|
||||
private List<ModFile> systemMods;
|
||||
|
||||
private ModSorter(final List<ModFile> modFiles)
|
||||
{
|
||||
this.uniqueModListBuilder = new UniqueModListBuilder(modFiles);
|
||||
}
|
||||
private record State(List<ModFile> files, List<ModInfo> mods) {}
|
||||
|
||||
private ModSorter() {}
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
public static LoadingModList sort(List<ModFile> mods, final List<ExceptionData> errors) {
|
||||
State systemMods = detectSystemMods(mods);
|
||||
List<ModFile> modFiles;
|
||||
|
||||
public static LoadingModList sort(List<ModFile> mods, final List<ExceptionData> errors)
|
||||
{
|
||||
final ModSorter ms = new ModSorter(mods);
|
||||
try {
|
||||
ms.buildUniqueList();
|
||||
modFiles = UniqueModListBuilder.buildUniqueList(mods).modFiles();
|
||||
} catch (EarlyLoadingException e) {
|
||||
// We cannot build any list with duped mods. We have to abort immediately and report it
|
||||
// Note this will never actually throw an error because the duplicate checks are done in ModDiscovererer before we get to this phase
|
||||
// So all this is really doing is wasting time.
|
||||
// But i'm leaving it here until I rewrite all of cpw's mod loading code because its such a clusterfuck.
|
||||
return LoadingModList.of(ms.systemMods, ms.systemMods.stream().map(mf->(ModInfo)mf.getModInfos().get(0)).collect(toList()), e);
|
||||
return LoadingModList.of(systemMods.files(), systemMods.mods(), 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(ms.verifyDependencyVersions().stream(), errors.stream()).toList();
|
||||
final List<ExceptionData> failedList = Stream.concat(verifyDependencyVersions(modFiles).stream(), errors.stream()).toList();
|
||||
|
||||
// if we miss one or the other, we abort now
|
||||
if (!failedList.isEmpty()) {
|
||||
return LoadingModList.of(ms.systemMods, ms.systemMods.stream().map(mf->(ModInfo)mf.getModInfos().get(0)).collect(toList()), new EarlyLoadingException("failure to validate mod list", null, failedList));
|
||||
return LoadingModList.of(systemMods.files(), systemMods.mods(), new EarlyLoadingException("failure to validate mod list", null, failedList));
|
||||
} else {
|
||||
// Otherwise, lets try and sort the modlist and proceed
|
||||
EarlyLoadingException earlyLoadingException = null;
|
||||
try {
|
||||
ms.sort();
|
||||
var sorted = sort(modFiles, named);
|
||||
return LoadingModList.of(sorted.files(), sorted.mods(), null);
|
||||
} catch (EarlyLoadingException e) {
|
||||
earlyLoadingException = e;
|
||||
// The only exception that can happen here is a cyclic exception, but fall back to system mods so we can display the nice screen.
|
||||
return LoadingModList.of(systemMods.files(), systemMods.mods(), e);
|
||||
}
|
||||
return LoadingModList.of(ms.modFiles, ms.sortedList, earlyLoadingException);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
private static State sort(final List<ModFile> modFiles, final Map<String, ModInfo> named) {
|
||||
final MutableGraph<ModInfo> graph = GraphBuilder.directed().build();
|
||||
|
||||
final List<ModFileInfo> sorted;
|
||||
try
|
||||
{
|
||||
sorted = TopologicalSort.topologicalSort(graph, Comparator.comparing(infos::get));
|
||||
}
|
||||
catch (CyclePresentException e)
|
||||
{
|
||||
Set<Set<ModFileInfo>> cycles = e.getCycles();
|
||||
if (LOGGER.isErrorEnabled(LOADING))
|
||||
{
|
||||
LOGGER.error(LOADING, "Mod Sorting failed.\nDetected Cycles: {}\n", cycles);
|
||||
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);
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
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 {
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
||||
LOGGER.error(LOADING, buf.toString());
|
||||
|
||||
throw new EarlyLoadingException("Sorting error", e, dataList);
|
||||
}
|
||||
this.sortedList = sorted.stream()
|
||||
.map(ModFileInfo::getMods)
|
||||
.<IModInfo>mapMulti(Iterable::forEach)
|
||||
.map(ModInfo.class::cast)
|
||||
.collect(toList());
|
||||
this.modFiles = sorted.stream()
|
||||
.map(ModFileInfo::getFile)
|
||||
.collect(toList());
|
||||
}
|
||||
|
||||
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);
|
||||
default -> {}
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
private static State detectSystemMods(final List<ModFile> modFiles) {
|
||||
// Capture system mods (ex. MC, Forge) here, so we can keep them for later
|
||||
var systemMods = List.of("minecraft", "forge");
|
||||
LOGGER.debug("Configured system mods: {}", systemMods);
|
||||
|
||||
this.systemMods = new ArrayList<>();
|
||||
var mods = new ArrayList<ModInfo>();
|
||||
var files = new ArrayList<ModFile>();
|
||||
for (var 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 {
|
||||
var mod = findMod(modFiles, systemMod);
|
||||
if (mod == null)
|
||||
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 List<EarlyLoadingException.ExceptionData> verifyDependencyVersions()
|
||||
{
|
||||
final var modVersions = modFiles.stream()
|
||||
.map(ModFile::getModInfos)
|
||||
.<IModInfo>mapMulti(Iterable::forEach)
|
||||
.collect(toMap(IModInfo::getModId, IModInfo::getVersion));
|
||||
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;
|
||||
}
|
||||
|
||||
final var modVersionDependencies = modFiles.stream()
|
||||
.map(ModFile::getModInfos)
|
||||
.<IModInfo>mapMulti(Iterable::forEach)
|
||||
.collect(groupingBy(Function.identity(), flatMapping(e -> e.getDependencies().stream(), toList())));
|
||||
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;
|
||||
|
||||
final var modRequirements = modVersionDependencies.values().stream()
|
||||
.<IModInfo.ModVersion>mapMulti(Iterable::forEach)
|
||||
.filter(mv -> mv.getSide().isCorrectSide())
|
||||
.collect(toSet());
|
||||
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 long mandatoryRequired = modRequirements.stream().filter(IModInfo.ModVersion::isMandatory).count();
|
||||
LOGGER.debug(LOADING, "Found {} mod requirements ({} mandatory, {} optional)", modRequirements.size(), mandatoryRequired, modRequirements.size() - mandatoryRequired);
|
||||
final var missingVersions = modRequirements.stream()
|
||||
.filter(mv -> (mv.isMandatory() || modVersions.containsKey(mv.getModId())) && this.modVersionNotContained(mv, modVersions))
|
||||
.collect(toSet());
|
||||
final long mandatoryMissing = missingVersions.stream().filter(IModInfo.ModVersion::isMandatory).count();
|
||||
LOGGER.debug(LOADING, "Found {} mod requirements missing ({} mandatory, {} optional)", missingVersions.size(), mandatoryMissing, missingVersions.size() - mandatoryMissing);
|
||||
|
||||
if (!missingVersions.isEmpty()) {
|
||||
if (mandatoryMissing > 0) {
|
||||
LOGGER.error(
|
||||
LOADING,
|
||||
"Missing or unsupported mandatory dependencies:\n{}",
|
||||
missingVersions.stream()
|
||||
.filter(IModInfo.ModVersion::isMandatory)
|
||||
.map(ver -> formatDependencyError(ver, modVersions))
|
||||
.collect(Collectors.joining("\n"))
|
||||
);
|
||||
}
|
||||
if (missingVersions.size() - mandatoryMissing > 0) {
|
||||
LOGGER.error(
|
||||
LOADING,
|
||||
"Unsupported installed optional dependencies:\n{}",
|
||||
missingVersions.stream()
|
||||
.filter(ver -> !ver.isMandatory())
|
||||
.map(ver -> formatDependencyError(ver, modVersions))
|
||||
.collect(Collectors.joining("\n"))
|
||||
);
|
||||
}
|
||||
final var missingMandatory = new HashSet<ModVersion>();
|
||||
final var missingOptional = new HashSet<ModVersion>();
|
||||
|
||||
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();
|
||||
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 Collections.emptyList();
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private static String formatDependencyError(IModInfo.ModVersion dependency, Map<String, ArtifactVersion> modVersions)
|
||||
{
|
||||
ArtifactVersion installed = modVersions.get(dependency.getModId());
|
||||
return String.format(
|
||||
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(
|
||||
"\tMod ID: '%s', Requested by: '%s', Expected range: '%s', Actual version: '%s'",
|
||||
dependency.getModId(),
|
||||
dependency.getOwner().getModId(),
|
||||
dependency.getVersionRange(),
|
||||
dep.getModId(),
|
||||
dep.getOwner().getModId(),
|
||||
dep.getVersionRange(),
|
||||
installed != null ? installed.toString() : "[MISSING]"
|
||||
);
|
||||
));
|
||||
}
|
||||
return String.join("\n", ret);
|
||||
}
|
||||
|
||||
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"))));
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -8,14 +8,8 @@ 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;
|
||||
@@ -28,148 +22,30 @@ import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||
import org.objectweb.asm.tree.AnnotationNode;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
import org.objectweb.asm.tree.FieldNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.Marker;
|
||||
import org.slf4j.MarkerFactory;
|
||||
|
||||
public class RuntimeDistCleaner implements ILaunchPluginService
|
||||
{
|
||||
public final 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);
|
||||
private static final String MOD = "Lnet/minecraftforge/fml/common/Mod;";
|
||||
|
||||
@Override
|
||||
public String name()
|
||||
{
|
||||
public String name() {
|
||||
return "runtimedistcleaner";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int processClassWithFlags(final Phase phase, final ClassNode classNode, final Type classType, final String reason)
|
||||
{
|
||||
AtomicBoolean changes = new AtomicBoolean();
|
||||
if (remove(classNode.visibleAnnotations, DIST))
|
||||
{
|
||||
LOGGER.error(DISTXFORM, "Attempted to load class {} for invalid dist {}", classNode.name, DIST);
|
||||
throw new RuntimeException("Attempted to load class "+ classNode.name + " for invalid dist "+ DIST);
|
||||
}
|
||||
|
||||
if (classNode.interfaces != null )
|
||||
{
|
||||
unpack(classNode.visibleAnnotations).stream()
|
||||
.filter(ann->Objects.equals(ann.desc, ONLYIN))
|
||||
.filter(ann->ann.values.indexOf("_interface") != -1)
|
||||
.filter(ann->!Objects.equals(((String[])ann.values.get(ann.values.indexOf("value") + 1))[1], DIST))
|
||||
.map(ann -> ((Type)ann.values.get(ann.values.indexOf("_interface") + 1)).getInternalName())
|
||||
.forEach(intf -> {
|
||||
if (classNode.interfaces.remove(intf)) {
|
||||
LOGGER.debug(DISTXFORM,"Removing Interface: {} implements {}", classNode.name, intf);
|
||||
changes.compareAndSet(false, true);
|
||||
}
|
||||
});
|
||||
|
||||
//Remove Class level @OnlyIn/@OnlyIns annotations, this is important if anyone gets ambitious and tries to reflect an annotation with _interface set.
|
||||
if (classNode.visibleAnnotations != null) {
|
||||
Iterator<AnnotationNode> itr = classNode.visibleAnnotations.iterator();
|
||||
while (itr.hasNext()) {
|
||||
AnnotationNode ann = itr.next();
|
||||
if (Objects.equals(ann.desc, ONLYIN) || Objects.equals(ann.desc, ONLYINS)) {
|
||||
LOGGER.debug(DISTXFORM,"Removing Class Annotation: {} @{}", classNode.name, ann.desc);
|
||||
itr.remove();
|
||||
changes.compareAndSet(false, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Iterator<FieldNode> fields = classNode.fields.iterator();
|
||||
while(fields.hasNext())
|
||||
{
|
||||
FieldNode field = fields.next();
|
||||
if (remove(field.visibleAnnotations, DIST))
|
||||
{
|
||||
LOGGER.debug(DISTXFORM,"Removing field: {}.{}", classNode.name, field.name);
|
||||
fields.remove();
|
||||
changes.compareAndSet(false, true);
|
||||
}
|
||||
}
|
||||
|
||||
LambdaGatherer lambdaGatherer = new LambdaGatherer();
|
||||
Iterator<MethodNode> methods = classNode.methods.iterator();
|
||||
while(methods.hasNext())
|
||||
{
|
||||
MethodNode method = methods.next();
|
||||
if (remove(method.visibleAnnotations, DIST))
|
||||
{
|
||||
LOGGER.debug(DISTXFORM,"Removing method: {}.{}{}", classNode.name, method.name, method.desc);
|
||||
methods.remove();
|
||||
lambdaGatherer.accept(method);
|
||||
changes.compareAndSet(false, true);
|
||||
}
|
||||
}
|
||||
|
||||
// remove dynamic synthetic lambda methods that are inside of removed methods
|
||||
for (List<Handle> dynamicLambdaHandles = lambdaGatherer.getDynamicLambdaHandles();
|
||||
!dynamicLambdaHandles.isEmpty(); dynamicLambdaHandles = lambdaGatherer.getDynamicLambdaHandles())
|
||||
{
|
||||
lambdaGatherer = new LambdaGatherer();
|
||||
methods = classNode.methods.iterator();
|
||||
while (methods.hasNext())
|
||||
{
|
||||
MethodNode method = methods.next();
|
||||
if ((method.access & Opcodes.ACC_SYNTHETIC) == 0) continue;
|
||||
for (Handle dynamicLambdaHandle : dynamicLambdaHandles)
|
||||
{
|
||||
if (method.name.equals(dynamicLambdaHandle.getName()) && method.desc.equals(dynamicLambdaHandle.getDesc()))
|
||||
{
|
||||
LOGGER.debug(DISTXFORM,"Removing lambda method: {}.{}{}", classNode.name, method.name, method.desc);
|
||||
methods.remove();
|
||||
lambdaGatherer.accept(method);
|
||||
changes.compareAndSet(false, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return changes.get() ? ComputeFlags.SIMPLE_REWRITE : ComputeFlags.NO_REWRITE;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static List<AnnotationNode> unpack(final List<AnnotationNode> anns) {
|
||||
if (anns == null) return Collections.emptyList();
|
||||
List<AnnotationNode> ret = anns.stream().filter(ann->Objects.equals(ann.desc, ONLYIN)).collect(Collectors.toList());
|
||||
anns.stream().filter(ann->Objects.equals(ann.desc, ONLYINS) && ann.values != null)
|
||||
.map( ann -> (List<AnnotationNode>)ann.values.get(ann.values.indexOf("value") + 1))
|
||||
.filter(v -> v != null)
|
||||
.forEach(v -> v.forEach(ret::add));
|
||||
return ret;
|
||||
}
|
||||
|
||||
private 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);
|
||||
private static final EnumSet<Phase> NAY = EnumSet.noneOf(Phase.class);
|
||||
|
||||
@Override
|
||||
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty) {
|
||||
// TODO: [FML][RuntimeDistCleaner] In 26.1, only return YAY for MC classes on mod dev server
|
||||
if (isEmpty)
|
||||
return NAY;
|
||||
|
||||
@@ -177,9 +53,203 @@ public class RuntimeDistCleaner implements ILaunchPluginService
|
||||
if (internalName.startsWith("net/minecraftforge/"))
|
||||
return NAY;
|
||||
|
||||
// No Vanilla classes use @OnlyIn(Dist.SERVER), so we can skip them entirely when we're running on a client
|
||||
if (FMLEnvironment.dist.isClient() && isMinecraftClass(internalName))
|
||||
return NAY;
|
||||
|
||||
return YAY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int processClassWithFlags(final Phase phase, final ClassNode classNode, final Type classType, final String reason) {
|
||||
var changed = false;
|
||||
|
||||
var annotations = unpack(classNode.visibleAnnotations);
|
||||
var isNonMinecraftClass = !isMinecraftClass(classNode.name);
|
||||
final class LazyInit {
|
||||
private LazyInit() {}
|
||||
private static final boolean CAN_EXPLODE = !FMLEnvironment.production && "21.6".equals(FMLLoader.versionInfo().mcVersion());
|
||||
}
|
||||
|
||||
if (remove(annotations, DIST)) {
|
||||
LOGGER.error(DISTXFORM, "Attempted to load class {} for invalid dist {}", classNode.name, DIST);
|
||||
throw new RuntimeException("Attempted to load class " + classNode.name + " for invalid dist " + DIST);
|
||||
}
|
||||
|
||||
if (!annotations.isEmpty()) {
|
||||
if (isNonMinecraftClass) {
|
||||
if (LazyInit.CAN_EXPLODE)
|
||||
throw new UnsupportedOperationException("Mod class " + classNode.name + " is annotated with @OnlyIn, this is no longer supported as it slowed down startup times");
|
||||
else
|
||||
LOGGER.warn(DISTXFORM, "Class {} is annotated with @OnlyIn, this is deprecated and won't work in a future MC release.", classNode.name);
|
||||
}
|
||||
|
||||
if (!FMLEnvironment.production && hasModAnnotation(classNode.visibleAnnotations)) {
|
||||
LOGGER.error(DISTXFORM, "Attempted to load class {} with @Mod and @OnlyIn/@OnlyIns annotations", classNode.name);
|
||||
throw new RuntimeException("Found @OnlyIn on @Mod class " + classNode.name + " - this is not allowed as it causes crashes. Remove the OnlyIn and consider setting clientSideOnly=true in the root of your mods.toml instead");
|
||||
}
|
||||
}
|
||||
|
||||
if (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) {
|
||||
if (LazyInit.CAN_EXPLODE)
|
||||
throw new UnsupportedOperationException("Field " + field.name + " in mod class " + classNode.name + " is annotated with @OnlyIn, this is no longer supported as it slowed down startup times");
|
||||
else
|
||||
LOGGER.warn(DISTXFORM, "Field {} in class {} is annotated with @OnlyIn, this is deprecated and won't work in a future MC release.", field.name, classNode.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
if (LazyInit.CAN_EXPLODE)
|
||||
throw new UnsupportedOperationException("Method " + method.name + " in mod class " + classNode.name + " is annotated with @OnlyIn, this is no longer supported as it slowed down startup times");
|
||||
else
|
||||
LOGGER.warn(DISTXFORM, "Method {} in class {} is annotated with @OnlyIn, this is deprecated and won't work in a future MC release.", method.name, classNode.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 hasModAnnotation(final List<AnnotationNode> anns) {
|
||||
if (anns == null || anns.isEmpty())
|
||||
return false;
|
||||
|
||||
for (var ann : anns) {
|
||||
if (ann.desc.equals(MOD))
|
||||
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 class LambdaGatherer extends MethodVisitor {
|
||||
private static final Handle META_FACTORY = new Handle(Opcodes.H_INVOKESTATIC,
|
||||
"java/lang/invoke/LambdaMetafactory", "metafactory",
|
||||
@@ -192,23 +262,21 @@ public class RuntimeDistCleaner implements ILaunchPluginService
|
||||
}
|
||||
|
||||
public void accept(MethodNode method) {
|
||||
Streams.stream(method.instructions.iterator()).
|
||||
filter(insnNode->insnNode.getType() == AbstractInsnNode.INVOKE_DYNAMIC_INSN).
|
||||
forEach(insnNode->insnNode.accept(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs)
|
||||
{
|
||||
if (META_FACTORY.equals(bsm))
|
||||
{
|
||||
Handle dynamicLambdaHandle = (Handle) bsmArgs[1];
|
||||
dynamicLambdaHandles.add(dynamicLambdaHandle);
|
||||
for (var insn : method.instructions) {
|
||||
if (insn.getType() == AbstractInsnNode.INVOKE_DYNAMIC_INSN)
|
||||
insn.accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Handle> getDynamicLambdaHandles()
|
||||
{
|
||||
@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 List<Handle> getDynamicLambdaHandles() {
|
||||
return dynamicLambdaHandles;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,16 +14,17 @@ import org.apache.commons.lang3.text.StrSubstitutor;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class StringSubstitutor {
|
||||
private static final Map<String, String> GLOBALS = Map.of(
|
||||
"mcVersion", FMLLoader.versionInfo().mcVersion(),
|
||||
"forgeVersion", FMLLoader.versionInfo().forgeVersion()
|
||||
);
|
||||
|
||||
public static String replace(final String in, final ModFile file) {
|
||||
return new StrSubstitutor(getStringLookup(file)).replace(in);
|
||||
}
|
||||
|
||||
private static StrLookup<String> getStringLookup(final ModFile file) {
|
||||
var globals = Map.of(
|
||||
"mcVersion", FMLLoader.versionInfo().mcVersion(),
|
||||
"forgeVersion", FMLLoader.versionInfo().forgeVersion()
|
||||
);
|
||||
return new StrLookup<String>() {
|
||||
return new StrLookup<>() {
|
||||
@Override
|
||||
public String lookup(String key) {
|
||||
var parts = key.split("\\.");
|
||||
@@ -32,7 +33,7 @@ public class StringSubstitutor {
|
||||
var pfx = parts[0];
|
||||
|
||||
if ("global".equals(pfx))
|
||||
return globals.get(parts[1]);
|
||||
return GLOBALS.get(parts[1]);
|
||||
else if ("file".equals(pfx) && file != null)
|
||||
return String.valueOf(file.getSubstitutionMap().get().get(parts[1]));
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.mojang.logging.LogUtils;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import org.apache.maven.artifact.versioning.ArtifactVersion;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -16,26 +17,19 @@ import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.stream.Collectors.groupingBy;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static net.minecraftforge.fml.loading.LogMarkers.LOADING;
|
||||
|
||||
public class UniqueModListBuilder
|
||||
{
|
||||
private final static Logger LOGGER = LogUtils.getLogger();
|
||||
@ApiStatus.Internal
|
||||
@Deprecated(since = "1.21.3", forRemoval = true) // TODO: [FML][Loading] Convert to package private in 1.22
|
||||
public final class UniqueModListBuilder {
|
||||
private UniqueModListBuilder() {}
|
||||
|
||||
private final List<ModFile> modFiles;
|
||||
|
||||
public UniqueModListBuilder(final List<ModFile> modFiles) {this.modFiles = modFiles;}
|
||||
|
||||
public UniqueModListData buildUniqueList()
|
||||
{
|
||||
List<ModFile> uniqueModList;
|
||||
List<ModFile> uniqueLibListWithVersion;
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
|
||||
public static UniqueModListData buildUniqueList(List<ModFile> modFiles) {
|
||||
// Collect mod files by module name. This will be used for deduping purposes
|
||||
final Map<String, List<ModFile>> modFilesByFirstId = modFiles.stream()
|
||||
.filter(mf -> mf.getModFileInfo() != null)
|
||||
@@ -46,13 +40,13 @@ public class UniqueModListBuilder
|
||||
.collect(groupingBy(UniqueModListBuilder::getModId));
|
||||
|
||||
// Select the newest by artifact version sorting of non-unique files thus identified
|
||||
uniqueModList = modFilesByFirstId.entrySet().stream()
|
||||
.map(this::selectNewestModInfo)
|
||||
final List<ModFile> uniqueModList = modFilesByFirstId.entrySet().stream()
|
||||
.map(UniqueModListBuilder::selectNewestModInfo)
|
||||
.toList();
|
||||
|
||||
// Select the newest by artifact version sorting of non-unique files thus identified
|
||||
uniqueLibListWithVersion = libFilesWithVersionByModuleName.entrySet().stream()
|
||||
.map(this::selectNewestModInfo)
|
||||
final List<ModFile> uniqueLibListWithVersion = libFilesWithVersionByModuleName.entrySet().stream()
|
||||
.map(UniqueModListBuilder::selectNewestModInfo)
|
||||
.toList();
|
||||
|
||||
// Transform to the full mod id list
|
||||
@@ -63,43 +57,40 @@ public class UniqueModListBuilder
|
||||
.collect(groupingBy(IModInfo::getModId));
|
||||
|
||||
// Transform to the full lib id list
|
||||
final Map<String, List<ModFile>> versionedLibIds = uniqueLibListWithVersion.stream()
|
||||
final List<List<ModFile>> versionedLibs = uniqueLibListWithVersion.stream()
|
||||
.map(UniqueModListBuilder::getModId)
|
||||
.collect(Collectors.toMap(
|
||||
Function.identity(),
|
||||
libFilesWithVersionByModuleName::get
|
||||
));
|
||||
.map(libFilesWithVersionByModuleName::get)
|
||||
.toList();
|
||||
|
||||
// Its theoretically possible that some mod has somehow moved an id to a secondary place, thus causing a dupe.
|
||||
// We can't handle this
|
||||
final List<String> dupedModErrors = modIds.values().stream()
|
||||
.filter(modInfos -> modInfos.size() > 1)
|
||||
.map(mods -> String.format("\tMod ID: '%s' from mod files: %s",
|
||||
mods.get(0).getModId(),
|
||||
mods.getFirst().getModId(),
|
||||
mods.stream()
|
||||
.map(modInfo -> modInfo.getOwningFile().getFile().getFileName()).collect(joining(", "))
|
||||
)).toList();
|
||||
|
||||
if (!dupedModErrors.isEmpty()) {
|
||||
LOGGER.error(LOADING, "Found duplicate mods:\n{}", dupedModErrors.stream().collect(joining("\n")));
|
||||
LOGGER.error(LOADING, "Found duplicate mods:\n{}", String.join("\n", dupedModErrors));
|
||||
throw new EarlyLoadingException("Duplicate mods found", null, dupedModErrors.stream()
|
||||
.map(s -> new EarlyLoadingException.ExceptionData(s))
|
||||
.map(EarlyLoadingException.ExceptionData::new)
|
||||
.toList());
|
||||
}
|
||||
|
||||
|
||||
final List<String> dupedLibErrors = versionedLibIds.values().stream()
|
||||
.filter(modFiles -> modFiles.size() > 1)
|
||||
final List<String> dupedLibErrors = versionedLibs.stream()
|
||||
.filter(libModFiles -> libModFiles.size() > 1)
|
||||
.map(mods -> String.format("\tLibrary: '%s' from files: %s",
|
||||
getModId(mods.get(0)),
|
||||
mods.stream()
|
||||
.map(modFile -> modFile.getFileName()).collect(joining(", "))
|
||||
getModId(mods.getFirst()),
|
||||
mods.stream().map(ModFile::getFileName).collect(joining(", "))
|
||||
)).toList();
|
||||
|
||||
if (!dupedLibErrors.isEmpty()) {
|
||||
LOGGER.error(LOADING, "Found duplicate plugins or libraries:\n{}", dupedLibErrors.stream().collect(joining("\n")));
|
||||
LOGGER.error(LOADING, "Found duplicate plugins or libraries:\n{}", String.join("\n", dupedLibErrors));
|
||||
throw new EarlyLoadingException("Duplicate plugins or libraries found", null, dupedLibErrors.stream()
|
||||
.map(s -> new EarlyLoadingException.ExceptionData(s))
|
||||
.map(EarlyLoadingException.ExceptionData::new)
|
||||
.toList());
|
||||
}
|
||||
|
||||
@@ -107,30 +98,28 @@ public class UniqueModListBuilder
|
||||
final Map<String, List<ModFile>> uniqueModFilesByFirstId = uniqueModList.stream()
|
||||
.collect(groupingBy(UniqueModListBuilder::getModId));
|
||||
|
||||
final List<ModFile> loadedList = new ArrayList<>();
|
||||
loadedList.addAll(uniqueModList);
|
||||
final List<ModFile> loadedList = new ArrayList<>(uniqueModList);
|
||||
loadedList.addAll(uniqueLibListWithVersion);
|
||||
|
||||
return new UniqueModListData(loadedList, uniqueModFilesByFirstId);
|
||||
}
|
||||
|
||||
private ModFile selectNewestModInfo(Map.Entry<String, List<ModFile>> fullList) {
|
||||
private static ModFile selectNewestModInfo(Map.Entry<String, List<ModFile>> fullList) {
|
||||
List<ModFile> modInfoList = fullList.getValue();
|
||||
if (modInfoList.size() > 1) {
|
||||
LOGGER.debug("Found {} mods for first modid {}, selecting most recent based on version data", modInfoList.size(), fullList.getKey());
|
||||
modInfoList.sort(Comparator.comparing(this::getVersion).reversed());
|
||||
LOGGER.debug("Selected file {} for modid {} with version {}", modInfoList.get(0).getFileName(), fullList.getKey(), this.getVersion(modInfoList.get(0)));
|
||||
modInfoList.sort(Comparator.comparing(UniqueModListBuilder::getVersion).reversed());
|
||||
LOGGER.debug("Selected file {} for modid {} with version {}", modInfoList.getFirst().getFileName(), fullList.getKey(), getVersion(modInfoList.getFirst()));
|
||||
}
|
||||
return modInfoList.get(0);
|
||||
return modInfoList.getFirst();
|
||||
}
|
||||
|
||||
private ArtifactVersion getVersion(final ModFile mf)
|
||||
{
|
||||
private static ArtifactVersion getVersion(final ModFile mf) {
|
||||
if (mf.getModFileInfo() == null || mf.getModInfos() == null || mf.getModInfos().isEmpty()) {
|
||||
return mf.getJarVersion();
|
||||
}
|
||||
|
||||
return mf.getModInfos().get(0).getVersion();
|
||||
return mf.getModInfos().getFirst().getVersion();
|
||||
}
|
||||
|
||||
private static String getModId(ModFile modFile) {
|
||||
@@ -138,9 +127,8 @@ public class UniqueModListBuilder
|
||||
return modFile.getSecureJar().name();
|
||||
}
|
||||
|
||||
return modFile.getModFileInfo().moduleName();
|
||||
return modFile.getModFileInfo().getMods().getFirst().getModId();
|
||||
}
|
||||
|
||||
public record UniqueModListData(List<ModFile> modFiles, Map<String, List<ModFile>> modFilesByFirstId) {}
|
||||
|
||||
}
|
||||
@@ -5,33 +5,53 @@
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import net.minecraftforge.forgespi.language.MavenVersionAdapter;
|
||||
import org.apache.maven.artifact.versioning.ArtifactVersion;
|
||||
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
|
||||
import org.apache.maven.artifact.versioning.VersionRange;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
@ApiStatus.Internal // since 1.21.1, will be made non-public in a later MC version
|
||||
public class VersionSupportMatrix {
|
||||
private static final HashMap<String, List<ArtifactVersion>> overrideVersions = new HashMap<>();
|
||||
private static final Map<String, List<ArtifactVersion>> OVERRIDE_VERSIONS;
|
||||
|
||||
static {
|
||||
final ArtifactVersion version = new DefaultArtifactVersion(FMLLoader.versionInfo().mcVersion());
|
||||
if (MavenVersionAdapter.createFromVersionSpec("[1.19.2]").containsVersion(version)) {
|
||||
// 1.19.2 is Compatible with 1.19.1
|
||||
add("languageloader.javafml", "42");
|
||||
add("mod.minecraft", "1.19.1");
|
||||
add("mod.forge", "42.0.9");
|
||||
if ("1.21.1".equals(FMLLoader.versionInfo().mcVersion())) {
|
||||
OVERRIDE_VERSIONS = Map.ofEntries(
|
||||
// 1.21.1 is compatible with 1.21
|
||||
entry("languageloader.javafml", "51"),
|
||||
entry("mod.minecraft", "1.21"),
|
||||
entry("mod.forge", "51.0.33")
|
||||
);
|
||||
} else {
|
||||
OVERRIDE_VERSIONS = Collections.emptyMap();
|
||||
}
|
||||
}
|
||||
private static void add(String key, String value) {
|
||||
overrideVersions.computeIfAbsent(key, k -> new ArrayList<>()).add(new DefaultArtifactVersion(value));
|
||||
}
|
||||
public static <T> boolean testVersionSupportMatrix(VersionRange declaredRange, String lookupId, String type, BiPredicate<String, VersionRange> standardLookup) {
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #testVersionSupportMatrix(VersionRange, String, String)} instead, unwrapping your BiPredicate.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "1.21.1")
|
||||
public static boolean testVersionSupportMatrix(VersionRange declaredRange, String lookupId, String type, BiPredicate<String, VersionRange> standardLookup) {
|
||||
if (standardLookup.test(lookupId, declaredRange)) return true;
|
||||
List<ArtifactVersion> custom = overrideVersions.get(type + "." + lookupId);
|
||||
return custom == null ? false : custom.stream().anyMatch(declaredRange::containsVersion);
|
||||
return testVersionSupportMatrix(declaredRange, lookupId, type);
|
||||
}
|
||||
|
||||
public static boolean testVersionSupportMatrix(VersionRange declaredRange, String lookupId, String type) {
|
||||
if (OVERRIDE_VERSIONS.isEmpty()) return false;
|
||||
List<ArtifactVersion> custom = OVERRIDE_VERSIONS.get(type + "." + lookupId);
|
||||
return custom != null && custom.stream().anyMatch(declaredRange::containsVersion);
|
||||
}
|
||||
|
||||
private static Map.Entry<String, List<ArtifactVersion>> entry(String typeAndLookupId, String declaredRange) {
|
||||
return Map.entry(typeAndLookupId, List.of(new DefaultArtifactVersion(declaredRange)));
|
||||
}
|
||||
|
||||
private static Map.Entry<String, List<ArtifactVersion>> entry(String typeAndLookupId, List<String> declaredRanges) {
|
||||
return Map.entry(typeAndLookupId, declaredRanges.stream().map(DefaultArtifactVersion::new).map(it -> (ArtifactVersion) it).toList());
|
||||
}
|
||||
}
|
||||
@@ -18,9 +18,7 @@ import org.apache.logging.log4j.util.PerformanceSensitive;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A wrapper for {@link HighlightConverter} that auto-disables ANSI when the terminal doesn't support it.
|
||||
@@ -49,10 +47,7 @@ public class ForgeHighlight {
|
||||
LOGGER.warn("Failed to invoke initializeTerminal on TCA", e);
|
||||
}
|
||||
if (!TerminalConsoleAppender.isAnsiSupported() && Arrays.stream(options).noneMatch(s -> s.equals("disableAnsi=true"))) {
|
||||
List<String> optionList = new ArrayList<>();
|
||||
optionList.add(options[0]);
|
||||
optionList.add("disableAnsi=true");
|
||||
options = optionList.toArray(new String[0]);
|
||||
options = new String[] { options[0], "disableAnsi=true" };
|
||||
}
|
||||
return HighlightConverter.newInstance(config, options);
|
||||
}
|
||||
|
||||
+94
-14
@@ -6,6 +6,7 @@
|
||||
package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
|
||||
import cpw.mods.jarhandling.JarMetadata;
|
||||
import cpw.mods.jarhandling.SecureJar;
|
||||
import net.minecraftforge.fml.loading.LogMarkers;
|
||||
@@ -18,33 +19,52 @@ import net.minecraftforge.forgespi.locating.IModProvider;
|
||||
import net.minecraftforge.forgespi.locating.ModFileLoadingException;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.module.InvalidModuleDescriptorException;
|
||||
import java.lang.module.ModuleDescriptor;
|
||||
import java.lang.module.ModuleDescriptor.Version;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public abstract class AbstractModProvider implements IModProvider {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
protected static final String MODS_TOML = "META-INF/mods.toml";
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
protected static final String MODS_TOML = "META-INF/mods.toml";
|
||||
protected static final String MODULE_INFO = "module-info.class";
|
||||
|
||||
protected IModLocator.ModFileOrException createMod(Path path) {
|
||||
return this.createMod(path, getDefaultJarModType());
|
||||
return createMod(path, false);
|
||||
}
|
||||
|
||||
protected IModLocator.ModFileOrException createMod(Path path, String defaultType) {
|
||||
@Nullable
|
||||
protected IModLocator.ModFileOrException createMod(Path path, boolean ignoreUnknown) {
|
||||
return createMod(path, ignoreUnknown, getDefaultJarModType());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected IModLocator.ModFileOrException createMod(Path path, boolean ignoreUnknown, String defaultType) {
|
||||
var debug = path.toString();
|
||||
if (debug.equals("/"))
|
||||
debug = path.toUri().toString();
|
||||
|
||||
var mjm = new ModJarMetadata();
|
||||
var sj = SecureJar.from(
|
||||
jar -> jar.moduleDataProvider().findFile(MODS_TOML).isPresent() ? mjm : JarMetadata.from(jar, path),
|
||||
path
|
||||
);
|
||||
SecureJar sj = null;
|
||||
try {
|
||||
sj = SecureJar.from(jar -> loadMetaFromJar(jar, mjm), path);
|
||||
} catch (Throwable t) {
|
||||
return new IModLocator.ModFileOrException(null, new ModFileLoadingException("Failed to create secure jar for \"" + debug + "\" - " + t.getMessage()));
|
||||
}
|
||||
|
||||
IModFile mod;
|
||||
var type = sj.moduleDataProvider().getManifest().getMainAttributes().getValue(ModFile.TYPE);
|
||||
@@ -52,30 +72,89 @@ public abstract class AbstractModProvider implements IModProvider {
|
||||
type = defaultType;
|
||||
|
||||
if (sj.moduleDataProvider().findFile(MODS_TOML).isPresent()) {
|
||||
LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", MODS_TOML, type, path);
|
||||
LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", MODS_TOML, type, debug);
|
||||
mod = new ModFile(sj, this, ModFileParser::modsTomlParser);
|
||||
if (ModFileInfo.DETECT_NON_FORGE_MODS_TOML && mod.getModFileInfo().getFileProperties().containsKey(ModFileInfo.NOT_A_FORGE_MOD_PROP)) {
|
||||
// ModJarMetadata is only used when loading mods.toml mods as ModJarMetadata
|
||||
mjm.setModFile(mod);
|
||||
if (mod.getModFileInfo().getFileProperties().containsKey(ModFileInfo.NOT_A_FORGE_MOD_PROP)) {
|
||||
LOGGER.error(LogMarkers.SCAN, "Unable to load file \"{}\" because its mods.toml is requesting an invalid javafml loaderVersion (use \"*\" if you want to allow all versions) and is missing a forge modId dependency declaration (see the sample mods.toml in the MDK).", path);
|
||||
return new IModLocator.ModFileOrException(null, new ModFileLoadingException("File \"%s\" is not a Forge mod and cannot be loaded. Look for a Forge version of this mod or consider alternative mods.".formatted(mod.getFileName())));
|
||||
}
|
||||
} else if (type != null) {
|
||||
LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", JarFile.MANIFEST_NAME, type, path);
|
||||
LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", JarFile.MANIFEST_NAME, type, debug);
|
||||
mod = new ModFile(sj, this, this::manifestParser, type);
|
||||
} else if (ignoreUnknown) {
|
||||
return null;
|
||||
} else
|
||||
return new IModLocator.ModFileOrException(null, new ModFileLoadingException("Invalid mod file found " + path));
|
||||
return new IModLocator.ModFileOrException(null, new ModFileLoadingException("Invalid mod file found " + debug));
|
||||
|
||||
mjm.setModFile(mod);
|
||||
return new IModLocator.ModFileOrException(mod, null);
|
||||
}
|
||||
|
||||
protected static JarMetadata loadMetaFromJar(SecureJar jar, ModJarMetadata mjm) {
|
||||
var info = jar.moduleDataProvider().open(MODULE_INFO).orElse(null);
|
||||
if (info != null) {
|
||||
try {
|
||||
var desc = ModuleDescriptor.read(info, jar::getPackages);
|
||||
var all = new HashSet<>(jar.getPackages());
|
||||
all.removeAll(desc.packages());
|
||||
if (!all.isEmpty()) {
|
||||
var missing = all.stream().sorted().collect(Collectors.joining(", "));
|
||||
LOGGER.error("Invalid module-info, missing packages " + missing);
|
||||
throw new ModFileLoadingException("Invalid module-info, missing packages " + missing);
|
||||
} else {
|
||||
return new JarMetadata() {
|
||||
@Override
|
||||
public String name() {
|
||||
return desc.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String version() {
|
||||
return desc.version().map(Version::toString).or(desc::rawVersion).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModuleDescriptor descriptor() {
|
||||
return desc;
|
||||
}
|
||||
};
|
||||
}
|
||||
} catch (InvalidModuleDescriptorException | IOException e) {
|
||||
LOGGER.error("Failed to parse " + jar.getPrimaryPath() + " module-info", e);
|
||||
throw new ModFileLoadingException("Invalid module-info: " + e.getMessage());
|
||||
} finally {
|
||||
try {
|
||||
info.close();
|
||||
} catch (IOException e) {}
|
||||
}
|
||||
}
|
||||
|
||||
if (jar.moduleDataProvider().findFile(MODS_TOML).isEmpty())
|
||||
return JarMetadata.from(jar, jar.getPrimaryPath());
|
||||
|
||||
return mjm;
|
||||
}
|
||||
|
||||
protected IModFileInfo manifestParser(final IModFile mod) {
|
||||
var mf = mod.getSecureJar().moduleDataProvider().getManifest().getMainAttributes();
|
||||
var license = mf.getValue("LICENSE");
|
||||
var dummy = new IConfigurable() {
|
||||
@Override
|
||||
public <T> Optional<T> getConfigElement(String key) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> getConfigElement(String... key) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends IConfigurable> getConfigList(String key) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends IConfigurable> getConfigList(String... key) {
|
||||
return Collections.emptyList();
|
||||
@@ -131,10 +210,11 @@ public abstract class AbstractModProvider implements IModProvider {
|
||||
}
|
||||
|
||||
private static final class Holder<T> {
|
||||
T value;
|
||||
private T value;
|
||||
}
|
||||
|
||||
private record DefaultModFileInfo(IModFile mod, String license, IConfigurable configurable) implements IModFileInfo, IConfigurable {
|
||||
@Override public <T> Optional<T> getConfigElement(final String string) { return Optional.empty(); }
|
||||
@Override public <T> Optional<T> getConfigElement(final String... strings) { return Optional.empty(); }
|
||||
@Override public List<? extends IConfigurable> getConfigList(final String... strings) { return null; }
|
||||
@Override public List<IModInfo> getMods() { return Collections.emptyList(); }
|
||||
|
||||
+6
-5
@@ -29,6 +29,7 @@ public class BackgroundScanHandler
|
||||
}
|
||||
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static final boolean DEBUG = LOGGER.isErrorEnabled(LogMarkers.SCAN);
|
||||
private final ExecutorService modContentScanner;
|
||||
private final List<ModFile> modFiles;
|
||||
private ScanStatus status;
|
||||
@@ -55,16 +56,16 @@ public class BackgroundScanHandler
|
||||
}
|
||||
status = ScanStatus.RUNNING;
|
||||
ImmediateWindowHandler.updateProgress("Scanning mod candidates");
|
||||
final CompletableFuture<ModFileScanData> future = CompletableFuture.supplyAsync(file::compileContent, modContentScanner)
|
||||
.whenComplete(file::setScanResult)
|
||||
.whenComplete((r,t)-> this.addCompletedFile(file,r,t));
|
||||
CompletableFuture<ModFileScanData> future = CompletableFuture.supplyAsync(file::compileContent, modContentScanner)
|
||||
.whenComplete(file::setScanResult);
|
||||
if (DEBUG) future = future.whenComplete((r, t) -> addCompletedFile(file, t));
|
||||
file.setFutureScanResult(future);
|
||||
}
|
||||
|
||||
private void addCompletedFile(final ModFile file, final ModFileScanData modFileScanData, final Throwable throwable) {
|
||||
private void addCompletedFile(final ModFile file, final Throwable throwable) {
|
||||
if (throwable != null) {
|
||||
status = ScanStatus.ERRORED;
|
||||
LOGGER.error(LogMarkers.SCAN,"An error occurred scanning file {}", file, throwable);
|
||||
LOGGER.error(LogMarkers.SCAN, "An error occurred scanning file {}", file, throwable);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-8
@@ -18,9 +18,9 @@ import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.jar.Attributes;
|
||||
@@ -28,7 +28,7 @@ import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public class ClasspathLocator extends AbstractModProvider implements IModLocator {
|
||||
public final class ClasspathLocator extends AbstractModProvider implements IModLocator {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static final Attributes.Name MOD_TYPE = new Attributes.Name("FMLModType");
|
||||
|
||||
@@ -74,17 +74,18 @@ public class ClasspathLocator extends AbstractModProvider implements IModLocator
|
||||
var ret = new ArrayList<ModFileOrException>();
|
||||
for (var path : claimed) {
|
||||
// Filter out anything found by the ServiceLoader
|
||||
if (!ModDirTransformerDiscoverer.isServiceProvider(path))
|
||||
if (!ModDirTransformerDiscoverer.isServiceProvider(path)
|
||||
&& !JarInJarDependencyLocator.isOption(path))
|
||||
ret.add(createMod(path));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private List<URL> getUrls(ClassLoader cl, String resource) {
|
||||
private static List<URL> getUrls(ClassLoader cl, String resource) {
|
||||
try {
|
||||
var lst = Collections.list(cl.getResources(resource));
|
||||
if (LOGGER.isDebugEnabled(LogMarkers.SCAN)) {
|
||||
Collections.sort(lst, (a, b) -> a.toString().compareTo(b.toString()));
|
||||
lst.sort(Comparator.comparing(URL::toString));
|
||||
LOGGER.debug(LogMarkers.SCAN, "Scanning Classloader: {} for {}", cl, resource);
|
||||
for (var url : lst)
|
||||
LOGGER.debug(LogMarkers.SCAN, "\t{}", url);
|
||||
@@ -104,11 +105,10 @@ public class ClasspathLocator extends AbstractModProvider implements IModLocator
|
||||
len += 2;
|
||||
}
|
||||
str = str.substring(0, str.length() - len);
|
||||
var path = Paths.get(URI.create(str));
|
||||
return path;
|
||||
return Path.of(URI.create(str));
|
||||
}
|
||||
|
||||
private static Path getPathFromResource(ClassLoader cl, String resource) {
|
||||
static Path getPathFromResource(ClassLoader cl, String resource) {
|
||||
var url = cl.getResource(resource);
|
||||
if (url == null)
|
||||
return null;
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import net.minecraftforge.forgespi.coremod.ICoreModFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public class CoreModFile implements ICoreModFile {
|
||||
private final Path internalPath;
|
||||
private final ModFile file;
|
||||
private final String name;
|
||||
|
||||
CoreModFile(final String name, final Path path, final ModFile file) {
|
||||
this.name = name;
|
||||
this.internalPath = path;
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Reader readCoreMod() throws IOException {
|
||||
return Files.newBufferedReader(this.internalPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getPath() {
|
||||
return this.internalPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Reader getAdditionalFile(final String fileName) throws IOException {
|
||||
return Files.newBufferedReader(file.findResource(fileName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOwnerId() {
|
||||
return this.file.getModInfos().get(0).getModId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{Name: " + name + ", Owner: " + getOwnerId() + " @ " + getPath() + "}";
|
||||
}
|
||||
}
|
||||
-1
@@ -9,7 +9,6 @@ import cpw.mods.modlauncher.api.LamdbaExceptionUtils.Supplier_WithExceptions;
|
||||
import net.minecraftforge.fml.loading.StringUtils;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
+622
-103
@@ -5,37 +5,60 @@
|
||||
|
||||
package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.electronwill.nightconfig.core.UnmodifiableConfig;
|
||||
import com.electronwill.nightconfig.core.file.FileConfig;
|
||||
import com.mojang.logging.LogUtils;
|
||||
|
||||
import cpw.mods.modlauncher.Launcher;
|
||||
import cpw.mods.modlauncher.api.TypesafeMap;
|
||||
import net.minecraftforge.fml.loading.EarlyLoadingException;
|
||||
import net.minecraftforge.fml.loading.EarlyLoadingException.ExceptionData;
|
||||
import net.minecraftforge.fml.loading.FMLEnvironment;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import net.minecraftforge.forgespi.locating.IDependencyLocator;
|
||||
import net.minecraftforge.forgespi.locating.IModFile;
|
||||
import net.minecraftforge.forgespi.locating.ModFileLoadingException;
|
||||
import net.minecraftforge.forgespi.locating.IModFile.Type;
|
||||
import net.minecraftforge.jarjar.metadata.ContainedJarIdentifier;
|
||||
import net.minecraftforge.jarjar.metadata.ContainedJarMetadata;
|
||||
import net.minecraftforge.jarjar.metadata.ContainedVersion;
|
||||
import net.minecraftforge.jarjar.metadata.MetadataIOHandler;
|
||||
import net.minecraftforge.jarjar.selection.JarSelector;
|
||||
import org.apache.maven.artifact.versioning.ArtifactVersion;
|
||||
|
||||
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
|
||||
import org.apache.maven.artifact.versioning.VersionRange;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.Marker;
|
||||
import org.slf4j.MarkerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.module.ModuleDescriptor;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.TreeMap;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public class JarInJarDependencyLocator extends AbstractModProvider implements IDependencyLocator {
|
||||
private static final String ROIMFS = "roimfs";
|
||||
private static final String COLOR_CODE = "\u00a7";
|
||||
private static final String RESET = COLOR_CODE + "r";
|
||||
private static final String YELLOW = COLOR_CODE + "e";
|
||||
@@ -43,32 +66,238 @@ public class JarInJarDependencyLocator extends AbstractModProvider implements ID
|
||||
private static final String GREEN = COLOR_CODE + "2";
|
||||
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static final Marker MARKER = MarkerFactory.getMarker("JAR-JAR"); // LogMarkers.SCAN;
|
||||
|
||||
private static final Map<Path, Option> OPTIONS = new HashMap<>();
|
||||
private static volatile boolean optionsLoaded = false;
|
||||
|
||||
private static final Attributes.Name MIXIN_CONFIGS_ATTR = new Attributes.Name("MixinConfigs");
|
||||
private static final Attributes.Name MIXIN_CONNECTOR_ATTR = new Attributes.Name("MixinConnector");
|
||||
private static final ContainedJarMetadata MIXIN_EXTRAS_DEPENDENCY = makeMixinExtraDependency();
|
||||
|
||||
private static ContainedJarMetadata makeMixinExtraDependency() {
|
||||
VersionRange range = null;
|
||||
try {
|
||||
range = VersionRange.createFromVersionSpec("[0,)");
|
||||
} catch (InvalidVersionSpecificationException e) {
|
||||
throw new RuntimeException("Failed to create version range for mixinextras-forge dependency", e);
|
||||
}
|
||||
|
||||
return new ContainedJarMetadata(
|
||||
new ContainedJarIdentifier("io.github.llamalad7", "mixinextras-forge"),
|
||||
new ContainedVersion(range, null),
|
||||
null, false
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "JarInJar";
|
||||
}
|
||||
|
||||
private int fsID = 0;
|
||||
private synchronized int nextId() {
|
||||
return fsID++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IModFile> scanMods(Iterable<IModFile> loadedMods) {
|
||||
final List<IModFile> sources = Lists.newArrayList();
|
||||
loadedMods.forEach(sources::add);
|
||||
var mods = new ArrayList<IModFile>();
|
||||
loadedMods.forEach(mods::add);
|
||||
|
||||
var dependenciesToLoad = JarSelector.detectAndSelect(
|
||||
sources,
|
||||
this::loadResourceFromModFile,
|
||||
this::loadModFileFrom,
|
||||
this::identifyMod,
|
||||
this::exception
|
||||
);
|
||||
var selector = new Selector(mods);
|
||||
|
||||
if (dependenciesToLoad.isEmpty()) {
|
||||
// Add a synthetic Mixin-Extras dependency if we detect anyone using Mixin
|
||||
if (anyMixinsLoaded(mods)) {
|
||||
if (!selector.isRequired(MIXIN_EXTRAS_DEPENDENCY.identifier()))
|
||||
selector.addRequirement(MIXIN_EXTRAS_DEPENDENCY);
|
||||
} else if (selector.entries.size() == mods.size()) {
|
||||
LOGGER.info("No dependencies to load found. Skipping!");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
LOGGER.info("Found {} dependencies adding them to mods collection", dependenciesToLoad.size());
|
||||
return dependenciesToLoad;
|
||||
loadOptions();
|
||||
|
||||
// Ideally, the json would be in order so we only have to loop once. But its written this way just in case.
|
||||
var seen = new HashSet<Option>();
|
||||
var added = false;
|
||||
do {
|
||||
added = false;
|
||||
for (var entry : OPTIONS.values()) {
|
||||
if (seen.contains(entry) || !selector.isRequired(entry.meta.identifier()))
|
||||
continue;
|
||||
|
||||
seen.add(entry);
|
||||
|
||||
entry.addTo(selector);
|
||||
|
||||
if (entry.deps != null && !entry.deps.isEmpty()) {
|
||||
added = true;
|
||||
for (var dep : entry.deps)
|
||||
selector.addRequirement(dep);
|
||||
}
|
||||
}
|
||||
} while (added);
|
||||
|
||||
var selected = selector.select();
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
var ids = new TreeMap<String, List<Entry>>();
|
||||
for (var entry : selector.entries.values()) {
|
||||
if (entry != FAILED && entry.coord != null)
|
||||
ids.computeIfAbsent(entry.coord, id -> new ArrayList<>()).add(entry);
|
||||
}
|
||||
|
||||
for (var entry : ids.entrySet()) {
|
||||
LOGGER.info(MARKER, "JarJar Candidated for {}", entry.getKey());
|
||||
Collections.sort(entry.getValue());
|
||||
for (var option : entry.getValue())
|
||||
LOGGER.info(MARKER, "\t{}{}", selected.contains(option) ? '*' : ' ', option);
|
||||
}
|
||||
}
|
||||
|
||||
if (selected.isEmpty()) {
|
||||
LOGGER.info("No dependencies to load found. Skipping!");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/* Since Java 11, the JRE decompresses all zip files to memory when getting a ByteChannel for them.
|
||||
* JarJar only targets Minecraft 1.18+ which is Java17+ Which means we can exploit the fact that
|
||||
* these are Disjointed file systems, and close all unselected libraries.
|
||||
*
|
||||
* https://github.com/openjdk/jdk11u/blob/25a9b9e5594e88c9e1e66f95c7ca45d5a9da8854/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java#L703
|
||||
*
|
||||
* Combined with the fact that the JarJarFileSystem has always simply called into FileSystems.newFileSystem for its wrapped targets.
|
||||
* https://github.com/MinecraftForge/JarJar/blame/b39b2051439c81519d826d38898778bf3081356f/src/main/java/net/minecraftforge/jarjar/nio/pathfs/PathFileSystem.java#L38
|
||||
*
|
||||
* This effectively means that JarJar has ALWAYS been a wrapper around in-memory copies of every jar it scans.
|
||||
*
|
||||
* It also never closes these FileSystems, which potentially means they are leaked if the GC can't inherently GC them.
|
||||
*
|
||||
* Ideally we would extract these contained jars to a temp folder, so that we can use the nice optimizations Java has for ZipFile/JarFile.
|
||||
* But in order to do that we would need to write the file to a place that could potentially have conflicts.
|
||||
* To resolve those conflicts we should take a hint out of maven/gradle/minecraft's book and key the filepath based on a hash of the file being extracted.
|
||||
* However, because that hash is no available in existing metadata we would need to re-calculate it.
|
||||
*
|
||||
* To do it in an efficient way we I needed to be able to share the single opened FileSystem with both the SecureJar implementation, and with our recursive search.
|
||||
* So I use my new ReadOnlyInMemoryFileSystem which is specifically designed to use the same backing array for the JarInJar search as it does with the resulting dependency
|
||||
* selected SecureJars.
|
||||
*
|
||||
* This means that if we wanted to we could rather efficiently calculate a hash for extracting the file. However, it also means that we don't have to extract to file to
|
||||
* support ZipFileSystem caching properly. So as long as we are okay with not using the JRE's optimized ZipFile implementation, or we want to support legacy JarInJar metadata,
|
||||
* using this efficient in memory file system is the best option performance wise. Extracting the files during launch without an efficient caching/unique identifier system
|
||||
* would incur a lot of Disc IO which can be super slow depending on your setup.
|
||||
*
|
||||
* So, it is a stated goal of our future re-write to extract files into a cache directory to gain the benefits of JRE optimizations, using a FileSystem much simpler then
|
||||
* the existing JarInJarFileSystem is good enough.
|
||||
*/
|
||||
|
||||
// Lets close all unselected FileSystems
|
||||
var wasSelected = new HashSet<>(selected);
|
||||
for (var entry : selector.entries.values())
|
||||
entry.cleanup(wasSelected.contains(entry));
|
||||
|
||||
LOGGER.info(MARKER, "Found {} dependencies adding them to mods collection", selected.size());
|
||||
var ret = new ArrayList<IModFile>(selected.size());
|
||||
|
||||
// Now lets build the actual mod files
|
||||
for (var entry : selected) {
|
||||
try {
|
||||
var root = entry.getFinalPath();
|
||||
var mod = createMod(root, false, entry.type.name());
|
||||
|
||||
if (mod.ex() != null)
|
||||
throw fail("Failed to load JarInJar file " + entry, mod.ex());
|
||||
|
||||
ret.add(mod.file());
|
||||
} catch (EarlyLoadingException e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
throw fail("Failed to load JarInJar file " + entry, e);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static Boolean anyMixinsLoaded = null;
|
||||
private static boolean anyMixinsLoaded(List<IModFile> mods) {
|
||||
var ret = anyMixinsLoaded;
|
||||
if (ret != null) return ret;
|
||||
|
||||
if (!FMLEnvironment.production
|
||||
&& Launcher.INSTANCE.blackboard().get(TypesafeMap.Key.getOrCreate(Launcher.INSTANCE.blackboard(), "isMixinEnabledInDev", Boolean.class)).orElse(false)) {
|
||||
anyMixinsLoaded = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
for (var mod : mods) {
|
||||
var attributes = mod.getSecureJar().moduleDataProvider().getManifest().getMainAttributes();
|
||||
if (attributes.getValue(MIXIN_CONFIGS_ATTR) != null || attributes.getValue(MIXIN_CONNECTOR_ATTR) != null) {
|
||||
anyMixinsLoaded = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
anyMixinsLoaded = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
private record Options(List<OptionMetadata> options) {}
|
||||
private record OptionMetadata(String resource, Type layer, String id, List<ContainedJarMetadata> deps, ContainedJarMetadata meta, boolean nested) {}
|
||||
|
||||
private static void loadOptions() {
|
||||
if (optionsLoaded)
|
||||
return;
|
||||
|
||||
synchronized (OPTIONS) {
|
||||
if (optionsLoaded)
|
||||
return;
|
||||
|
||||
optionsLoaded = true;
|
||||
|
||||
var self = JarInJarDependencyLocator.class;
|
||||
|
||||
try (var stream = self.getModule().getResourceAsStream("/jarjar_options.json")) {
|
||||
if (stream == null) {
|
||||
LOGGER.error(MARKER, "Failed to find jarjar_options.json");
|
||||
return;
|
||||
}
|
||||
|
||||
var meta = MetadataIOHandler.getGson().fromJson(new InputStreamReader(stream), Options.class);
|
||||
//var meta = MetadataIOHandler.fromStream(stream).orElse(null);
|
||||
if (meta == null) {
|
||||
LOGGER.error(MARKER, "Corrupted jarjar_options.json");
|
||||
return;
|
||||
}
|
||||
|
||||
for (var jar : meta.options()) {
|
||||
Path path = ClasspathLocator.getPathFromResource(self.getClassLoader(), jar.resource());
|
||||
if (path == null) {
|
||||
LOGGER.error(MARKER, "Failed to find JarJar option for {}", jar.resource());
|
||||
continue;
|
||||
}
|
||||
|
||||
LOGGER.debug(MARKER, "Found JarJar Option: {}", path.toAbsolutePath());
|
||||
if (jar.nested())
|
||||
OPTIONS.put(path, new NestedOption(jar.id(), path, jar.layer(), jar.meta(), jar.deps()));
|
||||
else
|
||||
OPTIONS.put(path, new Option(jar.id(), path, jar.layer(), jar.meta(), jar.deps()));
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
LOGGER.error(MARKER, "Failed to read JarJar Options file", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static boolean isOption(Path path) {
|
||||
loadOptions();
|
||||
return OPTIONS.containsKey(path);
|
||||
}
|
||||
|
||||
private static EarlyLoadingException fail(String message, @Nullable Throwable cause) {
|
||||
LOGGER.error(MARKER, message);
|
||||
return new EarlyLoadingException(message, cause, Collections.emptyList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -76,89 +305,7 @@ public class JarInJarDependencyLocator extends AbstractModProvider implements ID
|
||||
return IModFile.Type.GAMELIBRARY.name();
|
||||
}
|
||||
|
||||
protected Optional<IModFile> loadModFileFrom(IModFile file, Path path) {
|
||||
try {
|
||||
final Path pathInModFile = file.findResource(path.toString());
|
||||
final URI filePathUri = new URI("jij:" + (pathInModFile.toAbsolutePath().toUri().getRawSchemeSpecificPart())).normalize();
|
||||
final Map<String, ?> outerFsArgs = ImmutableMap.of("packagePath", pathInModFile);
|
||||
final FileSystem zipFS = FileSystems.newFileSystem(filePathUri, outerFsArgs);
|
||||
final Path pathInFS = zipFS.getPath("/");
|
||||
final IModFile.Type parentType = file.getType();
|
||||
final String modType;
|
||||
if (parentType == IModFile.Type.LIBRARY || parentType == IModFile.Type.LANGPROVIDER) {
|
||||
modType = IModFile.Type.LIBRARY.name();
|
||||
} else {
|
||||
modType = IModFile.Type.GAMELIBRARY.name();
|
||||
}
|
||||
return Optional.of(createMod(pathInFS, modType).file());
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to load mod file {} from {}", path, file.getFileName());
|
||||
final RuntimeException exception = new ModFileLoadingException("Failed to load mod file " + file.getFileName());
|
||||
exception.initCause(e);
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
protected EarlyLoadingException exception(Collection<JarSelector.ResolutionFailureInformation<IModFile>> failedDependencies) {
|
||||
final List<EarlyLoadingException.ExceptionData> errors = failedDependencies.stream()
|
||||
.filter(entry -> !entry.sources().isEmpty()) //Should never be the case, but just to be sure
|
||||
.map(this::buildExceptionData)
|
||||
.toList();
|
||||
|
||||
return new EarlyLoadingException(failedDependencies.size() + " Dependency restrictions were not met.", null, errors);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private EarlyLoadingException.ExceptionData buildExceptionData(JarSelector.ResolutionFailureInformation<IModFile> entry) {
|
||||
return new EarlyLoadingException.ExceptionData(
|
||||
getErrorTranslationKey(entry),
|
||||
entry.identifier().group() + ":" + entry.identifier().artifact(),
|
||||
entry.sources()
|
||||
.stream()
|
||||
.flatMap(this::getModWithVersionRangeStream)
|
||||
.map(this::formatError)
|
||||
.collect(Collectors.joining(", "))
|
||||
);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String getErrorTranslationKey(JarSelector.ResolutionFailureInformation<IModFile> entry) {
|
||||
return entry.failureReason() == JarSelector.FailureReason.VERSION_RESOLUTION_FAILED ?
|
||||
"fml.dependencyloading.conflictingdependencies" :
|
||||
"fml.dependencyloading.mismatchedcontaineddependencies";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Stream<ModWithVersionRange> getModWithVersionRangeStream(JarSelector.SourceWithRequestedVersionRange<IModFile> file) {
|
||||
return file.sources()
|
||||
.stream()
|
||||
.map(IModFile::getModFileInfo)
|
||||
.flatMap(modFileInfo -> modFileInfo.getMods().stream())
|
||||
.map(modInfo -> new ModWithVersionRange(modInfo, file.requestedVersionRange(), file.includedVersion()));
|
||||
}
|
||||
|
||||
protected Optional<InputStream> loadResourceFromModFile(IModFile modFile, Path path) {
|
||||
try {
|
||||
var pathInModFile = modFile.findResource(path.toString());
|
||||
if (!Files.exists(pathInModFile)) {
|
||||
LOGGER.debug("Failed to load resource {} from {}, it does not contain dependency information.", path, modFile.getFileName());
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(Files.newInputStream(pathInModFile));
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to load resource {} from mod {}, cause {}", path, modFile.getFileName(), e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String formatError(ModWithVersionRange modWithVersionRange){
|
||||
return YELLOW + modWithVersionRange.modInfo().getModId() + RESET + " - " +
|
||||
RED + modWithVersionRange.versionRange().toString() + RESET + " - " +
|
||||
GREEN + modWithVersionRange.artifactVersion().toString() + RESET;
|
||||
}
|
||||
|
||||
protected String identifyMod(IModFile modFile) {
|
||||
protected static String identifyMod(IModFile modFile) {
|
||||
if (modFile.getModFileInfo() != null && !modFile.getModInfos().isEmpty())
|
||||
return modFile.getModInfos().stream().map(IModInfo::getModId).collect(Collectors.joining());
|
||||
|
||||
@@ -169,5 +316,377 @@ public class JarInJarDependencyLocator extends AbstractModProvider implements ID
|
||||
return modFile.getFileName();
|
||||
}
|
||||
|
||||
private record ModWithVersionRange(IModInfo modInfo, VersionRange versionRange, ArtifactVersion artifactVersion) {}
|
||||
private static final Entry FAILED = new Entry("FAILED ENTRY", Path.of(""), Type.LIBRARY, null);
|
||||
private static sealed class Entry implements Comparable<Entry> {
|
||||
final String id;
|
||||
final Path path;
|
||||
final Type type;
|
||||
final @Nullable String coord;
|
||||
|
||||
private Entry(String id, Path path, Type type, @Nullable String coord) {
|
||||
this.id = id;
|
||||
this.path = path;
|
||||
this.type = type;
|
||||
this.coord = coord;
|
||||
}
|
||||
|
||||
Path getFinalPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
void cleanup(boolean selected) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (path.getFileName() == null)
|
||||
return path.toUri().toString();
|
||||
return path.getFileName().toString();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Path getResource(String path) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Entry o) {
|
||||
return o.toString().compareTo(toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ModEntry extends Entry {
|
||||
private final IModFile mod;
|
||||
|
||||
ModEntry(IModFile mod) {
|
||||
super(identifyMod(mod), mod.getFilePath(), mod.getType(), null);
|
||||
this.mod = mod;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
Path getResource(String path) {
|
||||
return mod.findResource(path);
|
||||
}
|
||||
}
|
||||
|
||||
private static sealed class Option extends Entry {
|
||||
final ContainedJarMetadata meta;
|
||||
final List<ContainedJarMetadata> deps;
|
||||
|
||||
private Option(String id, Path path, Type type, ContainedJarMetadata meta, List<ContainedJarMetadata> deps) {
|
||||
super(id, path, type, meta.identifier().group() + ':' + meta.identifier().artifact());
|
||||
this.meta = meta;
|
||||
this.deps = deps;
|
||||
}
|
||||
|
||||
public void addTo(Selector selector) {
|
||||
selector.option(this, meta);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class NestedOption extends Option {
|
||||
private FileSystem zip;
|
||||
private Path root;
|
||||
|
||||
private NestedOption(String id, Path path, Type type, ContainedJarMetadata meta, List<ContainedJarMetadata> deps) {
|
||||
super(id, path, type, meta, deps);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTo(Selector selector) {
|
||||
super.addTo(selector);
|
||||
selector.add(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
void cleanup(boolean selected) {
|
||||
if (this.zip != null) {
|
||||
try {
|
||||
zip.close();
|
||||
} catch (IOException e) {
|
||||
LOGGER.error(MARKER, "Failed to close unselected FileSystem {}", this, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Path getResource(String path) {
|
||||
if (zip == null) {
|
||||
try {
|
||||
this.zip = FileSystems.newFileSystem(this.path);
|
||||
this.root = zip.getRootDirectories().iterator().next();
|
||||
} catch (IOException e) {
|
||||
LOGGER.error(MARKER, "Failed to open FileSystem for option root {}", this, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
var target = root.resolve(path);
|
||||
if (Files.exists(target))
|
||||
return target;
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static final class Nested extends Entry {
|
||||
private final Entry parent;
|
||||
private final String nestedPath;
|
||||
private final Path zipPath;
|
||||
|
||||
Nested(String id, Path path, Type type, String coords, Entry parent, String nestedPath, Path zipPath) {
|
||||
super(id, path, type, coords);
|
||||
this.parent = parent;
|
||||
this.nestedPath = nestedPath;
|
||||
this.zipPath = zipPath;
|
||||
}
|
||||
|
||||
/*
|
||||
* Due to a bug in Mixin, anything that is not on the DefaultFileSystem MUST be a directory.
|
||||
* Or else it can't resolve resources correctly. So for anything we put on the roimfs we need
|
||||
* to return the nested zip file system.
|
||||
*
|
||||
* This will be an annoying to backport, but we can cross that bridge when we come to it.
|
||||
*
|
||||
* See: https://github.com/SpongePowered/Mixin/blob/4053421aa10aaac6127d969028a29c94fe3054f6/src/main/java/org/spongepowered/asm/launch/platform/MainAttributes.java#L102
|
||||
*/
|
||||
@Override
|
||||
public Path getFinalPath() {
|
||||
return zipPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
void cleanup(boolean selected) {
|
||||
if (selected)
|
||||
return;
|
||||
|
||||
LOGGER.info(MARKER, "Closeing unselected FileSystem {}", this);
|
||||
try {
|
||||
zipPath.getFileSystem().close();
|
||||
path.getFileSystem().close();
|
||||
} catch (IOException e) {
|
||||
LOGGER.error(MARKER, "Failed to close unselected FileSystem {}", this, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
Path getResource(String path) {
|
||||
return zipPath.resolve(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return parent + "!/" + nestedPath;
|
||||
}
|
||||
}
|
||||
|
||||
private record Key(Entry parent, String path) {}
|
||||
|
||||
private final class Selector extends JarSelector<Entry> {
|
||||
private final HashMap<Key, Entry> entries = new HashMap<>();
|
||||
private final HashMap<Entry, HashMap<String, String>> children = new HashMap<>();
|
||||
|
||||
private Selector(Iterable<IModFile> mods) {
|
||||
for (var mod : mods)
|
||||
this.force(new ModEntry(mod));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void force(Entry entry) {
|
||||
super.force(entry);
|
||||
entries.put(new Key(entry, ""), entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void option(Entry entry, ContainedJarMetadata meta) {
|
||||
super.option(entry, meta);
|
||||
entries.put(new Key(entry, ""), entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(Entry entry) {
|
||||
super.add(entry);
|
||||
entries.put(new Key(entry, ""), entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
protected InputStream getResource(Entry source, String path) {
|
||||
try {
|
||||
Path target = source.getResource(path);
|
||||
if (target == null || !Files.exists(target)) {
|
||||
LOGGER.debug(MARKER, "Failed to load resource {} from {}, it does not contain dependency information.", path, source);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Read the metadata file and store identifiers so we can group them later.
|
||||
if (JarSelector.CONTAINED_JARS_METADATA_PATH.equals(path)) {
|
||||
try (var is = Files.newInputStream(target)) {
|
||||
var meta = MetadataIOHandler.fromStream(is).orElse(null);
|
||||
if (meta == null)
|
||||
return Files.newInputStream(target);
|
||||
var children = new HashMap<String, String>();
|
||||
for (var child : meta.jars())
|
||||
children.put(child.path().replace('\\', '/'), child.identifier().group() + ':' + child.identifier().artifact());
|
||||
this.children.put(source, children);
|
||||
}
|
||||
}
|
||||
|
||||
return Files.newInputStream(target);
|
||||
} catch (Exception e) {
|
||||
LOGGER.error(MARKER, "Failed to load resource {} from {}, cause {}", path, source, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Entry getNested(Entry source, String path) {
|
||||
var cleaned = path.replace('\\', '/');
|
||||
var key = new Key(source, cleaned);
|
||||
var entry = entries.get(key);
|
||||
if (entry == null) {
|
||||
entry = createNested(source, path, cleaned);
|
||||
if (entry == null)
|
||||
entry = FAILED;
|
||||
entries.put(key, entry);
|
||||
}
|
||||
return entry == FAILED ? null : entry;
|
||||
}
|
||||
|
||||
private Entry createNested(Entry source, String path, String cleaned) {
|
||||
var targetPath = source.getResource(path);
|
||||
|
||||
if (targetPath == null || !Files.isRegularFile(targetPath))
|
||||
return null;
|
||||
|
||||
Path roimfsPath = null;
|
||||
Path zipPath = null;
|
||||
try {
|
||||
// Lets copy the uncompressed file to memory, and then create a ReadOnlyInMemorryFileSystem for it, this allows us to reference it by URI.
|
||||
// Which is needed because a lot of things require navigation via URI.
|
||||
// And ZipFileSystem only caches the FileSystem instance when accessed via URI.
|
||||
byte[] data = Files.readAllBytes(targetPath);
|
||||
String filename = targetPath.getFileName().toString();
|
||||
URI uri = new URI(ROIMFS, "jar-jar-" + nextId() + "/" + filename, null);
|
||||
var roimfs = FileSystems.newFileSystem(uri, Collections.singletonMap("data", data));
|
||||
roimfsPath = roimfs.getPath(filename);
|
||||
var zip = FileSystems.newFileSystem(new URI("jar:" + roimfsPath.toUri()), Collections.emptyMap());
|
||||
zipPath = zip.getRootDirectories().iterator().next(); // Get first root, which on ZipFileSystem is the only root
|
||||
} catch (IOException | URISyntaxException e) {
|
||||
LOGGER.error(MARKER, "Failed to create FileSystem for nested jar {}", source, e);
|
||||
return null;
|
||||
}
|
||||
|
||||
var mf = new Manifest();
|
||||
var mfPath = zipPath.resolve(JarFile.MANIFEST_NAME);
|
||||
if (Files.exists(mfPath)) {
|
||||
try (var is = Files.newInputStream(mfPath)) {
|
||||
mf = new Manifest(is);
|
||||
} catch (IOException e) {
|
||||
LOGGER.error(MARKER, "Failed to read manifest from {}", source, e);
|
||||
}
|
||||
}
|
||||
|
||||
IModFile.Type type = null;
|
||||
String id = null;
|
||||
var tomlPath = zipPath.resolve(MODS_TOML);
|
||||
if (Files.exists(tomlPath)) {
|
||||
var cfg = FileConfig.builder(tomlPath).build();
|
||||
cfg.load();
|
||||
cfg.close();
|
||||
if (cfg.getOrElse("mods", null) instanceof Collection collection) {
|
||||
@SuppressWarnings("unchecked")
|
||||
var mods = (Collection<UnmodifiableConfig>)collection;
|
||||
if (!mods.isEmpty()) {
|
||||
var first = mods.iterator().next();
|
||||
var modId = first.getOrElse("modId", null);
|
||||
if (modId instanceof String str)
|
||||
id = str;
|
||||
}
|
||||
}
|
||||
var value = mf.getMainAttributes().getValue(ModFile.TYPE);
|
||||
if (value != null)
|
||||
type = IModFile.Type.valueOf(value);
|
||||
else
|
||||
type = IModFile.Type.MOD;
|
||||
} else {
|
||||
var value = mf.getMainAttributes().getValue(ModFile.TYPE);
|
||||
if (value != null)
|
||||
type = IModFile.Type.valueOf(value);
|
||||
else {
|
||||
// No mods.toml, no entry value, so decide based on the container's type
|
||||
if (source.type == IModFile.Type.LIBRARY || source.type == IModFile.Type.LANGPROVIDER)
|
||||
type = IModFile.Type.LIBRARY;
|
||||
else
|
||||
type = IModFile.Type.GAMELIBRARY;
|
||||
}
|
||||
}
|
||||
|
||||
if (id == null) {
|
||||
var modulePath = zipPath.resolve(MODULE_INFO);
|
||||
if (Files.exists(modulePath)) {
|
||||
try (var is = Files.newInputStream(modulePath)) {
|
||||
var module = ModuleDescriptor.read(is);
|
||||
id = module.name();
|
||||
} catch (IOException e) {
|
||||
LOGGER.error(MARKER, "Failed to read module-info.class from {}", source, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (id == null)
|
||||
id = mf.getMainAttributes().getValue("Automatic-Module-Name");
|
||||
|
||||
if (id == null)
|
||||
id = Path.of(path).getFileName().toString();
|
||||
|
||||
String coords = null;
|
||||
var child = this.children.get(source);
|
||||
if (child != null)
|
||||
coords = child.get(cleaned);
|
||||
|
||||
return new Nested(id, roimfsPath, type, coords, source, cleaned, zipPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getIdentifier(Entry entry) {
|
||||
return entry.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Throwable getFailureException(Collection<ResolutionFailureInformation<Entry>> failures) {
|
||||
var errors = new ArrayList<ExceptionData>();
|
||||
for (var failure : failures) {
|
||||
var message = failure.failureReason() == JarSelector.FailureReason.VERSION_RESOLUTION_FAILED ? "fml.dependencyloading.conflictingdependencies" : "fml.dependencyloading.mismatchedcontaineddependencies";
|
||||
var joiner = new StringJoiner(", ");
|
||||
for (var source : failure.sources()) {
|
||||
String paths = null;
|
||||
if (source.sources().size() == 0) {
|
||||
paths = "[No Sources]";
|
||||
} else if (source.sources().size() == 1) {
|
||||
var entry = source.sources().iterator().next();
|
||||
paths = entry.toString();
|
||||
} else {
|
||||
var j = new StringJoiner(", ", "[", "]");
|
||||
for (var entry : source.sources())
|
||||
j.add(entry.toString());
|
||||
paths = j.toString();
|
||||
}
|
||||
|
||||
var error = YELLOW + paths + RESET + " - " +
|
||||
RED + source.requestedVersionRange() + RESET + " - " +
|
||||
GREEN + source.includedVersion() + RESET;
|
||||
joiner.add(error);
|
||||
}
|
||||
|
||||
errors.add(new ExceptionData(message, failure.identifier().group() + ":" + failure.identifier().artifact(), joiner.toString()));
|
||||
}
|
||||
|
||||
return new EarlyLoadingException(failures.size() + " Dependency restrictions were not met.", null, errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
-12
@@ -18,24 +18,22 @@ import java.util.List;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public class MinecraftLocator extends AbstractModProvider implements IModLocator {
|
||||
public final class MinecraftLocator extends AbstractModProvider implements IModLocator {
|
||||
@Override
|
||||
public List<IModLocator.ModFileOrException> scanMods() {
|
||||
var minecraft = FMLLoader.getLaunchHandler().getMinecraftPaths();
|
||||
var paths = minecraft.toArray(Path[]::new);
|
||||
|
||||
// Minecraft itself.
|
||||
var meta = new ModJarMetadata();
|
||||
var mcjar = SecureJar.from(
|
||||
jar -> meta,
|
||||
minecraft.toArray(Path[]::new)
|
||||
);
|
||||
var mc = ModFileFactory.FACTORY.build(mcjar, this, this::buildMinecraftTOML);
|
||||
var mcjar = SecureJar.from(jar -> meta, paths);
|
||||
var mc = ModFileFactory.FACTORY.build(mcjar, this, MinecraftLocator::buildMinecraftTOML);
|
||||
meta.setModFile(mc);
|
||||
|
||||
return List.of(new ModFileOrException(mc, null));
|
||||
}
|
||||
|
||||
private IModFileInfo buildMinecraftTOML(final IModFile iModFile) {
|
||||
private static IModFileInfo buildMinecraftTOML(final IModFile iModFile) {
|
||||
// We haven't changed this in years, and I can't be asked right now to special case this one file in the path.
|
||||
final var conf = Config.inMemory();
|
||||
conf.set("modLoader", "minecraft");
|
||||
@@ -45,14 +43,14 @@ public class MinecraftLocator extends AbstractModProvider implements IModLocator
|
||||
mods.set("modId", "minecraft");
|
||||
mods.set("version", FMLLoader.versionInfo().mcVersion());
|
||||
mods.set("displayName", "Minecraft");
|
||||
mods.set("logoFile", "mcplogo.png");
|
||||
mods.set("credits", "Mojang, deobfuscated by MCP");
|
||||
mods.set("authors", "MCP: Searge,ProfMobius,IngisKahn,Fesh0r,ZeuX,R4wk,LexManos,Bspkrs");
|
||||
mods.set("description", "Minecraft, decompiled and deobfuscated with MCP technology");
|
||||
//mods.set("logoFile", "mcplogo.png");
|
||||
//mods.set("credits", "Mojang, deobfuscated by MCP");
|
||||
//mods.set("authors", "MCP: Searge,ProfMobius,IngisKahn,Fesh0r,ZeuX,R4wk,LexManos,Bspkrs");
|
||||
mods.set("description", "The base Minecraft game, published by Mojang decompiled and deobfuscated by Forge");
|
||||
conf.set("mods", List.of(mods));
|
||||
|
||||
var configWrapper = new NightConfigWrapper(conf);
|
||||
return new ModFileInfo((ModFile)iModFile, configWrapper, configWrapper::setFile, List.of());
|
||||
return new ModFileInfo((ModFile) iModFile, configWrapper, configWrapper::setFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+42
-49
@@ -5,8 +5,6 @@
|
||||
|
||||
package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import cpw.mods.modlauncher.Launcher;
|
||||
import cpw.mods.modlauncher.api.IModuleLayerManager;
|
||||
@@ -28,6 +26,7 @@ import org.jetbrains.annotations.ApiStatus;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@@ -35,38 +34,36 @@ import java.util.ServiceLoader;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public class ModDiscoverer {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private final ServiceLoader<IModLocator> modLocators;
|
||||
private final ServiceLoader<IDependencyLocator> dependencyLocators;
|
||||
private final List<IModLocator> modLocatorList;
|
||||
private final List<IDependencyLocator> dependencyLocatorList;
|
||||
public final class ModDiscoverer {
|
||||
private ModDiscoverer() {}
|
||||
|
||||
public ModDiscoverer(Map<String, ?> arguments) {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
public static ModValidator discoverMods(Map<String, ?> arguments) {
|
||||
Launcher.INSTANCE.environment().computePropertyIfAbsent(Environment.Keys.MODDIRECTORYFACTORY.get(), v->ModsFolderLocator::new);
|
||||
Launcher.INSTANCE.environment().computePropertyIfAbsent(Environment.Keys.PROGRESSMESSAGE.get(), v-> StartupNotificationManager.locatorConsumer().orElseGet(()-> s->{}));
|
||||
final var moduleLayerManager = Launcher.INSTANCE.environment().findModuleLayerManager().orElseThrow();
|
||||
modLocators = ServiceLoader.load(moduleLayerManager.getLayer(IModuleLayerManager.Layer.SERVICE).orElseThrow(), IModLocator.class);
|
||||
dependencyLocators = ServiceLoader.load(moduleLayerManager.getLayer(IModuleLayerManager.Layer.SERVICE).orElseThrow(), IDependencyLocator.class);
|
||||
modLocatorList = ServiceLoaderUtils.streamServiceLoader(()-> modLocators, sce->LOGGER.error("Failed to load mod locator list", sce)).collect(Collectors.toList());
|
||||
modLocatorList.forEach(l->l.initArguments(arguments));
|
||||
dependencyLocatorList = ServiceLoaderUtils.streamServiceLoader(()-> dependencyLocators, sce->LOGGER.error("Failed to load dependency locator list", sce)).collect(Collectors.toList());
|
||||
dependencyLocatorList.forEach(l->l.initArguments(arguments));
|
||||
if (LOGGER.isDebugEnabled(LogMarkers.CORE))
|
||||
{
|
||||
ServiceLoader<IModLocator> modLocators = ServiceLoader.load(moduleLayerManager.getLayer(IModuleLayerManager.Layer.SERVICE).orElseThrow(), IModLocator.class);
|
||||
ServiceLoader<IDependencyLocator> dependencyLocators = ServiceLoader.load(moduleLayerManager.getLayer(IModuleLayerManager.Layer.SERVICE).orElseThrow(), IDependencyLocator.class);
|
||||
List<IModLocator> modLocatorList = ServiceLoaderUtils.streamWithErrorHandling(modLocators, sce -> LOGGER.error("Failed to load mod locator list", sce)).toList();
|
||||
for (IModLocator iModLocator : modLocatorList) {
|
||||
iModLocator.initArguments(arguments);
|
||||
}
|
||||
List<IDependencyLocator> dependencyLocatorList = ServiceLoaderUtils.streamWithErrorHandling(dependencyLocators, sce -> LOGGER.error("Failed to load dependency locator list", sce)).toList();
|
||||
for (IDependencyLocator l : dependencyLocatorList) {
|
||||
l.initArguments(arguments);
|
||||
}
|
||||
if (LOGGER.isDebugEnabled(LogMarkers.CORE)) {
|
||||
LOGGER.debug(LogMarkers.CORE, "Found Mod Locators : {}", modLocatorList.stream()
|
||||
.map(modLocator -> "(%s:%s)".formatted(modLocator.name(),
|
||||
modLocator.getClass().getPackage().getImplementationVersion())).collect(Collectors.joining(",")));
|
||||
}
|
||||
if (LOGGER.isDebugEnabled(LogMarkers.CORE))
|
||||
{
|
||||
LOGGER.debug(LogMarkers.CORE, "Found Dependency Locators : {}", dependencyLocatorList.stream()
|
||||
.map(dependencyLocator -> "(%s:%s)".formatted(dependencyLocator.name(),
|
||||
dependencyLocator.getClass().getPackage().getImplementationVersion())).collect(Collectors.joining(",")));
|
||||
}
|
||||
}
|
||||
.map(modLocator -> "(%s:%s)".formatted(modLocator.name(),
|
||||
modLocator.getClass().getPackage().getImplementationVersion())).collect(Collectors.joining(",")));
|
||||
|
||||
LOGGER.debug(LogMarkers.CORE, "Found Dependency Locators : {}", dependencyLocatorList.stream()
|
||||
.map(dependencyLocator -> "(%s:%s)".formatted(dependencyLocator.name(),
|
||||
dependencyLocator.getClass().getPackage().getImplementationVersion())).collect(Collectors.joining(",")));
|
||||
}
|
||||
|
||||
public ModValidator discoverMods() {
|
||||
LOGGER.debug(LogMarkers.SCAN,"Scanning for mods and other resources to load. We know {} ways to find mods", modLocatorList.size());
|
||||
List<ModFile> loadedFiles = new ArrayList<>();
|
||||
List<EarlyLoadingException.ExceptionData> discoveryErrorData = new ArrayList<>();
|
||||
@@ -114,7 +111,7 @@ public class ModDiscoverer {
|
||||
}
|
||||
|
||||
LOGGER.debug(LogMarkers.SCAN, "Locator {} found {} valid mod files", locator, locatedFiles.size());
|
||||
handleLocatedFiles(loadedFiles, locatedFiles);
|
||||
handleLocatedFiles(loadedFiles, locatedFiles, locator);
|
||||
} catch (InvalidModFileException imfe) {
|
||||
// We don't generally expect this exception, since it should come from the candidates stream above and be handled in the Locator, but just in case.
|
||||
LOGGER.error(LogMarkers.SCAN, "Locator {} found an invalid mod file {}", locator, imfe.getBrokenFile(), imfe);
|
||||
@@ -126,15 +123,14 @@ public class ModDiscoverer {
|
||||
}
|
||||
|
||||
//First processing run of the mod list. Any duplicates will cause resolution failure and dependency loading will be skipped.
|
||||
Map<IModFile.Type, List<ModFile>> modFilesMap = Maps.newHashMap();
|
||||
Map<IModFile.Type, List<ModFile>> modFilesMap = new EnumMap<>(IModFile.Type.class);
|
||||
try {
|
||||
final UniqueModListBuilder modsUniqueListBuilder = new UniqueModListBuilder(loadedFiles);
|
||||
final UniqueModListBuilder.UniqueModListData uniqueModsData = modsUniqueListBuilder.buildUniqueList();
|
||||
final UniqueModListBuilder.UniqueModListData uniqueModsData = UniqueModListBuilder.buildUniqueList(loadedFiles);
|
||||
|
||||
//Grab the temporary results.
|
||||
//This allows loading to continue to a base state, in case dependency loading fails.
|
||||
modFilesMap = uniqueModsData.modFiles().stream()
|
||||
.collect(Collectors.groupingBy(IModFile::getType));
|
||||
.collect(Collectors.groupingBy(IModFile::getType, () -> new EnumMap<>(IModFile.Type.class), Collectors.toList()));
|
||||
loadedFiles = uniqueModsData.modFiles();
|
||||
}
|
||||
catch (EarlyLoadingException exception) {
|
||||
@@ -149,14 +145,10 @@ public class ModDiscoverer {
|
||||
for (IDependencyLocator locator : dependencyLocatorList) {
|
||||
try {
|
||||
LOGGER.debug(LogMarkers.SCAN,"Trying locator {}", locator);
|
||||
final List<IModFile> locatedMods = ImmutableList.copyOf(loadedFiles);
|
||||
final List<IModFile> locatedMods = List.copyOf(loadedFiles);
|
||||
|
||||
var locatedFiles = locator.scanMods(locatedMods);
|
||||
if (locatedFiles.stream().anyMatch(file -> !(file instanceof ModFile))) {
|
||||
LOGGER.error(LogMarkers.SCAN, "A dependency locator returned a file which is not a ModFile instance!. They will be skipped!");
|
||||
}
|
||||
|
||||
handleLocatedFiles(loadedFiles, locatedFiles);
|
||||
handleLocatedFiles(loadedFiles, locatedFiles, locator);
|
||||
}
|
||||
catch (EarlyLoadingException exception) {
|
||||
LOGGER.error(LogMarkers.SCAN, "Failed to load dependencies with locator {}", locator, exception);
|
||||
@@ -166,16 +158,15 @@ public class ModDiscoverer {
|
||||
|
||||
//Second processing run of the mod list. Any duplicates will cause resolution failure and only the mods list will be loaded.
|
||||
try {
|
||||
final UniqueModListBuilder modsAndDependenciesUniqueListBuilder = new UniqueModListBuilder(loadedFiles);
|
||||
final UniqueModListBuilder.UniqueModListData uniqueModsAndDependenciesData = modsAndDependenciesUniqueListBuilder.buildUniqueList();
|
||||
final UniqueModListBuilder.UniqueModListData uniqueModsAndDependenciesData = UniqueModListBuilder.buildUniqueList(loadedFiles);
|
||||
|
||||
//We now only need the mod files map, not the list.
|
||||
modFilesMap = uniqueModsAndDependenciesData.modFiles().stream()
|
||||
.collect(Collectors.groupingBy(IModFile::getType));
|
||||
.collect(Collectors.groupingBy(IModFile::getType, () -> new EnumMap<>(IModFile.Type.class), Collectors.toList()));
|
||||
} catch (EarlyLoadingException exception) {
|
||||
LOGGER.error(LogMarkers.SCAN, "Failed to build unique mod list after dependency discovery.", exception);
|
||||
discoveryErrorData.addAll(exception.getAllData());
|
||||
modFilesMap = loadedFiles.stream().collect(Collectors.groupingBy(IModFile::getType));
|
||||
modFilesMap = loadedFiles.stream().collect(Collectors.groupingBy(IModFile::getType, () -> new EnumMap<>(IModFile.Type.class), Collectors.toList()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -190,12 +181,14 @@ public class ModDiscoverer {
|
||||
return validator;
|
||||
}
|
||||
|
||||
private void handleLocatedFiles(final List<ModFile> loadedFiles, final List<IModFile> locatedFiles)
|
||||
{
|
||||
var locatedModFiles = locatedFiles.stream().filter(ModFile.class::isInstance).map(ModFile.class::cast).toList();
|
||||
for (IModFile mf : locatedModFiles) {
|
||||
LOGGER.info(LogMarkers.SCAN, "Found mod file {} of type {} with provider {}", mf.getFileName(), mf.getType(), mf.getProvider());
|
||||
private static void handleLocatedFiles(final List<ModFile> loadedFiles, final List<IModFile> locatedFiles, final Object locator) {
|
||||
for (IModFile mf : locatedFiles) {
|
||||
if (mf instanceof ModFile modFile) {
|
||||
LOGGER.info(LogMarkers.SCAN, "Found mod file {} of type {} with provider {}", mf.getFileName(), mf.getType(), mf.getProvider());
|
||||
loadedFiles.add(modFile);
|
||||
} else {
|
||||
LOGGER.error(LogMarkers.SCAN, "Skipping mod file {} found by {}, as it was not a ModFile instance!", mf.getFileName(), locator);
|
||||
}
|
||||
}
|
||||
loadedFiles.addAll(locatedModFiles);
|
||||
}
|
||||
}
|
||||
@@ -21,13 +21,13 @@ import org.apache.maven.artifact.versioning.ArtifactVersion;
|
||||
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
@@ -39,19 +39,17 @@ import java.util.jar.Manifest;
|
||||
public class ModFile implements IModFile {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private final String jarVersion;
|
||||
private final ModFileFactory.ModFileInfoParser parser;
|
||||
private Map<String, Object> fileProperties;
|
||||
private List<IModLanguageProvider> loaders;
|
||||
private Throwable scanError;
|
||||
private final SecureJar jar;
|
||||
private final Type modFileType;
|
||||
private final Manifest manifest;
|
||||
private final IModProvider provider;
|
||||
private IModFileInfo modFileInfo;
|
||||
private final IModFileInfo modFileInfo;
|
||||
private ModFileScanData fileModFileScanData;
|
||||
private CompletableFuture<ModFileScanData> futureScanResult;
|
||||
private List<CoreModFile> coreMods;
|
||||
private Path accessTransformer;
|
||||
private List<Path> accessTransformers = Collections.emptyList();
|
||||
|
||||
static final Attributes.Name TYPE = new Attributes.Name("FMLModType");
|
||||
private SecureJar.Status securityStatus;
|
||||
@@ -63,12 +61,11 @@ public class ModFile implements IModFile {
|
||||
public ModFile(final SecureJar jar, final IModProvider provider, final ModFileFactory.ModFileInfoParser parser, String type) {
|
||||
this.provider = provider;
|
||||
this.jar = jar;
|
||||
this.parser = parser;
|
||||
|
||||
manifest = this.jar.moduleDataProvider().getManifest();
|
||||
var manifest = this.jar.moduleDataProvider().getManifest();
|
||||
modFileType = Type.valueOf(type);
|
||||
jarVersion = Optional.ofNullable(manifest.getMainAttributes().getValue(Attributes.Name.IMPLEMENTATION_VERSION)).orElse("0.0NONE");
|
||||
this.modFileInfo = ModFileParser.readModList(this, this.parser);
|
||||
this.modFileInfo = ModFileParser.readModList(this, parser);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -95,22 +92,36 @@ public class ModFile implements IModFile {
|
||||
return modFileInfo.getMods();
|
||||
}
|
||||
|
||||
@Deprecated(forRemoval = true, since = "1.21.11")
|
||||
public Optional<Path> getAccessTransformer() {
|
||||
return Optional.ofNullable(Files.exists(accessTransformer) ? accessTransformer : null);
|
||||
return Optional.ofNullable(accessTransformer);
|
||||
}
|
||||
|
||||
public List<Path> getAccessTransformers() {
|
||||
return this.accessTransformers;
|
||||
}
|
||||
|
||||
public boolean identifyMods() {
|
||||
this.modFileInfo = ModFileParser.readModList(this, this.parser);
|
||||
if (this.modFileInfo == null) return this.getType() != Type.MOD;
|
||||
LOGGER.debug(LogMarkers.LOADING,"Loading mod file {} with languages {}", this.getFilePath(), this.modFileInfo.requiredLanguageLoaders());
|
||||
this.coreMods = ModFileParser.getCoreMods(this);
|
||||
this.coreMods.forEach(mi-> LOGGER.debug(LogMarkers.LOADING,"Found coremod {}", mi.getPath()));
|
||||
this.accessTransformer = findResource("META-INF", "accesstransformer.cfg");
|
||||
return true;
|
||||
}
|
||||
LOGGER.debug(LogMarkers.LOADING,"Loading mod file {} with languages {}", this.getFileName(), this.modFileInfo.requiredLanguageLoaders());
|
||||
var cfg = this.modFileInfo.getConfig()
|
||||
.<List<String>>getConfigElement("accessTransformers")
|
||||
.orElse(null);
|
||||
|
||||
public List<CoreModFile> getCoreMods() {
|
||||
return coreMods;
|
||||
if (cfg == null) {
|
||||
var path = findResource("META-INF", "accesstransformer.cfg");
|
||||
if (Files.exists(path)) {
|
||||
this.accessTransformer = path;
|
||||
this.accessTransformers = List.of(path);
|
||||
}
|
||||
} else if (!cfg.isEmpty()) {
|
||||
var paths = new ArrayList<Path>(cfg.size());
|
||||
for (var path : cfg)
|
||||
paths.add(getSecureJar().getPath(path.replace('\\', '/')));
|
||||
this.accessTransformers = List.copyOf(paths);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -180,12 +191,28 @@ public class ModFile implements IModFile {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Mod File: " + Objects.toString(this.jar.getPrimaryPath());
|
||||
var path = getFilePath();
|
||||
return "Mod File: " + path == null ? "null" : path.toUri().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFileName() {
|
||||
return getFilePath().getFileName().toString();
|
||||
// This is only used for debugging, so lets try and make it a useful debug
|
||||
var path = getFilePath();
|
||||
var filename = path.getFileName();
|
||||
|
||||
// ZipFileSystem toString will return the outer file path
|
||||
var ret = filename == null ? path.getFileSystem().toString() : filename.toString();
|
||||
|
||||
// If it's a directory on the default file system, return the full path
|
||||
if (Files.isDirectory(path) && path.getFileSystem() == FileSystems.getDefault())
|
||||
ret = path.toAbsolutePath().toString();
|
||||
|
||||
// If we are in a FileSystem 'root' like UnionFS, return the full uri
|
||||
if (ret.isEmpty() || ret.equals("/"))
|
||||
ret = path.toUri().toString();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+94
-76
@@ -7,8 +7,6 @@ package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
|
||||
import net.minecraftforge.fml.loading.FMLConfig;
|
||||
import net.minecraftforge.fml.loading.LogMarkers;
|
||||
import net.minecraftforge.fml.loading.StringUtils;
|
||||
import net.minecraftforge.forgespi.language.IConfigurable;
|
||||
@@ -19,17 +17,15 @@ import org.jetbrains.annotations.ApiStatus;
|
||||
import org.slf4j.Logger;
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
import java.net.URL;
|
||||
import java.security.CodeSigner;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.SignatureException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
@@ -38,10 +34,8 @@ import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class ModFileInfo implements IModFileInfo, IConfigurable
|
||||
{
|
||||
public class ModFileInfo implements IModFileInfo, IConfigurable {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static final String MAYBE_NOT_JAVAFML_VER = "[2,)";
|
||||
private static final String JAVAFML = "javafml";
|
||||
@@ -49,9 +43,6 @@ public class ModFileInfo implements IModFileInfo, IConfigurable
|
||||
@ApiStatus.Internal
|
||||
public static final String NOT_A_FORGE_MOD_PROP = "__FORGE__not_a_forge_mod";
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static final boolean DETECT_NON_FORGE_MODS_TOML = FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.DETECT_NON_FORGE_MODS_TOML);
|
||||
|
||||
private final IConfigurable config;
|
||||
private final ModFile modFile;
|
||||
private final URL issueURL;
|
||||
@@ -64,8 +55,7 @@ public class ModFileInfo implements IModFileInfo, IConfigurable
|
||||
private final String license;
|
||||
private final List<String> usesServices;
|
||||
|
||||
ModFileInfo(final ModFile modFile, final IConfigurable config, Consumer<IModFileInfo> configFileConsumer)
|
||||
{
|
||||
ModFileInfo(final ModFile modFile, final IConfigurable config, Consumer<IModFileInfo> configFileConsumer) {
|
||||
this.modFile = modFile;
|
||||
this.config = config;
|
||||
configFileConsumer.accept(this);
|
||||
@@ -79,8 +69,7 @@ public class ModFileInfo implements IModFileInfo, IConfigurable
|
||||
this.languageSpecs = new ArrayList<>(List.of(new LanguageSpec(modLoader, modLoaderVersion)));
|
||||
|
||||
// if true, this might not be a Forge mod
|
||||
boolean maybeNotAForgeMod = DETECT_NON_FORGE_MODS_TOML
|
||||
&& modLoader.equals(JAVAFML)
|
||||
boolean maybeNotAForgeMod = modLoader.equals(JAVAFML)
|
||||
&& modLoaderVersion.hasRestrictions() // if loaderVersion is not "*"
|
||||
&& modLoaderVerStr.equals(MAYBE_NOT_JAVAFML_VER); // if loaderVersion is exactly "[2,)"
|
||||
|
||||
@@ -91,10 +80,6 @@ public class ModFileInfo implements IModFileInfo, IConfigurable
|
||||
.orElse(false);
|
||||
this.clientSideOnly = config.<Boolean>getConfigElement("clientSideOnly")
|
||||
.orElse(false);
|
||||
if (this.clientSideOnly) {
|
||||
// if clientSideOnly, then this is definitely a Forge mod
|
||||
maybeNotAForgeMod = false;
|
||||
}
|
||||
this.showAsDataPack = config.<Boolean>getConfigElement("showAsDataPack")
|
||||
.orElse(false);
|
||||
this.usesServices = config.<List<String>>getConfigElement("services")
|
||||
@@ -103,6 +88,10 @@ public class ModFileInfo implements IModFileInfo, IConfigurable
|
||||
.map(StringUtils::toURL)
|
||||
.orElse(null);
|
||||
|
||||
// if clientSideOnly, then this is definitely a Forge mod
|
||||
if (this.clientSideOnly)
|
||||
maybeNotAForgeMod = false;
|
||||
|
||||
if (maybeNotAForgeMod) {
|
||||
// mark this file as not a Forge mod. Note: this marker may be removed later based on the mod dependencies
|
||||
this.properties = config.<Map<String, Object>>getConfigElement("properties")
|
||||
@@ -115,15 +104,14 @@ public class ModFileInfo implements IModFileInfo, IConfigurable
|
||||
this.modFile.setFileProperties(this.properties);
|
||||
|
||||
final List<? extends IConfigurable> modConfigs = config.getConfigList("mods");
|
||||
if (modConfigs.isEmpty())
|
||||
{
|
||||
if (modConfigs == null || modConfigs.isEmpty())
|
||||
throw new InvalidModFileException("Missing mods list", this);
|
||||
}
|
||||
|
||||
this.mods = modConfigs.stream()
|
||||
.map(mi-> (IModInfo)new ModInfo(this, mi))
|
||||
.map(mi -> (IModInfo) ModInfo.of(this, mi))
|
||||
.toList();
|
||||
if (LOGGER.isDebugEnabled(LogMarkers.LOADING))
|
||||
{
|
||||
|
||||
if (LOGGER.isDebugEnabled(LogMarkers.LOADING)) {
|
||||
LOGGER.debug(LogMarkers.LOADING, "Found valid mod file {} with {} mods - versions {}",
|
||||
this.modFile.getFileName(),
|
||||
this.mods.stream().map(IModInfo::getModId).collect(Collectors.joining(",", "{", "}")),
|
||||
@@ -137,13 +125,11 @@ public class ModFileInfo implements IModFileInfo, IConfigurable
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IModInfo> getMods()
|
||||
{
|
||||
public List<IModInfo> getMods() {
|
||||
return mods;
|
||||
}
|
||||
|
||||
public ModFile getFile()
|
||||
{
|
||||
public ModFile getFile() {
|
||||
return this.modFile;
|
||||
}
|
||||
|
||||
@@ -153,44 +139,42 @@ public class ModFileInfo implements IModFileInfo, IConfigurable
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getFileProperties()
|
||||
{
|
||||
public Map<String, Object> getFileProperties() {
|
||||
return this.properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean showAsResourcePack()
|
||||
{
|
||||
public boolean showAsResourcePack() {
|
||||
return this.showAsResourcePack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClientSideOnly()
|
||||
{
|
||||
public boolean isClientSideOnly() {
|
||||
return this.clientSideOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean showAsDataPack()
|
||||
{
|
||||
public boolean showAsDataPack() {
|
||||
return this.showAsDataPack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> getConfigElement(final String... key)
|
||||
{
|
||||
public <T> Optional<T> getConfigElement(final String key) {
|
||||
return this.config.getConfigElement(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends IConfigurable> getConfigList(final String... key)
|
||||
{
|
||||
public <T> Optional<T> getConfigElement(final String... key) {
|
||||
return this.config.getConfigElement(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends IConfigurable> getConfigList(final String... key) {
|
||||
return this.config.getConfigList(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLicense()
|
||||
{
|
||||
public String getLicense() {
|
||||
return license;
|
||||
}
|
||||
|
||||
@@ -199,63 +183,97 @@ public class ModFileInfo implements IModFileInfo, IConfigurable
|
||||
return this;
|
||||
}
|
||||
|
||||
public URL getIssueURL()
|
||||
{
|
||||
public URL getIssueURL() {
|
||||
return issueURL;
|
||||
}
|
||||
|
||||
public boolean missingLicense()
|
||||
{
|
||||
public boolean missingLicense() {
|
||||
return Strings.isNullOrEmpty(license);
|
||||
}
|
||||
|
||||
private static final char[] HEX = "0123456789ABCDEF".toCharArray();
|
||||
public Optional<String> getCodeSigningFingerprint() {
|
||||
var signers = this.modFile.getSecureJar().getManifestSigners();
|
||||
return (signers == null ? Stream.<CodeSigner>of() : Arrays.stream(signers))
|
||||
.flatMap(csa->csa.getSignerCertPath().getCertificates().stream())
|
||||
.findFirst()
|
||||
.map(LamdbaExceptionUtils.rethrowFunction(Certificate::getEncoded))
|
||||
.map(bytes->LamdbaExceptionUtils.uncheck(()->MessageDigest.getInstance("SHA-256")).digest(bytes))
|
||||
.map(StringUtils::binToHex)
|
||||
.map(str-> String.join(":", str.split("(?<=\\G.{2})")));
|
||||
if (signers == null)
|
||||
return Optional.empty();
|
||||
|
||||
for (var signer : signers) {
|
||||
for (var cert : signer.getSignerCertPath().getCertificates()) {
|
||||
try {
|
||||
var encoded = cert.getEncoded();
|
||||
var sha256 = MessageDigest.getInstance("SHA-256");
|
||||
var digest = sha256.digest(encoded);
|
||||
var buf = new StringBuilder(digest.length * 3);
|
||||
for (int x = 0; x < digest.length - 1; x++) {
|
||||
buf.append(HEX[(digest[x] & 0xF0) >> 4]);
|
||||
buf.append(HEX[(digest[x] & 0x0F) ]);
|
||||
buf.append(':');
|
||||
}
|
||||
buf.append(HEX[(digest[digest.length - 1] & 0xF0) >> 4]);
|
||||
buf.append(HEX[(digest[digest.length - 1] & 0x0F) ]);
|
||||
|
||||
// We only care about the first. Should we list them all?
|
||||
return Optional.of(buf.toString());
|
||||
} catch (CertificateEncodingException | NoSuchAlgorithmException e) {
|
||||
sneak(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
|
||||
}
|
||||
|
||||
public Optional<String> getTrustData() {
|
||||
return Arrays.stream(this.modFile.getSecureJar().getManifestSigners())
|
||||
.flatMap(csa->csa.getSignerCertPath().getCertificates().stream())
|
||||
.findFirst()
|
||||
.map(X509Certificate.class::cast)
|
||||
.map(c->{
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append(c.getSubjectX500Principal().getName(X500Principal.RFC2253).split(",")[0]);
|
||||
boolean selfSigned = false;
|
||||
try {
|
||||
c.verify(c.getPublicKey());
|
||||
selfSigned = true;
|
||||
} catch (CertificateException | NoSuchAlgorithmException | InvalidKeyException | SignatureException | NoSuchProviderException e) {
|
||||
// not self signed
|
||||
}
|
||||
if (selfSigned) {
|
||||
var signers = this.modFile.getSecureJar().getManifestSigners();
|
||||
if (signers == null)
|
||||
return Optional.empty();
|
||||
|
||||
for (var signer : signers) {
|
||||
for (var cert : signer.getSignerCertPath().getCertificates()) {
|
||||
if (!(cert instanceof X509Certificate x509))
|
||||
continue;
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.append(x509.getSubjectX500Principal().getName(X500Principal.RFC2253).split(",")[0]);
|
||||
|
||||
var selfSigned = false;
|
||||
try {
|
||||
x509.verify(x509.getPublicKey());
|
||||
selfSigned = true;
|
||||
} catch (CertificateException | NoSuchAlgorithmException | InvalidKeyException | SignatureException | NoSuchProviderException e) {
|
||||
// not self signed
|
||||
}
|
||||
|
||||
if (selfSigned)
|
||||
sb.append(" self-signed");
|
||||
} else {
|
||||
sb.append(" signed by ").append(c.getIssuerX500Principal().getName(X500Principal.RFC2253).split(",")[0]);
|
||||
};
|
||||
return sb.toString();
|
||||
});
|
||||
else
|
||||
sb.append(" signed by ").append(x509.getIssuerX500Principal().getName(X500Principal.RFC2253).split(",")[0]);
|
||||
|
||||
return Optional.of(sb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String moduleName() {
|
||||
return getMods().get(0).getModId();
|
||||
return this.modFile.getSecureJar().name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String versionString() {
|
||||
return getMods().get(0).getVersion().toString();
|
||||
return getMods().getFirst().getVersion().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> usesServices() {
|
||||
return usesServices;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <E extends Throwable, R> R sneak(Throwable e) throws E {
|
||||
throw (E)e;
|
||||
}
|
||||
}
|
||||
-28
@@ -6,8 +6,6 @@
|
||||
package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import com.electronwill.nightconfig.core.file.FileConfig;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraftforge.fml.loading.LogMarkers;
|
||||
import net.minecraftforge.forgespi.language.IModFileInfo;
|
||||
@@ -16,13 +14,8 @@ import net.minecraftforge.forgespi.locating.ModFileFactory;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ModFileParser {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
@@ -46,25 +39,4 @@ public class ModFileParser {
|
||||
final NightConfigWrapper configWrapper = new NightConfigWrapper(fileConfig);
|
||||
return new ModFileInfo(modFile, configWrapper, configWrapper::setFile);
|
||||
}
|
||||
|
||||
protected static List<CoreModFile> getCoreMods(final ModFile modFile) {
|
||||
Map<String,String> coreModPaths;
|
||||
try {
|
||||
final Path coremodsjson = modFile.findResource("META-INF", "coremods.json");
|
||||
if (!Files.exists(coremodsjson)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
final Type type = new TypeToken<Map<String, String>>() {}.getType();
|
||||
final Gson gson = new Gson();
|
||||
coreModPaths = gson.fromJson(Files.newBufferedReader(coremodsjson), type);
|
||||
} catch (IOException e) {
|
||||
LOGGER.debug(LogMarkers.LOADING,"Failed to read coremod list coremods.json", e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return coreModPaths.entrySet().stream()
|
||||
.peek(e-> LOGGER.debug(LogMarkers.LOADING,"Found coremod {} with Javascript path {}", e.getKey(), e.getValue()))
|
||||
.map(e -> new CoreModFile(e.getKey(), modFile.findResource(e.getValue()),modFile))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
+153
-198
@@ -19,202 +19,189 @@ import org.jetbrains.annotations.ApiStatus;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public class ModInfo implements IModInfo, IConfigurable {
|
||||
public record ModInfo(
|
||||
ModFileInfo getOwningFile,
|
||||
IConfigurable getConfig,
|
||||
|
||||
String getModId,
|
||||
String getNamespace,
|
||||
ArtifactVersion getVersion,
|
||||
|
||||
String getDisplayName,
|
||||
String getDescription,
|
||||
Optional<String> getLogoFile,
|
||||
boolean getLogoBlur,
|
||||
Optional<URL> getUpdateURL,
|
||||
Optional<URL> getModURL,
|
||||
|
||||
Holder<List<? extends ModVersion>> dependencies,
|
||||
Holder<List<ForgeFeature.Bound>> forgeFeatures,
|
||||
Map<String, Object> getModProperties
|
||||
) implements IModInfo, IConfigurable {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static final DefaultArtifactVersion DEFAULT_VERSION = new DefaultArtifactVersion("1");
|
||||
private static final Pattern VALID_MODID = Pattern.compile("^[a-z][a-z0-9_]{1,63}$");
|
||||
private static final Pattern VALID_NAMESPACE = Pattern.compile("^[a-z][a-z0-9_.-]{1,63}$");
|
||||
private static final Pattern VALID_VERSION = Pattern.compile("^\\d+.*");
|
||||
|
||||
private final ModFileInfo owningFile;
|
||||
private final String modId;
|
||||
private final String namespace;
|
||||
private final ArtifactVersion version;
|
||||
private final String displayName;
|
||||
private final String description;
|
||||
private final Optional<String> logoFile;
|
||||
private final boolean logoBlur;
|
||||
private final Optional<URL> updateJSONURL;
|
||||
private final List<? extends IModInfo.ModVersion> dependencies;
|
||||
|
||||
private final List<ForgeFeature.Bound> features;
|
||||
private final Map<String,Object> properties;
|
||||
private final IConfigurable config;
|
||||
private final Optional<URL> modUrl;
|
||||
|
||||
public ModInfo(final ModFileInfo owningFile, final IConfigurable config)
|
||||
{
|
||||
Optional<ModFileInfo> ownFile = Optional.ofNullable(owningFile);
|
||||
this.owningFile = owningFile;
|
||||
this.config = config;
|
||||
public static ModInfo of(ModFileInfo owningFile, IConfigurable config) {
|
||||
// These are sourced from the mod specific [[mod]] entry
|
||||
this.modId = config.<String>getConfigElement("modId")
|
||||
.orElseThrow(() -> new InvalidModFileException("Missing modId", owningFile));
|
||||
String modId = config.<String>getConfigElement("modId").orElse(null);
|
||||
if (modId == null)
|
||||
throw new InvalidModFileException("Missing modId", owningFile);
|
||||
|
||||
// verify we have a valid modid
|
||||
if (!VALID_MODID.matcher(this.modId).matches()) {
|
||||
LOGGER.error(LogUtils.FATAL_MARKER, "Invalid modId found in file {} - {} does not match the standard: {}", this.owningFile.getFile().getFilePath(), this.modId, VALID_MODID.pattern());
|
||||
throw new InvalidModFileException("Invalid modId found : " + this.modId, owningFile);
|
||||
if (!VALID_MODID.matcher(modId).matches()) {
|
||||
LOGGER.error(LogUtils.FATAL_MARKER, "Invalid modId found in file {} - {} does not match the standard: {}", owningFile.getFile().getFilePath(), modId, VALID_MODID.pattern());
|
||||
throw new InvalidModFileException("Invalid modId found : " + modId, owningFile);
|
||||
}
|
||||
this.namespace = config.<String>getConfigElement("namespace")
|
||||
.orElse(this.modId);
|
||||
|
||||
String namespace = config.<String>getConfigElement("namespace")
|
||||
.orElse(modId);
|
||||
|
||||
// verify our namespace is valid
|
||||
if (!VALID_NAMESPACE.matcher(this.namespace).matches()) {
|
||||
LOGGER.error(LogUtils.FATAL_MARKER, "Invalid override namespace found in file {} - {} does not match the standard: {}", this.owningFile.getFile().getFilePath(), this.namespace, VALID_NAMESPACE.pattern());
|
||||
throw new InvalidModFileException("Invalid override namespace found : " + this.namespace, owningFile);
|
||||
if (!VALID_NAMESPACE.matcher(namespace).matches()) {
|
||||
LOGGER.error(LogUtils.FATAL_MARKER, "Invalid override namespace found in file {} - {} does not match the standard: {}", owningFile.getFile().getFilePath(), namespace, VALID_NAMESPACE.pattern());
|
||||
throw new InvalidModFileException("Invalid override namespace found : " + namespace, owningFile);
|
||||
}
|
||||
this.version = config.<String>getConfigElement("version")
|
||||
.map(s -> StringSubstitutor.replace(s, ownFile.map(ModFileInfo::getFile).orElse(null)))
|
||||
|
||||
ArtifactVersion version = config.<String>getConfigElement("version")
|
||||
.map(s -> StringSubstitutor.replace(s, owningFile.getFile()))
|
||||
.map(DefaultArtifactVersion::new)
|
||||
.orElse(DEFAULT_VERSION);
|
||||
|
||||
// verify we have a valid mod version
|
||||
if (!VALID_VERSION.matcher(this.version.toString()).matches()) {
|
||||
throw new InvalidModFileException("Illegal version number specified "+this.version, this.getOwningFile());
|
||||
}
|
||||
if (!VALID_VERSION.matcher(version.toString()).matches())
|
||||
throw new InvalidModFileException("Illegal version number specified " + version, owningFile);
|
||||
|
||||
// The remaining properties all default to sensible values and are not essential
|
||||
this.displayName = config.<String>getConfigElement("displayName")
|
||||
.orElse(this.modId);
|
||||
this.description = config.<String>getConfigElement("description")
|
||||
String displayName = config.<String>getConfigElement("displayName")
|
||||
.orElse(modId);
|
||||
|
||||
String description = config.<String>getConfigElement("description")
|
||||
.orElse("MISSING DESCRIPTION")
|
||||
.replace("\r\n", "\n").stripIndent();
|
||||
this.logoFile = Optional.ofNullable(config.<String>getConfigElement("logoFile")
|
||||
.orElseGet(() -> ownFile.flatMap(mf -> mf.<String>getConfigElement("logoFile"))
|
||||
.orElse(null)));
|
||||
this.logoBlur = config.<Boolean>getConfigElement("logoBlur")
|
||||
.orElseGet(() -> ownFile.flatMap(f -> f.<Boolean>getConfigElement("logoBlur"))
|
||||
.orElse(true));
|
||||
this.updateJSONURL = config.<String>getConfigElement("updateJSONURL")
|
||||
|
||||
Optional<String> logoFile = Optional.ofNullable(
|
||||
config.<String>getConfigElement("logoFile")
|
||||
.orElseGet(() -> owningFile.<String>getConfigElement("logoFile").orElse(null))
|
||||
);
|
||||
|
||||
Boolean logoBlur = config.<Boolean>getConfigElement("logoBlur").orElse(null);
|
||||
if (logoBlur == null)
|
||||
logoBlur = owningFile.<Boolean>getConfigElement("logoBlur").orElse(true);
|
||||
|
||||
Optional<URL> updateJSONURL = config.<String>getConfigElement("updateJSONURL")
|
||||
.map(StringUtils::toURL);
|
||||
this.modUrl = config.<String>getConfigElement("modUrl")
|
||||
|
||||
Optional<URL> modUrl = config.<String>getConfigElement("modUrl")
|
||||
.map(StringUtils::toURL);
|
||||
// These are sourced from the file rather than the mod-specific block, but with a modid tag
|
||||
this.dependencies = ownFile.map(mfi -> mfi.getConfigList("dependencies", this.modId))
|
||||
.orElse(Collections.emptyList())
|
||||
.stream()
|
||||
.map(dep -> new ModVersion(this, dep))
|
||||
.toList();
|
||||
this.features = ownFile.flatMap(mfi -> mfi.<Map<String, Object>>getConfigElement("features", this.modId))
|
||||
.stream()
|
||||
.flatMap(m->m.entrySet().stream())
|
||||
.map(this::makeBound)
|
||||
.toList();
|
||||
this.properties = ownFile.flatMap(mfi -> mfi.<Map<String, Object>>getConfigElement("modproperties", this.modId))
|
||||
|
||||
// dependencies and features are done after the constructor as they need to reference the ModInfo we are creating
|
||||
List<? extends ModVersion> dependencies = Collections.emptyList();
|
||||
List<ForgeFeature.Bound> forgeFeatures = Collections.emptyList();
|
||||
|
||||
Map<String, Object> modProperties = owningFile.<Map<String, Object>>getConfigElement("modproperties", modId)
|
||||
.map(Collections::unmodifiableMap)
|
||||
.orElse(Collections.emptyMap());
|
||||
|
||||
return new ModInfo(
|
||||
owningFile, config,
|
||||
modId, namespace, version,
|
||||
displayName, description, logoFile, logoBlur, updateJSONURL, modUrl,
|
||||
new Holder<>(dependencies), new Holder<>(forgeFeatures), modProperties
|
||||
).setupDependencies().setupForgeFeatures();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModFileInfo getOwningFile() {
|
||||
return owningFile;
|
||||
private ModInfo setupDependencies() {
|
||||
var deps = getOwningFile.getConfigList("dependencies", getModId);
|
||||
if (deps == null || deps.isEmpty()) {
|
||||
dependencies.value = Collections.emptyList();
|
||||
return this;
|
||||
}
|
||||
|
||||
var tmp = new ModVersion[deps.size()];
|
||||
for (int i = 0; i < deps.size(); i++) {
|
||||
tmp[i] = ModVersion.of(this, deps.get(i));
|
||||
}
|
||||
dependencies.value = List.of(tmp);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModId() {
|
||||
return modId;
|
||||
}
|
||||
private ModInfo setupForgeFeatures() {
|
||||
var feats = getOwningFile.<Map<String, Object>>getConfigElement("features", getModId).orElse(null);
|
||||
if (feats == null) {
|
||||
forgeFeatures.value = Collections.emptyList();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName()
|
||||
{
|
||||
return this.displayName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription()
|
||||
{
|
||||
return this.description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArtifactVersion getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends IModInfo.ModVersion> getDependencies() {
|
||||
return this.dependencies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespace() {
|
||||
return this.namespace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getModProperties() {
|
||||
return this.properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<URL> getUpdateURL() {
|
||||
return this.updateJSONURL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getLogoFile()
|
||||
{
|
||||
return this.logoFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getLogoBlur()
|
||||
{
|
||||
return this.logoBlur;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IConfigurable getConfig() {
|
||||
var tmp = new ArrayList<ForgeFeature.Bound>();
|
||||
for (var entry : feats.entrySet()) {
|
||||
if (!(entry.getValue() instanceof String val))
|
||||
throw new InvalidModFileException("Invalid feature bound {" + entry.getValue() + "} for key {" + entry.getKey() + "} only strings are accepted", getOwningFile);
|
||||
tmp.add(new ForgeFeature.Bound(entry.getKey(), val, this));
|
||||
}
|
||||
forgeFeatures.value = List.copyOf(tmp);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends ForgeFeature.Bound> getForgeFeatures() {
|
||||
return this.features;
|
||||
public <T> Optional<T> getConfigElement(String key) {
|
||||
return getConfig.getConfigElement(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> getConfigElement(final String... key) {
|
||||
return this.config.getConfigElement(key);
|
||||
public <T> Optional<T> getConfigElement(String... key) {
|
||||
return getConfig.getConfigElement(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends IConfigurable> getConfigList(final String... key) {
|
||||
public List<? extends IConfigurable> getConfigList(String... key) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<URL> getModURL() {
|
||||
return modUrl;
|
||||
public List<? extends IModInfo.ModVersion> getDependencies() {
|
||||
return dependencies.value;
|
||||
}
|
||||
|
||||
private ForgeFeature.Bound makeBound(Map.Entry<String, Object> e) {
|
||||
if (e.getValue() instanceof String val) {
|
||||
return new ForgeFeature.Bound(e.getKey(), val, this);
|
||||
} else {
|
||||
throw new InvalidModFileException("Invalid feature bound {" + e.getValue() + "} for key {" + e.getKey() + "} only strings are accepted", this.owningFile);
|
||||
@Override
|
||||
public List<? extends ForgeFeature.Bound> getForgeFeatures() {
|
||||
return forgeFeatures.value;
|
||||
}
|
||||
|
||||
private static final class Holder<T> {
|
||||
private T value;
|
||||
|
||||
public Holder(T value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
class ModVersion implements net.minecraftforge.forgespi.language.IModInfo.ModVersion {
|
||||
private IModInfo owner;
|
||||
private final String modId;
|
||||
private final VersionRange versionRange;
|
||||
private final boolean mandatory;
|
||||
private final Ordering ordering;
|
||||
private final DependencySide side;
|
||||
private final Optional<URL> referralUrl;
|
||||
private record ModVersion(
|
||||
Holder<IModInfo> owner,
|
||||
|
||||
public ModVersion(final IModInfo owner, final IConfigurable config) {
|
||||
this.owner = owner;
|
||||
this.modId = config.<String>getConfigElement("modId")
|
||||
.orElseThrow(()->new InvalidModFileException("Missing required field modid in dependency", getOwningFile()));
|
||||
if (ModFileInfo.DETECT_NON_FORGE_MODS_TOML && this.modId.equals("forge")) {
|
||||
String getModId,
|
||||
VersionRange getVersionRange,
|
||||
boolean isMandatory,
|
||||
Ordering getOrdering,
|
||||
DependencySide getSide,
|
||||
Optional<URL> getReferralURL
|
||||
) implements IModInfo.ModVersion {
|
||||
public static ModVersion of(IModInfo owner, IConfigurable config) {
|
||||
var modId = config.<String>getConfigElement("modId").orElse(null);
|
||||
if (modId == null)
|
||||
throw new InvalidModFileException("Missing required field modid in dependency", owner.getOwningFile());
|
||||
|
||||
if (modId.equals("forge")) {
|
||||
var fileProps = owner.getOwningFile().getFileProperties();
|
||||
// Checking containsKey to avoid a possible exception if the property is not present (due to Collections.emptyMap())
|
||||
if (!fileProps.isEmpty() && fileProps.containsKey(ModFileInfo.NOT_A_FORGE_MOD_PROP)) {
|
||||
@@ -223,74 +210,42 @@ public class ModInfo implements IModInfo, IConfigurable {
|
||||
fileProps.remove(ModFileInfo.NOT_A_FORGE_MOD_PROP);
|
||||
}
|
||||
}
|
||||
var mandatory = config.<Boolean>getConfigElement("mandatory");
|
||||
if (mandatory.isPresent()) {
|
||||
this.mandatory = mandatory.get();
|
||||
} else if (ModFileInfo.DETECT_NON_FORGE_MODS_TOML && owner.getOwningFile().getFileProperties().containsKey(ModFileInfo.NOT_A_FORGE_MOD_PROP)) {
|
||||
this.mandatory = true;
|
||||
} else {
|
||||
throw new InvalidModFileException("Missing required field mandatory in dependency", getOwningFile());
|
||||
}
|
||||
this.versionRange = config.<String>getConfigElement("versionRange")
|
||||
|
||||
boolean mandatory;
|
||||
var mandatoryValue = config.<Boolean>getConfigElement("mandatory");
|
||||
if (mandatoryValue.isPresent())
|
||||
mandatory = mandatoryValue.get();
|
||||
else if (owner.getOwningFile().getFileProperties().containsKey(ModFileInfo.NOT_A_FORGE_MOD_PROP))
|
||||
mandatory = true;
|
||||
else
|
||||
throw new InvalidModFileException("Missing required field mandatory in dependency", owner.getOwningFile());
|
||||
|
||||
var versionRange = config.<String>getConfigElement("versionRange")
|
||||
.map(MavenVersionAdapter::createFromVersionSpec)
|
||||
.orElse(UNBOUNDED);
|
||||
this.ordering = config.<String>getConfigElement("ordering")
|
||||
var ordering = config.<String>getConfigElement("ordering")
|
||||
.map(Ordering::valueOf)
|
||||
.orElse(Ordering.NONE);
|
||||
this.side = config.<String>getConfigElement("side")
|
||||
var side = config.<String>getConfigElement("side")
|
||||
.map(DependencySide::valueOf)
|
||||
.orElse(DependencySide.BOTH);
|
||||
this.referralUrl = config.<String>getConfigElement("referralUrl")
|
||||
var referralUrl = config.<String>getConfigElement("referralUrl")
|
||||
.map(StringUtils::toURL);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getModId()
|
||||
{
|
||||
return modId;
|
||||
return new ModVersion(
|
||||
new Holder<>(owner),
|
||||
modId, versionRange, mandatory, ordering, side, referralUrl
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VersionRange getVersionRange()
|
||||
{
|
||||
return versionRange;
|
||||
public IModInfo getOwner() {
|
||||
return owner.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMandatory()
|
||||
{
|
||||
return mandatory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Ordering getOrdering()
|
||||
{
|
||||
return ordering;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DependencySide getSide()
|
||||
{
|
||||
return side;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOwner(final IModInfo owner)
|
||||
{
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IModInfo getOwner()
|
||||
{
|
||||
return owner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<URL> getReferralURL() {
|
||||
return referralUrl;
|
||||
public void setOwner(IModInfo newOwner) {
|
||||
owner.value = newOwner;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+31
-16
@@ -9,49 +9,64 @@ import cpw.mods.jarhandling.JarMetadata;
|
||||
import net.minecraftforge.forgespi.locating.IModFile;
|
||||
import java.lang.module.ModuleDescriptor;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public final class ModJarMetadata implements JarMetadata {
|
||||
private static final String AUTOMATIC_MODULE_NAME = "Automatic-Module-Name";
|
||||
private IModFile modFile;
|
||||
private String name;
|
||||
private String version;
|
||||
private ModuleDescriptor descriptor;
|
||||
|
||||
ModJarMetadata() {
|
||||
}
|
||||
ModJarMetadata() { }
|
||||
|
||||
public void setModFile(IModFile file) {
|
||||
this.modFile = file;
|
||||
var mods = this.modFile.getModFileInfo().getMods();
|
||||
|
||||
if (!mods.isEmpty()) {
|
||||
var main = mods.get(0);
|
||||
this.name = main.getModId();
|
||||
this.version = main.getVersion().toString();
|
||||
}
|
||||
|
||||
var jar = file.getSecureJar();
|
||||
var aname = jar.moduleDataProvider().getManifest().getMainAttributes().getValue(AUTOMATIC_MODULE_NAME);
|
||||
if (aname != null)
|
||||
this.name = aname;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return modFile.getModFileInfo().moduleName();
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String version() {
|
||||
return modFile.getModFileInfo().versionString();
|
||||
return this.version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModuleDescriptor descriptor() {
|
||||
if (descriptor != null) return descriptor;
|
||||
if (descriptor != null)
|
||||
return descriptor;
|
||||
|
||||
var bld = ModuleDescriptor.newAutomaticModule(name())
|
||||
.version(version())
|
||||
.packages(modFile.getSecureJar().getPackages());
|
||||
modFile.getSecureJar().getProviders().stream()
|
||||
.filter(p -> !p.providers().isEmpty())
|
||||
.forEach(p -> bld.provides(p.serviceName(), p.providers()));
|
||||
modFile.getModFileInfo().usesServices().forEach(bld::uses);
|
||||
.version(version())
|
||||
.packages(modFile.getSecureJar().getPackages());
|
||||
|
||||
for (var provider : modFile.getSecureJar().getProviders()) {
|
||||
if (provider.providers().isEmpty())
|
||||
continue;
|
||||
|
||||
bld.provides(provider.serviceName(), provider.providers());
|
||||
}
|
||||
|
||||
descriptor = bld.build();
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public IModFile modFile() {
|
||||
return modFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) return true;
|
||||
|
||||
+7
-8
@@ -34,7 +34,6 @@ public class ModValidator {
|
||||
private LoadingModList loadingModList;
|
||||
private final List<IModFile> brokenFiles = new ArrayList<>();
|
||||
private final List<EarlyLoadingException.ExceptionData> discoveryErrorData;
|
||||
private final List<ModFileLoadingException> modFileLoadingExceptions;
|
||||
|
||||
public ModValidator(Map<IModFile.Type, List<ModFile>> modFiles, List<IModFileInfo> brokenFiles, List<EarlyLoadingException.ExceptionData> discoveryErrorData, List<ModFileLoadingException> modFileLoadingExceptions) {
|
||||
this.candidateMods = lst(modFiles.get(IModFile.Type.MOD));
|
||||
@@ -44,7 +43,6 @@ public class ModValidator {
|
||||
this.candidatePlugins.addAll(lst(modFiles.get(IModFile.Type.LIBRARY)));
|
||||
this.discoveryErrorData = discoveryErrorData;
|
||||
this.brokenFiles.addAll(brokenFiles.stream().map(IModFileInfo::getFile).toList());
|
||||
this.modFileLoadingExceptions = modFileLoadingExceptions;
|
||||
}
|
||||
|
||||
public ModValidator(Map<IModFile.Type, List<ModFile>> modFiles, List<IModFileInfo> brokenFiles, List<EarlyLoadingException.ExceptionData> discoveryErrorData) {
|
||||
@@ -52,7 +50,9 @@ public class ModValidator {
|
||||
}
|
||||
|
||||
private static List<ModFile> lst(List<ModFile> files) {
|
||||
return files == null ? new ArrayList<>() : new ArrayList<>(files);
|
||||
if (files == null) return new ArrayList<>();
|
||||
if (files instanceof ArrayList) return files;
|
||||
return new ArrayList<>(files);
|
||||
}
|
||||
|
||||
public void stage1Validation() {
|
||||
@@ -60,11 +60,11 @@ public class ModValidator {
|
||||
if (LOGGER.isDebugEnabled(LogMarkers.SCAN)) {
|
||||
LOGGER.debug(LogMarkers.SCAN, "Found {} mod files with {} mods", candidateMods.size(), candidateMods.stream().mapToInt(mf -> mf.getModInfos().size()).sum());
|
||||
}
|
||||
ImmediateWindowHandler.updateProgress("Found "+candidateMods.size()+" mod candidates");
|
||||
ImmediateWindowHandler.updateProgress("Found " + candidateMods.size() + " mod candidates");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<ModFile> validateFiles(final List<ModFile> mods) {
|
||||
private static List<ModFile> validateFiles(final List<ModFile> mods) {
|
||||
final List<ModFile> brokenFiles = new ArrayList<>();
|
||||
for (Iterator<ModFile> iterator = mods.iterator(); iterator.hasNext();) {
|
||||
ModFile modFile = iterator.next();
|
||||
@@ -106,8 +106,8 @@ public class ModValidator {
|
||||
|
||||
private List<EarlyLoadingException.ExceptionData> validateLanguages() {
|
||||
List<EarlyLoadingException.ExceptionData> errorData = new ArrayList<>();
|
||||
for (Iterator<ModFile> iterator = this.candidateMods.iterator(); iterator.hasNext(); ) {
|
||||
final ModFile modFile = iterator.next();
|
||||
for (Iterator<ModFile> iterator = this.candidateMods.iterator(); iterator.hasNext();) {
|
||||
var modFile = iterator.next();
|
||||
try {
|
||||
modFile.identifyLanguage();
|
||||
} catch (EarlyLoadingException e) {
|
||||
@@ -125,7 +125,6 @@ public class ModValidator {
|
||||
allErrors.addAll(this.discoveryErrorData);
|
||||
|
||||
loadingModList = ModSorter.sort(candidateMods, allErrors);
|
||||
loadingModList.addCoreMods();
|
||||
loadingModList.addAccessTransformers();
|
||||
loadingModList.setBrokenFiles(brokenFiles);
|
||||
BackgroundScanHandler backgroundScanHandler = new BackgroundScanHandler(candidateMods);
|
||||
|
||||
+16
-6
@@ -16,7 +16,7 @@ import org.slf4j.Logger;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Comparator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
@@ -42,11 +42,21 @@ public class ModsFolderLocator extends AbstractModProvider implements IModLocato
|
||||
public List<IModLocator.ModFileOrException> scanMods() {
|
||||
LOGGER.debug(LogMarkers.SCAN, "Scanning mods dir {} for mods", this.modFolder);
|
||||
var excluded = ModDirTransformerDiscoverer.allExcluded();
|
||||
try (var stream = Files.list(this.modFolder)) {
|
||||
return stream.filter(path -> !excluded.contains(path) && path.getFileName().toString().toLowerCase(Locale.ROOT).endsWith(SUFFIX))
|
||||
.sorted(Comparator.comparing(path -> path.getFileName().toString().toLowerCase(Locale.ROOT)))
|
||||
.map(this::createMod)
|
||||
.toList();
|
||||
try {
|
||||
var ret = new ArrayList<IModLocator.ModFileOrException>();
|
||||
var files = Files.list(this.modFolder).toList();
|
||||
for (var file : files) {
|
||||
var name = file.getFileName().toString().toLowerCase(Locale.ROOT);
|
||||
if (excluded.contains(file) || !name.endsWith(SUFFIX))
|
||||
continue;
|
||||
var mod = this.createMod(file, true);
|
||||
if (mod == null) {
|
||||
LOGGER.debug(LogMarkers.SCAN, "Found unknown jar file {} ignoring", file);
|
||||
} else {
|
||||
ret.add(mod);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
} catch (IOException e) {
|
||||
return sneak(e);
|
||||
}
|
||||
|
||||
+34
-15
@@ -6,6 +6,8 @@
|
||||
package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import com.electronwill.nightconfig.core.UnmodifiableConfig;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
import net.minecraftforge.forgespi.language.IConfigurable;
|
||||
import net.minecraftforge.forgespi.language.IModFileInfo;
|
||||
|
||||
@@ -16,11 +18,12 @@ import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
@ApiStatus.Internal
|
||||
class NightConfigWrapper implements IConfigurable {
|
||||
final class NightConfigWrapper implements IConfigurable {
|
||||
private final UnmodifiableConfig config;
|
||||
private IModFileInfo file;
|
||||
|
||||
@@ -28,35 +31,51 @@ class NightConfigWrapper implements IConfigurable {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
NightConfigWrapper setFile(IModFileInfo file) {
|
||||
private NightConfigWrapper(UnmodifiableConfig config, IModFileInfo file) {
|
||||
this.config = config;
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
void setFile(IModFileInfo file) {
|
||||
this.file = file;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Optional<T> getConfigElement(String key) {
|
||||
var path = List.of(key);
|
||||
return Optional.ofNullable(validate(this.config.get(path), path));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> getConfigElement(final String... key) {
|
||||
var path = asList(key);
|
||||
return this.config.getOptional(path).map(value -> {
|
||||
if (value instanceof UnmodifiableConfig) {
|
||||
return (T) ((UnmodifiableConfig) value).valueMap();
|
||||
} else if (value instanceof ArrayList<?> al && al.size() > 0 && al.get(0) instanceof UnmodifiableConfig) {
|
||||
throw new InvalidModFileException("The configuration path " + path + " is invalid. I wasn't expecting a multi-object list - remove one of the [[ ]]", file);
|
||||
}
|
||||
return (T) value;
|
||||
});
|
||||
return Optional.ofNullable(validate(this.config.get(path), path));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T validate(@Nullable T value, List<String> path) {
|
||||
if (value instanceof UnmodifiableConfig cfg) {
|
||||
// New Night config doesn't implement valueMap(), so do a copy.
|
||||
var entries = cfg.entrySet();
|
||||
var builder = ImmutableMap.builderWithExpectedSize(entries.size());
|
||||
for (var e : entries)
|
||||
builder.put(e.getKey(), e.getValue());
|
||||
return (T) builder.build();
|
||||
} else if (value instanceof ArrayList<?> al && !al.isEmpty() && al.getFirst() instanceof UnmodifiableConfig) {
|
||||
throw new InvalidModFileException("The configuration path " + path + " is invalid. I wasn't expecting a multi-object list - remove one of the [[ ]]", file);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends IConfigurable> getConfigList(final String... key) {
|
||||
final List<String> path = asList(key);
|
||||
if (this.config.contains(path) && !(this.config.get(path) instanceof Collection)) {
|
||||
throw new InvalidModFileException("The configuration path "+path+" is invalid. Expecting a collection!", file);
|
||||
throw new InvalidModFileException("The configuration path " + path + " is invalid. Expecting a collection!", file);
|
||||
}
|
||||
final Collection<UnmodifiableConfig> nestedConfigs = this.config.getOrElse(path, ArrayList::new);
|
||||
return nestedConfigs.stream()
|
||||
.map(NightConfigWrapper::new)
|
||||
.map(cw->cw.setFile(file))
|
||||
.map(conf -> new NightConfigWrapper(conf, file))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -21,37 +21,36 @@ import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
@ApiStatus.Internal
|
||||
class Scanner {
|
||||
record Scanner(ModFile fileToScan, ModFileScanData result) {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private final ModFile fileToScan;
|
||||
private static final boolean DEBUG = LOGGER.isDebugEnabled(LogMarkers.SCAN);
|
||||
|
||||
public Scanner(final ModFile fileToScan) {
|
||||
this.fileToScan = fileToScan;
|
||||
public Scanner(ModFile fileToScan) {
|
||||
this(fileToScan, new ModFileScanData());
|
||||
}
|
||||
|
||||
public ModFileScanData scan() {
|
||||
ModFileScanData result = new ModFileScanData();
|
||||
result.addModFileInfo(fileToScan.getModFileInfo());
|
||||
fileToScan.scanFile(p -> fileVisitor(p, result));
|
||||
fileToScan.scanFile(this::fileVisitor);
|
||||
final List<IModLanguageProvider> loaders = fileToScan.getLoaders();
|
||||
if (loaders != null) {
|
||||
loaders.forEach(loader -> {
|
||||
LOGGER.debug(LogMarkers.SCAN, "Scanning {} with language loader {}", fileToScan.getFilePath(), loader.name());
|
||||
for (IModLanguageProvider loader : loaders) {
|
||||
if (DEBUG) LOGGER.debug("Scanning {} with language loader {}", fileToScan.getFilePath(), loader.name());
|
||||
loader.getFileVisitor().accept(result);
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void fileVisitor(final Path path, final ModFileScanData result) {
|
||||
LOGGER.debug(LogMarkers.SCAN,"Scanning {} path {}", fileToScan, path);
|
||||
try (InputStream in = Files.newInputStream(path)){
|
||||
private void fileVisitor(final Path path) {
|
||||
try (InputStream in = Files.newInputStream(path)) {
|
||||
ModClassVisitor mcv = new ModClassVisitor();
|
||||
ClassReader cr = new ClassReader(in);
|
||||
cr.accept(mcv, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
|
||||
mcv.buildData(result.getClasses(), result.getAnnotations());
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
// mark path bad
|
||||
if (DEBUG) LOGGER.warn("Failed scanning {} path {}", fileToScan, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,8 @@ import java.util.function.Supplier;
|
||||
@ApiStatus.Internal
|
||||
class ArgumentList {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private List<Supplier<String[]>> entries = new ArrayList<>();
|
||||
private Map<String, EntryValue> values = new HashMap<>();
|
||||
private final List<Supplier<String[]>> entries = new ArrayList<>();
|
||||
private final Map<String, EntryValue> values = new HashMap<>();
|
||||
|
||||
public static ArgumentList from(String... args) {
|
||||
ArgumentList ret = new ArgumentList();
|
||||
@@ -77,8 +77,12 @@ class ArgumentList {
|
||||
|
||||
public String[] getArguments() {
|
||||
return entries.stream()
|
||||
.flatMap(e -> Arrays.asList(e.get()).stream())
|
||||
.toArray(size -> new String[size]);
|
||||
.flatMap(e -> Arrays.stream(e.get()))
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
|
||||
public boolean has(String key) {
|
||||
return this.values.containsKey(key);
|
||||
}
|
||||
|
||||
public boolean hasValue(String key) {
|
||||
@@ -122,7 +126,7 @@ class ArgumentList {
|
||||
return ent.getValue();
|
||||
}
|
||||
|
||||
private class EntryValue implements Supplier<String[]> {
|
||||
private static final class EntryValue implements Supplier<String[]> {
|
||||
private final String prefix;
|
||||
private final String key;
|
||||
private final boolean split;
|
||||
|
||||
+31
-6
@@ -22,6 +22,10 @@ abstract class CommonDevLaunchHandler extends CommonLaunchHandler {
|
||||
super(type, prefix);
|
||||
}
|
||||
|
||||
protected CommonDevLaunchHandler(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
@Override public String getNaming() { return "mcp"; }
|
||||
@Override public boolean isProduction() { return false; }
|
||||
|
||||
@@ -29,18 +33,40 @@ abstract class CommonDevLaunchHandler extends CommonLaunchHandler {
|
||||
protected String[] preLaunch(String[] arguments, ModuleLayer layer) {
|
||||
super.preLaunch(arguments, layer);
|
||||
|
||||
var args = ArgumentList.from(arguments);
|
||||
|
||||
if (this.module == null) {
|
||||
var entry = args.get("launchEntry");
|
||||
if (entry == null)
|
||||
throw new IllegalArgumentException("When using " + this.name() + " launch target you must specify a --launchEntry argument");
|
||||
|
||||
args.remove("launchEntry");
|
||||
|
||||
int idx = entry.indexOf('/');
|
||||
if (idx == -1) {
|
||||
this.module = "minecraft";
|
||||
this.main = entry;
|
||||
} else {
|
||||
this.module = entry.substring(0, idx);
|
||||
this.main = entry.substring(idx + 1);
|
||||
}
|
||||
|
||||
if (args.has("launchData")) {
|
||||
args.remove("launchData");
|
||||
this.isData = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (getDist().isDedicatedServer())
|
||||
return arguments;
|
||||
return args.getArguments();
|
||||
|
||||
if (isData())
|
||||
return arguments;
|
||||
|
||||
var args = ArgumentList.from(arguments);
|
||||
return args.getArguments();
|
||||
|
||||
String username = args.get("username");
|
||||
if (username != null) { // Replace '#' placeholders with random numbers
|
||||
Matcher m = Pattern.compile("#+").matcher(username);
|
||||
StringBuffer replaced = new StringBuffer();
|
||||
StringBuilder replaced = new StringBuilder();
|
||||
while (m.find()) {
|
||||
m.appendReplacement(replaced, getRandomNumbers(m.group().length()));
|
||||
}
|
||||
@@ -130,7 +156,6 @@ abstract class CommonDevLaunchHandler extends CommonLaunchHandler {
|
||||
return new String[] {
|
||||
"net/minecraftforge/",
|
||||
"META-INF/services/",
|
||||
"META-INF/coremods.json",
|
||||
"META-INF/mods.toml"
|
||||
};
|
||||
}
|
||||
|
||||
+21
-16
@@ -7,7 +7,6 @@ package net.minecraftforge.fml.loading.targets;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import cpw.mods.modlauncher.api.ILaunchHandlerService;
|
||||
import cpw.mods.modlauncher.api.ITransformingClassLoaderBuilder;
|
||||
import cpw.mods.modlauncher.api.ServiceRunner;
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
@@ -19,7 +18,6 @@ import org.slf4j.Logger;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -31,26 +29,32 @@ import java.util.List;
|
||||
public abstract class CommonLaunchHandler implements ILaunchHandlerService {
|
||||
protected static final Logger LOGGER = LogUtils.getLogger();
|
||||
|
||||
protected final LaunchType type;
|
||||
private final String prefix;
|
||||
private final String name;
|
||||
protected Dist dist = Dist.CLIENT;
|
||||
protected boolean isData = false;
|
||||
protected String module;
|
||||
protected String main;
|
||||
|
||||
protected CommonLaunchHandler(LaunchType type, String prefix) {
|
||||
this.type = type;
|
||||
this.prefix = prefix;
|
||||
this(prefix + type.name());
|
||||
this.dist = type.dist();
|
||||
this.isData = type.data();
|
||||
this.module = type.module();
|
||||
this.main = type.main();
|
||||
}
|
||||
|
||||
@Override public String name() { return this.prefix + this.type.name(); }
|
||||
public Dist getDist() { return this.type.dist(); }
|
||||
public boolean isData() { return this.type.data(); }
|
||||
protected CommonLaunchHandler(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override public String name() { return this.name; }
|
||||
public Dist getDist() { return this.dist; }
|
||||
public boolean isData() { return this.isData; }
|
||||
public boolean isProduction() { return false; }
|
||||
public abstract String getNaming();
|
||||
|
||||
public abstract List<Path> getMinecraftPaths();
|
||||
|
||||
@Override
|
||||
public void configureTransformationClassLoader(final ITransformingClassLoaderBuilder builder) {
|
||||
}
|
||||
|
||||
protected String[] preLaunch(String[] arguments, ModuleLayer layer) {
|
||||
URI uri;
|
||||
try (var reader = layer.configuration().findModule("net.minecraftforge.fmlloader").orElseThrow().reference().open()) {
|
||||
@@ -72,14 +76,15 @@ public abstract class CommonLaunchHandler implements ILaunchHandlerService {
|
||||
}
|
||||
|
||||
protected ServiceRunner makeService(final String[] arguments, final ModuleLayer gameLayer) {
|
||||
return () -> runTarget(this.type.module(), this.type.main(), arguments, gameLayer);
|
||||
return () -> runTarget(this.module, this.main, arguments, gameLayer);
|
||||
}
|
||||
|
||||
protected record LaunchType(String name, String module, String main, Dist dist, boolean data) {};
|
||||
protected static final LaunchType CLIENT = new LaunchType("client", "minecraft", "net.minecraft.client.main.Main", Dist.CLIENT, false);
|
||||
protected static final LaunchType CLIENT_DATA = new LaunchType("client_data", "minecraft", "net.minecraft.client.data.Main", Dist.CLIENT, true);
|
||||
protected static final LaunchType DATA = new LaunchType("data", "minecraft", "net.minecraft.data.Main", Dist.CLIENT, true);
|
||||
protected static final LaunchType SERVER = new LaunchType("server", "minecraft", "net.minecraft.server.Main", Dist.DEDICATED_SERVER, false);
|
||||
protected static final LaunchType SERVER_GAMETEST = new LaunchType("server_gametest", "forge", "net.minecraftforge.gametest.GameTestMain", Dist.DEDICATED_SERVER, false);
|
||||
protected static final LaunchType SERVER_GAMETEST = new LaunchType("server_gametest", "minecraft", "net.minecraft.gametest.Main", Dist.DEDICATED_SERVER, false);
|
||||
|
||||
protected void runTarget(String module, String target, final String[] arguments, final ModuleLayer layer) throws Throwable {
|
||||
var mod = layer.findModule(module).orElse(null);
|
||||
@@ -107,7 +112,7 @@ public abstract class CommonLaunchHandler implements ILaunchHandlerService {
|
||||
len += 2;
|
||||
}
|
||||
str = str.substring(0, str.length() - len);
|
||||
var path = Paths.get(URI.create(str));
|
||||
var path = Path.of(URI.create(str));
|
||||
return path;
|
||||
}
|
||||
}
|
||||
+23
-9
@@ -9,17 +9,19 @@ import java.util.List;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
@ApiStatus.Internal
|
||||
abstract class ForgeDevLaunchHandler extends CommonDevLaunchHandler {
|
||||
sealed abstract class ForgeDevLaunchHandler extends CommonDevLaunchHandler {
|
||||
private ForgeDevLaunchHandler(LaunchType type) {
|
||||
super(type, "forge_dev_");
|
||||
}
|
||||
|
||||
private ForgeDevLaunchHandler(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Path> getMinecraftPaths() {
|
||||
|
||||
// The extra jar is on the classpath, so try and pull it out of the legacy classpath
|
||||
var legacyCP = findClassPath();
|
||||
var extra = findJarOnClasspath(legacyCP, "client-extra");
|
||||
// The client extra jar is on the classpath we can try locating it using the .mcassetsroot which vanilla uses for loading data
|
||||
var extra = getPathFromResource("assets/.mcassetsroot");
|
||||
// Minecraft is an exploded directory, so find it.
|
||||
var minecraft = getPathFromResource("net/minecraft/client/Minecraft.class");
|
||||
var forge = getPathFromResource("net/minecraftforge/common/MinecraftForge.class");
|
||||
@@ -33,27 +35,39 @@ abstract class ForgeDevLaunchHandler extends CommonDevLaunchHandler {
|
||||
return List.of(filtered);
|
||||
}
|
||||
|
||||
public static class Client extends ForgeDevLaunchHandler {
|
||||
public static final class Client extends ForgeDevLaunchHandler {
|
||||
public Client() {
|
||||
super(CLIENT);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Data extends ForgeDevLaunchHandler {
|
||||
public static final class ClientData extends ForgeDevLaunchHandler {
|
||||
public ClientData() {
|
||||
super(CLIENT_DATA);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Data extends ForgeDevLaunchHandler {
|
||||
public Data() {
|
||||
super(DATA);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Server extends ForgeDevLaunchHandler {
|
||||
public static final class Server extends ForgeDevLaunchHandler {
|
||||
public Server() {
|
||||
super(SERVER);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ServerGameTest extends ForgeDevLaunchHandler {
|
||||
public static final class ServerGameTest extends ForgeDevLaunchHandler {
|
||||
public ServerGameTest() {
|
||||
super(SERVER_GAMETEST);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Custom extends ForgeDevLaunchHandler {
|
||||
public Custom() {
|
||||
super("forge_dev");
|
||||
}
|
||||
}
|
||||
}
|
||||
+38
-21
@@ -35,7 +35,7 @@ import net.minecraftforge.fml.loading.moddiscovery.AbstractModProvider;
|
||||
import net.minecraftforge.forgespi.locating.IModLocator;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public class ForgeDevLocator extends AbstractModProvider implements IModLocator {
|
||||
public final class ForgeDevLocator extends AbstractModProvider implements IModLocator {
|
||||
private static final String PACK_META = "pack.mcmeta";
|
||||
|
||||
@Override
|
||||
@@ -60,7 +60,7 @@ public class ForgeDevLocator extends AbstractModProvider implements IModLocator
|
||||
return ret;
|
||||
}
|
||||
|
||||
private List<Path> getMods() {
|
||||
private static List<Path> getMods() {
|
||||
// Forge is an exploded directory as well
|
||||
var minecraft = ForgeDevLaunchHandler.getPathFromResource("net/minecraft/client/Minecraft.class");
|
||||
var forge = ForgeDevLaunchHandler.getPathFromResource("net/minecraftforge/common/MinecraftForge.class");
|
||||
@@ -84,30 +84,31 @@ public class ForgeDevLocator extends AbstractModProvider implements IModLocator
|
||||
return ret;
|
||||
}
|
||||
|
||||
private List<Path> explodeTestMods(Path path) {
|
||||
private static List<Path> explodeTestMods(Path path) {
|
||||
var mod = new ArrayList<Path>();
|
||||
var memory = Jimfs.newFileSystem();
|
||||
|
||||
// First lets find all class files, that have the @Mod annotation and map packages to modids.
|
||||
var packages = findTestModPackages(path);
|
||||
for (var entry : packages.entrySet()) {
|
||||
var pkg = entry.getKey();
|
||||
var pkg = entry.getKey() + '/';
|
||||
var modids = entry.getValue();
|
||||
|
||||
// Find resource directories for every mod in this group
|
||||
var resourcePaths = new LinkedHashSet<Path>();
|
||||
for (var modid : modids) {
|
||||
var paths = new LinkedHashSet<Path>();
|
||||
for (var modid : modids.stream().sorted().toList()) {
|
||||
var rsc = path.resolve(modid);
|
||||
if (Files.exists(rsc))
|
||||
resourcePaths.add(rsc);
|
||||
paths.add(rsc);
|
||||
}
|
||||
|
||||
var root = memory.getPath(modids.iterator().next()); // use the first modid as our root
|
||||
buildModsToml(resourcePaths, modids, root);
|
||||
buildPackMeta(resourcePaths, root);
|
||||
buildModsToml(paths, modids, root, pkg.startsWith("net/minecraftforge/debug/client/"));
|
||||
buildPackMeta(paths, root);
|
||||
moveModuleInfo(paths, root);
|
||||
|
||||
if (Files.exists(root))
|
||||
resourcePaths.add(root);
|
||||
paths.add(root);
|
||||
|
||||
// We want just the class files from the root of the input paths. So make a new union with a filter.
|
||||
var classes = UnionHelper.newFileSystem(
|
||||
@@ -121,14 +122,10 @@ public class ForgeDevLocator extends AbstractModProvider implements IModLocator
|
||||
}, new Path[] { path }
|
||||
);
|
||||
|
||||
// And we want the resources, and none of the classes, just in case things get kinda fucky and somehow a package overlaps a sub package {shading?}
|
||||
var resources = UnionHelper.newFileSystem((name, base) -> !name.endsWith(".class"), resourcePaths.toArray(Path[]::new));
|
||||
paths.addFirst(classes.getRootDirectories().iterator().next());
|
||||
|
||||
// Union of unions, Yay!
|
||||
var union = UnionHelper.newFileSystem(null, new Path[] {
|
||||
classes.getRootDirectories().iterator().next(),
|
||||
resources.getRootDirectories().iterator().next()
|
||||
});
|
||||
var union = UnionHelper.newFileSystem(null, paths.stream().toArray(Path[]::new));
|
||||
mod.add(union.getRootDirectories().iterator().next());
|
||||
}
|
||||
|
||||
@@ -137,7 +134,7 @@ public class ForgeDevLocator extends AbstractModProvider implements IModLocator
|
||||
|
||||
// Find all the @Mods so we can generate tomls, and so we can pick packages. Right now it doesn't allow mods in parent directories,
|
||||
// I could make it merge all the way up, but I think this would be fine.
|
||||
private Map<String, Set<String>> findTestModPackages(Path path) {
|
||||
private static Map<String, Set<String>> findTestModPackages(Path path) {
|
||||
var mods = new HashMap<String, Set<String>>();
|
||||
try (var files = Files.walk(path)) {
|
||||
var classes = files
|
||||
@@ -190,7 +187,7 @@ public class ForgeDevLocator extends AbstractModProvider implements IModLocator
|
||||
}
|
||||
|
||||
// Builds or update the mods.toml file for all @Mods in this package
|
||||
private void buildModsToml(Set<Path> resources, Set<String> modids, Path root) {
|
||||
private static void buildModsToml(Set<Path> resources, Set<String> modids, Path root, boolean clientOnly) {
|
||||
var toml = resources.stream()
|
||||
.map(p -> p.resolve(MODS_TOML))
|
||||
.filter(Files::exists)
|
||||
@@ -208,7 +205,8 @@ public class ForgeDevLocator extends AbstractModProvider implements IModLocator
|
||||
var defaults = Map.of(
|
||||
"modLoader", "javafml",
|
||||
"loaderVersion", "[0,)",
|
||||
"license", "Doesnt fucking matter"
|
||||
"license", "Doesnt fucking matter",
|
||||
"clientSideOnly", clientOnly
|
||||
);
|
||||
|
||||
for (var key : defaults.keySet()) {
|
||||
@@ -226,7 +224,7 @@ public class ForgeDevLocator extends AbstractModProvider implements IModLocator
|
||||
}
|
||||
|
||||
for (var modid : modids) {
|
||||
if (!modlist.stream().anyMatch(c -> modid.equals(c.get("modId")))) {
|
||||
if (modlist.stream().noneMatch(c -> modid.equals(c.get("modId")))) {
|
||||
modified = true;
|
||||
var tmp = Config.inMemory();
|
||||
tmp.set("modId", modid);
|
||||
@@ -248,7 +246,7 @@ public class ForgeDevLocator extends AbstractModProvider implements IModLocator
|
||||
}
|
||||
|
||||
// This is optional, it just hides a warning screen when starting up. I should probably remove this once I restructure how data gen for test mods work and make it generate there.
|
||||
private void buildPackMeta(Set<Path> paths, Path root) {
|
||||
private static void buildPackMeta(Set<Path> paths, Path root) {
|
||||
var existing = paths.stream()
|
||||
.map(p -> p.resolve(PACK_META))
|
||||
.filter(Files::exists)
|
||||
@@ -274,6 +272,25 @@ public class ForgeDevLocator extends AbstractModProvider implements IModLocator
|
||||
}
|
||||
}
|
||||
|
||||
private static void moveModuleInfo(Set<Path> paths, Path root) {
|
||||
var existing = paths.stream()
|
||||
.map(p -> p.resolve("module-info.dat"))
|
||||
.filter(Files::exists)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
if (existing == null)
|
||||
return;
|
||||
|
||||
var target = root.resolve("module-info.class");
|
||||
try {
|
||||
Files.createDirectories(target.getParent());
|
||||
Files.copy(existing, target);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Failed to copy module-info.dat to memory: " + target, e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <E extends Throwable, R> R sneak(Throwable e) throws E {
|
||||
throw (E)e;
|
||||
|
||||
+4
-4
@@ -10,15 +10,15 @@ import java.util.List;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
@ApiStatus.Internal
|
||||
abstract class ForgeProdLaunchHandler extends CommonLaunchHandler {
|
||||
sealed abstract class ForgeProdLaunchHandler extends CommonLaunchHandler {
|
||||
protected ForgeProdLaunchHandler(LaunchType type) {
|
||||
super(type, "forge_");
|
||||
}
|
||||
|
||||
@Override public String getNaming() { return "srg"; }
|
||||
@Override public String getNaming() { return "mcp"; }
|
||||
@Override public boolean isProduction() { return true; }
|
||||
|
||||
public static class Client extends ForgeProdLaunchHandler {
|
||||
public static final class Client extends ForgeProdLaunchHandler {
|
||||
public Client() {
|
||||
super(CLIENT);
|
||||
}
|
||||
@@ -29,7 +29,7 @@ abstract class ForgeProdLaunchHandler extends CommonLaunchHandler {
|
||||
}
|
||||
}
|
||||
|
||||
public static class Server extends ForgeProdLaunchHandler {
|
||||
public static final class Server extends ForgeProdLaunchHandler {
|
||||
public Server() {
|
||||
super(SERVER);
|
||||
}
|
||||
|
||||
+22
-9
@@ -8,17 +8,18 @@ package net.minecraftforge.fml.loading.targets;
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.function.BiPredicate;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import cpw.mods.jarhandling.SecureJar;
|
||||
|
||||
@ApiStatus.Internal
|
||||
abstract class ForgeUserdevLaunchHandler extends CommonDevLaunchHandler {
|
||||
sealed abstract class ForgeUserdevLaunchHandler extends CommonDevLaunchHandler {
|
||||
private ForgeUserdevLaunchHandler(LaunchType type) {
|
||||
super(type, "forge_userdev_");
|
||||
}
|
||||
|
||||
private ForgeUserdevLaunchHandler(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Path> getMinecraftPaths() {
|
||||
var legacyCP = findClassPath();
|
||||
@@ -26,34 +27,46 @@ abstract class ForgeUserdevLaunchHandler extends CommonDevLaunchHandler {
|
||||
|
||||
// Minecraft is extra jar {resources} + forge jar {patches}
|
||||
// The MC extra and forge jars are on the classpath, so try and pull them out
|
||||
var extra = findJarOnClasspath(legacyCP, "client-extra");
|
||||
var extra = findJarOnClasspath(legacyCP, "client-extra"); // This should be "client-" + vers.mcAndMCPVersion() + "-extra" but FG6 qwerks
|
||||
var forge = findJarOnClasspath(legacyCP, "forge-" + vers.mcAndForgeVersion());
|
||||
// We need to filter the forge jar to just MC code
|
||||
var minecraft = CommonDevLaunchHandler.getMinecraftOnly(extra, forge);
|
||||
return List.of(minecraft);
|
||||
}
|
||||
|
||||
public static class Client extends ForgeUserdevLaunchHandler {
|
||||
public static final class Client extends ForgeUserdevLaunchHandler {
|
||||
public Client() {
|
||||
super(CLIENT);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Data extends ForgeUserdevLaunchHandler {
|
||||
public static final class ClientData extends ForgeUserdevLaunchHandler {
|
||||
public ClientData() {
|
||||
super(CLIENT_DATA);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Data extends ForgeUserdevLaunchHandler {
|
||||
public Data() {
|
||||
super(DATA);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Server extends ForgeUserdevLaunchHandler {
|
||||
public static final class Server extends ForgeUserdevLaunchHandler {
|
||||
public Server() {
|
||||
super(SERVER);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ServerGameTest extends ForgeUserdevLaunchHandler {
|
||||
public static final class ServerGameTest extends ForgeUserdevLaunchHandler {
|
||||
public ServerGameTest() {
|
||||
super(SERVER_GAMETEST);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Custom extends ForgeUserdevLaunchHandler {
|
||||
public Custom() {
|
||||
super("forge_userdev");
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -12,7 +12,7 @@ import net.minecraftforge.fml.loading.moddiscovery.AbstractModProvider;
|
||||
import net.minecraftforge.forgespi.locating.IModLocator;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public class ForgeUserdevLocator extends AbstractModProvider implements IModLocator {
|
||||
public final class ForgeUserdevLocator extends AbstractModProvider implements IModLocator {
|
||||
@Override
|
||||
public String name() {
|
||||
return "forge_userdev_locator";
|
||||
|
||||
+1
-2
@@ -25,7 +25,7 @@ import java.util.Set;
|
||||
*/
|
||||
public class StronglyConnectedComponentDetector<T> {
|
||||
private final Graph<T> graph;
|
||||
private Map<T, Integer> ids;
|
||||
private final Map<T, Integer> ids = new HashMap<>();
|
||||
private T[] elements;
|
||||
private int[] dfn;
|
||||
private int[] low;
|
||||
@@ -49,7 +49,6 @@ public class StronglyConnectedComponentDetector<T> {
|
||||
private void calculate() {
|
||||
components = new HashSet<>();
|
||||
int t = 0;
|
||||
ids = new HashMap<>();
|
||||
Set<T> nodes = graph.nodes();
|
||||
elements = (T[]) new Object[nodes.size()];
|
||||
for (T node : nodes) {
|
||||
|
||||
+4
@@ -4,12 +4,16 @@ net.minecraftforge.fml.loading.targets.ForgeProdLaunchHandler$Server
|
||||
|
||||
# Mod Developers
|
||||
net.minecraftforge.fml.loading.targets.ForgeUserdevLaunchHandler$Client
|
||||
net.minecraftforge.fml.loading.targets.ForgeUserdevLaunchHandler$ClientData
|
||||
net.minecraftforge.fml.loading.targets.ForgeUserdevLaunchHandler$Custom
|
||||
net.minecraftforge.fml.loading.targets.ForgeUserdevLaunchHandler$Data
|
||||
net.minecraftforge.fml.loading.targets.ForgeUserdevLaunchHandler$Server
|
||||
net.minecraftforge.fml.loading.targets.ForgeUserdevLaunchHandler$ServerGameTest
|
||||
|
||||
#Working on Forge itself
|
||||
net.minecraftforge.fml.loading.targets.ForgeDevLaunchHandler$Client
|
||||
net.minecraftforge.fml.loading.targets.ForgeDevLaunchHandler$ClientData
|
||||
net.minecraftforge.fml.loading.targets.ForgeDevLaunchHandler$Custom
|
||||
net.minecraftforge.fml.loading.targets.ForgeDevLaunchHandler$Data
|
||||
net.minecraftforge.fml.loading.targets.ForgeDevLaunchHandler$Server
|
||||
net.minecraftforge.fml.loading.targets.ForgeDevLaunchHandler$ServerGameTest
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
net.minecraftforge.fml.loading.log4j.SLF4JFixerLaunchPluginService
|
||||
net.minecraftforge.fml.loading.RuntimeDistCleaner
|
||||
net.minecraftforge.fml.common.asm.RuntimeEnumExtender
|
||||
net.minecraftforge.fml.common.asm.ObjectHolderDefinalize
|
||||
net.minecraftforge.fml.common.asm.CapabilityTokenSubclass
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"options": [
|
||||
{
|
||||
"resource": "com/llamalad7/mixinextras/platform/forge/MixinExtrasConfigPlugin.class",
|
||||
"layer": "GAMELIBRARY",
|
||||
"id": "mixinextras",
|
||||
"deps": [
|
||||
{
|
||||
"identifier": {
|
||||
"group": "com.github.LlamaLad7",
|
||||
"artifact": "MixinExtras"
|
||||
},
|
||||
"version": {
|
||||
"range": "[0.5.3,)",
|
||||
"artifactVersion": "0.5.3"
|
||||
},
|
||||
"path": "",
|
||||
"isObfuscated": false
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"identifier": {
|
||||
"group": "io.github.llamalad7",
|
||||
"artifact": "mixinextras-forge"
|
||||
},
|
||||
"version": {
|
||||
"artifactVersion": "0.5.3"
|
||||
},
|
||||
"path": "",
|
||||
"isObfuscated": false
|
||||
},
|
||||
"nested": true
|
||||
}
|
||||
]
|
||||
}
|
||||
Loaded 100 of 2878 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user