mirror of
https://github.com/MinecraftForge/MinecraftForge.git
synced 2026-11-14 01:04:24 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44c689f171 | ||
|
|
1f44339d6e | ||
|
|
001372bef2 | ||
|
|
8f72c143a1 | ||
|
|
3c75e1269d | ||
|
|
121120aee1 | ||
|
|
62ce55d8d7 | ||
|
|
183d43f5f8 | ||
|
|
79dfdb0ace | ||
|
|
bb4a0b7dd6 | ||
|
|
4cddc62be7 | ||
|
|
914b2fe984 | ||
|
|
308d4fa68e | ||
|
|
50f325c8fe | ||
|
|
57fd0ecb2e | ||
|
|
94af5cc43a | ||
|
|
3c19a0309e | ||
|
|
0eb9d94bf3 | ||
|
|
4d57a1bec6 | ||
|
|
59ab01a559 | ||
|
|
5946356f8e | ||
|
|
f0f7b18528 | ||
|
|
5311b0bc90 | ||
|
|
43580f5804 | ||
|
|
b0a68b8c79 | ||
|
|
f225d1f3fb | ||
|
|
15214a6e9a | ||
|
|
dfce6b62d4 | ||
|
|
0232ad7424 | ||
|
|
e6ff2e0c24 | ||
|
|
72980d2b03 | ||
|
|
6e3b51d81f | ||
|
|
6b1c628326 | ||
|
|
85f9b1012c | ||
|
|
7569de7004 | ||
|
|
5ccde7422c | ||
|
|
ad6bf9428e | ||
|
|
d176bab28e | ||
|
|
58d6f072e4 | ||
|
|
cc393818e4 | ||
|
|
e84098a011 | ||
|
|
f2db3941ea | ||
|
|
c2bfe12068 | ||
|
|
d9e3a0081e | ||
|
|
84d7a0facb | ||
|
|
9c66928854 | ||
|
|
4b494451db | ||
|
|
e9ea4ca169 | ||
|
|
c5ca054936 | ||
|
|
7d2c7c0f91 | ||
|
|
54b872af69 | ||
|
|
ae954bea17 | ||
|
|
ad59985a9b | ||
|
|
f8fdd43552 | ||
|
|
b269f7dbfd | ||
|
|
e688f136dd | ||
|
|
81276b341a | ||
|
|
f050617b47 | ||
|
|
e6c7958456 | ||
|
|
4802ceeccc | ||
|
|
2ea16d89ca | ||
|
|
e541201129 | ||
|
|
09aac3a4d3 | ||
|
|
dccb1ab3a4 | ||
|
|
8bf96d8738 | ||
|
|
069a94f205 | ||
|
|
332ed4ab76 | ||
|
|
030f2dbd38 | ||
|
|
ec3abdea14 | ||
|
|
4cf5508e04 | ||
|
|
d861f647d7 | ||
|
|
992f793695 | ||
|
|
69eb940764 | ||
|
|
c1f26d544a | ||
|
|
8255d7aed3 | ||
|
|
617ecb7ad3 | ||
|
|
dd8239c255 | ||
|
|
bdddc8c5d4 | ||
|
|
c1c619a109 | ||
|
|
f7eea35cb9 | ||
|
|
a2434e1607 | ||
|
|
b806ecd0ff | ||
|
|
8358b800e9 | ||
|
|
b25a092cf1 | ||
|
|
d812fdd0a3 | ||
|
|
2d3ee8a228 | ||
|
|
97a9ca65f1 | ||
|
|
f043fbbc51 | ||
|
|
a19ab3a8d4 | ||
|
|
bc0517a404 | ||
|
|
dc9c0d7e35 | ||
|
|
1a2207b3b2 | ||
|
|
84ab9764e3 | ||
|
|
bef68991f0 | ||
|
|
526c13d662 | ||
|
|
67ffb78c23 | ||
|
|
8f407c3a22 | ||
|
|
8980252e16 | ||
|
|
3baee80485 | ||
|
|
ca9599bb4d | ||
|
|
051db7907a | ||
|
|
bdba5ef2d3 | ||
|
|
838b9c0eda | ||
|
|
be584c54aa | ||
|
|
1b3640b42d | ||
|
|
be5dab5801 | ||
|
|
aae628cd18 | ||
|
|
e7bd08649f | ||
|
|
e1017a4c7e | ||
|
|
6f8ba9d295 | ||
|
|
ac94478ae7 | ||
|
|
515c6082e0 | ||
|
|
a753598113 | ||
|
|
bc6c4be4e4 | ||
|
|
98823a8f07 | ||
|
|
ecd8f1e54d | ||
|
|
b0caea8e20 | ||
|
|
e0477f8776 | ||
|
|
94e1e54b53 | ||
|
|
9ce72331b7 | ||
|
|
1c31f42dae | ||
|
|
0978a94358 | ||
|
|
e956a9c9f3 | ||
|
|
82026d11a4 | ||
|
|
6d233ac75c | ||
|
|
73c1934e7e | ||
|
|
bf1194ec75 | ||
|
|
22d2740725 | ||
|
|
02f26dc7a1 | ||
|
|
db14cbb0e0 | ||
|
|
a2b35cae92 | ||
|
|
aab0e4c82e | ||
|
|
6609708d14 | ||
|
|
ed198f062c | ||
|
|
4e2d656e9d | ||
|
|
2a4230868f | ||
|
|
cf778b1759 | ||
|
|
1b6daddd40 | ||
|
|
d7b137d144 | ||
|
|
b5d5855559 | ||
|
|
c0aa8dd808 | ||
|
|
4f2f5c02a0 | ||
|
|
7a6843fa4a | ||
|
|
f0ecf16c6b | ||
|
|
8998fa71b2 | ||
|
|
4e677a7558 | ||
|
|
07254b36d7 | ||
|
|
a51b8d256d | ||
|
|
75fcd9448e | ||
|
|
6b45999702 | ||
|
|
a4f52f4057 | ||
|
|
b8b3201372 | ||
|
|
41deadc1e2 | ||
|
|
82e8d06e54 | ||
|
|
51db5f2ff7 | ||
|
|
2ec9e515ff | ||
|
|
0579572148 | ||
|
|
83f4e6353c | ||
|
|
130953e519 | ||
|
|
7981093591 | ||
|
|
79112d6208 | ||
|
|
a3eb6263f4 | ||
|
|
d91bc9c73d | ||
|
|
c807665ab9 | ||
|
|
7b97c181b9 | ||
|
|
c42b0847d2 | ||
|
|
f182cb00c1 | ||
|
|
3181b5da23 | ||
|
|
983c119477 | ||
|
|
874ff1bba2 | ||
|
|
06d9d89f98 | ||
|
|
c713bad8c3 | ||
|
|
fe19491822 | ||
|
|
c80e8a1aca | ||
|
|
6845c094ab | ||
|
|
3c1126a7b4 | ||
|
|
326e0329b9 | ||
|
|
f9d0daa91c | ||
|
|
094200e687 | ||
|
|
bb4818630c | ||
|
|
7af3155b39 | ||
|
|
d988b4a811 | ||
|
|
18f5bdb7ca | ||
|
|
f41d493552 | ||
|
|
8cd2c9c63f | ||
|
|
427b335cac | ||
|
|
2049096c6f | ||
|
|
98d4fa6dc2 | ||
|
|
5306471aec | ||
|
|
aa8818b049 | ||
|
|
2d86f17363 | ||
|
|
3a36a01913 | ||
|
|
47d70a7f31 | ||
|
|
d5b5e6f4db | ||
|
|
e76d814439 | ||
|
|
b8b40e1f54 | ||
|
|
5e8c786406 | ||
|
|
5b8224f839 | ||
|
|
cd621eb081 | ||
|
|
b1113f0363 | ||
|
|
daf5933b79 | ||
|
|
47fd2e8604 | ||
|
|
05297fd72f | ||
|
|
f20cf19f94 | ||
|
|
061cd3da53 | ||
|
|
03202a8ab4 | ||
|
|
8a67be4afd | ||
|
|
4c1ab67f33 | ||
|
|
7e06a2105f | ||
|
|
4a0c8be742 | ||
|
|
0c030ee5e1 | ||
|
|
1134fac54c | ||
|
|
ac44d72530 | ||
|
|
12468bd8d2 | ||
|
|
a98eeb9d08 | ||
|
|
b558b132cd | ||
|
|
c102633566 | ||
|
|
cce002ad29 | ||
|
|
b1ed321438 | ||
|
|
c7c2478cb0 | ||
|
|
b70d99489b | ||
|
|
57f45eaca0 | ||
|
|
52fcc4d649 | ||
|
|
959ab9abc9 | ||
|
|
c3a91872fe | ||
|
|
4d074f0a1e | ||
|
|
c2225ab1da | ||
|
|
bb03b1a7d1 | ||
|
|
be7275443f | ||
|
|
dc1c973684 | ||
|
|
06d568408c | ||
|
|
2256e88fb4 | ||
|
|
9b962c8ff0 | ||
|
|
7656b75992 | ||
|
|
58a5449c2f | ||
|
|
702abe0665 | ||
|
|
2070b77fd4 | ||
|
|
fd00822015 | ||
|
|
17565c0ea2 | ||
|
|
22668ad2c2 | ||
|
|
0dcc3d700d | ||
|
|
2ab3d9b8a3 | ||
|
|
d578d8f6df | ||
|
|
fd61016a7e | ||
|
|
7f77d24f45 | ||
|
|
3ebdcb34d1 | ||
|
|
05240fecf7 | ||
|
|
d5860d7cfa | ||
|
|
7bb97dafda | ||
|
|
ca90babc4a | ||
|
|
4b813e4319 | ||
|
|
fa84c22662 | ||
|
|
2ae640b530 | ||
|
|
4f95a20c5e | ||
|
|
915d570b17 | ||
|
|
5979636ee5 | ||
|
|
5acfc6ae3c | ||
|
|
48341120fb | ||
|
|
ff45bb4c27 | ||
|
|
eabba46595 | ||
|
|
683bf99fe4 | ||
|
|
2a05c7c01d | ||
|
|
e386229b66 | ||
|
|
59b8ec5625 | ||
|
|
50ae5db357 | ||
|
|
3aa99a1250 | ||
|
|
00c8513129 | ||
|
|
9c1404fecc | ||
|
|
c11686ab6c | ||
|
|
2818984f6c | ||
|
|
db225ed04a | ||
|
|
0ec4c34a34 | ||
|
|
597ebc3267 | ||
|
|
e16f54eebe | ||
|
|
27480c15fa | ||
|
|
df0dea59f3 | ||
|
|
d7bc564512 | ||
|
|
e6a73ef36c | ||
|
|
1ba7c6aecd | ||
|
|
7c5e95222e | ||
|
|
38df3d2a15 | ||
|
|
3beaa817f3 | ||
|
|
3ebd7ba0ef | ||
|
|
1ba9c34650 | ||
|
|
2f9e726aea | ||
|
|
ade2b8cc80 | ||
|
|
9b144f4541 | ||
|
|
0c35436217 | ||
|
|
0e66b7b217 |
No files matched your search
@@ -1,16 +0,0 @@
|
||||
# Configuration for support-requests - https://github.com/dessant/support-requests
|
||||
|
||||
# Label used to mark issues as support requests
|
||||
supportLabel: Forum
|
||||
# Comment to post on issues marked as support requests. Add a link
|
||||
# to a support page, or set to `false` to disable
|
||||
supportComment: >
|
||||
👋 We use the issue tracker exclusively for final bug reports and feature requests.
|
||||
However, this issue appears to be better suited for the
|
||||
[Forge Support Forums](http://www.minecraftforge.net/forum/forum/18-support-bug-reports/).
|
||||
Please create a new topic on the support forum with this issue, and the conversation can
|
||||
continue there.
|
||||
# Whether to close issues marked as support requests
|
||||
close: true
|
||||
# Whether to lock issues marked as support requests
|
||||
lock: true
|
||||
@@ -0,0 +1,16 @@
|
||||
name: 'Comment actions'
|
||||
|
||||
on: issue_comment
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
discussions: write
|
||||
|
||||
jobs:
|
||||
labels:
|
||||
uses: MinecraftForge/SharedActions/.github/workflows/comment_actions.yml@main
|
||||
secrets:
|
||||
GH_APP_KEY: ${{ secrets.GH_APP_KEY }}
|
||||
GH_APP_NAME: ${{ secrets.GH_APP_NAME }}
|
||||
@@ -0,0 +1,16 @@
|
||||
name: 'Issue actions'
|
||||
|
||||
on: issues
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
discussions: write
|
||||
|
||||
jobs:
|
||||
labels:
|
||||
uses: MinecraftForge/SharedActions/.github/workflows/issue_actions.yml@main
|
||||
secrets:
|
||||
GH_APP_KEY: ${{ secrets.GH_APP_KEY }}
|
||||
GH_APP_NAME: ${{ secrets.GH_APP_NAME }}
|
||||
@@ -0,0 +1,13 @@
|
||||
name: 'PR Review'
|
||||
|
||||
on: pull_request_review
|
||||
|
||||
jobs:
|
||||
upload_payload:
|
||||
name: Upload payload
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/upload-artifact@v3 # Payload artifact is consumed in Actionable
|
||||
with:
|
||||
name: payload
|
||||
path: ${{ github.event_path }}
|
||||
@@ -0,0 +1,13 @@
|
||||
name: 'PR Review Comment'
|
||||
|
||||
on: pull_request_review_comment
|
||||
|
||||
jobs:
|
||||
upload_payload:
|
||||
name: Upload payload
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/upload-artifact@v3 # Payload artifact is consumed in Actionable
|
||||
with:
|
||||
name: payload
|
||||
path: ${{ github.event_path }}
|
||||
@@ -0,0 +1,20 @@
|
||||
name: 'Pull Request Review Comment actions'
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: [PR Review Comment]
|
||||
types:
|
||||
- completed
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
discussions: write
|
||||
|
||||
jobs:
|
||||
labels:
|
||||
uses: MinecraftForge/SharedActions/.github/workflows/pr_review_comment_actions.yml@main
|
||||
secrets:
|
||||
GH_APP_KEY: ${{ secrets.GH_APP_KEY }}
|
||||
GH_APP_NAME: ${{ secrets.GH_APP_NAME }}
|
||||
@@ -0,0 +1,20 @@
|
||||
name: 'Pull Request Review actions'
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: [PR Review]
|
||||
types:
|
||||
- completed
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
discussions: write
|
||||
|
||||
jobs:
|
||||
labels:
|
||||
uses: MinecraftForge/SharedActions/.github/workflows/pr_review_actions.yml@main
|
||||
secrets:
|
||||
GH_APP_KEY: ${{ secrets.GH_APP_KEY }}
|
||||
GH_APP_NAME: ${{ secrets.GH_APP_NAME }}
|
||||
@@ -0,0 +1,18 @@
|
||||
name: 'Pull Request actions'
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [assigned, unassigned, labeled, unlabeled, opened, edited, closed, reopened, synchronize, converted_to_draft, ready_for_review, locked, unlocked, review_requested, review_request_removed, auto_merge_enabled, auto_merge_disabled]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
discussions: write
|
||||
|
||||
jobs:
|
||||
labels:
|
||||
uses: MinecraftForge/SharedActions/.github/workflows/pr_actions.yml@main
|
||||
secrets:
|
||||
GH_APP_KEY: ${{ secrets.GH_APP_KEY }}
|
||||
GH_APP_NAME: ${{ secrets.GH_APP_NAME }}
|
||||
@@ -0,0 +1,16 @@
|
||||
name: 'Push actions'
|
||||
|
||||
on: push
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
discussions: write
|
||||
|
||||
jobs:
|
||||
labels:
|
||||
uses: MinecraftForge/SharedActions/.github/workflows/push_actions.yml@main
|
||||
secrets:
|
||||
GH_APP_KEY: ${{ secrets.GH_APP_KEY }}
|
||||
GH_APP_NAME: ${{ secrets.GH_APP_NAME }}
|
||||
Vendored
+74
-2
@@ -1,5 +1,7 @@
|
||||
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
|
||||
@@ -30,11 +32,13 @@ 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.4", 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.18.x", label = "Git Main Branch", description = "The git main or default branch to use in VCS operations.", 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.19.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*)
|
||||
@@ -134,3 +138,71 @@ object PullRequests : BuildType({
|
||||
""".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()
|
||||
}
|
||||
})
|
||||
+97
-68
@@ -1,4 +1,10 @@
|
||||
import groovy.json.JsonBuilder
|
||||
import net.minecraftforge.forge.tasks.checks.CheckATs
|
||||
import net.minecraftforge.forge.tasks.checks.CheckExcs
|
||||
import net.minecraftforge.forge.tasks.checks.CheckPatches
|
||||
import net.minecraftforge.forge.tasks.checks.CheckSAS
|
||||
import net.minecraftforge.forge.tasks.checks.CheckTask
|
||||
import net.minecraftforge.forge.tasks.checks.CheckMode
|
||||
|
||||
import java.nio.file.Files
|
||||
import java.util.Date
|
||||
@@ -19,9 +25,9 @@ plugins {
|
||||
id 'com.github.ben-manes.versions' version '0.36.0'
|
||||
id 'net.minecraftforge.gradleutils' version '2.+'
|
||||
id 'eclipse'
|
||||
id 'de.undercouch.download' version '4.1.2'
|
||||
id 'net.minecraftforge.gradle.patcher' version '[5.1.39,)' apply false
|
||||
id 'net.minecraftforge.gradle.mcp' version '[5.1.39,)' apply false
|
||||
id 'de.undercouch.download' version '5.1.3'
|
||||
id 'net.minecraftforge.gradle.patcher' version '[5.1.50,5.2.0)' apply false
|
||||
id 'net.minecraftforge.gradle.mcp' version '[5.1.50,5.2.0)' apply false
|
||||
}
|
||||
|
||||
Util.init() //Init all our extension methods!
|
||||
@@ -36,16 +42,17 @@ ext {
|
||||
]
|
||||
}
|
||||
MAPPING_CHANNEL = 'official'
|
||||
MAPPING_VERSION = '1.19'
|
||||
MC_VERSION = '1.19'
|
||||
MCP_VERSION = '20220607.102129'
|
||||
SPI_VERSION = '4.0.10'
|
||||
MAPPING_VERSION = '1.19.4'
|
||||
MC_VERSION = '1.19.4'
|
||||
MC_NEXT_VERSION = '1.20'
|
||||
MCP_VERSION = '20230314.122934'
|
||||
SPI_VERSION = '6.0.0'
|
||||
ACCESSTRANSFORMERS_VERSION = '8.0.4'
|
||||
COREMODS_VERSION = '5.0.1'
|
||||
EVENTBUS_VERSION = '5.0.3'
|
||||
MODLAUNCHER_VERSION = '9.1.3'
|
||||
SECUREJARHANDLER_VERSION = '1.0.3'
|
||||
BOOTSTRAPLAUNCHER_VERSION = '1.0.0'
|
||||
EVENTBUS_VERSION = '6.0.3'
|
||||
MODLAUNCHER_VERSION = '10.0.8'
|
||||
SECUREJARHANDLER_VERSION = '2.1.6'
|
||||
BOOTSTRAPLAUNCHER_VERSION = '1.1.2'
|
||||
ASM_VERSION = '9.3'
|
||||
INSTALLER_VERSION = '2.1.+'
|
||||
MIXIN_VERSION = '0.8.5'
|
||||
@@ -55,13 +62,14 @@ ext {
|
||||
JETBRAINS_ANNOTATIONS_VERSION = '23.0.0'
|
||||
SLF4J_API_VERSION = '1.8.0-beta4'
|
||||
APACHE_MAVEN_ARTIFACT_VERSION = '3.8.5'
|
||||
JARJAR_VERSION = '0.3.19'
|
||||
|
||||
// These versions should be kept in sync with the version manifest JSON
|
||||
// To use a version newer than the version manifest JSON, the dependency must be added to the installer configuration
|
||||
MOJANG_LOGGING_VERSION = '1.0.0'
|
||||
LOG4J_VERSION = '2.17.0'
|
||||
GUAVA_VERSION = '31.0.1-jre'
|
||||
GSON_VERSION = '2.8.9'
|
||||
MOJANG_LOGGING_VERSION = '1.1.1'
|
||||
LOG4J_VERSION = '2.19.0'
|
||||
GUAVA_VERSION = '31.1-jre'
|
||||
GSON_VERSION = '2.10'
|
||||
APACHE_COMMONS_LANG3_VERSION = '3.12.0'
|
||||
JOPT_SIMPLE_VERSION = '5.0.4'
|
||||
COMMONS_IO_VERSION = '2.11.0'
|
||||
@@ -75,7 +83,7 @@ ext {
|
||||
INSTALLER_TOOLS = 'net.minecraftforge:installertools:1.3.0'
|
||||
JAR_SPLITTER = 'net.minecraftforge:jarsplitter:1.1.4'
|
||||
FART = 'net.minecraftforge:ForgeAutoRenamingTool:0.1.22:all'
|
||||
MIN_TAG_FOR_CHANGELOG = "39.0"
|
||||
MIN_TAG_FOR_CHANGELOG = '41.0'
|
||||
}
|
||||
|
||||
println('Version: ' + VERSION +
|
||||
@@ -94,7 +102,7 @@ def extraTxts = [
|
||||
]
|
||||
|
||||
task downloadVersionManifest(type: Download) {
|
||||
src 'https://launchermeta.mojang.com/mc/game/version_manifest_v2.json'
|
||||
src 'https://piston-meta.mojang.com/mc/game/version_manifest_v2.json'
|
||||
dest file('build/versions/version_manifest.json')
|
||||
useETag 'all'
|
||||
onlyIfModified true
|
||||
@@ -260,6 +268,7 @@ def sharedDeps = {
|
||||
moduleonly "org.ow2.asm:asm-util:${ASM_VERSION}"
|
||||
moduleonly "org.ow2.asm:asm-analysis:${ASM_VERSION}"
|
||||
moduleonly "cpw.mods:bootstraplauncher:${BOOTSTRAPLAUNCHER_VERSION}"
|
||||
moduleonly "net.minecraftforge:JarJarFileSystems:${JARJAR_VERSION}"
|
||||
|
||||
installer "cpw.mods:securejarhandler:${SECUREJARHANDLER_VERSION}"
|
||||
installer "org.ow2.asm:asm:${ASM_VERSION}"
|
||||
@@ -283,6 +292,8 @@ def sharedDeps = {
|
||||
installer "org.jline:jline-terminal:${JLINE_VERSION}" // Dep of TerminalConsoleAppender
|
||||
installer "org.spongepowered:mixin:${MIXIN_VERSION}"
|
||||
installer 'org.openjdk.nashorn:nashorn-core:15.3'
|
||||
installer "net.minecraftforge:JarJarSelector:${JARJAR_VERSION}"
|
||||
installer "net.minecraftforge:JarJarMetadata:${JARJAR_VERSION}"
|
||||
|
||||
/*
|
||||
installer 'org.lwjgl:lwjgl:3.2.2'
|
||||
@@ -375,7 +386,8 @@ def sharedFmlonlyForge = { Project prj ->
|
||||
environment 'MOD_CLASSES', '{source_roots}'
|
||||
environment 'MCP_MAPPINGS', '{mcp_mappings}'
|
||||
|
||||
property 'nativesDirectory', '{natives}'
|
||||
// Natives are loaded from the classpath now in all cases
|
||||
// property 'nativesDirectory', '{natives}'
|
||||
if (prj.name == 'forge') {
|
||||
property 'forge.enableGameTest', 'true'
|
||||
}
|
||||
@@ -429,6 +441,8 @@ def sharedFmlonlyForge = { Project prj ->
|
||||
run.args '--fml.forgeGroup', prj.group
|
||||
run.args '--fml.mcpVersion', MCP_VERSION
|
||||
|
||||
run.jvmArgs '-Djava.net.preferIPv6Addresses=system'
|
||||
|
||||
if (run.name.contains('client')) {
|
||||
run.client true
|
||||
} else {
|
||||
@@ -438,7 +452,7 @@ def sharedFmlonlyForge = { Project prj ->
|
||||
// SecureJarHandler bootstrap values.
|
||||
run.property 'ignoreList', prj.configurations.moduleonly.files.collect {it.name.replaceAll(/([-_]([.\d]*\d+)|\.jar$)/, '') }.join(',') + ",client-extra,fmlcore,javafmllanguage,lowcodelanguage,mclanguage,${prj.name}-"
|
||||
// FIXME: Without this jna doesn't work at runtime. Someone figure out why please?
|
||||
run.property 'mergeModules', 'jna-5.10.0.jar,jna-platform-5.10.0.jar,java-objc-bridge-1.0.0.jar'
|
||||
run.property 'mergeModules', 'jna-5.10.0.jar,jna-platform-5.10.0.jar'
|
||||
if (userdevRuns.contains(run)) {
|
||||
run.property 'legacyClassPath.file', '{minecraft_classpath_file}'
|
||||
run.jvmArgs '-p', '{modules}'
|
||||
@@ -449,6 +463,7 @@ def sharedFmlonlyForge = { Project prj ->
|
||||
run.jvmArgs '--add-modules', 'ALL-MODULE-PATH'
|
||||
// Additions to these JVM module args should be mirrored to server_files/args.txt and other similar blocks in the buildscript
|
||||
run.jvmArgs '--add-opens', 'java.base/java.util.jar=cpw.mods.securejarhandler'
|
||||
run.jvmArgs '--add-opens', 'java.base/java.lang.invoke=cpw.mods.securejarhandler'
|
||||
run.jvmArgs '--add-exports', 'java.base/sun.security.util=cpw.mods.securejarhandler'
|
||||
run.jvmArgs '--add-exports', 'jdk.naming.dns/com.sun.jndi.dns=java.naming'
|
||||
}
|
||||
@@ -457,7 +472,8 @@ def sharedFmlonlyForge = { Project prj ->
|
||||
run.workingDirectory prj.file('run')
|
||||
|
||||
if (run.name.contains('client') || run.name.contains('data')) {
|
||||
run.property 'nativesDirectory', prj.extractNatives.output.get().asFile
|
||||
// Natives are loaded from the classpath now in all cases
|
||||
// run.property 'nativesDirectory', prj.extractNatives.output.get().asFile
|
||||
run.args '--assetsDir', prj.downloadAssets.output
|
||||
run.args '--assetIndex', "{asset_index}"
|
||||
}
|
||||
@@ -639,6 +655,7 @@ project(':fmlonly') {
|
||||
'--add-modules', 'ALL-MODULE-PATH',
|
||||
// Additions to these JVM module args should be mirrored to server_files/args.txt and other similar blocks in the buildscript
|
||||
'--add-opens', 'java.base/java.util.jar=cpw.mods.securejarhandler',
|
||||
'--add-opens', 'java.base/java.lang.invoke=cpw.mods.securejarhandler',
|
||||
'--add-exports', 'java.base/sun.security.util=cpw.mods.securejarhandler',
|
||||
'--add-exports', 'jdk.naming.dns/com.sun.jndi.dns=java.naming'
|
||||
]
|
||||
@@ -1059,6 +1076,7 @@ project(':forge') {
|
||||
'--mod', 'custom_transformtype_test',
|
||||
'--mod', 'data_pack_registries_test',
|
||||
'--mod', 'biome_modifiers_test',
|
||||
'--mod', 'structure_modifiers_test',
|
||||
'--output', rootProject.file('src/generated_test/resources/'),
|
||||
'--existing', sourceSets.main.resources.srcDirs[0],
|
||||
'--existing', sourceSets.test.resources.srcDirs[0]
|
||||
@@ -1156,89 +1174,98 @@ project(':forge') {
|
||||
}
|
||||
}
|
||||
|
||||
task checkATs(type: CheckATs, dependsOn: [extractInheritance, createSrg2Mcp]) {
|
||||
inheritance = extractInheritance.output
|
||||
tasks.register('checkAll') {
|
||||
dependsOn 'checkLicenses'
|
||||
group = 'checks'
|
||||
}
|
||||
tasks.register('checkAllAndFix') {
|
||||
dependsOn 'findFieldInstanceChecks', 'checkLicenses'
|
||||
group = 'checks'
|
||||
}
|
||||
|
||||
CheckTask.registerTask(tasks, 'ATs', CheckATs) {
|
||||
dependsOn extractInheritance, createSrg2Mcp
|
||||
ats.from patcher.accessTransformers
|
||||
inheritance = extractInheritance.output
|
||||
mappings = createSrg2Mcp.output
|
||||
}
|
||||
|
||||
task checkSAS(type: CheckSAS, dependsOn: extractInheritance) {
|
||||
inheritance = extractInheritance.output
|
||||
CheckTask.registerTask(tasks, 'SAS', CheckSAS) {
|
||||
dependsOn extractInheritance
|
||||
sass.from patcher.sideAnnotationStrippers
|
||||
inheritance = extractInheritance.output
|
||||
}
|
||||
|
||||
task checkExcs(type: CheckExcs, dependsOn: jar) {
|
||||
CheckTask.registerTask(tasks, 'Excs', CheckExcs) {
|
||||
dependsOn jar
|
||||
binary = jar.archiveFile.get().asFile
|
||||
excs.from patcher.excs
|
||||
}
|
||||
|
||||
task checkAll(dependsOn: [checkATs, checkSAS, checkExcs, findFieldInstanceChecks]){}
|
||||
|
||||
task checkPatchesAndFix(type: CheckPatches, dependsOn: genPatches) {
|
||||
CheckTask.registerTask(tasks, 'Patches', CheckPatches) {
|
||||
dependsOn genPatches
|
||||
patchDir = file("$rootDir/patches")
|
||||
autoFix = true
|
||||
}
|
||||
|
||||
task checkPatches(type: CheckPatches, dependsOn: genPatches) {
|
||||
patchDir = file("$rootDir/patches")
|
||||
autoFix = false
|
||||
patchesWithS2SArtifact = [
|
||||
'minecraft/net/minecraft/client/renderer/ViewArea.java.patch',
|
||||
'minecraft/net/minecraft/data/models/blockstates/Variant.java.patch',
|
||||
]
|
||||
}
|
||||
|
||||
genPatches {
|
||||
finalizedBy checkPatches
|
||||
finalizedBy checkAndFixPatches
|
||||
autoHeader true
|
||||
lineEnding = '\n'
|
||||
}
|
||||
|
||||
def baseForgeVersion = System.getProperty("forge.compatibility_check_version")
|
||||
if (baseForgeVersion == null)
|
||||
baseForgeVersion = "${MC_VERSION}-${GIT_INFO.tag}.0"
|
||||
if (baseForgeVersion == "null")
|
||||
baseForgeVersion = null // Allow disabling from system property if not wanted
|
||||
def baseForgeUniversal = null
|
||||
if (baseForgeVersion != null)
|
||||
baseForgeUniversal = project.dependencies.create("net.minecraftforge:forge:${baseForgeVersion}:universal")
|
||||
def baseForgeVersionProperty = project.objects.property(String)
|
||||
baseForgeVersionProperty.set(project.provider { getLatestForgeVersion(MC_VERSION) }.map { MC_VERSION + '-' + it })
|
||||
baseForgeVersionProperty.finalizeValueOnRead()
|
||||
def jarCompatibilityTaskSetup = { task ->
|
||||
task.group = 'jar compatibility'
|
||||
task.onlyIf {
|
||||
baseForgeVersion != null
|
||||
baseForgeVersionProperty.getOrNull() != null
|
||||
}
|
||||
task.inputs.property 'baseForgeVersion', baseForgeVersion
|
||||
}
|
||||
|
||||
task downloadBaseCompatibilityBinPatches(type: DownloadBaseCompatibilityBinPatches) {
|
||||
inputVersion = baseForgeVersion
|
||||
tasks.register('setupCheckJarCompatibility', SetupCheckJarCompatibility) {
|
||||
inputVersion = baseForgeVersionProperty
|
||||
}
|
||||
|
||||
task applyBaseCompatibilityJarBinPatches(type: ApplyBinPatches) {
|
||||
tasks.register('applyBaseCompatibilityJarBinPatches', ApplyBinPatches) {
|
||||
jarCompatibilityTaskSetup(it)
|
||||
|
||||
clean = createJoinedSRG.output
|
||||
patch = downloadBaseCompatibilityBinPatches.output
|
||||
clean = project.tasks.createJoinedSRG.output
|
||||
patch = project.tasks.named('setupCheckJarCompatibility').flatMap { it.baseBinPatchesOutput }
|
||||
output = project.layout.buildDirectory.dir(name).map { it.file('output.jar') }
|
||||
}
|
||||
|
||||
task checkJarCompatibility(type: CheckJarCompatibility) {
|
||||
tasks.register('mergeBaseForgeJar', MergeJars) {
|
||||
jarCompatibilityTaskSetup(it)
|
||||
|
||||
if (baseForgeVersion != null) {
|
||||
def fmlLibs = project.configurations.detachedConfiguration(PACKED_DEPS.collect {
|
||||
def artifactId = it.split(':')[1]
|
||||
return project.dependencies.create("net.minecraftforge:${artifactId}:${baseForgeVersion}")
|
||||
}.toArray(Dependency[]::new))
|
||||
inputJars.from(project.tasks.named('applyBaseCompatibilityJarBinPatches').flatMap { it.output })
|
||||
inputJars.from(baseForgeVersionProperty.map { inputVersion ->
|
||||
def output = project.layout.buildDirectory.dir(name).map { it.file("forge-${inputVersion}-universal.jar") }.get().asFile
|
||||
project.rootProject.extensions.download.run {
|
||||
src "https://maven.minecraftforge.net/net/minecraftforge/forge/${inputVersion}/forge-${inputVersion}-universal.jar"
|
||||
dest output
|
||||
}
|
||||
return output
|
||||
})
|
||||
}
|
||||
|
||||
baseJar = project.tasks.applyBaseCompatibilityJarBinPatches.output
|
||||
baseLibraries.from(baseForgeUniversal)
|
||||
baseLibraries.from(project.tasks.createJoinedSRG.output)
|
||||
baseLibraries.from(fmlLibs)
|
||||
tasks.register('checkJarCompatibility', CheckJarCompatibility) {
|
||||
jarCompatibilityTaskSetup(it)
|
||||
dependsOn 'setupCheckJarCompatibility'
|
||||
|
||||
inputJar = project.tasks.reobfJar.output
|
||||
concreteLibraries.from(PACKED_DEPS.collect { rootProject.tasks.getByPath(it).archiveFile })
|
||||
baseJar = project.tasks.named('mergeBaseForgeJar').flatMap { it.output }
|
||||
baseLibraries.from(project.tasks.named('createJoinedSRG').flatMap { it.output })
|
||||
|
||||
commonLibraries.from(project.configurations.minecraftImplementation)
|
||||
commonLibraries.from(project.configurations.installer)
|
||||
commonLibraries.from(project.configurations.moduleonly)
|
||||
}
|
||||
inputJar = project.tasks.named('reobfJar').flatMap { it.output }
|
||||
concreteLibraries.from(project.PACKED_DEPS.collect { project.rootProject.tasks.getByPath(it).archiveFile })
|
||||
|
||||
commonLibraries.from(project.configurations.minecraftImplementation)
|
||||
commonLibraries.from(project.configurations.installer)
|
||||
commonLibraries.from(project.configurations.moduleonly)
|
||||
}
|
||||
|
||||
task launcherJson(type: LauncherJson) {
|
||||
@@ -1260,13 +1287,15 @@ project(':forge') {
|
||||
'--fml.mcVersion', MC_VERSION,
|
||||
'--fml.forgeGroup', project.group,
|
||||
'--fml.mcpVersion', MCP_VERSION],
|
||||
jvm: ["-DignoreList=${forge_client.properties.ignoreList},\${version_name}.jar",
|
||||
jvm: ['-Djava.net.preferIPv6Addresses=system',
|
||||
"-DignoreList=${forge_client.properties.ignoreList},\${version_name}.jar",
|
||||
"-DmergeModules=${forge_client.properties.mergeModules}",
|
||||
'-DlibraryDirectory=${library_directory}',
|
||||
'-p', Util.getArtifacts(project, configurations.moduleonly, false).values().collect{'${library_directory}/' + it.downloads.artifact.path}.join('${classpath_separator}'),
|
||||
'--add-modules', 'ALL-MODULE-PATH',
|
||||
// Additions to these JVM module args should be mirrored to server_files/args.txt and other similar blocks in the buildscript
|
||||
'--add-opens', 'java.base/java.util.jar=cpw.mods.securejarhandler',
|
||||
'--add-opens', 'java.base/java.lang.invoke=cpw.mods.securejarhandler',
|
||||
'--add-exports', 'java.base/sun.security.util=cpw.mods.securejarhandler',
|
||||
'--add-exports', 'jdk.naming.dns/com.sun.jndi.dns=java.naming'
|
||||
]
|
||||
@@ -1550,7 +1579,7 @@ project(':forge') {
|
||||
MAPPING_CHANNEL: MAPPING_CHANNEL,
|
||||
MAPPING_VERSION: MAPPING_VERSION,
|
||||
FORGE_SPEC_VERSION: SPEC_VERSION.split("\\.")[0],
|
||||
MC_NEXT_VERSION: "1."+((MC_VERSION.split("[\\.-]")[1] as int)+1)
|
||||
MC_NEXT_VERSION: MC_NEXT_VERSION
|
||||
])
|
||||
rename 'gitignore\\.txt', '.gitignore'
|
||||
rename 'gitattributes\\.txt', '.gitattributes'
|
||||
|
||||
@@ -7,4 +7,6 @@ dependencies {
|
||||
implementation 'org.ow2.asm:asm:9.3'
|
||||
implementation 'org.ow2.asm:asm-tree:9.3'
|
||||
implementation 'net.minecraftforge:srgutils:0.4.+'
|
||||
implementation 'commons-io:commons-io:2.8.0'
|
||||
implementation 'com.google.code.gson:gson:2.10'
|
||||
}
|
||||
@@ -1,267 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import net.minecraftforge.srgutils.IMappingFile
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.tasks.InputFile
|
||||
import org.gradle.api.tasks.InputFiles
|
||||
import org.gradle.api.tasks.Optional
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.objectweb.asm.Opcodes
|
||||
|
||||
abstract class CheckATs extends DefaultTask {
|
||||
@InputFile abstract RegularFileProperty getInheritance()
|
||||
@InputFiles abstract ConfigurableFileCollection getAts()
|
||||
@InputFile @Optional abstract RegularFileProperty getMappings()
|
||||
|
||||
@TaskAction
|
||||
protected void exec() {
|
||||
def mappings = mappings.map { IMappingFile.load(it.asFile) }.getOrNull()
|
||||
Util.init()
|
||||
def parse = { line ->
|
||||
def idx = line.indexOf('#')
|
||||
def comment = idx == -1 ? null : line.substring(idx)
|
||||
if (idx != -1) line = line.substring(0, idx - 1)
|
||||
def (modifier, cls, desc) = (line.trim() + ' ').split(' ', -1)
|
||||
def key = cls + (desc.isEmpty() ? '' : ' ' + desc)
|
||||
return [modifier, cls, desc, comment, key]
|
||||
}
|
||||
def accessLevel = { access ->
|
||||
if ((access & Opcodes.ACC_PUBLIC) != 0) return 3
|
||||
if ((access & Opcodes.ACC_PROTECTED) != 0) return 2
|
||||
if ((access & Opcodes.ACC_PRIVATE) != 0) return 0
|
||||
return 1
|
||||
}
|
||||
def accessStr = { access ->
|
||||
if (access.endsWith('-f') || access.endsWith('+f'))
|
||||
return 4
|
||||
switch (access.toLowerCase()) {
|
||||
case 'public': return 3
|
||||
case 'protected': return 2
|
||||
case 'default': return 1
|
||||
case 'private': return 0
|
||||
default: return -1
|
||||
}
|
||||
}
|
||||
def json = inheritance.get().asFile.json()
|
||||
|
||||
ats.each { f ->
|
||||
TreeMap lines = [:]
|
||||
def group = null
|
||||
for (def line : f.readLines()) {
|
||||
if (line.isEmpty()) continue
|
||||
if (line.startsWith('#group ')) {
|
||||
def (modifier, cls, desc, comment, key) = parse.call(line.substring(7))
|
||||
|
||||
if (desc != '*' && desc != '*()' && desc != '<init>')
|
||||
throw new IllegalStateException('Invalid group: ' + line)
|
||||
|
||||
group = [modifier: modifier, cls: cls, desc: desc, comment: comment,
|
||||
existing: [] as Set,
|
||||
children: [] as TreeSet,
|
||||
group: true
|
||||
]
|
||||
if (lines.containsKey(key))
|
||||
throw new IllegalStateException('Duplicate group: ' + line)
|
||||
|
||||
lines[key] = group
|
||||
} else if (group != null) {
|
||||
if (line.startsWith('#endgroup')) {
|
||||
group = null
|
||||
} else {
|
||||
def (modifier, cls, desc, comment, key) = parse.call(line)
|
||||
group['existing'].add(key)
|
||||
}
|
||||
} else if (line.startsWith('#endgroup')) {
|
||||
throw new IllegalStateException('Invalid group ending: ' + line)
|
||||
} else if (line.startsWith('#')) {
|
||||
//Nom
|
||||
} else {
|
||||
def (modifier, cls, desc, comment, key) = parse.call(line)
|
||||
if (lines.containsKey(key)) {
|
||||
println('Duplicate: ' + line)
|
||||
continue
|
||||
}
|
||||
lines[key] = [modifier: modifier, cls: cls, desc: desc, comment: comment, group: false]
|
||||
}
|
||||
}
|
||||
|
||||
// Process Groups, this will remove any entries outside the group that is covered by the group
|
||||
for (def key : new ArrayList<>(lines.keySet())) {
|
||||
def entry = lines.get(key)
|
||||
if (entry != null && entry['group']) {
|
||||
def cls = entry['cls']
|
||||
def jcls = json.get(cls.replaceAll('\\.', '/'))
|
||||
if (jcls == null) {
|
||||
lines.remove(key)
|
||||
println('Invalid Group: ' + key)
|
||||
} else if ('*' == entry['desc']) {
|
||||
if (!jcls.containsKey('fields')) {
|
||||
lines.remove(key)
|
||||
println('Invalid Group, Class has no fields: ' + key)
|
||||
} else {
|
||||
jcls['fields'].each { field, value ->
|
||||
def fkey = cls + ' ' + field
|
||||
if (accessLevel.call(value['access']) < accessStr.call(entry['modifier'])) {
|
||||
if (lines.containsKey(fkey)) {
|
||||
lines.remove(fkey)
|
||||
} else if (!entry['existing'].contains(fkey)) {
|
||||
println('Added: ' + fkey)
|
||||
}
|
||||
entry['children'].add(fkey)
|
||||
} else if (lines.containsKey(fkey)) {
|
||||
lines.remove(fkey)
|
||||
println('Removed: ' + fkey)
|
||||
}
|
||||
}
|
||||
entry['existing'].stream().findAll{ !entry['children'].contains(it) }.each{ println('Removed: ' + it) }
|
||||
}
|
||||
} else if ('*()' == entry['desc']) {
|
||||
if (!jcls.containsKey('methods')) {
|
||||
lines.remove(key)
|
||||
println('Invalid Group, Class has no methods: ' + key)
|
||||
} else {
|
||||
jcls['methods'].each{ mtd, value ->
|
||||
if (mtd.startsWith('<clinit>') || mtd.startsWith('lambda$'))
|
||||
return
|
||||
key = cls + ' ' + mtd.replace(' ', '')
|
||||
if (accessLevel.call(value['access']) < accessStr.call(entry['modifier'])) {
|
||||
if (lines.containsKey(key)) {
|
||||
lines.remove(key)
|
||||
} else if (!entry['existing'].contains(key)) {
|
||||
println('Added: ' + key)
|
||||
}
|
||||
entry['children'].add(key)
|
||||
} else if (lines.containsKey(key)) {
|
||||
lines.remove(key)
|
||||
println('Removed: ' + key)
|
||||
}
|
||||
}
|
||||
entry['existing'].stream().findAll{ !entry['children'].contains(it) }.each{ println('Removed: ' + it) }
|
||||
}
|
||||
} else if ('<init>' == entry['desc']) { //Make all public non-abstract subclasses
|
||||
json.each{ tcls,value ->
|
||||
if (!value.containsKey('methods') || ((value['access'] & Opcodes.ACC_ABSTRACT) != 0))
|
||||
return
|
||||
def parents = [] as Set
|
||||
def parent = tcls
|
||||
while (parent != null && json.containsKey(parent)) {
|
||||
parents.add(parent)
|
||||
def p = json[parent]
|
||||
parent = p == null ? null : p['superName']
|
||||
}
|
||||
if (parents.contains(cls.replaceAll('\\.', '/'))) {
|
||||
value['methods'].each{ mtd, v ->
|
||||
if (mtd.startsWith('<init>')) {
|
||||
def child = tcls.replaceAll('/', '\\.') + ' ' + mtd.replace(' ', '')
|
||||
if (accessLevel.call(v['access']) < 3) {
|
||||
if (lines.containsKey(child)) {
|
||||
lines.remove(child)
|
||||
} else if (!entry['existing'].contains(child)) {
|
||||
println('Added: ' + child)
|
||||
}
|
||||
entry['children'].add(child)
|
||||
} else if (lines.containsKey(child)) {
|
||||
lines.remove(child)
|
||||
println('Removed: ' + child)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
entry['existing'].stream().findAll{ !entry['children'].contains(it) }.each{ println('Removed: ' + it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process normal lines, remove invalid and remove narrowing
|
||||
for (def key : new ArrayList<>(lines.keySet())) {
|
||||
def entry = lines.get(key)
|
||||
if (entry != null && !entry['group']) {
|
||||
def cls = entry['cls']
|
||||
def jcls = json.get(cls.replaceAll('\\.', '/'))
|
||||
if (jcls == null) {
|
||||
lines.remove(key)
|
||||
println('Invalid: ' + key)
|
||||
} else if (entry['desc'] == '') {
|
||||
if (accessLevel.call(jcls['access']) > accessStr.call(entry['modifier']) && (entry.comment == null || !entry.comment.startsWith('#force '))) {
|
||||
lines.remove(key)
|
||||
println('Invalid Narrowing: ' + key)
|
||||
}
|
||||
} else if (!entry['desc'].contains('(')) {
|
||||
if (!jcls.containsKey('fields') || !jcls['fields'].containsKey(entry['desc'])) {
|
||||
lines.remove(key)
|
||||
println('Invalid: ' + key)
|
||||
} else {
|
||||
def value = jcls['fields'][entry['desc']]
|
||||
if (accessLevel.call(value['access']) > accessStr.call(entry['modifier']) && (entry.comment == null || !entry.comment.startsWith('#force '))) {
|
||||
lines.remove(key)
|
||||
println('Invalid Narrowing: ' + key)
|
||||
println(entry.comment)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
def jdesc = entry['desc'].replace('(', ' (')
|
||||
if (!jcls.containsKey('methods') || !jcls['methods'].containsKey(jdesc)) {
|
||||
lines.remove(key)
|
||||
println('Invalid: ' + key)
|
||||
} else {
|
||||
def value = jcls['methods'][jdesc]
|
||||
if (accessLevel.call(value['access']) > accessStr.call(entry['modifier']) && (entry.comment == null || !entry.comment.startsWith('#force '))) {
|
||||
lines.remove(key)
|
||||
println('Invalid Narrowing: ' + key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def data = []
|
||||
def remapComment = { entry ->
|
||||
if (!mappings || !entry || !entry.desc) return null
|
||||
def comment = entry.comment?.substring(1)?.trim()
|
||||
def jsonCls = json.get(entry.cls.replaceAll('\\.', '/'))
|
||||
def mappingsClass = mappings?.getClass(jsonCls.name)
|
||||
if (!mappingsClass) return entry.comment
|
||||
def idx = entry.desc.indexOf('(')
|
||||
def mappedName = idx == -1
|
||||
? mappingsClass.remapField(entry.desc)
|
||||
: mappingsClass.remapMethod(entry.desc.substring(0, idx), entry.desc.substring(idx))
|
||||
if (!mappedName) return entry.comment
|
||||
if (mappedName == '<init>')
|
||||
mappedName = 'constructor'
|
||||
|
||||
if (comment?.startsWith(mappedName))
|
||||
return '# ' + comment
|
||||
if (comment && comment.indexOf(' ') != -1) {
|
||||
def split = comment.split(" - ").toList()
|
||||
if (split[0].indexOf(' ') != -1)
|
||||
// The first string is more than one word, so append before it
|
||||
return "# ${mappedName} - ${comment}"
|
||||
split.remove(0)
|
||||
return "# ${mappedName} - ${String.join(' - ', split)}"
|
||||
}
|
||||
return '# ' + mappedName
|
||||
}
|
||||
lines.each { key,value ->
|
||||
if (!value.group) {
|
||||
def comment = remapComment.call(value)
|
||||
data.add(value.modifier + ' ' + key + (comment ? ' ' + comment : ''))
|
||||
} else {
|
||||
data.add('#group ' + value.modifier + ' ' + key + (value.comment == null ? '' : ' ' + value.comment))
|
||||
value.children.each {
|
||||
def line = value.modifier + ' ' + it
|
||||
def (modifier, cls, desc, comment) = parse.call(line)
|
||||
comment = remapComment.call([modifier: modifier, cls: cls, desc: desc, comment: comment])
|
||||
data.add(line + (comment ? ' ' + comment : ''))
|
||||
}
|
||||
data.add('#endgroup')
|
||||
}
|
||||
}
|
||||
f.text = data.join('\n')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.tasks.InputFile
|
||||
import org.gradle.api.tasks.InputFiles
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
|
||||
import org.objectweb.asm.ClassReader
|
||||
import org.objectweb.asm.ClassVisitor
|
||||
import org.objectweb.asm.MethodVisitor
|
||||
import org.objectweb.asm.Opcodes
|
||||
import org.objectweb.asm.Type
|
||||
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
abstract class CheckExcs extends DefaultTask {
|
||||
@InputFile abstract RegularFileProperty getBinary()
|
||||
@InputFiles abstract ConfigurableFileCollection getExcs()
|
||||
|
||||
@TaskAction
|
||||
protected void exec() {
|
||||
Util.init()
|
||||
def known = []
|
||||
binary.get().asFile.withInputStream { i ->
|
||||
new ZipInputStream(i).withCloseable { zin ->
|
||||
def visitor = new ClassVisitor(Opcodes.ASM9) {
|
||||
private String cls
|
||||
@Override
|
||||
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
|
||||
this.cls = name
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
|
||||
known.add(this.cls + '.' + name + descriptor)
|
||||
super.visitMethod(access, name, descriptor, signature, exceptions)
|
||||
}
|
||||
}
|
||||
ZipEntry zein
|
||||
while ((zein = zin.nextEntry) != null) {
|
||||
if (zein.name.endsWith('.class')) {
|
||||
ClassReader reader = new ClassReader(zin)
|
||||
reader.accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
excs.each { f ->
|
||||
def lines = []
|
||||
f.eachLine { line ->
|
||||
def idx = line.indexOf('#')
|
||||
if (idx == 0 || line.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
def comment = idx == -1 ? null : line.substring(idx)
|
||||
if (idx != -1) line = line.substring(0, idx - 1)
|
||||
|
||||
if (!line.contains('=')) {
|
||||
println('Invalid: ' + line)
|
||||
return
|
||||
}
|
||||
|
||||
def (key, value) = line.split('=', 2)
|
||||
if (!known.contains(key)) {
|
||||
println('Unknown: ' + line)
|
||||
return
|
||||
}
|
||||
|
||||
def (cls, desc) = key.split('\\.', 2)
|
||||
if (!desc.contains('(')) {
|
||||
println('Invalid: ' + line)
|
||||
return
|
||||
}
|
||||
def name = desc.split('\\(', 2)[0]
|
||||
desc = '(' + desc.split('\\(', 2)[1]
|
||||
|
||||
def (exceptions, args) = value.contains('|') ? value.split('|', 2) : [value, '']
|
||||
|
||||
if (args.split(',').length != Type.getArgumentTypes(desc).length) {
|
||||
println('Invalid: ' + line)
|
||||
return
|
||||
}
|
||||
lines.add(line)
|
||||
}
|
||||
f.text = lines.sort().join('\n')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,219 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.DirectoryProperty
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.InputDirectory
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
import java.util.regex.Pattern
|
||||
|
||||
abstract class CheckPatches extends DefaultTask {
|
||||
@InputDirectory abstract DirectoryProperty getPatchDir()
|
||||
@Input boolean autoFix = false
|
||||
|
||||
@TaskAction
|
||||
protected void exec() {
|
||||
def hasS2SArtifact = [
|
||||
Paths.get("patches/minecraft/net/minecraft/client/renderer/ViewArea.java.patch"),
|
||||
Paths.get("patches/minecraft/net/minecraft/data/models/blockstates/Variant.java.patch")
|
||||
]
|
||||
|
||||
def verified = true;
|
||||
patchDir.get().asFileTree.each { patch ->
|
||||
def patchPath = project.rootDir.toPath().relativize(patch.toPath())
|
||||
verified &= verifyPatch(patch, autoFix, patchPath.toString(), hasS2SArtifact.contains(patchPath))
|
||||
}
|
||||
|
||||
if (!verified)
|
||||
throw new RuntimeException('One or more patches failed verification. Check the log for errors.')
|
||||
}
|
||||
|
||||
def verifyPatch(patch, fix, patchPath, hasS2SArtifact) {
|
||||
def hunk_start_pattern = Pattern.compile('^@@ -[0-9,]* \\+[0-9,_]* @@$')
|
||||
def white_space_pattern = Pattern.compile('^[+\\-]\\s*$')
|
||||
def import_pattern = Pattern.compile('^[+\\-]\\s*import.*')
|
||||
def field_pattern = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final)?([^=;]*)(=.*)?;\\s*$')
|
||||
def method_pattern = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final)?([^(]*)[(]([^)]*)?[)]\\s*[{]\\s*$')
|
||||
def class_pattern = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final[\\s]*)?(class|interface)([^{]*)[{]\\s*$')
|
||||
|
||||
def accessMap = [("private"):0, (null):1, ("protected"):2, ("public"):3]
|
||||
|
||||
def hasProblem = false;
|
||||
def lines = patch.readLines()
|
||||
|
||||
def hunksStart = 0
|
||||
def onlyWhiteSpace = false
|
||||
|
||||
def didFix = false
|
||||
def newLines = []
|
||||
def whiteSpaceErrors = []
|
||||
|
||||
// First two lines are file name ++/-- and we do not care
|
||||
newLines.add(lines[0] + '\n')
|
||||
newLines.add(lines[1] + '\n')
|
||||
|
||||
int i = 2
|
||||
for (; i < lines.size(); ++i) {
|
||||
def line = lines[i]
|
||||
newLines.add(line + '\n')
|
||||
|
||||
if (hunk_start_pattern.matcher(line).find()) {
|
||||
if (onlyWhiteSpace) {
|
||||
if (fix || hasS2SArtifact) {
|
||||
logger.lifecycle("Removing white space hunk starting at line {}, file: {}", hunksStart + 1, patchPath)
|
||||
def toRemove = i - hunksStart;
|
||||
while (toRemove-- > 0)
|
||||
newLines.remove(newLines.size() - 1)
|
||||
didFix = true
|
||||
}
|
||||
else {
|
||||
logger.lifecycle("Patch contains only white space hunk starting at line {}, file: {}", hunksStart + 1, patchPath)
|
||||
hasProblem = true
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!whiteSpaceErrors.empty)
|
||||
hasProblem = true
|
||||
whiteSpaceErrors.each { error -> logger.lifecycle(error) }
|
||||
whiteSpaceErrors.clear()
|
||||
}
|
||||
hunksStart = i
|
||||
onlyWhiteSpace = true
|
||||
continue
|
||||
}
|
||||
|
||||
if (line.startsWithAny('+','-')) {
|
||||
def prefixChange = false
|
||||
def prevLine = lines[i - 1]
|
||||
|
||||
if (line.charAt(0) == (char)'+' && prevLine.charAt(0) == (char)'-') {
|
||||
def prevTrim = prevLine.substring(1).replaceAll("\\s", "")
|
||||
def currTrim = line.substring(1).replaceAll("\\s", "")
|
||||
|
||||
if (prevTrim.equals(currTrim)) {
|
||||
prefixChange = true
|
||||
}
|
||||
|
||||
def pMatcher = field_pattern.matcher(prevLine)
|
||||
def cMatcher = field_pattern.matcher(line)
|
||||
|
||||
if (pMatcher.find() && cMatcher.find() &&
|
||||
pMatcher.group(6) == cMatcher.group(6) && // = ...
|
||||
pMatcher.group(5) == cMatcher.group(5) && // field name
|
||||
pMatcher.group(3) == cMatcher.group(3) && // static
|
||||
(accessMap[pMatcher.group(2)] < accessMap[cMatcher.group(2)] || pMatcher.group(4) != cMatcher.group(4))) {
|
||||
logger.lifecycle("Patch contains access changes or final removal at line {}, file: {}", i + 1, patchPath)
|
||||
hasProblem = true
|
||||
}
|
||||
|
||||
pMatcher = method_pattern.matcher(prevLine)
|
||||
cMatcher = method_pattern.matcher(line)
|
||||
|
||||
if (pMatcher.find() && cMatcher.find() &&
|
||||
pMatcher.group(6) == cMatcher.group(6) && // params
|
||||
pMatcher.group(5) == cMatcher.group(5) && // <T> void name
|
||||
pMatcher.group(3) == cMatcher.group(3) && // static
|
||||
(accessMap[pMatcher.group(2)] < accessMap[cMatcher.group(2)] || pMatcher.group(4) != cMatcher.group(4))) {
|
||||
logger.lifecycle("Patch contains access changes or final removal at line {}, file: {}", i + 1, patchPath)
|
||||
hasProblem = true
|
||||
}
|
||||
|
||||
pMatcher = class_pattern.matcher(prevLine)
|
||||
cMatcher = class_pattern.matcher(line)
|
||||
|
||||
if (pMatcher.find() && cMatcher.find() &&
|
||||
pMatcher.group(6) == cMatcher.group(6) && // ClassName<> extends ...
|
||||
pMatcher.group(5) == cMatcher.group(5) && // class | interface
|
||||
pMatcher.group(3) == cMatcher.group(3) && // static
|
||||
(accessMap[pMatcher.group(2)] < accessMap[cMatcher.group(2)] || pMatcher.group(4) != cMatcher.group(4))) {
|
||||
logger.lifecycle("Patch contains access changes or final removal at line {}, file: {}", i + 1, patchPath)
|
||||
hasProblem = true
|
||||
}
|
||||
}
|
||||
|
||||
if (line.charAt(0) == (char)'-' && i + 1 < lines.size()) {
|
||||
def nextLine = lines[i + 1]
|
||||
if (nextLine.charAt(0) == (char)'+') {
|
||||
def nextTrim = nextLine.substring(1).replaceAll("\\s", "")
|
||||
def currTrim = line.substring(1).replaceAll("\\s", "")
|
||||
|
||||
if (nextTrim.equals(currTrim)) {
|
||||
prefixChange = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def isWhiteSpaceChange = white_space_pattern.matcher(line).find()
|
||||
|
||||
if (!prefixChange && !isWhiteSpaceChange) {
|
||||
onlyWhiteSpace = hasS2SArtifact && import_pattern.matcher(line).find()
|
||||
}
|
||||
else if (isWhiteSpaceChange) {
|
||||
def prevLineChange = prevLine.startsWithAny('+','-')
|
||||
def nextLineChange = i + 1 < lines.size() && lines[i + 1].startsWithAny('+','-')
|
||||
|
||||
if (!prevLineChange && !nextLineChange) {
|
||||
whiteSpaceErrors.add(String.format("Patch contains white space change in valid hunk at line %d (cannot auto fix), file: %s", i + 1, patchPath))
|
||||
}
|
||||
}
|
||||
|
||||
if (line.contains("\t")) {
|
||||
if (!fix) {
|
||||
logger.lifecycle("Patch contains tabs on line {}, file: {}", i + 1, patchPath)
|
||||
hasProblem = true
|
||||
}
|
||||
else {
|
||||
logger.lifecycle("Fixing tabs on line {}, file: {}", i + 1, patchPath)
|
||||
line = line.replaceAll('\t', ' ')
|
||||
newLines.remove(newLines.size() - 1)
|
||||
newLines.add(line + '\n')
|
||||
didFix = true
|
||||
}
|
||||
}
|
||||
|
||||
if (import_pattern.matcher(line).find() && !hasS2SArtifact) {
|
||||
logger.lifecycle("Patch contains import change on line {}, file: {}", i + 1, patchPath)
|
||||
hasProblem = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (onlyWhiteSpace) {
|
||||
if (fix || hasS2SArtifact) {
|
||||
logger.lifecycle("Removing white space hunk starting at line {}, file: {}", hunksStart + 1, patchPath)
|
||||
def toRemove = i - hunksStart;
|
||||
while (toRemove-- > 0)
|
||||
newLines.remove(newLines.size() - 1)
|
||||
didFix = true
|
||||
}
|
||||
else {
|
||||
logger.lifecycle("Patch contains only white space hunk starting at line {}, file: {}", hunksStart + 1, patchPath)
|
||||
hasProblem = true
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!whiteSpaceErrors.empty)
|
||||
hasProblem = true
|
||||
whiteSpaceErrors.each { error -> logger.lifecycle(error) }
|
||||
}
|
||||
|
||||
if (didFix) {
|
||||
if (newLines.size() <= 2) {
|
||||
logger.lifecycle("Patch is now empty removing, file: {}", patchPath)
|
||||
Files.delete(patch.toPath())
|
||||
}
|
||||
else {
|
||||
if (!hasS2SArtifact)
|
||||
logger.lifecycle("*** Updating patch file. Please run setup then genPatches again. ***")
|
||||
patch.withWriter('UTF-8') { writer ->
|
||||
newLines.each { l -> writer.write(l) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !hasProblem
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.tasks.InputFile
|
||||
import org.gradle.api.tasks.InputFiles
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
|
||||
abstract class CheckSAS extends DefaultTask {
|
||||
@InputFile abstract RegularFileProperty getInheritance()
|
||||
@InputFiles abstract ConfigurableFileCollection getSass()
|
||||
|
||||
@TaskAction
|
||||
protected void exec() {
|
||||
Util.init()
|
||||
def json = inheritance.get().asFile.json()
|
||||
|
||||
sass.each { f ->
|
||||
def lines = []
|
||||
f.eachLine { line ->
|
||||
if (line[0] == '\t') return //Skip any tabbed lines, those are ones we add
|
||||
def idx = line.indexOf('#')
|
||||
if (idx == 0 || line.isEmpty()) {
|
||||
lines.add(line)
|
||||
return
|
||||
}
|
||||
def comment = idx == -1 ? null : line.substring(idx)
|
||||
if (idx != -1) line = line.substring(0, idx - 1)
|
||||
def (cls, name, desc) = (line.trim().replace('(', ' (') + ' ').split(' ', -1)
|
||||
cls = cls.replaceAll('\\.', '/')
|
||||
|
||||
if (json[cls] == null) {
|
||||
println('Invalid: ' + line)
|
||||
} else if (name.isEmpty()) { //Class SAS
|
||||
def toAdd = []
|
||||
def clsSided = isSided(json[cls])
|
||||
def sided = clsSided
|
||||
|
||||
/* TODO: MergeTool doesn't do fields
|
||||
if (json[cls]['fields'] != null) {
|
||||
for (entry in json[cls]['fields']) {
|
||||
if (isSided(entry.value)) {
|
||||
sided = true
|
||||
toAdd.add('\t' + cls + ' ' + entry.key)
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
if (json[cls]['methods'] != null) {
|
||||
for (entry in json[cls]['methods']) {
|
||||
if (isSided(entry.value)) {
|
||||
sided = true
|
||||
toAdd.add('\t' + cls + ' ' + entry.key.replaceAll(' ', ''))
|
||||
findChildMethods(json, cls, entry.key).each { lines.add('\t' + it) }
|
||||
findChildMethods(json, cls, entry.key).each { println(line + ' -- ' + it) }
|
||||
} else if (clsSided) {
|
||||
findChildMethods(json, cls, entry.key).each { lines.add('\t' + it) }
|
||||
findChildMethods(json, cls, entry.key).each { println(line + ' -- ' + it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sided) {
|
||||
lines.add(cls + (comment == null ? '' : ' ' + comment))
|
||||
lines.addAll(toAdd.sort())
|
||||
} else
|
||||
println('Invalid: ' + line)
|
||||
|
||||
} else if (desc.isEmpty()) { // Fields
|
||||
/* TODO: MergeTool doesn't do fields
|
||||
if (json[cls]['fields'] != null && isSided(json[cls]['fields'][name]))
|
||||
lines.add(cls + ' ' + name + (comment == null ? '' : ' ' + comment))
|
||||
else
|
||||
*/
|
||||
println('Invalid: ' + line)
|
||||
} else { // Methods
|
||||
if (json[cls]['methods'] == null || !isSided(json[cls]['methods'][name + ' ' + desc]))
|
||||
println('Invalid: ' + line)
|
||||
else {
|
||||
lines.add(cls + ' ' + name + desc + (comment == null ? '' : ' ' + comment))
|
||||
findChildMethods(json, cls, name + ' ' + desc).each { println(line + ' -- ' + it) }
|
||||
findChildMethods(json, cls, name + ' ' + desc).each { lines.add('\t' + it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
f.text = lines.join('\n')
|
||||
}
|
||||
}
|
||||
|
||||
protected static isSided(json) {
|
||||
if (json == null || json['annotations'] == null)
|
||||
return false
|
||||
for (ann in json['annotations']) {
|
||||
if ('Lnet/minecraftforge/api/distmarker/OnlyIn;'.equals(ann.desc))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
protected static findChildMethods(json, cls, desc)
|
||||
{
|
||||
return json.values().findAll{ it.methods != null && it.methods[desc] != null && it.methods[desc].override == cls && isSided(it.methods[desc]) }
|
||||
.collect { it.name + ' ' + desc.replace(' ', '') } as TreeSet
|
||||
}
|
||||
}
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.Optional
|
||||
import org.gradle.api.tasks.OutputFile
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
|
||||
import java.nio.file.Files
|
||||
|
||||
abstract class DownloadBaseCompatibilityBinPatches extends DefaultTask {
|
||||
DownloadBaseCompatibilityBinPatches() {
|
||||
group = 'jar compatibility'
|
||||
onlyIf {
|
||||
inputVersion.getOrNull() != null
|
||||
}
|
||||
|
||||
output.convention(project.layout.buildDirectory.dir(name).map { it.file('joined.lzma') })
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
void run() {
|
||||
def dep = project.dependencies.create("net.minecraftforge:forge:${inputVersion.get()}:userdev")
|
||||
def baseForgeUserdev = project.configurations.detachedConfiguration(dep).resolve().iterator().next()
|
||||
|
||||
def joinedLzma = project.zipTree(baseForgeUserdev).matching { it.include('joined.lzma') }.singleFile
|
||||
|
||||
Files.copy(joinedLzma.toPath(), output.get().asFile.toPath())
|
||||
}
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
abstract Property<String> getInputVersion()
|
||||
|
||||
@OutputFile
|
||||
abstract RegularFileProperty getOutput()
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.TupleConstructor
|
||||
|
||||
@CompileStatic
|
||||
@TupleConstructor
|
||||
final class InheritanceData implements Annotatable {
|
||||
String name
|
||||
int access
|
||||
String superName
|
||||
List<String> interfaces = []
|
||||
Map<String, Method> methods = [:]
|
||||
Map<String, Field> fields = [:]
|
||||
List<Annotation> annotations = []
|
||||
|
||||
@TupleConstructor
|
||||
static final class Method implements Annotatable {
|
||||
int access
|
||||
String override
|
||||
List<Annotation> annotations = []
|
||||
}
|
||||
|
||||
@TupleConstructor
|
||||
static final class Field implements Annotatable {
|
||||
int access
|
||||
String desc
|
||||
List<Annotation> annotations = []
|
||||
}
|
||||
|
||||
@TupleConstructor
|
||||
static final class Annotation {
|
||||
String desc
|
||||
}
|
||||
|
||||
private static final Gson GSON = new Gson()
|
||||
static Map<String, InheritanceData> parse(File file) {
|
||||
try (final reader = file.newReader()) {
|
||||
return GSON.fromJson(reader, new TypeToken<Map<String, InheritanceData>>() {})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@CompileStatic
|
||||
interface Annotatable {
|
||||
List<InheritanceData.Annotation> getAnnotations()
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.tasks.InputFiles
|
||||
import org.gradle.api.tasks.OutputFile
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
abstract class MergeJars extends DefaultTask {
|
||||
MergeJars() {
|
||||
output.convention(project.layout.buildDirectory.dir(name).map { it.file('output.jar') })
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
void run() {
|
||||
def jars = inputJars.files
|
||||
|
||||
try (ZipOutputStream zout = new ZipOutputStream(new FileOutputStream(output.get().asFile))) {
|
||||
for (def jar : jars) {
|
||||
try (ZipInputStream zin = new ZipInputStream(new FileInputStream(jar))) {
|
||||
def entry
|
||||
while ((entry = zin.getNextEntry()) != null) {
|
||||
ZipEntry _new = new ZipEntry(entry.getName())
|
||||
_new.setTime(0) //SHOULD be the same time as the main entry, but NOOOO _new.setTime(entry.getTime()) throws DateTimeException, so you get 0, screw you!
|
||||
zout.putNextEntry(_new)
|
||||
IOUtils.copy(zin, zout)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@InputFiles
|
||||
abstract ConfigurableFileCollection getInputJars()
|
||||
|
||||
@OutputFile
|
||||
abstract RegularFileProperty getOutput()
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.artifacts.Dependency
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.Optional
|
||||
import org.gradle.api.tasks.OutputFile
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.StandardCopyOption
|
||||
|
||||
abstract class SetupCheckJarCompatibility extends DefaultTask {
|
||||
SetupCheckJarCompatibility() {
|
||||
group = 'jar compatibility'
|
||||
onlyIf {
|
||||
inputVersion.getOrNull() != null
|
||||
}
|
||||
outputs.upToDateWhen { false } // Never up to date, because this setup task should always run
|
||||
|
||||
baseBinPatchesOutput.convention(project.layout.buildDirectory.dir(name).map { it.file('joined.lzma') })
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
void run() {
|
||||
def inputVersion = inputVersion.get()
|
||||
def fmlLibs = project.configurations.detachedConfiguration(project.PACKED_DEPS.collect {
|
||||
def artifactId = it.split(':')[1]
|
||||
return project.dependencies.create("net.minecraftforge:${artifactId}:${inputVersion}")
|
||||
}.toArray(Dependency[]::new))
|
||||
|
||||
project.tasks.named('checkJarCompatibility') {
|
||||
baseLibraries.from(project.provider {
|
||||
fmlLibs.resolvedConfiguration.lenientConfiguration.files
|
||||
})
|
||||
}
|
||||
|
||||
def baseForgeUserdev = project.layout.buildDirectory.dir(name).map { it.file("forge-${inputVersion}-userdev.jar") }.get().asFile
|
||||
project.rootProject.extensions.download.run {
|
||||
src "https://maven.minecraftforge.net/net/minecraftforge/forge/${inputVersion}/forge-${inputVersion}-userdev.jar"
|
||||
dest baseForgeUserdev
|
||||
}
|
||||
|
||||
def joinedLzma = project.zipTree(baseForgeUserdev).matching { it.include('joined.lzma') }.singleFile
|
||||
|
||||
Files.copy(joinedLzma.toPath(), baseBinPatchesOutput.get().asFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
||||
}
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
abstract Property<String> getInputVersion()
|
||||
|
||||
@OutputFile
|
||||
abstract RegularFileProperty getBaseBinPatchesOutput()
|
||||
}
|
||||
@@ -18,17 +18,17 @@ public class Util {
|
||||
return md.digest().collect {String.format "%02x", it}.join()
|
||||
}
|
||||
File.metaClass.getSha1 = { !delegate.exists() ? null : delegate.sha1() }
|
||||
|
||||
|
||||
File.metaClass.json = { -> new JsonSlurper().parseText(delegate.text) }
|
||||
File.metaClass.getJson = { return delegate.exists() ? new JsonSlurper().parse(delegate) : [:] }
|
||||
File.metaClass.setJson = { json -> delegate.text = new JsonBuilder(json).toPrettyString() }
|
||||
|
||||
|
||||
Date.metaClass.iso8601 = { ->
|
||||
def format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
|
||||
def result = format.format(delegate)
|
||||
return result[0..21] + ':' + result[22..-1]
|
||||
}
|
||||
|
||||
|
||||
String.metaClass.rsplit = { String del, int limit = -1 ->
|
||||
def lst = new ArrayList()
|
||||
def x = 0, idx
|
||||
@@ -41,7 +41,7 @@ public class Util {
|
||||
return lst
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static String[] getClasspath(project, libs, artifact) {
|
||||
def ret = []
|
||||
artifactTree(project, artifact).each { key, lib ->
|
||||
@@ -51,7 +51,7 @@ public class Util {
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
|
||||
public static def getArtifacts(project, config, classifiers) {
|
||||
def ret = [:]
|
||||
config.resolvedConfiguration.resolvedArtifacts.each {
|
||||
@@ -75,7 +75,7 @@ public class Util {
|
||||
url = "https://maven.minecraftforge.net/${path}"
|
||||
}
|
||||
//TODO remove when Mojang launcher is updated
|
||||
if (!classifiers && art.classifier != null) {
|
||||
if (!classifiers && art.classifier != null) {
|
||||
//Mojang launcher doesn't currently support classifiers, so... move it to part of the version, and force the extension to 'jar'
|
||||
// However, keep the path normal so that our mirror system works.
|
||||
art.version = "${art.version}-${art.classifier}"
|
||||
@@ -96,14 +96,14 @@ public class Util {
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
public static def getMavenPath(task) {
|
||||
|
||||
public static def getMavenPath(task) {
|
||||
def classifier = task.archiveClassifier.get()
|
||||
def dep = "${task.project.group}:${task.project.name}:${task.project.version}" + (classifier == '' ? '' : ':' + classifier)
|
||||
return "${task.project.group.replace('.', '/')}/${task.project.name}/${task.project.version}/${task.project.name}-${task.project.version}".toString() + (classifier == '' ? '' : '-' + classifier) + '.jar'
|
||||
}
|
||||
|
||||
public static def getMavenDep(task) {
|
||||
|
||||
public static def getMavenDep(task) {
|
||||
def classifier = task.archiveClassifier.get()
|
||||
return "${task.project.group}:${task.project.name}:${task.project.version}" + (classifier == '' ? '' : ':' + classifier)
|
||||
}
|
||||
@@ -127,7 +127,7 @@ public class Util {
|
||||
def files = cfg.resolve()
|
||||
return getArtifacts(project, cfg, true)
|
||||
}
|
||||
|
||||
|
||||
private static boolean checkExists(url) {
|
||||
try {
|
||||
def code = new URL(url).openConnection().with {
|
||||
@@ -142,4 +142,9 @@ public class Util {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
public static String getLatestForgeVersion(mcVersion) {
|
||||
def json = new JsonSlurper().parseText(new URL("https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json").getText("UTF-8"))
|
||||
return json.promos["$mcVersion-latest"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
package net.minecraftforge.forge.tasks.checks
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.TupleConstructor
|
||||
import net.minecraftforge.forge.tasks.InheritanceData
|
||||
import net.minecraftforge.srgutils.IMappingFile
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.file.RegularFile
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.tasks.InputFile
|
||||
import org.gradle.api.tasks.InputFiles
|
||||
import org.gradle.api.tasks.Optional
|
||||
import org.objectweb.asm.Opcodes
|
||||
|
||||
@CompileStatic
|
||||
abstract class CheckATs extends CheckTask {
|
||||
|
||||
@InputFile abstract RegularFileProperty getInheritance()
|
||||
@InputFiles abstract ConfigurableFileCollection getAts()
|
||||
@InputFile @Optional abstract RegularFileProperty getMappings()
|
||||
|
||||
@Override
|
||||
void check(Reporter reporter, boolean fix) {
|
||||
final IMappingFile mappings = mappings.map { RegularFile it -> IMappingFile.load(it.asFile) }.getOrNull()
|
||||
final inheritance = InheritanceData.parse(this.inheritance.get().asFile)
|
||||
|
||||
ats.each {
|
||||
final lines = process(it, reporter, inheritance)
|
||||
if (fix) {
|
||||
it.text = joinBack(lines, inheritance, mappings).join('\n')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static TreeMap<String, ATParser.Entry> process(File file, Reporter reporter, Map<String, InheritanceData> inheritance) {
|
||||
final TreeMap<String, ATParser.Entry> lines = ATParser.parse(file.readLines(), reporter)
|
||||
final Map<String, ATParser.Entry> constructorGroups = [:]
|
||||
|
||||
final itr = lines.entrySet().iterator()
|
||||
final toRemove = []
|
||||
while (itr.hasNext()) {
|
||||
final next = itr.next()
|
||||
String key = next.key
|
||||
final entry = next.value
|
||||
if (entry === null) continue
|
||||
|
||||
final binaryName = entry.cls.replaceAll('\\.', '/')
|
||||
|
||||
// Process Groups, this will remove any entries outside the group that is covered by the group
|
||||
if (entry.group) {
|
||||
final jcls = inheritance[binaryName]
|
||||
if (jcls === null) {
|
||||
itr.remove()
|
||||
reporter.report("Invalid group: $key")
|
||||
} else if ('*' == entry.desc) {
|
||||
if (!jcls.fields) {
|
||||
itr.remove()
|
||||
reporter.report("Invalid group, class has no fields: $key")
|
||||
} else {
|
||||
jcls.fields.each { field, value ->
|
||||
final fkey = entry.cls + ' ' + field
|
||||
if (accessLevel(value.access) < accessStr(entry.modifier)) {
|
||||
if (lines.containsKey(fkey)) {
|
||||
toRemove.add(fkey)
|
||||
} else if (!entry.existing.contains(fkey)) {
|
||||
reporter.report("Missing group entry: $fkey")
|
||||
}
|
||||
entry.children.add(fkey)
|
||||
} else if (lines.containsKey(fkey)) {
|
||||
toRemove.add(fkey)
|
||||
reporter.report("Found invalid group entry: $fkey")
|
||||
}
|
||||
}
|
||||
entry.existing.findAll { it !in entry.children }.each { println('Removed: ' + it) }
|
||||
}
|
||||
} else if ('*()' == entry.desc) {
|
||||
if (!jcls.methods) {
|
||||
itr.remove()
|
||||
reporter.report("Invalid group, class has no methods: $key")
|
||||
} else {
|
||||
jcls.methods.each { mtd, value ->
|
||||
if (mtd.startsWith('<clinit>') || mtd.startsWith('lambda$')) return
|
||||
key = entry.cls + ' ' + mtd.replace(' ', '')
|
||||
if (accessLevel(value.access) < accessStr(entry.modifier)) {
|
||||
if (lines.containsKey(key)) {
|
||||
toRemove.add(key)
|
||||
} else if (!entry.existing.contains(key)) {
|
||||
reporter.report("Missing group entry: $key")
|
||||
}
|
||||
entry.children.add(key)
|
||||
} else if (lines.containsKey(key)) {
|
||||
toRemove.add(key)
|
||||
reporter.report("Found invalid group entry: $key")
|
||||
}
|
||||
}
|
||||
entry.existing.findAll { it !in entry.children }.each { println('Removed: ' + it) }
|
||||
}
|
||||
} else if ('<init>' == entry.desc) { //Make all public non-abstract subclasses
|
||||
constructorGroups.put(binaryName, entry)
|
||||
}
|
||||
}
|
||||
|
||||
// Process normal lines, remove invalid and remove narrowing
|
||||
else {
|
||||
def jcls = inheritance.get(binaryName)
|
||||
if (jcls === null) {
|
||||
itr.remove()
|
||||
reporter.report("Invalid: $key")
|
||||
} else if (entry.desc == '') {
|
||||
if (accessLevel(jcls.access) > accessStr(entry.modifier) && (entry.comment === null || !entry.comment.startsWith('#force '))) {
|
||||
itr.remove()
|
||||
reporter.report("Invalid Narrowing: $key")
|
||||
}
|
||||
} else if (!entry.desc.contains('(')) {
|
||||
if (!jcls.fields || !jcls.fields.containsKey(entry.desc)) {
|
||||
itr.remove()
|
||||
reporter.report("Invalid: $key")
|
||||
} else {
|
||||
final value = jcls.fields[entry.desc]
|
||||
if (accessLevel(value.access) > accessStr(entry.modifier) && (entry.comment === null || !entry.comment.startsWith('#force '))) {
|
||||
itr.remove()
|
||||
reporter.report("Invalid Narrowing: $key - ${entry.comment}")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
final jdesc = entry.desc.replace('(', ' (')
|
||||
if (!jcls.methods || !jcls.methods.containsKey(jdesc)) {
|
||||
itr.remove()
|
||||
reporter.report("Invalid: $key")
|
||||
} else {
|
||||
final value = jcls.methods[jdesc]
|
||||
if (accessLevel(value.access) > accessStr(entry.modifier) && (entry.comment === null || !entry.comment.startsWith('#force '))) {
|
||||
itr.remove()
|
||||
reporter.report("Invalid Narrowing: $key")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inheritance.each { tcls, value ->
|
||||
if (!value.methods || ((value.access & Opcodes.ACC_ABSTRACT) !== 0)) return
|
||||
String parent = tcls
|
||||
while (parent !== null) {
|
||||
constructorGroups[parent]?.tap { entry ->
|
||||
value.methods.each { mtd, v ->
|
||||
if (mtd.startsWith('<init>')) {
|
||||
final child = tcls.replaceAll('/', '\\.') + ' ' + mtd.replace(' ', '')
|
||||
if (accessLevel(v.access) < 3) {
|
||||
if (lines.containsKey(child)) {
|
||||
toRemove.add(child)
|
||||
} else if (child !in entry.existing) {
|
||||
reporter.report("Missing group entry: $child")
|
||||
}
|
||||
entry.children.add(child)
|
||||
} else if (lines.containsKey(child)) {
|
||||
toRemove.add(child)
|
||||
reporter.report("Found invalid group entry: $child")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
parent = inheritance[parent]?.superName
|
||||
}
|
||||
}
|
||||
constructorGroups.values().each { entry -> entry.existing.findAll { it !in entry.children }.each{ reporter.report("Found invalid group entry: $it") } }
|
||||
|
||||
toRemove.each(lines.&remove)
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
private static List<String> joinBack(TreeMap<String, ATParser.Entry> lines, Map<String, InheritanceData> inheritance, IMappingFile mappings) {
|
||||
final data = []
|
||||
final remapComment = { ATParser.Entry entry ->
|
||||
if (!mappings || !entry || !entry.desc) return null
|
||||
final comment = entry.comment?.substring(1)?.trim()
|
||||
final jsonCls = inheritance.get(entry.cls.replaceAll('\\.', '/'))
|
||||
final mappingsClass = mappings?.getClass(jsonCls.name)
|
||||
if (mappingsClass === null) return entry.comment
|
||||
final idx = entry.desc.indexOf('(')
|
||||
|
||||
String mappedName = idx == -1
|
||||
? mappingsClass.remapField(entry.desc)
|
||||
: mappingsClass.remapMethod(entry.desc.substring(0, idx), entry.desc.substring(idx))
|
||||
if (!mappedName) return entry.comment
|
||||
if (mappedName == '<init>')
|
||||
mappedName = 'constructor'
|
||||
|
||||
if (comment?.startsWith(mappedName))
|
||||
return '# ' + comment
|
||||
if (comment && comment.indexOf(' ') !== -1) {
|
||||
def split = comment.split(' - ').toList()
|
||||
if (split[0].indexOf(' ') !== -1)
|
||||
// The first string is more than one word, so append before it
|
||||
return "# ${mappedName} - ${comment}"
|
||||
split.remove(0)
|
||||
return "# ${mappedName} - ${String.join(' - ', split)}"
|
||||
}
|
||||
return '# ' + mappedName
|
||||
}
|
||||
lines.each { key, value ->
|
||||
if (!value.group) {
|
||||
def comment = remapComment.call(value)
|
||||
data.add(value.modifier + ' ' + key + (comment ? ' ' + comment : ''))
|
||||
} else {
|
||||
data.add(('#group ' + value.modifier + ' ' + key + ' ' + (value.comment ?: '')).trim())
|
||||
value.children.each {
|
||||
final line = value.modifier + ' ' + it
|
||||
final entry = ATParser.parseEntry(line)
|
||||
final comment = remapComment(entry)
|
||||
data.add(line + (comment ? ' ' + comment : ''))
|
||||
}
|
||||
data.add('#endgroup')
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
static int accessStr(String access) {
|
||||
if (access.endsWith('-f') || access.endsWith('+f')) return 4
|
||||
switch (access.toLowerCase()) {
|
||||
case 'public': return 3
|
||||
case 'protected': return 2
|
||||
case 'default': return 1
|
||||
case 'private': return 0
|
||||
default: return -1
|
||||
}
|
||||
}
|
||||
|
||||
static int accessLevel(int access) {
|
||||
if ((access & Opcodes.ACC_PUBLIC) !== 0) return 3
|
||||
if ((access & Opcodes.ACC_PROTECTED) !== 0) return 2
|
||||
if ((access & Opcodes.ACC_PRIVATE) !== 0) return 0
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
@CompileStatic
|
||||
class ATParser {
|
||||
static TreeMap<String, Entry> parse(List<String> lines, CheckTask.Reporter reporter) {
|
||||
TreeMap<String, Entry> outLines = new TreeMap<>()
|
||||
Entry group = null
|
||||
for (final line : lines) {
|
||||
if (line.isEmpty()) continue
|
||||
if (line.startsWith('#group ')) {
|
||||
final entry = parseEntry(line.substring(7))
|
||||
|
||||
if (entry.desc != '*' && entry.desc != '*()' && entry.desc != '<init>') {
|
||||
reporter.report("Invalid group: $line", false)
|
||||
}
|
||||
|
||||
entry.group = true
|
||||
entry.children = []
|
||||
entry.existing = []
|
||||
|
||||
group = entry
|
||||
|
||||
if (outLines.containsKey(entry.key)) {
|
||||
reporter.report("Duplicate group: $line", false)
|
||||
}
|
||||
|
||||
outLines[entry.key] = group
|
||||
} else if (group !== null) {
|
||||
if (line.startsWith('#endgroup')) {
|
||||
group = null
|
||||
} else {
|
||||
final key = parseEntry(line).key
|
||||
group.existing.add(key)
|
||||
}
|
||||
} else if (line.startsWith('#endgroup')) {
|
||||
reporter.report("Invalid group ending: $line", false)
|
||||
} else if (line.startsWith('#')) {
|
||||
//Nom
|
||||
} else {
|
||||
final entry = parseEntry(line)
|
||||
if (outLines.containsKey(entry.key)) {
|
||||
reporter.report("Found duplicate: $line")
|
||||
continue
|
||||
}
|
||||
outLines[entry.key] = entry
|
||||
}
|
||||
}
|
||||
return outLines
|
||||
}
|
||||
|
||||
static Entry parseEntry(String line) {
|
||||
final idx = line.indexOf('#')
|
||||
final String comment = idx === -1 ? null : line.substring(idx)
|
||||
if (idx !== -1) line = line.substring(0, idx - 1)
|
||||
final data = (line.trim() + ' ').split(' ', -1)
|
||||
new Entry(data[0], data[1], data[2], comment)
|
||||
}
|
||||
|
||||
@TupleConstructor
|
||||
static final class Entry {
|
||||
String modifier, cls, desc, comment
|
||||
|
||||
Set<String> existing
|
||||
TreeSet<String> children
|
||||
boolean group = false
|
||||
|
||||
@Lazy
|
||||
String key = {cls + (desc.isEmpty() ? '' : ' ' + desc)}()
|
||||
|
||||
Object getAt(String key) {
|
||||
return getProperty(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package net.minecraftforge.forge.tasks.checks
|
||||
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.tasks.InputFile
|
||||
import org.gradle.api.tasks.InputFiles
|
||||
import org.objectweb.asm.ClassReader
|
||||
import org.objectweb.asm.ClassVisitor
|
||||
import org.objectweb.asm.MethodVisitor
|
||||
import org.objectweb.asm.Opcodes
|
||||
import org.objectweb.asm.Type
|
||||
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
abstract class CheckExcs extends CheckTask {
|
||||
|
||||
@InputFile abstract RegularFileProperty getBinary()
|
||||
@InputFiles abstract ConfigurableFileCollection getExcs()
|
||||
|
||||
@Override
|
||||
void check(Reporter reporter, boolean fix) {
|
||||
final Set<String> known = []
|
||||
collectKnown(known)
|
||||
|
||||
excs.each { f ->
|
||||
final lines = []
|
||||
f.eachLine { line ->
|
||||
def idx = line.indexOf('#')
|
||||
if (idx == 0 || line.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (idx != -1) line = line.substring(0, idx - 1)
|
||||
|
||||
if (!line.contains('=')) {
|
||||
reporter.report("Invalid: $line")
|
||||
return
|
||||
}
|
||||
|
||||
def (String key, String value) = line.split('=', 2)
|
||||
if (!known.contains(key)) {
|
||||
reporter.report("Unknown: $line")
|
||||
return
|
||||
}
|
||||
|
||||
String desc = key.split('\\.', 2)[1]
|
||||
if (!desc.contains('(')) {
|
||||
reporter.report("Invalid: $line")
|
||||
return
|
||||
}
|
||||
desc = '(' + desc.split('\\(', 2)[1]
|
||||
|
||||
def (exceptions, String args) = value.contains('|') ? value.split('|', 2) : [value, '']
|
||||
|
||||
if (args.split(',').length !== Type.getArgumentTypes(desc).length) {
|
||||
reporter.report("Invalid: $line")
|
||||
return
|
||||
}
|
||||
lines.add(line)
|
||||
}
|
||||
|
||||
if (fix) f.text = lines.sort().join('\n')
|
||||
}
|
||||
}
|
||||
|
||||
private void collectKnown(Collection<String> known) {
|
||||
binary.get().asFile.withInputStream { i ->
|
||||
new ZipInputStream(i).withCloseable { zin ->
|
||||
final visitor = new ClassVisitor(Opcodes.ASM9) {
|
||||
private String cls
|
||||
@Override
|
||||
void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
|
||||
this.cls = name
|
||||
}
|
||||
|
||||
@Override
|
||||
MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
|
||||
known.add(this.cls + '.' + name + descriptor)
|
||||
super.visitMethod(access, name, descriptor, signature, exceptions)
|
||||
}
|
||||
}
|
||||
ZipEntry zein
|
||||
while ((zein = zin.nextEntry) !== null) {
|
||||
if (zein.name.endsWith('.class')) {
|
||||
ClassReader reader = new ClassReader(zin)
|
||||
reader.accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package net.minecraftforge.forge.tasks.checks
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
|
||||
@CompileStatic
|
||||
enum CheckMode {
|
||||
CHECK,
|
||||
FIX
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package net.minecraftforge.forge.tasks.checks
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import org.gradle.api.file.DirectoryProperty
|
||||
import org.gradle.api.provider.ListProperty
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.InputDirectory
|
||||
import org.gradle.api.tasks.Optional
|
||||
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.util.regex.Pattern
|
||||
|
||||
@CompileStatic
|
||||
abstract class CheckPatches extends CheckTask {
|
||||
|
||||
private static final Pattern HUNK_START_PATTERN = Pattern.compile('^@@ -[0-9,]* \\+[0-9,_]* @@$')
|
||||
private static final Pattern WHITESPACE_PATTERN = Pattern.compile('^[+\\-]\\s*$')
|
||||
private static final Pattern IMPORT_PATTERN = Pattern.compile('^[+\\-]\\s*import.*')
|
||||
private static final Pattern FIELD_PATTERN = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final)?([^=;]*)(=.*)?;\\s*$')
|
||||
private static final Pattern METHOD_PATTERN = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final)?([^(]*)[(]([^)]*)?[)]\\s*[{]\\s*$')
|
||||
private static final Pattern CLASS_PATTERN = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final[\\s]*)?(class|interface)([^{]*)[{]\\s*$')
|
||||
private static final Map<String, Integer> ACCESS_MAP = [private: 0, protected: 2, public: 3].tap { it.put(null, 1) }
|
||||
|
||||
@InputDirectory abstract DirectoryProperty getPatchDir()
|
||||
@Input @Optional abstract ListProperty<String> getPatchesWithS2SArtifact()
|
||||
|
||||
@Override
|
||||
void check(Reporter reporter, boolean fix) {
|
||||
final patchDir = getPatchDir().get().asFile.toPath()
|
||||
Files.walk(patchDir).withCloseable {
|
||||
it.filter(Files.&isRegularFile).forEach { path ->
|
||||
final String relativeName = patchDir.relativize(path).toString()
|
||||
verifyPatch(path, reporter, fix, relativeName, patchesWithS2SArtifact.get().contains(relativeName.replace('\\', '/')))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void verifyPatch(Path patch, Reporter reporter, boolean fix, String patchPath, boolean hasS2SArtifact) {
|
||||
final oldFixedErrors = reporter.fixed.size()
|
||||
|
||||
final lines = Files.readAllLines(patch)
|
||||
|
||||
int hunksStart = 0
|
||||
boolean onlyWhiteSpace = false
|
||||
|
||||
final List<String> newLines = []
|
||||
|
||||
// First two lines are file name ++/-- and we do not care
|
||||
newLines.add(lines[0] + '\n')
|
||||
newLines.add(lines[1] + '\n')
|
||||
|
||||
int i
|
||||
for (i = 2; i < lines.size(); ++i) {
|
||||
def line = lines[i]
|
||||
newLines.add(line + '\n')
|
||||
|
||||
if (HUNK_START_PATTERN.matcher(line).find()) {
|
||||
if (onlyWhiteSpace) {
|
||||
if (!hasS2SArtifact)
|
||||
reporter.report("Patch contains only white space hunk starting at line ${hunksStart + 1}, file: $patchPath")
|
||||
int toRemove = i - hunksStart
|
||||
while (toRemove-- > 0)
|
||||
newLines.remove(newLines.size() - 1)
|
||||
}
|
||||
hunksStart = i
|
||||
onlyWhiteSpace = true
|
||||
continue
|
||||
}
|
||||
|
||||
if (line.startsWithAny('+','-')) {
|
||||
def prefixChange = false
|
||||
def prevLine = lines[i - 1]
|
||||
|
||||
if (line.charAt(0) == (char)'+' && prevLine.charAt(0) == (char)'-') {
|
||||
def prevTrim = prevLine.substring(1).replaceAll("\\s", "")
|
||||
def currTrim = line.substring(1).replaceAll("\\s", "")
|
||||
|
||||
if (prevTrim == currTrim) {
|
||||
prefixChange = true
|
||||
}
|
||||
|
||||
def pMatcher = FIELD_PATTERN.matcher(prevLine)
|
||||
def cMatcher = FIELD_PATTERN.matcher(line)
|
||||
|
||||
if (pMatcher.find() && cMatcher.find() &&
|
||||
pMatcher.group(6) == cMatcher.group(6) && // = ...
|
||||
pMatcher.group(5) == cMatcher.group(5) && // field name
|
||||
pMatcher.group(3) == cMatcher.group(3) && // static
|
||||
(ACCESS_MAP[pMatcher.group(2)] < ACCESS_MAP[cMatcher.group(2)] || pMatcher.group(4) != cMatcher.group(4))) {
|
||||
reporter.report("Patch contains access changes or final removal at line ${i + 1}, file: $patchPath", false)
|
||||
}
|
||||
|
||||
pMatcher = METHOD_PATTERN.matcher(prevLine)
|
||||
cMatcher = METHOD_PATTERN.matcher(line)
|
||||
|
||||
if (pMatcher.find() && cMatcher.find() &&
|
||||
pMatcher.group(6) == cMatcher.group(6) && // params
|
||||
pMatcher.group(5) == cMatcher.group(5) && // <T> void name
|
||||
pMatcher.group(3) == cMatcher.group(3) && // static
|
||||
(ACCESS_MAP[pMatcher.group(2)] < ACCESS_MAP[cMatcher.group(2)] || pMatcher.group(4) != cMatcher.group(4))) {
|
||||
reporter.report("Patch contains access changes or final removal at line ${i + 1}, file: $patchPath", false)
|
||||
}
|
||||
|
||||
pMatcher = CLASS_PATTERN.matcher(prevLine)
|
||||
cMatcher = CLASS_PATTERN.matcher(line)
|
||||
|
||||
if (pMatcher.find() && cMatcher.find() &&
|
||||
pMatcher.group(6) == cMatcher.group(6) && // ClassName<> extends ...
|
||||
pMatcher.group(5) == cMatcher.group(5) && // class | interface
|
||||
pMatcher.group(3) == cMatcher.group(3) && // static
|
||||
(ACCESS_MAP[pMatcher.group(2)] < ACCESS_MAP[cMatcher.group(2)] || pMatcher.group(4) != cMatcher.group(4))) {
|
||||
reporter.report("Patch contains access changes or final removal at line ${i + 1}, file: $patchPath", false)
|
||||
}
|
||||
}
|
||||
|
||||
if (line.charAt(0) == (char)'-' && i + 1 < lines.size()) {
|
||||
final nextLine = lines[i + 1]
|
||||
if (nextLine.charAt(0) == (char)'+') {
|
||||
final nextTrim = nextLine.substring(1).replaceAll("\\s", "")
|
||||
final currTrim = line.substring(1).replaceAll("\\s", "")
|
||||
|
||||
if (nextTrim == currTrim) {
|
||||
prefixChange = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final isWhiteSpaceChange = WHITESPACE_PATTERN.matcher(line).find()
|
||||
|
||||
if (!prefixChange && !isWhiteSpaceChange) {
|
||||
onlyWhiteSpace = hasS2SArtifact && IMPORT_PATTERN.matcher(line).find()
|
||||
} else if (isWhiteSpaceChange) {
|
||||
final prevLineChange = prevLine.startsWithAny('+','-')
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
if (line.contains('\t')) {
|
||||
reporter.report("Patch contains tabs on line ${i + 1}, file: $patchPath")
|
||||
line = line.replaceAll('\t', ' ')
|
||||
newLines.remove(newLines.size() - 1)
|
||||
newLines.add(line + '\n')
|
||||
}
|
||||
|
||||
if (IMPORT_PATTERN.matcher(line).find() && !hasS2SArtifact) {
|
||||
reporter.report("Patch contains import change on line ${i + 1}, file: $patchPath", false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (onlyWhiteSpace) {
|
||||
if (!hasS2SArtifact)
|
||||
reporter.report("Patch contains only white space hunk starting at line ${hunksStart + 1}, file: $patchPath")
|
||||
def toRemove = i - hunksStart;
|
||||
while (toRemove-- > 0)
|
||||
newLines.remove(newLines.size() - 1)
|
||||
}
|
||||
|
||||
if ((reporter.fixed.size() > oldFixedErrors && fix) || hasS2SArtifact) {
|
||||
if (newLines.size() <= 2) {
|
||||
logger.lifecycle("Patch is now empty removing, file: {}", patchPath)
|
||||
Files.delete(patch)
|
||||
}
|
||||
else {
|
||||
if (!hasS2SArtifact)
|
||||
logger.lifecycle("*** Updating patch file. Please run setup then genPatches again. ***")
|
||||
Files.newBufferedWriter(patch).withCloseable {
|
||||
newLines.each { l -> it.write(l) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package net.minecraftforge.forge.tasks.checks
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import net.minecraftforge.forge.tasks.Annotatable
|
||||
import net.minecraftforge.forge.tasks.InheritanceData
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.tasks.InputFile
|
||||
import org.gradle.api.tasks.InputFiles
|
||||
|
||||
@CompileStatic
|
||||
abstract class CheckSAS extends CheckTask {
|
||||
|
||||
@InputFile abstract RegularFileProperty getInheritance()
|
||||
@InputFiles abstract ConfigurableFileCollection getSass()
|
||||
|
||||
@Override
|
||||
void check(Reporter reporter, boolean fix) {
|
||||
final inheritance = InheritanceData.parse(this.inheritance.get().asFile)
|
||||
|
||||
sass.each { f ->
|
||||
final lines = []
|
||||
f.eachLine { line ->
|
||||
if (line[0] == '\t') return // Skip any tabbed lines, those are ones we add
|
||||
final idx = line.indexOf('#')
|
||||
if (idx == 0 || line.isEmpty()) {
|
||||
lines.add(line)
|
||||
return
|
||||
}
|
||||
def comment = idx == -1 ? null : line.substring(idx)
|
||||
if (idx != -1) line = line.substring(0, idx - 1)
|
||||
final spl = (line.trim().replace('(', ' (') + ' ').split(' ', -1)
|
||||
def (String cls, String name, String desc) = [spl[0], spl[1], spl[2]]
|
||||
cls = cls.replaceAll('\\.', '/')
|
||||
|
||||
if (inheritance[cls] === null) {
|
||||
reporter.report("Invalid: $line")
|
||||
} else if (name.isEmpty()) { //Class SAS
|
||||
final toAdd = []
|
||||
final clsSided = isSided(inheritance[cls])
|
||||
boolean sided = clsSided
|
||||
|
||||
/* TODO: MergeTool doesn't do fields
|
||||
if (json[cls]['fields'] != null) {
|
||||
for (entry in json[cls]['fields']) {
|
||||
if (isSided(entry.value)) {
|
||||
sided = true
|
||||
toAdd.add('\t' + cls + ' ' + entry.key)
|
||||
}
|
||||
}
|
||||
} */
|
||||
|
||||
final clsInh = inheritance[cls]
|
||||
if (clsInh.methods) {
|
||||
for (entry in clsInh.methods) {
|
||||
if (isSided(entry.value)) {
|
||||
sided = true
|
||||
toAdd.add('\t' + cls + ' ' + entry.key.replaceAll(' ', ''))
|
||||
findChildMethods(inheritance, cls, entry.key).each { lines.add('\t' + it) }
|
||||
findChildMethods(inheritance, cls, entry.key).each { println(line + ' -- ' + it) }
|
||||
} else if (clsSided) {
|
||||
findChildMethods(inheritance, cls, entry.key).each { lines.add('\t' + it) }
|
||||
findChildMethods(inheritance, cls, entry.key).each { println(line + ' -- ' + it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sided) {
|
||||
lines.add(cls + (comment == null ? '' : ' ' + comment))
|
||||
lines.addAll(toAdd.sort())
|
||||
} else {
|
||||
reporter.report("Invalid: $line")
|
||||
}
|
||||
|
||||
} else if (desc.isEmpty()) { // Fields
|
||||
/* TODO: MergeTool doesn't do fields
|
||||
if (json[cls]['fields'] != null && isSided(json[cls]['fields'][name]))
|
||||
lines.add(cls + ' ' + name + (comment == null ? '' : ' ' + comment))
|
||||
else */
|
||||
reporter.report("Invalid: $line")
|
||||
} else { // Methods
|
||||
final clsInh = inheritance[cls]
|
||||
if (clsInh.methods === null || !isSided(clsInh.methods[name + ' ' + desc]))
|
||||
reporter.report("Invalid: $line")
|
||||
else {
|
||||
lines.add(cls + ' ' + name + desc + (comment == null ? '' : ' ' + comment))
|
||||
findChildMethods(inheritance, cls, name + ' ' + desc).each { println(line + ' -- ' + it) }
|
||||
findChildMethods(inheritance, cls, name + ' ' + desc).each { lines.add('\t' + it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fix) f.text = lines.join('\n')
|
||||
}
|
||||
}
|
||||
|
||||
protected static isSided(Annotatable annotatable) {
|
||||
if (annotatable === null) return false
|
||||
for (ann in annotatable.annotations) {
|
||||
if ('Lnet/minecraftforge/api/distmarker/OnlyIn;' == ann.desc)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
protected static findChildMethods(Map<String, InheritanceData> json, String cls, String desc) {
|
||||
return json.values().findAll{ it.methods != null && it.methods[desc] != null && it.methods[desc].override == cls && isSided(it.methods[desc]) }
|
||||
.collect { it.name + ' ' + desc.replace(' ', '') } as TreeSet
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package net.minecraftforge.forge.tasks.checks
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import net.minecraftforge.forge.tasks.Util
|
||||
import org.gradle.api.Action
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.logging.LogLevel
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.gradle.api.tasks.TaskContainer
|
||||
import org.gradle.api.tasks.VerificationTask
|
||||
|
||||
@CompileStatic
|
||||
abstract class CheckTask extends DefaultTask implements VerificationTask {
|
||||
@Input
|
||||
abstract Property<CheckMode> getMode()
|
||||
|
||||
private boolean ignoreFailures = false
|
||||
|
||||
@Input
|
||||
@Override
|
||||
boolean getIgnoreFailures() {
|
||||
return ignoreFailures
|
||||
}
|
||||
|
||||
@Override
|
||||
void setIgnoreFailures(boolean ignoreFailures) {
|
||||
this.ignoreFailures = ignoreFailures
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
void run() {
|
||||
Util.init()
|
||||
|
||||
final doFix = getMode().get() === CheckMode.FIX
|
||||
final Reporter reporter = new Reporter(doFix)
|
||||
check(reporter, doFix)
|
||||
|
||||
if (reporter.messages) {
|
||||
if (getMode().get() === CheckMode.CHECK) {
|
||||
logger.error("Check task '{}' found errors:\n{}", name, reporter.messages.join('\n'))
|
||||
if (!ignoreFailures) {
|
||||
throw new IllegalArgumentException("${reporter.messages.size()} errors were found!")
|
||||
}
|
||||
} else {
|
||||
if (logger.isEnabled(LogLevel.DEBUG)) {
|
||||
logger.warn("Check task '{}' found {} errors and fixed {}:\n{}", name, reporter.messages.size(), reporter.fixed.size(), reporter.fixed.join('\n'))
|
||||
} else {
|
||||
logger.warn("Check task '{}' found {} errors and fixed {}.", name, reporter.messages.size(), reporter.fixed.size())
|
||||
}
|
||||
|
||||
if (reporter.notFixed) {
|
||||
logger.error('{} errors could not be fixed:\n{}', reporter.notFixed.size(), reporter.notFixed.join('\n'))
|
||||
if (!ignoreFailures) {
|
||||
throw new IllegalArgumentException("${reporter.notFixed.size()} errors which cannot be fixed were found!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract void check(Reporter reporter, boolean fix)
|
||||
|
||||
@CompileStatic
|
||||
static final class Reporter {
|
||||
final boolean trackFixed
|
||||
Reporter(boolean trackFixed) {
|
||||
this.trackFixed = trackFixed
|
||||
}
|
||||
|
||||
public final List<String> messages = []
|
||||
|
||||
public final List<String> fixed = []
|
||||
public final List<String> notFixed = []
|
||||
|
||||
void report(String message, boolean canBeFixed = true) {
|
||||
messages.add(message)
|
||||
|
||||
if (trackFixed) {
|
||||
if (canBeFixed) {
|
||||
fixed.add(message)
|
||||
} else {
|
||||
notFixed.add(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static <T extends CheckTask> void registerTask(TaskContainer tasks, String taskName, @DelegatesTo.Target('type') Class<T> clazz,
|
||||
@DelegatesTo(genericTypeIndex = 0, target = 'type') Closure configuration) {
|
||||
taskName = taskName.capitalize()
|
||||
tasks.register("check$taskName", clazz) { CheckTask task ->
|
||||
configuration.setDelegate((T) task)
|
||||
configuration.call(task)
|
||||
task.mode.set(CheckMode.CHECK)
|
||||
task.group = 'checks'
|
||||
}
|
||||
tasks.named('checkAll').configure { it.dependsOn("check$taskName") }
|
||||
|
||||
tasks.register("checkAndFix$taskName", clazz) { CheckTask task ->
|
||||
configuration.setDelegate((T) task)
|
||||
configuration.call(task)
|
||||
task.mode.set(CheckMode.FIX)
|
||||
task.group = 'checks'
|
||||
}
|
||||
tasks.named('checkAllAndFix').configure { it.dependsOn("checkAndFix$taskName") }
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -2,8 +2,8 @@
|
||||
|
||||
MinecraftForge
|
||||
=============
|
||||
[](https://files.minecraftforge.net)
|
||||
[](https://files.minecraftforge.net)
|
||||
[](https://files.minecraftforge.net) [](https://discord.gg/UvedJ9m) [](https://www.patreon.com/LexManos)
|
||||
|
||||
Forge is a free, open-source modding API all of your favourite mods use!
|
||||
|
||||
@@ -93,5 +93,7 @@ public interface IExtensionPoint<T extends Record>
|
||||
* @see net.minecraftforge.client.ForgeHooksClient#processForgeListPingData(net.minecraft.network.protocol.status.ServerStatus, net.minecraft.client.multiplayer.ServerData)
|
||||
*/
|
||||
@SuppressWarnings("JavadocReference") // reference to NetworkConstants, ForgeHooksClient
|
||||
record DisplayTest(Supplier<String> suppliedVersion, BiPredicate<String, Boolean> remoteVersionTest) implements IExtensionPoint<DisplayTest> {}
|
||||
record DisplayTest(Supplier<String> suppliedVersion, BiPredicate<String, Boolean> remoteVersionTest) implements IExtensionPoint<DisplayTest> {
|
||||
public static final String IGNORESERVERONLY = "OHNOES\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31";
|
||||
}
|
||||
}
|
||||
@@ -56,9 +56,24 @@ public abstract class ModContainer
|
||||
this.namespace = this.modId;
|
||||
this.modInfo = info;
|
||||
this.modLoadingStage = ModLoadingStage.CONSTRUCT;
|
||||
// default displaytest extension checks for version string match
|
||||
registerExtensionPoint(IExtensionPoint.DisplayTest.class, () -> new IExtensionPoint.DisplayTest(()->this.modInfo.getVersion().toString(),
|
||||
(incoming, isNetwork)->Objects.equals(incoming, this.modInfo.getVersion().toString())));
|
||||
|
||||
final String displayTestString = info.getConfig().<String>getConfigElement("displayTest").orElse("MATCH_VERSION"); // missing defaults to DEFAULT type
|
||||
Supplier<IExtensionPoint.DisplayTest> displayTestSupplier = switch (displayTestString) {
|
||||
case "MATCH_VERSION" -> // default displaytest checks for version string match
|
||||
() -> new IExtensionPoint.DisplayTest(() -> this.modInfo.getVersion().toString(),
|
||||
(incoming, isNetwork) -> Objects.equals(incoming, this.modInfo.getVersion().toString()));
|
||||
case "IGNORE_SERVER_VERSION" -> // Ignores any version information coming from the server - use for server only mods
|
||||
() -> new IExtensionPoint.DisplayTest(() -> IExtensionPoint.DisplayTest.IGNORESERVERONLY, (incoming, isNetwork) -> true);
|
||||
case "IGNORE_ALL_VERSION" -> // Ignores all information and provides no information
|
||||
() -> new IExtensionPoint.DisplayTest(() -> "", (incoming, isNetwork) -> true);
|
||||
case "NONE" -> null; // NO display test at all - use this if you're going to do your own display test
|
||||
default -> // any other value throws an exception
|
||||
throw new IllegalArgumentException("Invalid displayTest value supplied in mods.toml");
|
||||
};
|
||||
if (displayTestSupplier != null)
|
||||
registerExtensionPoint(IExtensionPoint.DisplayTest.class, displayTestSupplier);
|
||||
else
|
||||
extensionPoints.remove(IExtensionPoint.DisplayTest.class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
@@ -53,6 +54,7 @@ public class ModList
|
||||
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)
|
||||
{
|
||||
@@ -155,6 +157,7 @@ public class ModList
|
||||
void setLoadedMods(final List<ModContainer> modContainers)
|
||||
{
|
||||
this.mods = modContainers;
|
||||
this.sortedContainers = modContainers.stream().sorted(Comparator.comparingInt(c->sortedList.indexOf(c.getModInfo()))).toList();
|
||||
this.indexedMods = modContainers.stream().collect(Collectors.toMap(ModContainer::getModId, Function.identity()));
|
||||
}
|
||||
|
||||
@@ -218,6 +221,10 @@ public class ModList
|
||||
indexedMods.forEach(modContainerConsumer);
|
||||
}
|
||||
|
||||
public void forEachModInOrder(Consumer<ModContainer> containerConsumer) {
|
||||
this.sortedContainers.forEach(containerConsumer);
|
||||
}
|
||||
|
||||
public <T> Stream<T> applyForEachModContainer(Function<ModContainer, T> function) {
|
||||
return indexedMods.values().stream().map(function);
|
||||
}
|
||||
|
||||
@@ -8,26 +8,25 @@ package net.minecraftforge.fml;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.fml.loading.FMLEnvironment;
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
import net.minecraftforge.fml.loading.LoadingModList;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.InvalidModIdentifier;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
|
||||
import net.minecraftforge.fml.loading.progress.StartupMessageManager;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import net.minecraftforge.forgespi.language.IModLanguageProvider;
|
||||
import net.minecraftforge.forgespi.locating.ForgeFeature;
|
||||
import net.minecraftforge.forgespi.locating.IModFile;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -96,7 +95,7 @@ public class ModLoader
|
||||
.map(file -> new ModLoadingWarning(null, ModLoadingStage.VALIDATE, InvalidModIdentifier.identifyJarProblem(file.getFilePath()).orElse("fml.modloading.brokenfile"), file.getFileName()))
|
||||
.collect(Collectors.toList());
|
||||
FMLLoader.getLoadingModList().getModFiles().stream()
|
||||
.filter(ModFileInfo::missingLicense) //Search for files with missing licenses
|
||||
.filter(ModFileInfo::missingLicense)
|
||||
.filter(modFileInfo -> modFileInfo.getMods().stream().noneMatch(thisModInfo -> this.loadingExceptions.stream().map(ModLoadingException::getModInfo).anyMatch(otherInfo -> otherInfo == thisModInfo))) //Ignore files where any other mod already encountered an error
|
||||
.map(modFileInfo -> new ModLoadingException(null, ModLoadingStage.VALIDATE, "fml.modloading.missinglicense", null, modFileInfo.getFile()))
|
||||
.forEach(this.loadingExceptions::add);
|
||||
@@ -133,6 +132,7 @@ public class ModLoader
|
||||
* @param periodicTask Optional periodic task to perform on the main thread while other activities run
|
||||
*/
|
||||
public void gatherAndInitializeMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
|
||||
ForgeFeature.registerFeature("java_version", ForgeFeature.VersionFeatureTest.forVersionString(IModInfo.DependencySide.SERVER, System.getProperty("java.version")));
|
||||
loadingStateValid = true;
|
||||
statusConsumer.ifPresent(c->c.accept("Waiting for scan to complete"));
|
||||
FMLLoader.backgroundScanHandler.waitForScanToComplete(periodicTask);
|
||||
@@ -145,6 +145,22 @@ public class ModLoader
|
||||
loadingStateValid = false;
|
||||
throw new LoadingFailedException(loadingExceptions);
|
||||
}
|
||||
statusConsumer.ifPresent(c->c.accept("Validating features"));
|
||||
List<? extends ForgeFeature.Bound> failedBounds = loadingModList.getMods().stream()
|
||||
.map(ModInfo::getForgeFeatures)
|
||||
.flatMap(Collection::stream)
|
||||
.filter(bound -> !ForgeFeature.testFeature(FMLEnvironment.dist, bound))
|
||||
.toList();
|
||||
|
||||
if (!failedBounds.isEmpty()) {
|
||||
LOGGER.fatal(CORE, "Failed to validate feature bounds for mods");
|
||||
modList.setLoadedMods(Collections.emptyList());
|
||||
loadingStateValid = false;
|
||||
throw new LoadingFailedException(failedBounds.stream()
|
||||
.map(fb -> new ModLoadingException(fb.modInfo(), ModLoadingStage.CONSTRUCT, "fml.modloading.feature.missing", null, fb.featureName(), fb.featureBound(), ForgeFeature.featureValue(fb)))
|
||||
.toList());
|
||||
}
|
||||
|
||||
statusConsumer.ifPresent(c->c.accept("Building Mod List"));
|
||||
final List<ModContainer> modContainers = loadingModList.getModFiles().stream()
|
||||
.map(ModFileInfo::getFile)
|
||||
@@ -222,7 +238,7 @@ public class ModLoader
|
||||
}
|
||||
}
|
||||
|
||||
private List<ModContainer> buildMods(final ModFile modFile)
|
||||
private List<ModContainer> buildMods(final IModFile modFile)
|
||||
{
|
||||
final Map<String, IModInfo> modInfoMap = modFile.getModFileInfo().getMods().stream().collect(Collectors.toMap(IModInfo::getModId, Function.identity()));
|
||||
|
||||
@@ -234,17 +250,29 @@ public class ModLoader
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
if (containers.size() != modInfoMap.size()) {
|
||||
var modIds = modInfoMap.values().stream().map(IModInfo::getModId).sorted().collect(Collectors.toList());
|
||||
var containerIds = containers.stream().map(c -> c != null ? c.getModId() : "(null)").sorted().collect(Collectors.toList());
|
||||
|
||||
LOGGER.fatal(LOADING,"File {} constructed {} mods: {}, but had {} mods specified: {}",
|
||||
modFile.getFilePath(),
|
||||
containers.size(), containers.stream().map(c -> c != null ? c.getModId() : "(null)").sorted().collect(Collectors.toList()),
|
||||
modInfoMap.size(), modInfoMap.values().stream().map(IModInfo::getModId).sorted().collect(Collectors.toList()));
|
||||
containers.size(), containerIds,
|
||||
modInfoMap.size(), modIds);
|
||||
|
||||
var missingClasses = new ArrayList<>(modIds);
|
||||
missingClasses.removeAll(containerIds);
|
||||
LOGGER.fatal(LOADING, "The following classes are missing, but are reported in the mods.toml: {}", missingClasses);
|
||||
|
||||
var missingMods = new ArrayList<>(containerIds);
|
||||
missingMods.removeAll(modIds);
|
||||
LOGGER.fatal(LOADING, "The following mods are missing, but have classes in the jar: {}", missingMods);
|
||||
|
||||
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).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private ModContainer buildModContainerFromTOML(final ModFile 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 Map.Entry<String, ? extends IModLanguageProvider.IModLanguageLoader> idToProviderEntry) {
|
||||
try {
|
||||
final String modId = idToProviderEntry.getKey();
|
||||
final IModLanguageProvider.IModLanguageLoader languageLoader = idToProviderEntry.getValue();
|
||||
@@ -273,7 +301,7 @@ public class ModLoader
|
||||
LOGGER.error("Cowardly refusing to send event generator to a broken mod state");
|
||||
return;
|
||||
}
|
||||
ModList.get().forEachModContainer((id, mc) -> mc.acceptEvent(generator.apply(mc)));
|
||||
ModList.get().forEachModInOrder(mc -> mc.acceptEvent(generator.apply(mc)));
|
||||
}
|
||||
|
||||
public <T extends Event & IModBusEvent> void postEvent(T e) {
|
||||
@@ -281,7 +309,26 @@ public class ModLoader
|
||||
LOGGER.error("Cowardly refusing to send event {} to a broken mod state", e.getClass().getName());
|
||||
return;
|
||||
}
|
||||
ModList.get().forEachModContainer((id, mc) -> mc.acceptEvent(e));
|
||||
ModList.get().forEachModInOrder(mc -> mc.acceptEvent(e));
|
||||
}
|
||||
public <T extends Event & IModBusEvent> T postEventWithReturn(T e) {
|
||||
if (!loadingStateValid) {
|
||||
LOGGER.error("Cowardly refusing to send event {} to a broken mod state", e.getClass().getName());
|
||||
return e;
|
||||
}
|
||||
ModList.get().forEachModInOrder(mc -> mc.acceptEvent(e));
|
||||
return e;
|
||||
}
|
||||
public <T extends Event & IModBusEvent> void postEventWithWrapInModOrder(T e, BiConsumer<ModContainer, T> pre, BiConsumer<ModContainer, T> post) {
|
||||
if (!loadingStateValid) {
|
||||
LOGGER.error("Cowardly refusing to send event {} to a broken mod state", e.getClass().getName());
|
||||
return;
|
||||
}
|
||||
ModList.get().forEachModInOrder(mc -> {
|
||||
pre.accept(mc, e);
|
||||
mc.acceptEvent(e);
|
||||
post.accept(mc, e);
|
||||
});
|
||||
}
|
||||
|
||||
public List<ModLoadingWarning> getWarnings()
|
||||
|
||||
@@ -5,13 +5,18 @@
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraftforge.fml.config.IConfigSpec;
|
||||
import net.minecraftforge.fml.config.ModConfig;
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class ModLoadingContext
|
||||
{
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static final ThreadLocal<ModLoadingContext> context = ThreadLocal.withInitial(ModLoadingContext::new);
|
||||
private Object languageExtension;
|
||||
private ModLoadingStage stage;
|
||||
@@ -46,10 +51,24 @@ public class ModLoadingContext
|
||||
}
|
||||
|
||||
public void registerConfig(ModConfig.Type type, IConfigSpec<?> spec) {
|
||||
if (spec.isEmpty())
|
||||
{
|
||||
// This handles the case where a mod tries to register a config, without any options configured inside it.
|
||||
LOGGER.debug("Attempted to register an empty config for type {} on mod {}", type, getActiveContainer().getModId());
|
||||
return;
|
||||
}
|
||||
|
||||
getActiveContainer().addConfig(new ModConfig(type, spec, getActiveContainer()));
|
||||
}
|
||||
|
||||
public void registerConfig(ModConfig.Type type, IConfigSpec<?> spec, String fileName) {
|
||||
if (spec.isEmpty())
|
||||
{
|
||||
// This handles the case where a mod tries to register a config, without any options configured inside it.
|
||||
LOGGER.debug("Attempted to register an empty config for type {} on mod {} using file name {}", type, getActiveContainer().getModId(), fileName);
|
||||
return;
|
||||
}
|
||||
|
||||
getActiveContainer().addConfig(new ModConfig(type, spec, getActiveContainer(), fileName));
|
||||
}
|
||||
|
||||
|
||||
@@ -117,14 +117,22 @@ public class VersionChecker
|
||||
/**
|
||||
* Returns the response body as a String for the given URL while following redirects
|
||||
*/
|
||||
private String openUrlString(URL url) throws IOException, URISyntaxException, InterruptedException {
|
||||
private String openUrlString(URL url, IModInfo mod) throws IOException, URISyntaxException, InterruptedException {
|
||||
URL currentUrl = url;
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("Java-http-client/").append(System.getProperty("java.version")).append(' ');
|
||||
sb.append("MinecraftForge/").append(FMLLoader.versionInfo().mcAndForgeVersion()).append(' ');
|
||||
sb.append(mod.getModId()).append('/').append(mod.getVersion());
|
||||
final String userAgent = sb.toString();
|
||||
|
||||
for (int redirects = 0; redirects < MAX_HTTP_REDIRECTS; redirects++)
|
||||
{
|
||||
var request = HttpRequest.newBuilder()
|
||||
.uri(currentUrl.toURI())
|
||||
.timeout(Duration.ofSeconds(HTTP_TIMEOUT_SECS))
|
||||
.setHeader("Accept-Encoding", "gzip")
|
||||
.setHeader("User-Agent", userAgent)
|
||||
.GET()
|
||||
.build();
|
||||
|
||||
@@ -166,7 +174,7 @@ public class VersionChecker
|
||||
URL url = mod.getUpdateURL().get();
|
||||
LOGGER.info("[{}] Starting version check at {}", mod.getModId(), url.toString());
|
||||
|
||||
String data = openUrlString(url);
|
||||
String data = openUrlString(url, mod);
|
||||
|
||||
LOGGER.debug("[{}] Received version check data:\n{}", mod.getModId(), data);
|
||||
|
||||
|
||||
@@ -66,8 +66,12 @@ public class ConfigTracker {
|
||||
private void closeConfig(final ModConfig config, final Path configBasePath) {
|
||||
if (config.getConfigData() != null) {
|
||||
LOGGER.trace(CONFIG, "Closing config file type {} at {} for {}", config.getType(), config.getFileName(), config.getModId());
|
||||
config.save();
|
||||
// stop the filewatcher before we save the file and close it, so reload doesn't fire
|
||||
config.getHandler().unload(configBasePath, config);
|
||||
var unloading = IConfigEvent.unloading(config);
|
||||
if (unloading != null)
|
||||
config.fireEvent(unloading);
|
||||
config.save();
|
||||
config.setConfigData(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,17 @@ import net.minecraftforge.fml.Bindings;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface IConfigEvent {
|
||||
record ConfigConfig(Function<ModConfig, IConfigEvent> loading, Function<ModConfig, IConfigEvent> reloading) {}
|
||||
record ConfigConfig(Function<ModConfig, IConfigEvent> loading, Function<ModConfig, IConfigEvent> reloading, @Nullable Function<ModConfig, IConfigEvent> unloading) {
|
||||
@Deprecated(since = "1.19.3", forRemoval = true)
|
||||
@ApiStatus.Internal
|
||||
ConfigConfig(Function<ModConfig, IConfigEvent> loading, Function<ModConfig, IConfigEvent> reloading) {
|
||||
this(loading, reloading, null);
|
||||
}
|
||||
}
|
||||
ConfigConfig CONFIGCONFIG = Bindings.getConfigConfiguration().get();
|
||||
|
||||
static IConfigEvent reloading(ModConfig modConfig) {
|
||||
@@ -20,6 +29,9 @@ public interface IConfigEvent {
|
||||
static IConfigEvent loading(ModConfig modConfig) {
|
||||
return CONFIGCONFIG.loading().apply(modConfig);
|
||||
}
|
||||
@Nullable static IConfigEvent unloading(ModConfig modConfig) {
|
||||
return CONFIGCONFIG.unloading() == null ? null : CONFIGCONFIG.unloading().apply(modConfig);
|
||||
}
|
||||
ModConfig getConfig();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
||||
@@ -28,6 +28,8 @@ dependencies {
|
||||
api("cpw.mods:modlauncher:${MODLAUNCHER_VERSION}")
|
||||
api("net.minecraftforge:coremods:${COREMODS_VERSION}")
|
||||
api("com.mojang:logging:${MOJANG_LOGGING_VERSION}")
|
||||
api "net.minecraftforge:JarJarSelector:${JARJAR_VERSION}"
|
||||
api "net.minecraftforge:JarJarMetadata:${JARJAR_VERSION}"
|
||||
|
||||
implementation("net.sf.jopt-simple:jopt-simple:${JOPT_SIMPLE_VERSION}")
|
||||
implementation("cpw.mods:securejarhandler:${SECUREJARHANDLER_VERSION}")
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public class ClasspathLocatorUtils {
|
||||
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
|
||||
public static Path findJarPathFor(final String resourceName, final String jarName, final URL resource) {
|
||||
try {
|
||||
Path path;
|
||||
final URI uri = resource.toURI();
|
||||
if (uri.getScheme().equals("jar") && uri.getRawSchemeSpecificPart().contains("!/")) {
|
||||
int lastExcl = uri.getRawSchemeSpecificPart().lastIndexOf("!/");
|
||||
path = Paths.get(new URI(uri.getRawSchemeSpecificPart().substring(0, lastExcl)));
|
||||
} else {
|
||||
path = Paths.get(new URI("file://"+uri.getRawSchemeSpecificPart().substring(0, uri.getRawSchemeSpecificPart().length()-resourceName.length())));
|
||||
}
|
||||
//LOGGER.debug(CORE, "Found JAR {} at path {}", jarName, path.toString());
|
||||
return path;
|
||||
} catch (NullPointerException | URISyntaxException e) {
|
||||
LOGGER.error(LogMarkers.SCAN, "Failed to find JAR for class {} - {}", resourceName, jarName);
|
||||
throw new RuntimeException("Unable to locate "+resourceName+" - "+jarName, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import cpw.mods.modlauncher.api.NamedPath;
|
||||
import cpw.mods.modlauncher.serviceapi.ITransformerDiscoveryService;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
public class ClasspathTransformerDiscoverer implements ITransformerDiscoveryService {
|
||||
|
||||
private final List<Path> legacyClasspath = Arrays.stream(System.getProperty("legacyClassPath", "").split(File.pathSeparator)).map(Path::of).toList();
|
||||
|
||||
@Override
|
||||
public List<NamedPath> candidates(Path gameDirectory) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NamedPath> candidates(final Path gameDirectory, final String launchTarget) {
|
||||
if (launchTarget != null && launchTarget.contains("dev")) {
|
||||
this.scan(gameDirectory);
|
||||
}
|
||||
return List.copyOf(found);
|
||||
}
|
||||
|
||||
private final static List<NamedPath> found = new ArrayList<>();
|
||||
|
||||
public static List<Path> allExcluded() {
|
||||
return found.stream().map(np->np.paths()[0]).toList();
|
||||
}
|
||||
|
||||
private void scan(final Path gameDirectory) {
|
||||
try {
|
||||
locateTransformers("META-INF/services/cpw.mods.modlauncher.api.ITransformationService");
|
||||
locateTransformers("META-INF/services/net.minecraftforge.forgespi.locating.IModLocator");
|
||||
} catch (IOException e) {
|
||||
LogManager.getLogger().error("Error during discovery of transform services from the classpath", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void locateTransformers(String resource) throws IOException {
|
||||
final Enumeration<URL> resources = ClassLoader.getSystemClassLoader().getResources(resource);
|
||||
while (resources.hasMoreElements()) {
|
||||
URL url = resources.nextElement();
|
||||
Path path = ClasspathLocatorUtils.findJarPathFor(resource, url.toString(), url);
|
||||
if (legacyClasspath.stream().anyMatch(path::equals) || !Files.exists(path) || Files.isDirectory(path))
|
||||
continue;
|
||||
found.add(new NamedPath(path.toUri().toString(), path));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ public class EarlyLoadingException extends RuntimeException {
|
||||
return errorMessages;
|
||||
}
|
||||
|
||||
EarlyLoadingException(final String message, final Throwable originalException, List<ExceptionData> errorMessages) {
|
||||
public EarlyLoadingException(final String message, final Throwable originalException, List<ExceptionData> errorMessages) {
|
||||
super(message, originalException);
|
||||
this.errorMessages = errorMessages;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ public class FMLConfig
|
||||
configSpec.define("maxThreads", -1);
|
||||
configSpec.define("versionCheck", Boolean.TRUE);
|
||||
configSpec.define("defaultConfigPath", "defaultconfigs");
|
||||
configSpec.define("disableOptimizedDFU", Boolean.TRUE);
|
||||
}
|
||||
|
||||
private CommentedFileConfig configData;
|
||||
@@ -88,4 +89,8 @@ public class FMLConfig
|
||||
public static String defaultConfigPath() {
|
||||
return INSTANCE.configData.<String>getOptional("defaultConfigPath").orElse("defaultconfigs");
|
||||
}
|
||||
|
||||
public static boolean isOptimizedDFUDisabled() {
|
||||
return INSTANCE.configData.<Boolean>getOptional("disableOptimizedDFU").orElse(Boolean.TRUE);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ package net.minecraftforge.fml.loading;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.Files;
|
||||
@@ -53,4 +54,16 @@ public class FileUtils
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean matchFileName(String path, String... matches) {
|
||||
// Extract file name from path
|
||||
String name = path.substring(Math.min(path.lastIndexOf(File.separatorChar) + 1, path.length()));
|
||||
// Check if it contains any of the desired keywords
|
||||
for (String match : matches) {
|
||||
if (name.contains(match)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -134,7 +134,7 @@ public class LanguageLoadingProvider
|
||||
}
|
||||
|
||||
public IModLanguageProvider findLanguage(ModFile mf, String modLoader, VersionRange modLoaderVersion) {
|
||||
final String languageFileName = mf.getLocator() instanceof ExplodedDirectoryLocator ? "in-development" : mf.getFileName();
|
||||
final String languageFileName = mf.getProvider() instanceof ExplodedDirectoryLocator ? "in-development" : mf.getFileName();
|
||||
final ModLanguageWrapper mlw = languageProviderMap.get(modLoader);
|
||||
if (mlw == null) {
|
||||
LOGGER.error(LOADING,"Missing language {} version {} wanted by {}", modLoader, modLoaderVersion, languageFileName);
|
||||
|
||||
@@ -10,11 +10,18 @@ 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.*;
|
||||
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;
|
||||
|
||||
/**
|
||||
@@ -28,7 +35,7 @@ public class LoadingModList
|
||||
private final List<ModInfo> sortedList;
|
||||
private final Map<String, ModFileInfo> fileById;
|
||||
private final List<EarlyLoadingException> preLoadErrors;
|
||||
private List<ModFile> brokenFiles;
|
||||
private List<IModFile> brokenFiles;
|
||||
|
||||
private LoadingModList(final List<ModFile> modFiles, final List<ModInfo> sortedList)
|
||||
{
|
||||
@@ -154,11 +161,11 @@ public class LoadingModList
|
||||
return preLoadErrors;
|
||||
}
|
||||
|
||||
public void setBrokenFiles(final List<ModFile> brokenFiles) {
|
||||
public void setBrokenFiles(final List<IModFile> brokenFiles) {
|
||||
this.brokenFiles = brokenFiles;
|
||||
}
|
||||
|
||||
public List<ModFile> getBrokenFiles() {
|
||||
public List<IModFile> getBrokenFiles() {
|
||||
return this.brokenFiles;
|
||||
}
|
||||
}
|
||||
+14
-11
@@ -6,21 +6,27 @@
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import cpw.mods.jarhandling.SecureJar;
|
||||
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
|
||||
import cpw.mods.modlauncher.api.NamedPath;
|
||||
import cpw.mods.modlauncher.serviceapi.ITransformerDiscoveryService;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.module.ModuleDescriptor;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipFile;
|
||||
import java.util.Set;
|
||||
|
||||
public class ModDirTransformerDiscoverer implements ITransformerDiscoveryService {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static final Set<String> SERVICES = Set.of(
|
||||
"cpw.mods.modlauncher.api.ITransformationService",
|
||||
"net.minecraftforge.forgespi.locating.IModLocator",
|
||||
"net.minecraftforge.forgespi.locating.IDependencyLocator"
|
||||
);
|
||||
|
||||
@Override
|
||||
public List<NamedPath> candidates(final Path gameDirectory) {
|
||||
@@ -51,14 +57,11 @@ public class ModDirTransformerDiscoverer implements ITransformerDiscoveryService
|
||||
if (!Files.isRegularFile(path)) return;
|
||||
if (!path.toString().endsWith(".jar")) return;
|
||||
if (LamdbaExceptionUtils.uncheck(() -> Files.size(path)) == 0) return;
|
||||
try (ZipFile zf = new ZipFile(new File(path.toUri()))) {
|
||||
if (zf.getEntry("META-INF/services/cpw.mods.modlauncher.api.ITransformationService") != null) {
|
||||
found.add(new NamedPath(zf.getName(), path));
|
||||
} else if (zf.getEntry("META-INF/services/net.minecraftforge.forgespi.locating.IModLocator") != null) {
|
||||
found.add(new NamedPath(zf.getName(), path));
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
LOGGER.error("Zip Error when loading jar file {}", path, ioe);
|
||||
}
|
||||
|
||||
SecureJar jar = SecureJar.from(path);
|
||||
jar.moduleDataProvider().descriptor().provides().stream()
|
||||
.map(ModuleDescriptor.Provides::service)
|
||||
.filter(SERVICES::contains)
|
||||
.forEach(s -> found.add(new NamedPath(s, path)));
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
|
||||
import net.minecraftforge.fml.loading.toposort.CyclePresentException;
|
||||
import net.minecraftforge.fml.loading.toposort.TopologicalSort;
|
||||
import net.minecraftforge.forgespi.locating.IModFile;
|
||||
import org.apache.maven.artifact.versioning.ArtifactVersion;
|
||||
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
|
||||
import org.slf4j.Logger;
|
||||
@@ -36,14 +35,15 @@ import static net.minecraftforge.fml.loading.LogMarkers.LOADING;
|
||||
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, ModInfo> modIdNameLookup;
|
||||
private Map<String, IModInfo> modIdNameLookup;
|
||||
private List<ModFile> systemMods;
|
||||
|
||||
private ModSorter(final List<ModFile> modFiles)
|
||||
{
|
||||
this.modFiles = modFiles;
|
||||
this.uniqueModListBuilder = new UniqueModListBuilder(modFiles);
|
||||
}
|
||||
|
||||
public static LoadingModList sort(List<ModFile> mods, final List<ExceptionData> errors)
|
||||
@@ -78,11 +78,12 @@ public class ModSorter
|
||||
// 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<IModFileInfo, Integer> infos = modFiles.stream()
|
||||
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(i -> graph.addNode((ModFileInfo) i));
|
||||
infos.keySet().forEach(graph::addNode);
|
||||
modFiles.stream()
|
||||
.map(ModFile::getModInfos)
|
||||
.<IModInfo>mapMulti(Iterable::forEach)
|
||||
@@ -124,10 +125,9 @@ public class ModSorter
|
||||
private void addDependency(MutableGraph<ModFileInfo> topoGraph, IModInfo.ModVersion dep)
|
||||
{
|
||||
final ModFileInfo self = (ModFileInfo)dep.getOwner().getOwningFile();
|
||||
final ModInfo targetModInfo = modIdNameLookup.get(dep.getModId());
|
||||
final IModInfo targetModInfo = modIdNameLookup.get(dep.getModId());
|
||||
// soft dep that doesn't exist. Just return. No edge required.
|
||||
if (targetModInfo == null) return;
|
||||
final ModFileInfo target = targetModInfo.getOwningFile();
|
||||
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()) {
|
||||
@@ -138,19 +138,32 @@ public class ModSorter
|
||||
|
||||
private void buildUniqueList()
|
||||
{
|
||||
// Collect mod files by module name. This will be used for deduping purposes
|
||||
final Map<String, List<IModFile>> modFilesByFirstId = modFiles.stream()
|
||||
.collect(groupingBy(mf -> mf.getModFileInfo().moduleName()));
|
||||
final UniqueModListBuilder.UniqueModListData uniqueModListData = uniqueModListBuilder.buildUniqueList();
|
||||
|
||||
this.modFiles = uniqueModListData.modFiles();
|
||||
|
||||
detectSystemMods(uniqueModListData.modFilesByFirstId());
|
||||
|
||||
modIdNameLookup = uniqueModListData.modFilesByFirstId().entrySet().stream()
|
||||
.filter(e -> !e.getValue().get(0).getModInfos().isEmpty())
|
||||
.collect(Collectors.toMap(
|
||||
Map.Entry::getKey,
|
||||
e -> e.getValue().get(0).getModInfos().get(0)
|
||||
));
|
||||
}
|
||||
|
||||
private void detectSystemMods(final Map<String, List<ModFile>> modFilesByFirstId)
|
||||
{
|
||||
// Capture system mods (ex. MC, Forge) here, so we can keep them for later
|
||||
final Set<String> systemMods = new HashSet<>();
|
||||
// The minecraft mod is always a system mod
|
||||
systemMods.add("minecraft");
|
||||
// Find mod file from MinecraftLocator to define the system mods
|
||||
modFiles.stream()
|
||||
.filter(modFile -> modFile.getLocator().getClass() == MinecraftLocator.class)
|
||||
.filter(modFile -> modFile.getProvider().getClass() == MinecraftLocator.class)
|
||||
.map(ModFile::getSecureJar)
|
||||
.map(SecureJar::getManifest)
|
||||
.map(SecureJar::moduleDataProvider)
|
||||
.map(SecureJar.ModuleDataProvider::getManifest)
|
||||
.map(Manifest::getMainAttributes)
|
||||
.map(mf -> mf.getValue("FML-System-Mods"))
|
||||
.filter(Objects::nonNull)
|
||||
@@ -168,47 +181,6 @@ public class ModSorter
|
||||
throw new IllegalStateException("Failed to find system mod: " + systemMod);
|
||||
}
|
||||
}
|
||||
|
||||
// Select the newest by artifact version sorting of non-unique files thus identified
|
||||
this.modFiles = modFilesByFirstId.entrySet().stream()
|
||||
.map(this::selectNewestModInfo)
|
||||
.map(Map.Entry::getValue)
|
||||
.map(ModFile.class::cast)
|
||||
.collect(toList());
|
||||
|
||||
// Transform to the full mod id list
|
||||
final Map<String, List<ModInfo>> modIds = modFiles.stream()
|
||||
.map(ModFile::getModInfos)
|
||||
.flatMap(Collection::stream)
|
||||
.map(ModInfo.class::cast)
|
||||
.collect(groupingBy(IModInfo::getModId));
|
||||
|
||||
// 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<ModInfo> dupedMods = modIds.values().stream()
|
||||
.filter(modInfos -> modInfos.size() > 1)
|
||||
.map(modInfos -> modInfos.get(0))
|
||||
.toList();
|
||||
|
||||
if (!dupedMods.isEmpty()) {
|
||||
final List<EarlyLoadingException.ExceptionData> duplicateModErrors = dupedMods.stream()
|
||||
.map(dm -> new EarlyLoadingException.ExceptionData("fml.modloading.dupedmod",
|
||||
dm, Objects.toString(dm))).toList();
|
||||
throw new EarlyLoadingException("Duplicate mods found", null, duplicateModErrors);
|
||||
}
|
||||
|
||||
modIdNameLookup = modIds.entrySet().stream()
|
||||
.collect(toMap(Map.Entry::getKey, e -> e.getValue().get(0)));
|
||||
}
|
||||
|
||||
private Map.Entry<String, IModFile> selectNewestModInfo(Map.Entry<String, List<IModFile>> fullList) {
|
||||
List<IModFile> 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.<IModFile, ArtifactVersion>comparing(mf -> mf.getModInfos().get(0).getVersion()).reversed());
|
||||
LOGGER.debug("Selected file {} for modid {} with version {}", modInfoList.get(0).getFileName(), fullList.getKey(), modInfoList.get(0).getModInfos().get(0).getVersion());
|
||||
}
|
||||
return Map.entry(fullList.getKey(), modInfoList.get(0));
|
||||
}
|
||||
|
||||
private List<EarlyLoadingException.ExceptionData> verifyDependencyVersions()
|
||||
@@ -238,10 +210,24 @@ public class ModSorter
|
||||
|
||||
if (!missingVersions.isEmpty()) {
|
||||
if (mandatoryMissing > 0) {
|
||||
LOGGER.error(LOADING, "Missing mandatory dependencies: {}", missingVersions.stream().filter(IModInfo.ModVersion::isMandatory).map(IModInfo.ModVersion::getModId).collect(Collectors.joining(", ")));
|
||||
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: {}", missingVersions.stream().filter(ver -> !ver.isMandatory()).map(IModInfo.ModVersion::getModId).collect(Collectors.joining(", ")));
|
||||
LOGGER.error(
|
||||
LOADING,
|
||||
"Unsupported installed optional dependencies:\n{}",
|
||||
missingVersions.stream()
|
||||
.filter(ver -> !ver.isMandatory())
|
||||
.map(ver -> formatDependencyError(ver, modVersions))
|
||||
.collect(Collectors.joining("\n"))
|
||||
);
|
||||
}
|
||||
|
||||
return missingVersions.stream()
|
||||
@@ -253,6 +239,18 @@ public class ModSorter
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private static String formatDependencyError(IModInfo.ModVersion dependency, Map<String, ArtifactVersion> modVersions)
|
||||
{
|
||||
ArtifactVersion installed = modVersions.get(dependency.getModId());
|
||||
return String.format(
|
||||
"\tMod ID: '%s', Requested by: '%s', Expected range: '%s', Actual version: '%s'",
|
||||
dependency.getModId(),
|
||||
dependency.getOwner().getModId(),
|
||||
dependency.getVersionRange(),
|
||||
installed != null ? installed.toString() : "[MISSING]"
|
||||
);
|
||||
}
|
||||
|
||||
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) &&
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import org.apache.maven.artifact.versioning.ArtifactVersion;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.stream.Collectors.*;
|
||||
|
||||
public class UniqueModListBuilder
|
||||
{
|
||||
private final static Logger LOGGER = LogUtils.getLogger();
|
||||
|
||||
private final List<ModFile> modFiles;
|
||||
|
||||
public UniqueModListBuilder(final List<ModFile> modFiles) {this.modFiles = modFiles;}
|
||||
|
||||
public UniqueModListData buildUniqueList()
|
||||
{
|
||||
List<ModFile> uniqueModList;
|
||||
List<ModFile> uniqueLibListWithVersion;
|
||||
|
||||
// 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)
|
||||
.collect(groupingBy(UniqueModListBuilder::getModId));
|
||||
|
||||
final Map<String, List<ModFile>> libFilesWithVersionByModuleName = modFiles.stream()
|
||||
.filter(mf -> mf.getModFileInfo() == null)
|
||||
.collect(groupingBy(UniqueModListBuilder::getModId));
|
||||
|
||||
// Select the newest by artifact version sorting of non-unique files thus identified
|
||||
uniqueModList = modFilesByFirstId.entrySet().stream()
|
||||
.map(this::selectNewestModInfo)
|
||||
.toList();
|
||||
|
||||
// Select the newest by artifact version sorting of non-unique files thus identified
|
||||
uniqueLibListWithVersion = libFilesWithVersionByModuleName.entrySet().stream()
|
||||
.map(this::selectNewestModInfo)
|
||||
.toList();
|
||||
|
||||
// Transform to the full mod id list
|
||||
final Map<String, List<IModInfo>> modIds = uniqueModList.stream()
|
||||
.filter(mf -> mf.getModFileInfo() != null) //Filter out non-mod files, we don't care about those for now.....
|
||||
.map(ModFile::getModInfos)
|
||||
.flatMap(Collection::stream)
|
||||
.collect(groupingBy(IModInfo::getModId));
|
||||
|
||||
// Transform to the full lib id list
|
||||
final Map<String, List<ModFile>> versionedLibIds = uniqueLibListWithVersion.stream()
|
||||
.map(UniqueModListBuilder::getModId)
|
||||
.collect(Collectors.toMap(
|
||||
Function.identity(),
|
||||
libFilesWithVersionByModuleName::get
|
||||
));
|
||||
|
||||
// Its theoretically possible that some mod has somehow moved an id to a secondary place, thus causing a dupe.
|
||||
// We can't handle this
|
||||
final List<IModInfo> dupedMods = modIds.values().stream()
|
||||
.filter(modInfos -> modInfos.size() > 1)
|
||||
.map(modInfos -> modInfos.get(0))
|
||||
.toList();
|
||||
|
||||
if (!dupedMods.isEmpty()) {
|
||||
final List<EarlyLoadingException.ExceptionData> duplicateModErrors = dupedMods.stream()
|
||||
.map(dm -> new EarlyLoadingException.ExceptionData("fml.modloading.dupedmod", dm, Objects.toString(dm)))
|
||||
.toList();
|
||||
throw new EarlyLoadingException("Duplicate mods found", null, duplicateModErrors);
|
||||
}
|
||||
|
||||
final List<ModFile> dupedLibs = versionedLibIds.values().stream()
|
||||
.filter(modFiles -> modFiles.size() > 1)
|
||||
.map(modFiles -> modFiles.get(0))
|
||||
.toList();
|
||||
|
||||
if (!dupedLibs.isEmpty()) {
|
||||
final List<EarlyLoadingException.ExceptionData> duplicateLibErrors = dupedLibs.stream()
|
||||
.map(dm -> new EarlyLoadingException.ExceptionData("fml.modloading.dupedlib.versioned", dm, Objects.toString(dm)))
|
||||
.toList();
|
||||
throw new EarlyLoadingException("Duplicate plugins or libraries found", null, duplicateLibErrors);
|
||||
}
|
||||
|
||||
// Collect unique mod files by module name. This will be used for deduping purposes
|
||||
final Map<String, List<ModFile>> uniqueModFilesByFirstId = uniqueModList.stream()
|
||||
.collect(groupingBy(UniqueModListBuilder::getModId));
|
||||
|
||||
final List<ModFile> loadedList = new ArrayList<>();
|
||||
loadedList.addAll(uniqueModList);
|
||||
loadedList.addAll(uniqueLibListWithVersion);
|
||||
|
||||
return new UniqueModListData(loadedList, uniqueModFilesByFirstId);
|
||||
}
|
||||
|
||||
private 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)));
|
||||
}
|
||||
return modInfoList.get(0);
|
||||
}
|
||||
|
||||
private ArtifactVersion getVersion(final ModFile mf)
|
||||
{
|
||||
if (mf.getModFileInfo() == null || mf.getModInfos() == null || mf.getModInfos().isEmpty()) {
|
||||
return mf.getJarVersion();
|
||||
}
|
||||
|
||||
return mf.getModInfos().get(0).getVersion();
|
||||
}
|
||||
|
||||
private static String getModId(ModFile modFile) {
|
||||
if (modFile.getModFileInfo() == null || modFile.getModFileInfo().getMods().isEmpty()) {
|
||||
return modFile.getSecureJar().name();
|
||||
}
|
||||
|
||||
return modFile.getModFileInfo().moduleName();
|
||||
}
|
||||
|
||||
public record UniqueModListData(List<ModFile> modFiles, Map<String, List<ModFile>> modFilesByFirstId) {}
|
||||
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
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;
|
||||
@@ -18,16 +19,12 @@ public class VersionSupportMatrix {
|
||||
private static final HashMap<String, List<ArtifactVersion>> overrideVersions = new HashMap<>();
|
||||
static {
|
||||
final ArtifactVersion version = new DefaultArtifactVersion(FMLLoader.versionInfo().mcVersion());
|
||||
// if (MavenVersionAdapter.createFromVersionSpec("[1.16.4,1.16.5]").containsVersion(version)) {
|
||||
// // 1.16.4 is Compatible with 1.16.3
|
||||
// add("languageloader.javafml", "34");
|
||||
// add("mod.minecraft", "1.16.3");
|
||||
// add("mod.forge", "34.1.42");
|
||||
// // 1.16.5 is Compatible with 1.16.4, and thus 1.16.3
|
||||
// add("languageloader.javafml", "35");
|
||||
// add("mod.minecraft", "1.16.4");
|
||||
// add("mod.forge", "35.1.37");
|
||||
// }
|
||||
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");
|
||||
}
|
||||
}
|
||||
private static void add(String key, String value) {
|
||||
overrideVersions.computeIfAbsent(key, k -> new ArrayList<>()).add(new DefaultArtifactVersion(value));
|
||||
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraftforge.forgespi.locating.IDependencyLocator;
|
||||
import net.minecraftforge.forgespi.locating.IModFile;
|
||||
import net.minecraftforge.forgespi.locating.ModFileLoadingException;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
public abstract class AbstractJarFileDependencyLocator extends AbstractJarFileModProvider implements IDependencyLocator
|
||||
{
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
|
||||
protected Optional<InputStream> loadResourceFromModFile(final IModFile modFile, final Path path) {
|
||||
try {
|
||||
return Optional.of(Files.newInputStream(modFile.findResource(path.toString())));
|
||||
}
|
||||
catch (final FileNotFoundException e) {
|
||||
LOGGER.debug("Failed to load resource {} from {}, it does not contain dependency information.", path, modFile.getFileName());
|
||||
return Optional.empty();
|
||||
}
|
||||
catch (final Exception e) {
|
||||
LOGGER.error("Failed to load resource {} from mod {}, cause {}", path, modFile.getFileName(), e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
protected Optional<IModFile> loadModFileFrom(final IModFile file, final Path path) {
|
||||
try {
|
||||
final Path pathInModFile = file.findResource(path.toString());
|
||||
return Optional.of(createMod(pathInModFile).file());
|
||||
}
|
||||
catch (Exception e) {
|
||||
LOGGER.error("Failed to load mod file {} from {}", path, file.getFileName());
|
||||
throw new ModFileLoadingException("Failed to load mod file "+file.getFileName());
|
||||
}
|
||||
}
|
||||
|
||||
protected String identifyMod(final IModFile modFile) {
|
||||
return modFile.getFileName();
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import net.minecraftforge.forgespi.locating.IModLocator;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public abstract class AbstractJarFileModLocator extends AbstractJarFileModProvider implements IModLocator
|
||||
{
|
||||
@Override
|
||||
public List<IModLocator.ModFileOrException> scanMods()
|
||||
{
|
||||
return scanCandidates().map(this::createMod).toList();
|
||||
}
|
||||
|
||||
public abstract Stream<Path> scanCandidates();
|
||||
}
|
||||
+9
-21
@@ -7,20 +7,19 @@ package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import cpw.mods.jarhandling.SecureJar;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import net.minecraftforge.fml.loading.LogMarkers;
|
||||
import net.minecraftforge.forgespi.locating.IModFile;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
public abstract class AbstractJarFileLocator extends AbstractModLocator {
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public abstract class AbstractJarFileModProvider extends AbstractModProvider
|
||||
{
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
|
||||
@Override
|
||||
@@ -34,15 +33,4 @@ public abstract class AbstractJarFileLocator extends AbstractModLocator {
|
||||
}
|
||||
LOGGER.debug(LogMarkers.SCAN,"Scan finished: {}", file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IModFile> scanMods() {
|
||||
return scanCandidates()
|
||||
.map(this::createMod)
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public abstract Stream<Path> scanCandidates();
|
||||
}
|
||||
-96
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
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;
|
||||
import net.minecraftforge.forgespi.language.IConfigurable;
|
||||
import net.minecraftforge.forgespi.language.IModFileInfo;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import net.minecraftforge.forgespi.locating.IModFile;
|
||||
import net.minecraftforge.forgespi.locating.IModLocator;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
public abstract class AbstractModLocator implements IModLocator {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
protected static final String MODS_TOML = "META-INF/mods.toml";
|
||||
protected static final String MANIFEST = "META-INF/MANIFEST.MF";
|
||||
|
||||
protected Optional<IModFile> createMod(Path... path) {
|
||||
var mjm = new ModJarMetadata();
|
||||
var sj = SecureJar.from(
|
||||
Manifest::new,
|
||||
jar -> jar.findFile(MODS_TOML).isPresent() ? mjm : JarMetadata.from(jar, path),
|
||||
(root, p) -> true,
|
||||
path
|
||||
);
|
||||
|
||||
IModFile mod;
|
||||
var type = sj.getManifest().getMainAttributes().getValue(ModFile.TYPE);
|
||||
if (sj.findFile(MODS_TOML).isPresent()) {
|
||||
LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", MODS_TOML, type, path);
|
||||
mod = new ModFile(sj, this, ModFileParser::modsTomlParser);
|
||||
} else if (type != null) {
|
||||
LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", MANIFEST, type, path);
|
||||
mod = new ModFile(sj, this, this::manifestParser);
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
mjm.setModFile(mod);
|
||||
return Optional.of(mod);
|
||||
}
|
||||
|
||||
protected IModFileInfo manifestParser(final IModFile mod) {
|
||||
Function<String, Optional<String>> cfg = name -> Optional.ofNullable(mod.getSecureJar().getManifest().getMainAttributes().getValue(name));
|
||||
var license = cfg.apply("LICENSE").orElse("");
|
||||
var dummy = new IConfigurable() {
|
||||
@Override
|
||||
public <T> Optional<T> getConfigElement(String... key) {
|
||||
return Optional.empty();
|
||||
}
|
||||
@Override
|
||||
public List<? extends IConfigurable> getConfigList(String... key) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
};
|
||||
|
||||
return new IModFileInfo() {
|
||||
@Override public List<IModInfo> getMods() { return Collections.emptyList(); }
|
||||
@Override public List<LanguageSpec> requiredLanguageLoaders() { return Collections.emptyList(); }
|
||||
@Override public boolean showAsResourcePack() { return false; }
|
||||
@Override public Map<String, Object> getFileProperties() { return Collections.emptyMap(); }
|
||||
@Override public String getLicense() { return license; }
|
||||
@Override public IModFile getFile() { return mod; }
|
||||
@Override public IConfigurable getConfig() { return dummy; }
|
||||
|
||||
// These Should never be called as it's only called from ModJarMetadata.version and we bypass that
|
||||
@Override public String moduleName() { return mod.getSecureJar().name(); }
|
||||
@Override public String versionString() { return null; }
|
||||
@Override public List<String> usesServices() { return null; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "IModFileInfo(" + mod.getFilePath() + ")";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(final IModFile modFile) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
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;
|
||||
import net.minecraftforge.forgespi.language.IConfigurable;
|
||||
import net.minecraftforge.forgespi.language.IModFileInfo;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import net.minecraftforge.forgespi.locating.IModFile;
|
||||
import net.minecraftforge.forgespi.locating.IModLocator;
|
||||
import net.minecraftforge.forgespi.locating.IModProvider;
|
||||
import net.minecraftforge.forgespi.locating.ModFileLoadingException;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
public abstract class AbstractModProvider implements IModProvider
|
||||
{
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
protected static final String MODS_TOML = "META-INF/mods.toml";
|
||||
protected static final String MANIFEST = "META-INF/MANIFEST.MF";
|
||||
|
||||
protected IModLocator.ModFileOrException createMod(Path... path) {
|
||||
var mjm = new ModJarMetadata();
|
||||
var sj = SecureJar.from(
|
||||
Manifest::new,
|
||||
jar -> jar.moduleDataProvider().findFile(MODS_TOML).isPresent() ? mjm : JarMetadata.from(jar, path),
|
||||
(root, p) -> true,
|
||||
path
|
||||
);
|
||||
|
||||
IModFile mod;
|
||||
var type = sj.moduleDataProvider().getManifest().getMainAttributes().getValue(ModFile.TYPE);
|
||||
if (type == null) {
|
||||
type = getDefaultJarModType();
|
||||
}
|
||||
if (sj.moduleDataProvider().findFile(MODS_TOML).isPresent()) {
|
||||
LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", MODS_TOML, type, path);
|
||||
mod = new ModFile(sj, this, ModFileParser::modsTomlParser);
|
||||
} else if (type != null) {
|
||||
LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", MANIFEST, type, path);
|
||||
mod = new ModFile(sj, this, this::manifestParser, type);
|
||||
} else {
|
||||
return new IModLocator.ModFileOrException(null, new ModFileLoadingException("Invalid mod file found "+ Arrays.toString(path)));
|
||||
}
|
||||
|
||||
mjm.setModFile(mod);
|
||||
return new IModLocator.ModFileOrException(mod, null);
|
||||
}
|
||||
|
||||
protected IModFileInfo manifestParser(final IModFile mod) {
|
||||
Function<String, Optional<String>> cfg = name -> Optional.ofNullable(mod.getSecureJar().moduleDataProvider().getManifest().getMainAttributes().getValue(name));
|
||||
var license = cfg.apply("LICENSE").orElse("");
|
||||
var dummy = new IConfigurable() {
|
||||
@Override
|
||||
public <T> Optional<T> getConfigElement(String... key) {
|
||||
return Optional.empty();
|
||||
}
|
||||
@Override
|
||||
public List<? extends IConfigurable> getConfigList(String... key) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
};
|
||||
|
||||
return new DefaultModFileInfo(mod, license, dummy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(final IModFile modFile) {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected String getDefaultJarModType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
private record DefaultModFileInfo(IModFile mod, String license, IConfigurable configurable) implements IModFileInfo, IConfigurable {
|
||||
@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(); }
|
||||
@Override public List<LanguageSpec> requiredLanguageLoaders() { return Collections.emptyList(); }
|
||||
@Override public boolean showAsResourcePack() { return false; }
|
||||
@Override public Map<String, Object> getFileProperties() { return Collections.emptyMap(); }
|
||||
@Override
|
||||
public String getLicense() { return license; }
|
||||
|
||||
@Override public IModFile getFile() { return mod; }
|
||||
@Override public IConfigurable getConfig() { return configurable; }
|
||||
|
||||
// These Should never be called as it's only called from ModJarMetadata.version and we bypass that
|
||||
@Override public String moduleName() { return mod.getSecureJar().name(); }
|
||||
@Override public String versionString() { return null; }
|
||||
@Override public List<String> usesServices() { return null; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "IModFileInfo(" + mod.getFilePath() + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-2
@@ -5,6 +5,7 @@
|
||||
|
||||
package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import net.minecraftforge.fml.loading.ClasspathLocatorUtils;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraftforge.fml.loading.LogMarkers;
|
||||
import org.slf4j.Logger;
|
||||
@@ -24,7 +25,8 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class ClasspathLocator extends AbstractJarFileLocator {
|
||||
public class ClasspathLocator extends AbstractJarFileModLocator
|
||||
{
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private final List<Path> legacyClasspath = Arrays.stream(System.getProperty("legacyClassPath", "").split(File.pathSeparator)).map(Path::of).toList();
|
||||
private boolean enabled = false;
|
||||
@@ -58,7 +60,7 @@ public class ClasspathLocator extends AbstractJarFileLocator {
|
||||
final Enumeration<URL> resources = ClassLoader.getSystemClassLoader().getResources(resource);
|
||||
while (resources.hasMoreElements()) {
|
||||
URL url = resources.nextElement();
|
||||
Path path = findJarPathFor(resource, resource, url);
|
||||
Path path = ClasspathLocatorUtils.findJarPathFor(resource, resource, url);
|
||||
if (claimed.stream().anyMatch(path::equals) || !Files.exists(path) || Files.isDirectory(path))
|
||||
continue;
|
||||
ret.add(path);
|
||||
|
||||
+3
-3
@@ -30,14 +30,14 @@ public class ExplodedDirectoryLocator implements IModLocator {
|
||||
private final Map<ExplodedMod, IModFile> mods = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public List<IModFile> scanMods() {
|
||||
public List<IModLocator.ModFileOrException> scanMods() {
|
||||
explodedMods.forEach(explodedMod ->
|
||||
ModJarMetadata.buildFile(this,
|
||||
jar->jar.findFile("/META-INF/mods.toml").isPresent(),
|
||||
jar->jar.moduleDataProvider().findFile("/META-INF/mods.toml").isPresent(),
|
||||
(a,b) -> true,
|
||||
explodedMod.paths().toArray(Path[]::new))
|
||||
.ifPresentOrElse(f->mods.put(explodedMod, f), () -> LOGGER.warn(LogMarkers.LOADING, "Failed to find exploded resource mods.toml in directory {}", explodedMod.paths().get(0).toString())));
|
||||
return List.copyOf(mods.values());
|
||||
return mods.values().stream().map(mf->new IModLocator.ModFileOrException(mf, null)).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+8
-2
@@ -6,16 +6,22 @@
|
||||
package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import net.minecraftforge.forgespi.language.IModFileInfo;
|
||||
import net.minecraftforge.forgespi.locating.ModFileLoadingException;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
|
||||
public class InvalidModFileException extends RuntimeException
|
||||
public class InvalidModFileException extends ModFileLoadingException
|
||||
{
|
||||
private final IModFileInfo modFileInfo;
|
||||
|
||||
public InvalidModFileException(String message, IModFileInfo modFileInfo)
|
||||
{
|
||||
super(String.format(Locale.ENGLISH, "%s (%s)", message, ((ModFileInfo)modFileInfo).getFile().getFileName()));
|
||||
super(String.format(Locale.ROOT, "%s (%s)", message, Optional.ofNullable(modFileInfo).map(mf->mf.getFile().getFileName()).orElse("MISSING FILE NAME")));
|
||||
this.modFileInfo = modFileInfo;
|
||||
}
|
||||
|
||||
public IModFileInfo getBrokenFile() {
|
||||
return modFileInfo;
|
||||
}
|
||||
}
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraftforge.fml.loading.EarlyLoadingException;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import net.minecraftforge.forgespi.locating.IModFile;
|
||||
import net.minecraftforge.forgespi.locating.ModFileLoadingException;
|
||||
import net.minecraftforge.jarjar.selection.JarSelector;
|
||||
import org.apache.maven.artifact.versioning.ArtifactVersion;
|
||||
import org.apache.maven.artifact.versioning.VersionRange;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class JarInJarDependencyLocator extends AbstractJarFileDependencyLocator
|
||||
{
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
|
||||
@Override
|
||||
public String name()
|
||||
{
|
||||
return "JarInJar";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IModFile> scanMods(final Iterable<IModFile> loadedMods)
|
||||
{
|
||||
final List<IModFile> sources = Lists.newArrayList();
|
||||
loadedMods.forEach(sources::add);
|
||||
|
||||
final List<IModFile> dependenciesToLoad = JarSelector.detectAndSelect(sources, this::loadResourceFromModFile, this::loadModFileFrom, this::identifyMod, this::exception);
|
||||
|
||||
if (dependenciesToLoad.isEmpty())
|
||||
{
|
||||
LOGGER.info("No dependencies to load found. Skipping!");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
LOGGER.info("Found {} dependencies adding them to mods collection", dependenciesToLoad.size());
|
||||
return dependenciesToLoad;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initArguments(final Map<String, ?> arguments)
|
||||
{
|
||||
// NO-OP, for now
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDefaultJarModType()
|
||||
{
|
||||
return IModFile.Type.GAMELIBRARY.name();
|
||||
}
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
@Override
|
||||
protected Optional<IModFile> loadModFileFrom(final IModFile file, final 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("/");
|
||||
return Optional.of(createMod(pathInFS).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(final 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(final JarSelector.ResolutionFailureInformation<IModFile> entry)
|
||||
{
|
||||
return entry.failureReason() == JarSelector.FailureReason.VERSION_RESOLUTION_FAILED ?
|
||||
"fml.dependencyloading.conflictingdependencies" :
|
||||
"fml.dependencyloading.mismatchedcontaineddependencies";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Stream<ModWithVersionRange> getModWithVersionRangeStream(final 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()));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String formatError(final ModWithVersionRange modWithVersionRange)
|
||||
{
|
||||
return "\u00a7e" + modWithVersionRange.modInfo().getModId() + "\u00a7r - \u00a74" + modWithVersionRange.versionRange().toString() + "\u00a74 - \u00a72" + modWithVersionRange.artifactVersion().toString() + "\u00a72";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String identifyMod(final IModFile modFile)
|
||||
{
|
||||
if (modFile.getModFileInfo() == null || modFile.getModInfos().isEmpty())
|
||||
{
|
||||
return modFile.getFileName();
|
||||
}
|
||||
|
||||
return modFile.getModInfos().stream().map(IModInfo::getModId).collect(Collectors.joining());
|
||||
}
|
||||
|
||||
private record ModWithVersionRange(IModInfo modInfo, VersionRange versionRange, ArtifactVersion artifactVersion)
|
||||
{}
|
||||
}
|
||||
+2
-1
@@ -15,7 +15,8 @@ import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class MavenDirectoryLocator extends AbstractJarFileLocator {
|
||||
public class MavenDirectoryLocator extends AbstractJarFileModLocator
|
||||
{
|
||||
private List<Path> modCoords;
|
||||
|
||||
@Override
|
||||
|
||||
+13
-9
@@ -12,21 +12,26 @@ import net.minecraftforge.fml.loading.FMLLoader;
|
||||
import net.minecraftforge.fml.loading.LogMarkers;
|
||||
import net.minecraftforge.forgespi.language.IModFileInfo;
|
||||
import net.minecraftforge.forgespi.locating.IModFile;
|
||||
import net.minecraftforge.forgespi.locating.IModLocator;
|
||||
import net.minecraftforge.forgespi.locating.ModFileFactory;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class MinecraftLocator extends AbstractModLocator {
|
||||
public class MinecraftLocator extends AbstractModProvider implements IModLocator
|
||||
{
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
|
||||
@Override
|
||||
public List<IModFile> scanMods() {
|
||||
public List<IModLocator.ModFileOrException> scanMods() {
|
||||
final var launchHandler = FMLLoader.getLaunchHandler();
|
||||
var baseMC = launchHandler.getMinecraftPaths();
|
||||
var mcjar = ModJarMetadata.buildFile(j->ModFileFactory.FACTORY.build(j, this, this::buildMinecraftTOML), j->true, baseMC.minecraftFilter(), baseMC.minecraftPaths().toArray(Path[]::new)).orElseThrow();
|
||||
@@ -35,12 +40,11 @@ public class MinecraftLocator extends AbstractModLocator {
|
||||
.map(sj -> new ModFile(sj, this, ModFileParser::modsTomlParser))
|
||||
.collect(Collectors.<IModFile>toList());
|
||||
var othermods = baseMC.otherModPaths().stream()
|
||||
.map(p -> createMod(p.toArray(Path[]::new)).orElse(null))
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
.map(p -> createMod(p.toArray(Path[]::new)))
|
||||
.filter(Objects::nonNull);
|
||||
artifacts.add(mcjar);
|
||||
artifacts.addAll(othermods);
|
||||
return artifacts;
|
||||
|
||||
return Stream.concat(artifacts.stream().map(f -> new ModFileOrException(f, null)), othermods).toList();
|
||||
}
|
||||
|
||||
private IModFileInfo buildMinecraftTOML(final IModFile iModFile) {
|
||||
|
||||
+132
-27
@@ -5,13 +5,19 @@
|
||||
|
||||
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;
|
||||
import cpw.mods.modlauncher.util.ServiceLoaderUtils;
|
||||
import net.minecraftforge.fml.loading.EarlyLoadingException;
|
||||
import net.minecraftforge.fml.loading.LogMarkers;
|
||||
import net.minecraftforge.fml.loading.UniqueModListBuilder;
|
||||
import net.minecraftforge.fml.loading.progress.StartupMessageManager;
|
||||
import net.minecraftforge.forgespi.Environment;
|
||||
import net.minecraftforge.forgespi.language.IModFileInfo;
|
||||
import net.minecraftforge.forgespi.locating.IDependencyLocator;
|
||||
import net.minecraftforge.forgespi.locating.IModFile;
|
||||
import net.minecraftforge.forgespi.locating.IModLocator;
|
||||
import org.slf4j.Logger;
|
||||
@@ -19,52 +25,151 @@ import org.slf4j.Logger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ModDiscoverer {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private final ServiceLoader<IModLocator> locators;
|
||||
private final List<IModLocator> locatorList;
|
||||
private final ServiceLoader<IModLocator> modLocators;
|
||||
private final ServiceLoader<IDependencyLocator> dependencyLocators;
|
||||
private final List<IModLocator> modLocatorList;
|
||||
private final List<IDependencyLocator> dependencyLocatorList;
|
||||
|
||||
public ModDiscoverer(Map<String, ?> arguments) {
|
||||
Launcher.INSTANCE.environment().computePropertyIfAbsent(Environment.Keys.MODDIRECTORYFACTORY.get(), v->ModsFolderLocator::new);
|
||||
Launcher.INSTANCE.environment().computePropertyIfAbsent(Environment.Keys.PROGRESSMESSAGE.get(), v-> StartupMessageManager.locatorConsumer().orElseGet(()-> s->{}));
|
||||
final var moduleLayerManager = Launcher.INSTANCE.environment().findModuleLayerManager().orElseThrow();
|
||||
locators = ServiceLoader.load(moduleLayerManager.getLayer(IModuleLayerManager.Layer.SERVICE).orElseThrow(), IModLocator.class);
|
||||
locatorList = ServiceLoaderUtils.streamServiceLoader(()->locators, sce->LOGGER.error("Failed to load locator list", sce)).collect(Collectors.toList());
|
||||
locatorList.forEach(l->l.initArguments(arguments));
|
||||
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))
|
||||
{
|
||||
LOGGER.debug(LogMarkers.CORE, "Found Mod Locators : {}", locatorList.stream()
|
||||
.map(iModLocator -> "(" + iModLocator.name() + ":" + iModLocator.getClass().getPackage().getImplementationVersion() + ")").collect(Collectors.joining(",")));
|
||||
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(",")));
|
||||
}
|
||||
}
|
||||
|
||||
ModDiscoverer(List<IModLocator> locatorList) {
|
||||
this.locatorList = locatorList;
|
||||
this.locators = null;
|
||||
}
|
||||
|
||||
public ModValidator discoverMods() {
|
||||
LOGGER.debug(LogMarkers.SCAN,"Scanning for mods and other resources to load. We know {} ways to find mods", locatorList.size());
|
||||
var loadedFiles = new ArrayList<>();
|
||||
for (IModLocator locator : locatorList) {
|
||||
LOGGER.debug(LogMarkers.SCAN,"Trying locator {}", locator);
|
||||
var modFiles = locator.scanMods();
|
||||
for (IModFile mf : modFiles) {
|
||||
LOGGER.info(LogMarkers.SCAN, "Found mod file {} of type {} with locator {}", mf.getFileName(), mf.getType(), mf.getLocator());
|
||||
StartupMessageManager.modLoaderConsumer().ifPresent(c->c.accept("Found mod file "+mf.getFileName()+" of type "+mf.getType()));
|
||||
}
|
||||
loadedFiles.addAll(modFiles);
|
||||
}
|
||||
final var modFilesMap = loadedFiles.stream()
|
||||
.map(ModFile.class::cast)
|
||||
.collect(Collectors.groupingBy(IModFile::getType));
|
||||
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<>();
|
||||
boolean successfullyLoadedMods = true;
|
||||
List<IModFileInfo> brokenFiles = new ArrayList<>();
|
||||
|
||||
var validator = new ModValidator(modFilesMap);
|
||||
//Loop all mod locators to get the prime mods to load from.
|
||||
for (IModLocator locator : modLocatorList) {
|
||||
try {
|
||||
LOGGER.debug(LogMarkers.SCAN, "Trying locator {}", locator);
|
||||
var candidates = locator.scanMods();
|
||||
LOGGER.debug(LogMarkers.SCAN, "Locator {} found {} candidates or errors", locator, candidates.size());
|
||||
var exceptions = candidates.stream().map(IModLocator.ModFileOrException::ex).filter(Objects::nonNull).toList();
|
||||
if (!exceptions.isEmpty()) {
|
||||
LOGGER.debug(LogMarkers.SCAN, "Locator {} found {} invalid mod files", locator, exceptions.size());
|
||||
brokenFiles.addAll(exceptions.stream().map(e->e instanceof InvalidModFileException ime ? ime.getBrokenFile() : null).filter(Objects::nonNull).toList());
|
||||
}
|
||||
var locatedFiles = candidates.stream().map(IModLocator.ModFileOrException::file).filter(Objects::nonNull).collect(Collectors.toList());
|
||||
|
||||
var badModFiles = locatedFiles.stream().filter(file -> !(file instanceof ModFile)).toList();
|
||||
if (!badModFiles.isEmpty()) {
|
||||
LOGGER.error(LogMarkers.SCAN, "Locator {} returned {} files which is are not ModFile instances! They will be skipped!", locator, badModFiles.size());
|
||||
brokenFiles.addAll(badModFiles.stream().map(IModFile::getModFileInfo).toList());
|
||||
}
|
||||
locatedFiles.removeAll(badModFiles);
|
||||
LOGGER.debug(LogMarkers.SCAN, "Locator {} found {} valid mod files", locator, locatedFiles.size());
|
||||
handleLocatedFiles(loadedFiles, locatedFiles);
|
||||
} catch (InvalidModFileException imfe) {
|
||||
// We don't generally expect this exception, since it should come from the candidates stream above and be handled in the Locator, but just in case.
|
||||
LOGGER.error(LogMarkers.SCAN, "Locator {} found an invalid mod file {}", locator, imfe.getBrokenFile(), imfe);
|
||||
brokenFiles.add(imfe.getBrokenFile());
|
||||
} catch (EarlyLoadingException exception) {
|
||||
LOGGER.error(LogMarkers.SCAN, "Failed to load mods with locator {}", locator, exception);
|
||||
discoveryErrorData.addAll(exception.getAllData());
|
||||
}
|
||||
}
|
||||
|
||||
//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();
|
||||
try {
|
||||
final UniqueModListBuilder modsUniqueListBuilder = new UniqueModListBuilder(loadedFiles);
|
||||
final UniqueModListBuilder.UniqueModListData uniqueModsData = modsUniqueListBuilder.buildUniqueList();
|
||||
|
||||
//Grab the temporary results.
|
||||
//This allows loading to continue to a base state, in case dependency loading fails.
|
||||
modFilesMap = uniqueModsData.modFiles().stream()
|
||||
.collect(Collectors.groupingBy(IModFile::getType));
|
||||
loadedFiles = uniqueModsData.modFiles();
|
||||
}
|
||||
catch (EarlyLoadingException exception) {
|
||||
LOGGER.error(LogMarkers.SCAN, "Failed to build unique mod list after mod discovery.", exception);
|
||||
discoveryErrorData.addAll(exception.getAllData());
|
||||
successfullyLoadedMods = false;
|
||||
}
|
||||
|
||||
//We can continue loading if prime mods loaded successfully.
|
||||
if (successfullyLoadedMods) {
|
||||
LOGGER.debug(LogMarkers.SCAN, "Successfully Loaded {} mods. Attempting to load dependencies...", loadedFiles.size());
|
||||
for (IDependencyLocator locator : dependencyLocatorList) {
|
||||
try {
|
||||
LOGGER.debug(LogMarkers.SCAN,"Trying locator {}", locator);
|
||||
final List<IModFile> locatedMods = ImmutableList.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);
|
||||
}
|
||||
catch (EarlyLoadingException exception) {
|
||||
LOGGER.error(LogMarkers.SCAN, "Failed to load dependencies with locator {}", locator, exception);
|
||||
discoveryErrorData.addAll(exception.getAllData());
|
||||
}
|
||||
}
|
||||
|
||||
//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();
|
||||
|
||||
//We now only need the mod files map, not the list.
|
||||
modFilesMap = uniqueModsAndDependenciesData.modFiles().stream()
|
||||
.collect(Collectors.groupingBy(IModFile::getType));
|
||||
} 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));
|
||||
}
|
||||
}
|
||||
else {
|
||||
//Failure notify the listeners.
|
||||
LOGGER.error(LogMarkers.SCAN, "Mod Discovery failed. Skipping dependency discovery.");
|
||||
}
|
||||
|
||||
//Validate the loading. With a deduplicated list, we can now successfully process the artifacts and load
|
||||
//transformer plugins.
|
||||
var validator = new ModValidator(modFilesMap, brokenFiles, discoveryErrorData);
|
||||
validator.stage1Validation();
|
||||
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());
|
||||
StartupMessageManager.modLoaderConsumer().ifPresent(c->c.accept("Found mod file "+mf.getFileName()+" of type "+mf.getType()));
|
||||
}
|
||||
loadedFiles.addAll(locatedModFiles);
|
||||
}
|
||||
}
|
||||
@@ -16,8 +16,10 @@ import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import net.minecraftforge.forgespi.language.IModLanguageProvider;
|
||||
import net.minecraftforge.forgespi.language.ModFileScanData;
|
||||
import net.minecraftforge.forgespi.locating.IModFile;
|
||||
import net.minecraftforge.forgespi.locating.IModLocator;
|
||||
import net.minecraftforge.forgespi.locating.IModProvider;
|
||||
import net.minecraftforge.forgespi.locating.ModFileFactory;
|
||||
import org.apache.maven.artifact.versioning.ArtifactVersion;
|
||||
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.nio.file.Files;
|
||||
@@ -51,9 +53,9 @@ public class ModFile implements IModFile {
|
||||
private Throwable scanError;
|
||||
private final SecureJar jar;
|
||||
private final Type modFileType;
|
||||
private final Manifest manifest;
|
||||
private final IModLocator locator;
|
||||
private IModFileInfo modFileInfo;
|
||||
private final Manifest manifest;
|
||||
private final IModProvider provider;
|
||||
private IModFileInfo modFileInfo;
|
||||
private ModFileScanData fileModFileScanData;
|
||||
private CompletableFuture<ModFileScanData> futureScanResult;
|
||||
private List<CoreModFile> coreMods;
|
||||
@@ -62,15 +64,19 @@ public class ModFile implements IModFile {
|
||||
static final Attributes.Name TYPE = new Attributes.Name("FMLModType");
|
||||
private SecureJar.Status securityStatus;
|
||||
|
||||
public ModFile(final SecureJar jar, final IModLocator locator, final ModFileFactory.ModFileInfoParser parser) {
|
||||
this.locator = locator;
|
||||
public ModFile(final SecureJar jar, final IModProvider provider, final ModFileFactory.ModFileInfoParser parser) {
|
||||
this(jar, provider, parser, parseType(jar));
|
||||
}
|
||||
|
||||
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.getManifest();
|
||||
final Optional<String> value = Optional.ofNullable(manifest.getMainAttributes().getValue(TYPE));
|
||||
modFileType = Type.valueOf(value.orElse("MOD"));
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -123,7 +129,7 @@ public class ModFile implements IModFile {
|
||||
}
|
||||
|
||||
public void scanFile(Consumer<Path> pathConsumer) {
|
||||
locator.scanFile(this, pathConsumer);
|
||||
provider.scanFile(this, pathConsumer);
|
||||
}
|
||||
|
||||
public void setFutureScanResult(CompletableFuture<ModFileScanData> future) {
|
||||
@@ -188,8 +194,8 @@ public class ModFile implements IModFile {
|
||||
}
|
||||
|
||||
@Override
|
||||
public IModLocator getLocator() {
|
||||
return locator;
|
||||
public IModProvider getProvider() {
|
||||
return provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -201,4 +207,15 @@ public class ModFile implements IModFile {
|
||||
public void setSecurityStatus(final SecureJar.Status status) {
|
||||
this.securityStatus = status;
|
||||
}
|
||||
|
||||
public ArtifactVersion getJarVersion()
|
||||
{
|
||||
return new DefaultArtifactVersion(this.jarVersion);
|
||||
}
|
||||
|
||||
private static String parseType(final SecureJar jar) {
|
||||
final Manifest m = jar.moduleDataProvider().getManifest();
|
||||
final Optional<String> value = Optional.ofNullable(m.getMainAttributes().getValue(TYPE));
|
||||
return value.orElse("MOD");
|
||||
}
|
||||
}
|
||||
@@ -11,12 +11,11 @@ import net.minecraftforge.fml.loading.StringUtils;
|
||||
import net.minecraftforge.forgespi.language.IConfigurable;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import net.minecraftforge.forgespi.language.MavenVersionAdapter;
|
||||
import net.minecraftforge.forgespi.locating.ForgeFeature;
|
||||
import org.apache.maven.artifact.versioning.ArtifactVersion;
|
||||
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
|
||||
import org.apache.maven.artifact.versioning.VersionRange;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.Marker;
|
||||
import org.slf4j.MarkerFactory;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
@@ -32,6 +31,7 @@ public class ModInfo implements IModInfo, IConfigurable
|
||||
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;
|
||||
@@ -43,8 +43,11 @@ public class ModInfo implements IModInfo, IConfigurable
|
||||
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)
|
||||
{
|
||||
@@ -83,9 +86,23 @@ public class ModInfo implements IModInfo, IConfigurable
|
||||
.collect(Collectors.toList()))
|
||||
.orElse(Collections.emptyList());
|
||||
|
||||
this.features = ownFile.map(mfi -> mfi.<Map<String, String>>getConfigElement("features", this.modId)
|
||||
.stream()
|
||||
.flatMap(m->m.entrySet().stream())
|
||||
.map(e->new ForgeFeature.Bound(e.getKey(), e.getValue(), this))
|
||||
.collect(Collectors.toList())).orElse(Collections.emptyList());
|
||||
|
||||
this.properties = ownFile.map(mfi -> mfi.<Map<String, Object>>getConfigElement("modproperties", this.modId)
|
||||
.orElse(Collections.emptyMap()))
|
||||
.orElse(Collections.emptyMap());
|
||||
|
||||
this.modUrl = config.<String>getConfigElement("modUrl")
|
||||
.map(StringUtils::toURL);
|
||||
|
||||
// verify we have a valid mod version otherwise throw an exception
|
||||
if (!VALID_VERSION.matcher(this.version.toString()).matches()) {
|
||||
throw new InvalidModFileException("Illegal version number specified "+this.version, this.getOwningFile());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -152,6 +169,11 @@ public class ModInfo implements IModInfo, IConfigurable
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends ForgeFeature.Bound> getForgeFeatures() {
|
||||
return this.features;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> getConfigElement(final String... key) {
|
||||
return this.config.getConfigElement(key);
|
||||
@@ -162,6 +184,11 @@ public class ModInfo implements IModInfo, IConfigurable
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<URL> getModURL() {
|
||||
return modUrl;
|
||||
}
|
||||
|
||||
class ModVersion implements net.minecraftforge.forgespi.language.IModInfo.ModVersion {
|
||||
private IModInfo owner;
|
||||
private final String modId;
|
||||
@@ -169,6 +196,7 @@ public class ModInfo implements IModInfo, IConfigurable
|
||||
private final boolean mandatory;
|
||||
private final Ordering ordering;
|
||||
private final DependencySide side;
|
||||
private Optional<URL> referralUrl;
|
||||
|
||||
public ModVersion(final IModInfo owner, final IConfigurable config) {
|
||||
this.owner = owner;
|
||||
@@ -185,6 +213,8 @@ public class ModInfo implements IModInfo, IConfigurable
|
||||
this.side = config.<String>getConfigElement("side")
|
||||
.map(DependencySide::valueOf)
|
||||
.orElse(DependencySide.BOTH);
|
||||
this.referralUrl = config.<String>getConfigElement("referralUrl")
|
||||
.map(StringUtils::toURL);
|
||||
}
|
||||
|
||||
|
||||
@@ -229,6 +259,11 @@ public class ModInfo implements IModInfo, IConfigurable
|
||||
{
|
||||
return owner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<URL> getReferralURL() {
|
||||
return referralUrl;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+23
-11
@@ -13,11 +13,16 @@ import net.minecraftforge.fml.loading.LoadingModList;
|
||||
import net.minecraftforge.fml.loading.LogMarkers;
|
||||
import net.minecraftforge.fml.loading.ModSorter;
|
||||
import net.minecraftforge.fml.loading.progress.StartupMessageManager;
|
||||
import net.minecraftforge.forgespi.language.IModFileInfo;
|
||||
import net.minecraftforge.forgespi.locating.IModFile;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ModValidator {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
@@ -25,14 +30,17 @@ public class ModValidator {
|
||||
private final List<ModFile> candidatePlugins;
|
||||
private final List<ModFile> candidateMods;
|
||||
private LoadingModList loadingModList;
|
||||
private List<ModFile> brokenFiles;
|
||||
private List<IModFile> brokenFiles;
|
||||
private final List<EarlyLoadingException.ExceptionData> discoveryErrorData;
|
||||
|
||||
public ModValidator(final Map<IModFile.Type, List<ModFile>> modFiles) {
|
||||
public ModValidator(final Map<IModFile.Type, List<ModFile>> modFiles, final List<IModFileInfo> brokenFiles, final List<EarlyLoadingException.ExceptionData> discoveryErrorData) {
|
||||
this.modFiles = modFiles;
|
||||
this.candidateMods = lst(modFiles.get(IModFile.Type.MOD));
|
||||
this.candidateMods.addAll(lst(modFiles.get(IModFile.Type.GAMELIBRARY)));
|
||||
this.candidatePlugins = lst(modFiles.get(IModFile.Type.LANGPROVIDER));
|
||||
this.candidatePlugins.addAll(lst(modFiles.get(IModFile.Type.LIBRARY)));
|
||||
this.discoveryErrorData = discoveryErrorData;
|
||||
this.brokenFiles = brokenFiles.stream().map(IModFileInfo::getFile).collect(Collectors.toList()); // mutable list
|
||||
}
|
||||
|
||||
private static List<ModFile> lst(List<ModFile> files) {
|
||||
@@ -40,7 +48,7 @@ public class ModValidator {
|
||||
}
|
||||
|
||||
public void stage1Validation() {
|
||||
brokenFiles = validateFiles(candidateMods);
|
||||
brokenFiles.addAll(validateFiles(candidateMods));
|
||||
if (LOGGER.isDebugEnabled(LogMarkers.SCAN)) {
|
||||
LOGGER.debug(LogMarkers.SCAN, "Found {} mod files with {} mods", candidateMods.size(), candidateMods.stream().mapToInt(mf -> mf.getModInfos().size()).sum());
|
||||
}
|
||||
@@ -52,22 +60,22 @@ public class ModValidator {
|
||||
final List<ModFile> brokenFiles = new ArrayList<>();
|
||||
for (Iterator<ModFile> iterator = mods.iterator(); iterator.hasNext(); )
|
||||
{
|
||||
ModFile mod = iterator.next();
|
||||
if (!mod.getLocator().isValid(mod) || !mod.identifyMods()) {
|
||||
LOGGER.warn(LogMarkers.SCAN, "File {} has been ignored - it is invalid", mod.getFilePath());
|
||||
ModFile modFile = iterator.next();
|
||||
if (!modFile.getProvider().isValid(modFile) || !modFile.identifyMods()) {
|
||||
LOGGER.warn(LogMarkers.SCAN, "File {} has been ignored - it is invalid", modFile.getFilePath());
|
||||
iterator.remove();
|
||||
brokenFiles.add(mod);
|
||||
brokenFiles.add(modFile);
|
||||
}
|
||||
}
|
||||
return brokenFiles;
|
||||
}
|
||||
|
||||
public ITransformationService.Resource getPluginResources() {
|
||||
return new ITransformationService.Resource(IModuleLayerManager.Layer.PLUGIN, this.candidatePlugins.stream().map(ModFile::getSecureJar).toList());
|
||||
return new ITransformationService.Resource(IModuleLayerManager.Layer.PLUGIN, this.candidatePlugins.stream().map(IModFile::getSecureJar).toList());
|
||||
}
|
||||
|
||||
public ITransformationService.Resource getModResources() {
|
||||
return new ITransformationService.Resource(IModuleLayerManager.Layer.GAME, this.candidateMods.stream().map(ModFile::getSecureJar).toList());
|
||||
return new ITransformationService.Resource(IModuleLayerManager.Layer.GAME, this.candidateMods.stream().map(IModFile::getSecureJar).toList());
|
||||
}
|
||||
|
||||
private List<EarlyLoadingException.ExceptionData> validateLanguages() {
|
||||
@@ -86,7 +94,11 @@ public class ModValidator {
|
||||
|
||||
public BackgroundScanHandler stage2Validation() {
|
||||
var errors = validateLanguages();
|
||||
loadingModList = ModSorter.sort(candidateMods, errors);
|
||||
|
||||
var allErrors = new ArrayList<>(errors);
|
||||
allErrors.addAll(this.discoveryErrorData);
|
||||
|
||||
loadingModList = ModSorter.sort(candidateMods, allErrors);
|
||||
loadingModList.addCoreMods();
|
||||
loadingModList.addAccessTransformers();
|
||||
loadingModList.setBrokenFiles(brokenFiles);
|
||||
|
||||
+2
-1
@@ -23,7 +23,8 @@ import static cpw.mods.modlauncher.api.LamdbaExceptionUtils.uncheck;
|
||||
/**
|
||||
* Support loading mods located in JAR files in the mods folder
|
||||
*/
|
||||
public class ModsFolderLocator extends AbstractJarFileLocator {
|
||||
public class ModsFolderLocator extends AbstractJarFileModLocator
|
||||
{
|
||||
private static final String SUFFIX = ".jar";
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private final Path modFolder;
|
||||
|
||||
+2
-2
@@ -5,6 +5,7 @@
|
||||
|
||||
package net.minecraftforge.fml.loading.targets;
|
||||
|
||||
import cpw.mods.modlauncher.api.ServiceRunner;
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
import net.minecraftforge.fml.loading.LibraryFinder;
|
||||
import net.minecraftforge.fml.loading.VersionInfo;
|
||||
@@ -21,10 +22,9 @@ public abstract class CommonClientLaunchHandler extends CommonLaunchHandler {
|
||||
@Override public boolean isProduction() { return true; }
|
||||
|
||||
@Override
|
||||
public Callable<Void> launchService(String[] arguments, ModuleLayer layer) {
|
||||
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
|
||||
return () -> {
|
||||
Class.forName(layer.findModule("minecraft").orElseThrow(),"net.minecraft.client.main.Main").getMethod("main", String[].class).invoke(null, (Object)arguments);
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+11
-20
@@ -5,8 +5,8 @@
|
||||
|
||||
package net.minecraftforge.fml.loading.targets;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import cpw.mods.jarhandling.SecureJar;
|
||||
import net.minecraftforge.fml.loading.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
@@ -30,8 +30,7 @@ public abstract class CommonDevLaunchHandler extends CommonLaunchHandler {
|
||||
|
||||
// The extra jar is on the classpath, so try and pull it out of the legacy classpath
|
||||
var legacyCP = Objects.requireNonNull(System.getProperty("legacyClassPath"), "Missing legacyClassPath, cannot find client-extra").split(File.pathSeparator);
|
||||
var extra = Paths.get(Arrays.stream(legacyCP).filter(e -> e.contains("client-extra")).findFirst().orElseThrow(() -> new IllegalStateException("Could not find client-extra in legacy classpath")));
|
||||
mcstream.add(extra);
|
||||
var extra = findJarOnClasspath(legacyCP, "client-extra");
|
||||
|
||||
// The MC code/Patcher edits are in exploded directories
|
||||
final var modstream = Stream.<List<Path>>builder();
|
||||
@@ -42,6 +41,7 @@ public abstract class CommonDevLaunchHandler extends CommonLaunchHandler {
|
||||
minecraft.stream().distinct().forEach(mcstream::add);
|
||||
mods.values().forEach(modstream::add);
|
||||
|
||||
mcstream.add(extra);
|
||||
var mcFilter = getMcFilter(extra, minecraft, modstream);
|
||||
return new LocatedPaths(mcstream.build().toList(), mcFilter, modstream.build().toList(), getFmlStuff(legacyCP));
|
||||
}
|
||||
@@ -50,8 +50,6 @@ public abstract class CommonDevLaunchHandler extends CommonLaunchHandler {
|
||||
if (getDist().isDedicatedServer())
|
||||
return arguments;
|
||||
|
||||
fixNatives();
|
||||
|
||||
if (isData())
|
||||
return arguments;
|
||||
|
||||
@@ -80,11 +78,18 @@ public abstract class CommonDevLaunchHandler extends CommonLaunchHandler {
|
||||
protected List<Path> getFmlStuff(String[] classpath) {
|
||||
// We also want the FML things, fmlcore, javafmllanguage, mclanguage, I don't like hard coding these, but hey whatever works for now.
|
||||
return Arrays.stream(classpath)
|
||||
.filter(e -> e.contains("fmlcore") || e.contains("javafmllanguage") || e.contains("lowcodelanguage") || e.contains("mclanguage"))
|
||||
.filter(e -> FileUtils.matchFileName(e, "fmlcore", "javafmllanguage", "lowcodelanguage", "mclanguage"))
|
||||
.map(Paths::get)
|
||||
.toList();
|
||||
}
|
||||
|
||||
protected static Path findJarOnClasspath(String[] classpath, String match) {
|
||||
return Paths.get(Arrays.stream(classpath)
|
||||
.filter(e -> FileUtils.matchFileName(e, match))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalStateException("Could not find " + match + " in classpath")));
|
||||
}
|
||||
|
||||
protected BiPredicate<String, String> getMcFilter(Path extra, List<Path> minecraft, Stream.Builder<List<Path>> mods) {
|
||||
final var packages = getPackages();
|
||||
final var extraPath = extra.toString().replace('\\', '/');
|
||||
@@ -119,18 +124,4 @@ public abstract class CommonDevLaunchHandler extends CommonLaunchHandler {
|
||||
// Generate a time-based random number, to mimic how n.m.client.Main works
|
||||
return Long.toString(System.nanoTime() % (int) Math.pow(10, length));
|
||||
}
|
||||
|
||||
private static void fixNatives() {
|
||||
String paths = System.getProperty("java.library.path");
|
||||
String nativesDir = System.getProperty("nativesDirectory");
|
||||
if (nativesDir == null)
|
||||
return;
|
||||
|
||||
if (Strings.isNullOrEmpty(paths))
|
||||
paths = nativesDir;
|
||||
else
|
||||
paths += File.pathSeparator + nativesDir;
|
||||
|
||||
System.setProperty("java.library.path", paths);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -6,6 +6,7 @@
|
||||
package net.minecraftforge.fml.loading.targets;
|
||||
|
||||
import cpw.mods.jarhandling.SecureJar;
|
||||
import cpw.mods.modlauncher.api.ServiceRunner;
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
import net.minecraftforge.fml.loading.LibraryFinder;
|
||||
import net.minecraftforge.fml.loading.VersionInfo;
|
||||
@@ -23,10 +24,9 @@ public abstract class CommonServerLaunchHandler extends CommonLaunchHandler {
|
||||
@Override public boolean isProduction() { return true; }
|
||||
|
||||
@Override
|
||||
public Callable<Void> launchService(String[] arguments, ModuleLayer layer) {
|
||||
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
|
||||
return () -> {
|
||||
Class.forName(layer.findModule("minecraft").orElseThrow(),"net.minecraft.server.Main").getMethod("main", String[].class).invoke(null, (Object)arguments);
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
-6
@@ -10,8 +10,6 @@ import net.minecraftforge.fml.loading.VersionInfo;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -41,8 +39,4 @@ public abstract class CommonUserdevLaunchHandler extends CommonDevLaunchHandler
|
||||
}
|
||||
|
||||
protected abstract void processStreams(String[] classpath, VersionInfo versionInfo, Stream.Builder<Path> mc, Stream.Builder<List<Path>> mods);
|
||||
|
||||
protected static Path findJarOnClasspath(String[] classpath, String match) {
|
||||
return Paths.get(Arrays.stream(classpath).filter(e -> e.contains(match)).findFirst().orElseThrow(() -> new IllegalStateException("Could not find " + match + " in classpath")));
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -5,6 +5,7 @@
|
||||
|
||||
package net.minecraftforge.fml.loading.targets;
|
||||
|
||||
import cpw.mods.modlauncher.api.ServiceRunner;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
@@ -13,12 +14,11 @@ public class FMLClientDevLaunchHandler extends CommonDevLaunchHandler {
|
||||
@Override public Dist getDist() { return Dist.CLIENT; }
|
||||
|
||||
@Override
|
||||
public Callable<Void> launchService(String[] arguments, ModuleLayer layer) {
|
||||
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
|
||||
return () -> {
|
||||
var args = preLaunch(arguments, layer);
|
||||
|
||||
Class.forName(layer.findModule("minecraft").orElseThrow(), "net.minecraft.client.main.Main").getMethod("main", String[].class).invoke(null, (Object) args);
|
||||
return null;
|
||||
};
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -5,6 +5,7 @@
|
||||
|
||||
package net.minecraftforge.fml.loading.targets;
|
||||
|
||||
import cpw.mods.modlauncher.api.ServiceRunner;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
@@ -17,12 +18,11 @@ public class FMLClientUserdevLaunchHandler extends FMLUserdevLaunchHandler {
|
||||
public Dist getDist() { return Dist.CLIENT; }
|
||||
|
||||
@Override
|
||||
public Callable<Void> launchService(String[] arguments, ModuleLayer layer) {
|
||||
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
|
||||
return () -> {
|
||||
var args = preLaunch(arguments, layer);
|
||||
|
||||
Class.forName(layer.findModule("minecraft").orElseThrow(), "net.minecraft.client.main.Main").getMethod("main", String[].class).invoke(null, (Object) args);
|
||||
return null;
|
||||
};
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -5,6 +5,7 @@
|
||||
|
||||
package net.minecraftforge.fml.loading.targets;
|
||||
|
||||
import cpw.mods.modlauncher.api.ServiceRunner;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
@@ -20,12 +21,11 @@ public class FMLDataUserdevLaunchHandler extends FMLUserdevLaunchHandler {
|
||||
public boolean isData() { return true; }
|
||||
|
||||
@Override
|
||||
public Callable<Void> launchService(String[] arguments, ModuleLayer layer) {
|
||||
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
|
||||
return () -> {
|
||||
var args = preLaunch(arguments, layer);
|
||||
|
||||
Class.forName(layer.findModule("minecraft").orElseThrow(), "net.minecraft.data.Main").getMethod("main", String[].class).invoke(null, (Object) args);
|
||||
return null;
|
||||
};
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -5,6 +5,7 @@
|
||||
|
||||
package net.minecraftforge.fml.loading.targets;
|
||||
|
||||
import cpw.mods.modlauncher.api.ServiceRunner;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
@@ -13,12 +14,11 @@ public class FMLServerDevLaunchHandler extends CommonDevLaunchHandler {
|
||||
@Override public Dist getDist() { return Dist.DEDICATED_SERVER; }
|
||||
|
||||
@Override
|
||||
public Callable<Void> launchService(String[] arguments, ModuleLayer layer) {
|
||||
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
|
||||
return () -> {
|
||||
var args = preLaunch(arguments, layer);
|
||||
|
||||
Class.forName(layer.findModule("minecraft").orElseThrow(),"net.minecraft.server.Main").getMethod("main", String[].class).invoke(null, (Object)args);
|
||||
return null;
|
||||
};
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -5,6 +5,7 @@
|
||||
|
||||
package net.minecraftforge.fml.loading.targets;
|
||||
|
||||
import cpw.mods.modlauncher.api.ServiceRunner;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
@@ -14,12 +15,11 @@ public class FMLServerUserdevLaunchHandler extends FMLUserdevLaunchHandler {
|
||||
@Override public Dist getDist() { return Dist.DEDICATED_SERVER; }
|
||||
|
||||
@Override
|
||||
public Callable<Void> launchService(String[] arguments, ModuleLayer layer) {
|
||||
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
|
||||
return () -> {
|
||||
var args = preLaunch(arguments, layer);
|
||||
|
||||
Class.forName(layer.findModule("minecraft").orElseThrow(),"net.minecraft.server.Main").getMethod("main", String[].class).invoke(null, (Object)args);
|
||||
return null;
|
||||
};
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -5,6 +5,7 @@
|
||||
|
||||
package net.minecraftforge.fml.loading.targets;
|
||||
|
||||
import cpw.mods.modlauncher.api.ServiceRunner;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
@@ -13,12 +14,11 @@ public class ForgeClientDevLaunchHandler extends CommonDevLaunchHandler {
|
||||
@Override public Dist getDist() { return Dist.CLIENT; }
|
||||
|
||||
@Override
|
||||
public Callable<Void> launchService(String[] arguments, ModuleLayer layer) {
|
||||
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
|
||||
return () -> {
|
||||
var args = preLaunch(arguments, layer);
|
||||
|
||||
Class.forName(layer.findModule("minecraft").orElseThrow(), "net.minecraft.client.main.Main").getMethod("main", String[].class).invoke(null, (Object) args);
|
||||
return null;
|
||||
};
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -5,6 +5,7 @@
|
||||
|
||||
package net.minecraftforge.fml.loading.targets;
|
||||
|
||||
import cpw.mods.modlauncher.api.ServiceRunner;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
@@ -17,12 +18,11 @@ public class ForgeClientUserdevLaunchHandler extends ForgeUserdevLaunchHandler {
|
||||
public Dist getDist() { return Dist.CLIENT; }
|
||||
|
||||
@Override
|
||||
public Callable<Void> launchService(String[] arguments, ModuleLayer layer) {
|
||||
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
|
||||
return () -> {
|
||||
var args = preLaunch(arguments, layer);
|
||||
|
||||
Class.forName(layer.findModule("minecraft").orElseThrow(), "net.minecraft.client.main.Main").getMethod("main", String[].class).invoke(null, (Object) args);
|
||||
return null;
|
||||
};
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -5,6 +5,7 @@
|
||||
|
||||
package net.minecraftforge.fml.loading.targets;
|
||||
|
||||
import cpw.mods.modlauncher.api.ServiceRunner;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
@@ -15,12 +16,11 @@ public class ForgeDataDevLaunchHandler extends CommonDevLaunchHandler {
|
||||
@Override public boolean isData() { return true; }
|
||||
|
||||
@Override
|
||||
public Callable<Void> launchService(String[] arguments, ModuleLayer layer) {
|
||||
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
|
||||
return () -> {
|
||||
var args = preLaunch(arguments, layer);
|
||||
|
||||
Class.forName(layer.findModule("minecraft").orElseThrow(), "net.minecraft.data.Main").getMethod("main", String[].class).invoke(null, (Object) args);
|
||||
return null;
|
||||
};
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -5,6 +5,7 @@
|
||||
|
||||
package net.minecraftforge.fml.loading.targets;
|
||||
|
||||
import cpw.mods.modlauncher.api.ServiceRunner;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
@@ -20,12 +21,11 @@ public class ForgeDataUserdevLaunchHandler extends ForgeUserdevLaunchHandler {
|
||||
public boolean isData() { return true; }
|
||||
|
||||
@Override
|
||||
public Callable<Void> launchService(String[] arguments, ModuleLayer layer) {
|
||||
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
|
||||
return () -> {
|
||||
var args = preLaunch(arguments, layer);
|
||||
|
||||
Class.forName(layer.findModule("minecraft").orElseThrow(), "net.minecraft.data.Main").getMethod("main", String[].class).invoke(null, (Object) args);
|
||||
return null;
|
||||
};
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -5,6 +5,7 @@
|
||||
|
||||
package net.minecraftforge.fml.loading.targets;
|
||||
|
||||
import cpw.mods.modlauncher.api.ServiceRunner;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
@@ -14,12 +15,11 @@ public class ForgeGametestDevLaunchHandler extends CommonDevLaunchHandler {
|
||||
@Override public Dist getDist() { return Dist.DEDICATED_SERVER; }
|
||||
|
||||
@Override
|
||||
public Callable<Void> launchService(String[] arguments, ModuleLayer layer) {
|
||||
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
|
||||
return () -> {
|
||||
var args = preLaunch(arguments, layer);
|
||||
|
||||
Class.forName(layer.findModule("forge").orElseThrow(), "net.minecraftforge.gametest.GameTestMain").getMethod("main", String[].class).invoke(null, (Object)args);
|
||||
return null;
|
||||
};
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -5,6 +5,7 @@
|
||||
|
||||
package net.minecraftforge.fml.loading.targets;
|
||||
|
||||
import cpw.mods.modlauncher.api.ServiceRunner;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
@@ -14,12 +15,11 @@ public class ForgeGametestUserdevLaunchHandler extends ForgeUserdevLaunchHandler
|
||||
@Override public Dist getDist() { return Dist.DEDICATED_SERVER; }
|
||||
|
||||
@Override
|
||||
public Callable<Void> launchService(String[] arguments, ModuleLayer layer) {
|
||||
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
|
||||
return () -> {
|
||||
var args = preLaunch(arguments, layer);
|
||||
|
||||
Class.forName(layer.findModule("forge").orElseThrow(), "net.minecraftforge.gametest.GameTestMain").getMethod("main", String[].class).invoke(null, (Object)args);
|
||||
return null;
|
||||
};
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -5,6 +5,7 @@
|
||||
|
||||
package net.minecraftforge.fml.loading.targets;
|
||||
|
||||
import cpw.mods.modlauncher.api.ServiceRunner;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
@@ -13,12 +14,11 @@ public class ForgeServerDevLaunchHandler extends CommonDevLaunchHandler {
|
||||
@Override public Dist getDist() { return Dist.DEDICATED_SERVER; }
|
||||
|
||||
@Override
|
||||
public Callable<Void> launchService(String[] arguments, ModuleLayer layer) {
|
||||
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
|
||||
return () -> {
|
||||
var args = preLaunch(arguments, layer);
|
||||
|
||||
Class.forName(layer.findModule("minecraft").orElseThrow(),"net.minecraft.server.Main").getMethod("main", String[].class).invoke(null, (Object)args);
|
||||
return null;
|
||||
};
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -5,6 +5,7 @@
|
||||
|
||||
package net.minecraftforge.fml.loading.targets;
|
||||
|
||||
import cpw.mods.modlauncher.api.ServiceRunner;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
@@ -14,12 +15,11 @@ public class ForgeServerUserdevLaunchHandler extends ForgeUserdevLaunchHandler {
|
||||
@Override public Dist getDist() { return Dist.DEDICATED_SERVER; }
|
||||
|
||||
@Override
|
||||
public Callable<Void> launchService(String[] arguments, ModuleLayer layer) {
|
||||
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
|
||||
return () -> {
|
||||
var args = preLaunch(arguments, layer);
|
||||
|
||||
Class.forName(layer.findModule("minecraft").orElseThrow(),"net.minecraft.server.Main").getMethod("main", String[].class).invoke(null, (Object)args);
|
||||
return null;
|
||||
};
|
||||
}
|
||||
}
|
||||
+1
@@ -19,6 +19,7 @@ public final class CyclePresentException extends IllegalArgumentException {
|
||||
* @param cycles the cycles present
|
||||
*/
|
||||
CyclePresentException(Set<Set<?>> cycles) {
|
||||
super("Cycles present in graph: " + cycles);
|
||||
this.cycles = cycles;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,3 +4,5 @@ splashscreen = true
|
||||
maxThreads = -1
|
||||
# Enable forge global version checking
|
||||
versionCheck = true
|
||||
# Disables Optimized DFU client-side.
|
||||
disableOptimizedDFU = true
|
||||
+2
-1
@@ -1 +1,2 @@
|
||||
net.minecraftforge.fml.loading.ModDirTransformerDiscoverer
|
||||
net.minecraftforge.fml.loading.ModDirTransformerDiscoverer
|
||||
net.minecraftforge.fml.loading.ClasspathTransformerDiscoverer
|
||||
+1
@@ -0,0 +1 @@
|
||||
net.minecraftforge.fml.loading.moddiscovery.JarInJarDependencyLocator
|
||||
@@ -13,6 +13,7 @@
|
||||
<MarkerFilter marker="REGISTRIES" onMatch="${sys:forge.logging.marker.registries:-DENY}" onMismatch="NEUTRAL"/>
|
||||
<MarkerFilter marker="REGISTRYDUMP" onMatch="${sys:forge.logging.marker.registrydump:-DENY}" onMismatch="NEUTRAL"/>
|
||||
<MarkerFilter marker="SPLASH" onMatch="${sys:forge.logging.marker.splash:-DENY}" onMismatch="NEUTRAL"/>
|
||||
<MarkerFilter marker="RESOURCE-CACHE" onMatch="${sys:forge.logging.marker.resource.cache:-DENY}" onMismatch="NEUTRAL"/>
|
||||
<MarkerFilter marker="FORGEMOD" onMatch="${sys:forge.logging.marker.forgemod:-ACCEPT}" onMismatch="NEUTRAL"/>
|
||||
<MarkerFilter marker="LOADING" onMatch="${sys:forge.logging.marker.loading:-ACCEPT}" onMismatch="NEUTRAL"/>
|
||||
<MarkerFilter marker="CORE" onMatch="${sys:forge.logging.marker.core:-ACCEPT}" onMismatch="NEUTRAL"/>
|
||||
|
||||
@@ -42,6 +42,6 @@ public class FMLOnlyBindings implements IBindingsProvider {
|
||||
|
||||
@Override
|
||||
public Supplier<IConfigEvent.ConfigConfig> getConfigConfiguration() {
|
||||
return ()->new IConfigEvent.ConfigConfig(ModConfigEvent.Loading::new, ModConfigEvent.Reloading::new);
|
||||
return ()->new IConfigEvent.ConfigConfig(ModConfigEvent.Loading::new, ModConfigEvent.Reloading::new, ModConfigEvent.Unloading::new);
|
||||
}
|
||||
}
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
@@ -205,6 +205,12 @@ set -- \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
|
||||
Vendored
+8
-6
@@ -14,7 +14,7 @@
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@@ -25,7 +25,7 @@
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
@@ -75,13 +75,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ public class FMLModContainer extends ModContainer
|
||||
LOGGER.debug(LOADING,"Creating FMLModContainer instance for {}", className);
|
||||
this.scanResults = modFileScanResults;
|
||||
activityMap.put(ModLoadingStage.CONSTRUCT, this::constructMod);
|
||||
this.eventBus = BusBuilder.builder().setExceptionHandler(this::onEventFailed).setTrackPhases(false).markerType(IModBusEvent.class).build();
|
||||
this.eventBus = BusBuilder.builder().setExceptionHandler(this::onEventFailed).setTrackPhases(false).markerType(IModBusEvent.class).useModLauncher().build();
|
||||
this.configHandler = Optional.of(ce->this.eventBus.post(ce.self()));
|
||||
final FMLJavaModLoadingContext contextExtension = new FMLJavaModLoadingContext(this);
|
||||
this.contextExtension = () -> contextExtension;
|
||||
|
||||
@@ -12,7 +12,8 @@ dependencyUpdates.rejectVersionIf { isNonStable(it.candidate.version) }
|
||||
java.withSourcesJar()
|
||||
|
||||
dependencies {
|
||||
compileOnly('org.jetbrains:annotations:21.0.1')
|
||||
compileOnly("org.jetbrains:annotations:${JETBRAINS_ANNOTATIONS_VERSION}")
|
||||
|
||||
implementation(project(':fmlloader'))
|
||||
implementation(project(':fmlcore'))
|
||||
}
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ public class LowCodeModLanguageProvider implements IModLanguageProvider
|
||||
throw new ModLoadingException(info, ModLoadingStage.CONSTRUCT, "fml.modloading.failedtoloadmodclass", e);
|
||||
}
|
||||
} catch (NoSuchMethodException | ClassNotFoundException | InstantiationException | IllegalAccessException e) {
|
||||
LOGGER.fatal(LOADING, "Unable to load FMLModContainer, wut?", e);
|
||||
LOGGER.fatal(LOADING, "Unable to load LowCodeModContainer, wut?", e);
|
||||
final Class<RuntimeException> mle = (Class<RuntimeException>) LamdbaExceptionUtils.uncheck(() -> Class.forName("net.minecraftforge.fml.ModLoadingException", true, Thread.currentThread().getContextClassLoader()));
|
||||
final Class<ModLoadingStage> mls = (Class<ModLoadingStage>) LamdbaExceptionUtils.uncheck(() -> Class.forName("net.minecraftforge.fml.ModLoadingStage", true, Thread.currentThread().getContextClassLoader()));
|
||||
throw LamdbaExceptionUtils.uncheck(() -> LamdbaExceptionUtils.uncheck(() -> mle.getConstructor(IModInfo.class, mls, String.class, Throwable.class)).newInstance(info, Enum.valueOf(mls, "CONSTRUCT"), "fml.modloading.failedtoloadmodclass", e));
|
||||
|
||||
+2
-2
@@ -40,7 +40,7 @@ https://github.com/MinecraftForge/MCPConfig/blob/master/Mojang.md
|
||||
|
||||
Additional Resources:
|
||||
=========================
|
||||
Community Documentation: http://mcforge.readthedocs.io/en/latest/gettingstarted/
|
||||
Community Documentation: https://mcforge.readthedocs.io/en/latest/gettingstarted/
|
||||
LexManos' Install Video: https://www.youtube.com/watch?v=8VEdtQLuLO0
|
||||
Forge Forum: https://forums.minecraftforge.net/
|
||||
Forge Discord: https://discord.gg/UvedJ9m
|
||||
Forge Discord: https://discord.gg/UvedJ9m
|
||||
+2
-14
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'eclipse'
|
||||
id 'maven-publish'
|
||||
id 'net.minecraftforge.gradle' version '5.+'
|
||||
id 'net.minecraftforge.gradle' version '5.1.+'
|
||||
}
|
||||
|
||||
version = '1.0'
|
||||
@@ -22,7 +22,7 @@ minecraft {
|
||||
// See more information here: https://github.com/MinecraftForge/MCPConfig/blob/master/Mojang.md
|
||||
//
|
||||
// Parchment is an unofficial project maintained by ParchmentMC, separate from MinecraftForge
|
||||
// Additional setup is needed to use their mappings: https://github.com/ParchmentMC/Parchment/wiki/Getting-Started
|
||||
// Additional setup is needed to use their mappings: https://parchmentmc.org/docs/getting-started
|
||||
//
|
||||
// Use non-default mappings at your own risk. They may not always work.
|
||||
// Simply re-run your setup task after changing the mappings to update your workspace.
|
||||
@@ -65,7 +65,6 @@ minecraft {
|
||||
|
||||
property 'forge.logging.console.level', 'debug'
|
||||
|
||||
// Comma-separated list of namespaces to load gametests from. Empty = all namespaces.
|
||||
property 'forge.enabledGameTestNamespaces', 'examplemod'
|
||||
|
||||
mods {
|
||||
@@ -81,19 +80,10 @@ minecraft {
|
||||
gameTestServer {
|
||||
workingDirectory project.file('run')
|
||||
|
||||
// Recommended logging data for a userdev environment
|
||||
// The markers can be added/remove as needed separated by commas.
|
||||
// "SCAN": For mods scan.
|
||||
// "REGISTRIES": For firing of registry events.
|
||||
// "REGISTRYDUMP": For getting the contents of all registries.
|
||||
property 'forge.logging.markers', 'REGISTRIES'
|
||||
|
||||
// Recommended logging level for the console
|
||||
// You can set various levels here.
|
||||
// Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels
|
||||
property 'forge.logging.console.level', 'debug'
|
||||
|
||||
// Comma-separated list of namespaces to load gametests from. Empty = all namespaces.
|
||||
property 'forge.enabledGameTestNamespaces', 'examplemod'
|
||||
|
||||
mods {
|
||||
@@ -142,8 +132,6 @@ dependencies {
|
||||
minecraft '@FORGE_GROUP@:@FORGE_NAME@:@FORGE_VERSION@'
|
||||
|
||||
// Real mod deobf dependency examples - these get remapped to your current mappings
|
||||
// compileOnly fg.deobf("mezz.jei:jei-${mc_version}:${jei_version}:api") // Adds JEI API as a compile dependency
|
||||
// runtimeOnly fg.deobf("mezz.jei:jei-${mc_version}:${jei_version}") // Adds the full JEI mod as a runtime dependency
|
||||
// implementation fg.deobf("com.tterrag.registrate:Registrate:MC${mc_version}-${registrate_version}") // Adds registrate as a dependency
|
||||
|
||||
// Examples using mod jars from ./libs
|
||||
|
||||
@@ -1,61 +1,77 @@
|
||||
package com.example.examplemod;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.world.item.BlockItem;
|
||||
import net.minecraft.world.item.CreativeModeTabs;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||
import net.minecraft.world.level.material.Material;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.event.RegistryEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.InterModComms;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
|
||||
import net.minecraftforge.fml.event.lifecycle.InterModEnqueueEvent;
|
||||
import net.minecraftforge.fml.event.lifecycle.InterModProcessEvent;
|
||||
import net.minecraftforge.event.CreativeModeTabEvent;
|
||||
import net.minecraftforge.event.server.ServerStartingEvent;
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
|
||||
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
|
||||
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
|
||||
import net.minecraftforge.registries.DeferredRegister;
|
||||
import net.minecraftforge.registries.ForgeRegistries;
|
||||
import net.minecraftforge.registries.RegistryObject;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
// The value here should match an entry in the META-INF/mods.toml file
|
||||
@Mod("examplemod")
|
||||
@Mod(ExampleMod.MODID)
|
||||
public class ExampleMod
|
||||
{
|
||||
// Define mod id in a common place for everything to reference
|
||||
public static final String MODID = "examplemod";
|
||||
// Directly reference a slf4j logger
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
// Create a Deferred Register to hold Blocks which will all be registered under the "examplemod" namespace
|
||||
public static final DeferredRegister<Block> BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, MODID);
|
||||
// Create a Deferred Register to hold Items which will all be registered under the "examplemod" namespace
|
||||
public static final DeferredRegister<Item> ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, MODID);
|
||||
|
||||
// Creates a new Block with the id "examplemod:example_block", combining the namespace and path
|
||||
public static final RegistryObject<Block> EXAMPLE_BLOCK = BLOCKS.register("example_block", () -> new Block(BlockBehaviour.Properties.of(Material.STONE)));
|
||||
// Creates a new BlockItem with the id "examplemod:example_block", combining the namespace and path
|
||||
public static final RegistryObject<Item> EXAMPLE_BLOCK_ITEM = ITEMS.register("example_block", () -> new BlockItem(EXAMPLE_BLOCK.get(), new Item.Properties()));
|
||||
|
||||
public ExampleMod()
|
||||
{
|
||||
// Register the setup method for modloading
|
||||
FMLJavaModLoadingContext.get().getModEventBus().addListener(this::setup);
|
||||
// Register the enqueueIMC method for modloading
|
||||
FMLJavaModLoadingContext.get().getModEventBus().addListener(this::enqueueIMC);
|
||||
// Register the processIMC method for modloading
|
||||
FMLJavaModLoadingContext.get().getModEventBus().addListener(this::processIMC);
|
||||
IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();
|
||||
|
||||
// Register the commonSetup method for modloading
|
||||
modEventBus.addListener(this::commonSetup);
|
||||
|
||||
// Register the Deferred Register to the mod event bus so blocks get registered
|
||||
BLOCKS.register(modEventBus);
|
||||
// Register the Deferred Register to the mod event bus so items get registered
|
||||
ITEMS.register(modEventBus);
|
||||
|
||||
// Register ourselves for server and other game events we are interested in
|
||||
MinecraftForge.EVENT_BUS.register(this);
|
||||
|
||||
// Register the item to a creative tab
|
||||
modEventBus.addListener(this::addCreative);
|
||||
}
|
||||
|
||||
private void setup(final FMLCommonSetupEvent event)
|
||||
private void commonSetup(final FMLCommonSetupEvent event)
|
||||
{
|
||||
// some preinit code
|
||||
LOGGER.info("HELLO FROM PREINIT");
|
||||
LOGGER.info("DIRT BLOCK >> {}", Blocks.DIRT.getRegistryName());
|
||||
// Some common setup code
|
||||
LOGGER.info("HELLO FROM COMMON SETUP");
|
||||
LOGGER.info("DIRT BLOCK >> {}", ForgeRegistries.BLOCKS.getKey(Blocks.DIRT));
|
||||
}
|
||||
|
||||
private void enqueueIMC(final InterModEnqueueEvent event)
|
||||
private void addCreative(CreativeModeTabEvent.BuildContents event)
|
||||
{
|
||||
// Some example code to dispatch IMC to another mod
|
||||
InterModComms.sendTo("examplemod", "helloworld", () -> { LOGGER.info("Hello world from the MDK"); return "Hello world";});
|
||||
}
|
||||
|
||||
private void processIMC(final InterModProcessEvent event)
|
||||
{
|
||||
// Some example code to receive and process InterModComms from other mods
|
||||
LOGGER.info("Got IMC {}", event.getIMCStream().
|
||||
map(m->m.messageSupplier().get()).
|
||||
collect(Collectors.toList()));
|
||||
if (event.getTab() == CreativeModeTabs.BUILDING_BLOCKS)
|
||||
event.accept(EXAMPLE_BLOCK_ITEM);
|
||||
}
|
||||
|
||||
// You can use SubscribeEvent and let the Event Bus discover methods to call
|
||||
@@ -66,16 +82,16 @@ public class ExampleMod
|
||||
LOGGER.info("HELLO from server starting");
|
||||
}
|
||||
|
||||
// You can use EventBusSubscriber to automatically subscribe events on the contained class (this is subscribing to the MOD
|
||||
// Event bus for receiving Registry Events)
|
||||
@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD)
|
||||
public static class RegistryEvents
|
||||
// You can use EventBusSubscriber to automatically register all static methods in the class annotated with @SubscribeEvent
|
||||
@Mod.EventBusSubscriber(modid = MODID, bus = Mod.EventBusSubscriber.Bus.MOD, value = Dist.CLIENT)
|
||||
public static class ClientModEvents
|
||||
{
|
||||
@SubscribeEvent
|
||||
public static void onBlocksRegistry(final RegistryEvent.Register<Block> blockRegistryEvent)
|
||||
public static void onClientSetup(FMLClientSetupEvent event)
|
||||
{
|
||||
// Register a new block here
|
||||
LOGGER.info("HELLO from Register Block");
|
||||
// Some client setup code
|
||||
LOGGER.info("HELLO FROM CLIENT SETUP");
|
||||
LOGGER.info("MINECRAFT NAME >> {}", Minecraft.getInstance().getUser().getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ modId="examplemod" #mandatory
|
||||
version="${file.jarVersion}" #mandatory
|
||||
# A display name for the mod
|
||||
displayName="Example Mod" #mandatory
|
||||
# A URL to query for updates for this mod. See the JSON update specification https://mcforge.readthedocs.io/en/latest/gettingstarted/autoupdate/
|
||||
# A URL to query for updates for this mod. See the JSON update specification https://docs.minecraftforge.net/en/latest/misc/updatechecker/
|
||||
#updateJSONURL="https://change.me.example.invalid/updates.json" #optional
|
||||
# A URL for the "homepage" for this mod, displayed in the mod UI
|
||||
#displayURL="https://change.me.to.your.mods.homepage.example.invalid/" #optional
|
||||
@@ -32,6 +32,14 @@ logoFile="examplemod.png" #optional
|
||||
credits="Thanks for this example mod goes to Java" #optional
|
||||
# A text field displayed in the mod UI
|
||||
authors="Love, Cheese and small house plants" #optional
|
||||
# Display Test controls the display for your mod in the server connection screen
|
||||
# MATCH_VERSION means that your mod will cause a red X if the versions on client and server differ. This is the default behaviour and should be what you choose if you have server and client elements to your mod.
|
||||
# IGNORE_SERVER_VERSION means that your mod will not cause a red X if it's present on the server but not on the client. This is what you should use if you're a server only mod.
|
||||
# IGNORE_ALL_VERSION means that your mod will not cause a red X if it's present on the client or the server. This is a special case and should only be used if your mod has no server component.
|
||||
# NONE means that no display test is set on your mod. You need to do this yourself, see IExtensionPoint.DisplayTest for more information. You can define any scheme you wish with this value.
|
||||
# IMPORTANT NOTE: this is NOT an instruction as to which environments (CLIENT or DEDICATED SERVER) your mod loads on. Your mod should load (and maybe do nothing!) whereever it finds itself.
|
||||
#displayTest="MATCH_VERSION" # MATCH_VERSION is the default if nothing is specified (#optional)
|
||||
|
||||
# The description text for the mod (multi line!) (#mandatory)
|
||||
description='''
|
||||
This is a long form description of the mod. You can write whatever you want here
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"pack": {
|
||||
"description": "examplemod resources",
|
||||
"pack_format": 9,
|
||||
"forge:resource_pack_format": 9,
|
||||
"pack_format": 12,
|
||||
"forge:resource_pack_format": 12,
|
||||
"forge:data_pack_format": 10
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
--- a/com/mojang/blaze3d/platform/GlStateManager.java
|
||||
+++ b/com/mojang/blaze3d/platform/GlStateManager.java
|
||||
@@ -539,9 +_,16 @@
|
||||
f_84077_[f_84076_].f_84800_ = false;
|
||||
@@ -500,9 +_,16 @@
|
||||
|
||||
}
|
||||
|
||||
+ /* Stores the last values sent into glMultiTexCoord2f */
|
||||
|
||||
Loaded 100 of 1477 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user