Compare commits

..
46 Commits
Author SHA1 Message Date
Curle aa170facb7 Mark 1.18.2 Recommended Build 2 2023-01-02 04:08:25 +00:00
sciwhiz12 9919e5c1d5 Fix mob block breaking AI when chunk 0,0 is unloaded Fixes #9241 (#9250) 2022-12-30 10:56:34 -08:00
matyrobbrt 84a8997263 [1.18.x] Make check tasks fail and report errors (#9228)
* Make check tasks fail and report errors

* License header and sas
2022-12-20 21:50:05 +10:30
Jonathan Coates f7c4320b7f [1.18.x] Make Util.memoize thread-safe (#9166)
* Make Util.memoize thread-safe

* Add the forge marker present in the PR
2022-12-20 21:48:33 +10:30
Dennis C 84b1a40fe2 [1.18] Improve extensibility of DetectorRailBlock and PoweredRailBlock (#9156)
* Improve extensibility of DetectorRailBlock and PoweredRailBlock

* Add missing license header to BlockGrowFeatureTest
2022-12-20 21:48:18 +10:30
Minecraftschurli 03b75632d8 [1.18.x] Add supplier to FlowerBlock so it works with custom MobEffects (#9140)
* add supplier to FlowerBlock so it works with custom MobEffects

* address comments by @Matyrobbrt

* address comments by @ChampionAsh5357

* fix missing exc entry

* remove null check and rename constructor parameter
2022-12-20 21:48:08 +10:30
2c3cb2025e [1.18.x] Backport grindstone event from 1.19 (#9090)
* Backport grindstone event from 1.19

* Fix several bugs in the original PR related to canceling the event

Fix canceling the event not clearing the result item, causing it to dupe whatever the result with the previous item was
Fix canceling the event running vanilla behavior if vanilla behavior ran at all since the menu was opened and no other listener changed the XP

* Add comments to the test mod explaining the expected behavior

* Clarify docs on behavior of the XP

Makes it clear XP is ignored in on place if the output is empty or the event is canceled

* Fix broadcast changes not being called if the event overrides grindstone behavior

Not sure its strictly needed, but every vanilla branch in the function calls it and its a trivial change.

* Add enabled flag to the test mod

* Improve javadocs

(cherry picked from commit e12c849cc0)

* Formatting fixes

* Apply javadoc suggestions from review

Co-authored-by: matyrobbrt <[email protected]>

* Correct capitalization in event name

* Javadoc improvements

Remove some redundancy in certain javadoc comments
Add @see to the header
Inline single line javadocs

* Add some funny curly brackets next to returns

Seems the kids these days have invented a new way to write return statements in javadocs

* Remove extra dots and capital letters

The return tag apparently puts more dots at the end, and having two dots on each sentence is not in the budget. Have also reached the capital letter quota

* Apply suggestions from code review

Co-authored-by: matyrobbrt <[email protected]>

Co-authored-by: ferriarnus <[email protected]>
Co-authored-by: matyrobbrt <[email protected]>
2022-12-20 21:47:49 +10:30
sciwhiz12 0ca10b5569 [1.18.2] Fix compatibility checker task configuration (#9229) 2022-12-17 18:53:18 +01:00
Marc Hermans 1087e265aa Enable additional build types to handle pull request validation. (#9160) 2022-11-30 14:11:14 -08:00
Dennis C d3ca95aee2 [1.18.x] Improve logging of missing or unsupported dependencies (#9108) 2022-11-13 01:37:05 +00:00
Budschie c5f3f34c5a [1.18.x] Make LivingSetAttackTargetEvent compatible with the Brain/Behavior system. (#8918) 2022-10-14 17:45:13 +01:00
bconlon 9ef5d545f8 [1.18.x] Backport recipe grouping feature to 1.18. (#8984) 2022-10-14 17:44:30 +01:00
Magnus Jensen 1b0ffa2633 [1.18.x] Invalidate lazy optionals properly (#9082) 2022-10-14 17:41:09 +01:00
Magnus Jensen 763523d1c0 [1.18.x] Fix NPE when feeding cats and wolves (#9075) 2022-10-14 17:18:43 +01:00
Brennan Ward 24d3705e19 Revert "[1.18.x] Backport: Cache resource listing calls in resource packs (#8868)" (#9026)
This reverts commit 4df024ef43.
2022-09-09 17:12:59 -07:00
Jais Djurhuus-Kempel 40bce4b498 [1.18.x] Backport #8981 ("Add event to growing fungus") to 1.18 (#8998) 2022-09-08 16:19:07 +01:00
Rodrigo 845c5958e0 LTS Backport of ModMismachEvent (#8989) (#9010) 2022-09-08 16:11:20 +01:00
Brennan Ward 6faa5667d6 LTS Backport of #8927 (#9012) 2022-09-08 16:10:59 +01:00
Brennan Ward 8ab5cbca26 Backport Reach Distance fix (#9008) 2022-09-03 20:13:08 +01:00
Joshua Taylor 6ff02a6ba9 Backport version-checker user-agent changes to 1.18 (#8975) 2022-09-03 20:09:03 +01:00
Dennis C 4db9bc68d9 Fix circular dependency in static init of RecipeBookRegistry (#8996) 2022-08-27 11:17:17 -07:00
noeppi_noeppi 84d354a7dd Backport: Load custom ITransformationServices from the classpath in dev (#8350) 2022-08-08 15:07:38 +02:00
embeddedt 4df024ef43 [1.18.x] Backport: Cache resource listing calls in resource packs (#8868) 2022-08-08 15:05:43 +02:00
TheMuso 49c927a505 Backport: Re-add PotentialSpawns event (#8944) 2022-08-08 15:01:33 +02:00
Mysticdrew 73a937e088 [1.18.2][Backport] Let isRemotePresent check minecraft:register for packet ids from other mod loaders (#8943) 2022-08-08 14:58:16 +02:00
matyrobbrt 65e8de880d Fix crossbows not firing ArrowLooseEvent (#8888) 2022-08-03 15:01:29 -07:00
Dennis C 1dacb882e0 [1.18] Allow blocks to provide a dynamic MaterialColor for display on maps (#8855) 2022-07-17 22:27:48 +01:00
Silver_David 22a51250b8 [1.18.x] Add RenderLevelStageEvent to expand on and replace RenderLevelLastEvent (#8603) 2022-07-17 15:33:26 +01:00
sciwhiz12 d264674a85 [1.18] Fix EnumArgument to use enum names for suggestions (#8746)
Previously, the suggestions used the string representation of the enum
through Enum#toString, which can differ from the name of the enum as
required by Enum#valueOf, causing invalid suggestions (both in gui and
through the error message).

Using Enum#name fixes this discrepancy, so now the suggestions are
always valid inputs regardless of the return of Enum#toString.

Fixes #8618
2022-07-17 15:32:10 +01:00
TelepathicGrunt af9b4c1fe5 [1.18] Add MC-105317 fix into patch to rotate entities in structures properly (#8793) 2022-07-17 15:31:37 +01:00
Marc Hermans 5a6baccee5 [1.18] Update to the latest JarJar (#8848) 2022-07-17 15:30:58 +01:00
Robert Kämpf 6443adcb6f [1.18.x] Fix a weird interaction between shulker boxes and hoppers. (#8824) 2022-07-17 15:30:12 +01:00
Paint_Ninja fc649f4e06 [1.18.x] Implement full support for IPv6 (#8376) 2022-07-17 15:26:58 +01:00
Joseph Tarbit 2886f3ca34 [1.16] Make tryEmptyContainer respect doDrain (#8318) 2022-07-17 15:16:26 +01:00
Marc Hermans 1af16d70cc Jar-In-Jar (#8657)
* Jar-In-Jar

* Change to use the custom 4.x SPI.

* Bump JarJar to fix some version range selectors.

* Address requested changes.

* Mark the JiJ locator as a locator.

* Remove the unused imports.
2022-07-10 00:05:42 +09:30
sciwhiz12 87abbdb8ef Fix static member ordering crash in UnitSprite (#8839)
The previous ordering caused LOCATION to be null during the construction
of the UnitSprite INSTANCE, leading to errors as the atlas location for
the sprite would be null.

Fixes #8795
2022-07-09 18:53:10 +09:30
covers1624 1b25285462 Backport: ForgeChunkManager ticking ticket fixes. (#8784) 2022-07-09 18:52:29 +09:30
ferriarnus 0f4e3146e0 Delete LootItemRandomChanceCondition.java.patch (#8734) 2022-07-09 18:51:00 +09:30
Magnus Jensen b46f7c0d85 Use stack sensitive translation key by default (#8674) 2022-07-09 18:50:10 +09:30
Dennis C 28d71761cd Add callback after a BlockState was changed and the neighbors were updated (#8617) 2022-07-09 18:49:14 +09:30
Dennis C dbf1c8f10f [1.18.x] Allow safely registering RenderType predicates at any time (#8671)
* Allow safely registering RenderType predicates at any time

* Requested changes
2022-06-30 21:51:18 +09:30
LexManos f7dd6396e4 Add Filtered Message to ServerChatEvent 2022-06-29 12:05:27 -07:00
King Lemming 1a46f205d6 [1.18] Make ConfigValue implement Supplier. (#8777) 2022-06-20 23:08:24 +01:00
gigaherz be0facc2aa Make IVertexConsumers such as the lighting pipeline, be aware of which format they are dealing with (#8665)
Also fix Lighting pipeline ignoring the overlay coords from the block renderer.
2022-06-09 21:41:00 +02:00
cpw 67d4a0163b Revert "TeamCity change in 'MinecraftForge / MinecraftForge' project: build features of 'Build' build configuration were updated"
This reverts commit 0cc85bc314.
2022-06-08 20:56:41 -04:00
cpw 0cc85bc314 TeamCity change in 'MinecraftForge / MinecraftForge' project: build features of 'Build' build configuration were updated 2022-06-08 22:50:15 +00:00
2211 changed files with 37271 additions and 55484 deletions

No files matched your search

+16
View File
@@ -0,0 +1,16 @@
# 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
-16
View File
@@ -1,16 +0,0 @@
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 }}
-16
View File
@@ -1,16 +0,0 @@
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 }}
-13
View File
@@ -1,13 +0,0 @@
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 }}
@@ -1,13 +0,0 @@
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 }}
@@ -1,20 +0,0 @@
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 }}
@@ -1,20 +0,0 @@
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 }}
-18
View File
@@ -1,18 +0,0 @@
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 }}
-16
View File
@@ -1,16 +0,0 @@
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 }}
-2
View File
@@ -33,8 +33,6 @@
/projects/**/*.launch
/repo/
src/generated/resources/.cache/
src/generated_test/resources/.cache/
src/generated_mdk/resources/.cache/
#Patch rejects
/patches-/
+4 -4
View File
@@ -37,8 +37,8 @@ project {
params {
text("docker_jdk_version", "17", label = "Gradle version", description = "The version of the JDK to use during execution of tasks in a JDK.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
text("docker_gradle_version", "7.5.1", label = "Gradle version", description = "The version of Gradle to use during execution of Gradle tasks.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
text("git_main_branch", "1.20.x", label = "Git Main Branch", description = "The git main or default branch to use in VCS operations.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
text("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.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*)
@@ -155,7 +155,7 @@ object PullRequestChecks : BuildType({
enableStacktrace = true
dockerImage = "%docker_gradle_image%"
dockerRunParameters = """
-v "/opt/cache/agent/gradle:/home/gradle/.gradle"
-v "/opt/cache/agent/gradle:/home/gradle/.gradle"
-v "/opt/cache/shared/gradle:/home/gradle/rocache:ro"
--network=host
-u 1000:1000
@@ -189,7 +189,7 @@ object PullRequestCompatibility : BuildType({
enableStacktrace = true
dockerImage = "%docker_gradle_image%"
dockerRunParameters = """
-v "/opt/cache/agent/gradle:/home/gradle/.gradle"
-v "/opt/cache/agent/gradle:/home/gradle/.gradle"
-v "/opt/cache/shared/gradle:/home/gradle/rocache:ro"
--network=host
-u 1000:1000
+41 -192
View File
@@ -1,3 +1,14 @@
buildscript {
repositories {
mavenLocal()
maven { url = 'https://maven.minecraftforge.net/' }
mavenCentral()
}
dependencies {
classpath 'net.minecraftforge.gradle:ForgeGradle:[5.1.39,)'
}
}
import groovy.json.JsonBuilder
import net.minecraftforge.forge.tasks.checks.CheckATs
import net.minecraftforge.forge.tasks.checks.CheckExcs
@@ -19,7 +30,6 @@ import net.minecraftforge.gradle.common.tasks.SignJar
import net.minecraftforge.gradle.userdev.tasks.RenameJar
import org.apache.tools.ant.filters.ReplaceTokens
import org.gradle.plugins.ide.eclipse.model.SourceFolder
import org.objectweb.asm.Opcodes
plugins {
id 'org.cadixdev.licenser' version '0.6.1'
@@ -27,8 +37,6 @@ plugins {
id 'net.minecraftforge.gradleutils' version '2.+'
id 'eclipse'
id 'de.undercouch.download' version '5.1.3'
id 'net.minecraftforge.gradle.patcher' version '[5.1.77,5.2.0)' apply false
id 'net.minecraftforge.gradle.mcp' version '[5.1.77,5.2.0)' apply false
}
Util.init() //Init all our extension methods!
@@ -43,48 +51,33 @@ ext {
]
}
MAPPING_CHANNEL = 'official'
MAPPING_VERSION = '1.19.4'
MC_VERSION = '1.19.4'
MC_NEXT_VERSION = '1.20'
MCP_VERSION = '20230314.122934'
SPI_VERSION = '6.0.0'
MAPPING_VERSION = '1.18.2'
MC_VERSION = '1.18.2'
MCP_VERSION = '20220404.173914'
SPI_VERSION = '4.0.15-4.x'
ACCESSTRANSFORMERS_VERSION = '8.0.4'
COREMODS_VERSION = '5.0.1'
EVENTBUS_VERSION = '6.0.3'
MODLAUNCHER_VERSION = '10.0.8'
SECUREJARHANDLER_VERSION = '2.1.6'
BOOTSTRAPLAUNCHER_VERSION = '1.1.2'
ASM_VERSION = '9.5'
EVENTBUS_VERSION = '5.0.3'
MODLAUNCHER_VERSION = '9.1.3'
SECUREJARHANDLER_VERSION = '1.0.3'
BOOTSTRAPLAUNCHER_VERSION = '1.0.0'
ASM_VERSION = '9.2'
INSTALLER_VERSION = '2.1.+'
MIXIN_VERSION = '0.8.5'
TERMINALCONSOLEAPPENDER_VERSION = '1.2.0'
JLINE_VERSION = '3.12.+'
NIGHTCONFIG_VERSION = '3.6.4'
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.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'
JARJAR_VERSION = '0.3.0'
GIT_INFO = gradleutils.gitInfo
VERSION = gradleutils.getFilteredMCTagOffsetBranchVersion(true, '[0-9]', MC_VERSION)
// FML_VERSION = gradleutils.getFilteredMCTagOffsetBranchVersion(true, 'FML', MC_VERSION)
FORGE_VERSION = VERSION.substring(MC_VERSION.length() + 1)
BINPATCH_TOOL = 'net.minecraftforge:binarypatcher:1.1.1:fatjar'
SPECIAL_SOURCE = 'net.md-5:SpecialSource:1.10.0'
BINPATCH_TOOL = 'net.minecraftforge:binarypatcher:1.0.12:fatjar'
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 = '41.0'
MIN_TAG_FOR_CHANGELOG = "39.0"
LAST_RB = GIT_INFO.tag.rsplit('.', 1)[1] as int >= 1 ? GIT_INFO.tag + '.0' : null
}
println('Version: ' + VERSION +
@@ -103,7 +96,7 @@ def extraTxts = [
]
task downloadVersionManifest(type: Download) {
src 'https://piston-meta.mojang.com/mc/game/version_manifest_v2.json'
src 'https://launchermeta.mojang.com/mc/game/version_manifest_v2.json'
dest file('build/versions/version_manifest.json')
useETag 'all'
onlyIfModified true
@@ -152,10 +145,6 @@ subprojects {
name = 'forge'
url = 'https://maven.minecraftforge.net/'
}
maven {
name = 'minecraft'
url = 'https://libraries.minecraft.net/'
}
}
java.toolchain.languageVersion = JavaLanguageVersion.of(17)
@@ -269,7 +258,6 @@ 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}"
@@ -278,23 +266,22 @@ def sharedDeps = {
installer "org.ow2.asm:asm-util:${ASM_VERSION}"
installer "org.ow2.asm:asm-analysis:${ASM_VERSION}"
installer "net.minecraftforge:accesstransformers:${ACCESSTRANSFORMERS_VERSION}"
installer 'org.antlr:antlr4-runtime:4.9.1' // Dep of AccessTransformer
installer 'org.antlr:antlr4-runtime:4.9.1' //Dep of AccessTransformer
installer "net.minecraftforge:eventbus:${EVENTBUS_VERSION}"
installer "net.minecraftforge:forgespi:${SPI_VERSION}"
installer "net.minecraftforge:coremods:${COREMODS_VERSION}"
installer "cpw.mods:modlauncher:${MODLAUNCHER_VERSION}"
installer 'net.minecraftforge:unsafe:0.2.+'
installer "com.electronwill.night-config:core:${NIGHTCONFIG_VERSION}"
installer "com.electronwill.night-config:toml:${NIGHTCONFIG_VERSION}"
installer "org.apache.maven:maven-artifact:${APACHE_MAVEN_ARTIFACT_VERSION}"
installer 'com.electronwill.night-config:core:3.6.4'
installer 'com.electronwill.night-config:toml:3.6.4'
installer 'org.apache.maven:maven-artifact:3.6.3'
installer 'net.jodah:typetools:0.8.3'
installer "net.minecrell:terminalconsoleappender:${TERMINALCONSOLEAPPENDER_VERSION}"
installer "org.jline:jline-reader:${JLINE_VERSION}" // Dep of TerminalConsoleAppender
installer "org.jline:jline-terminal:${JLINE_VERSION}" // Dep of TerminalConsoleAppender
installer 'net.minecrell:terminalconsoleappender:1.2.0'
installer 'org.jline:jline-reader:3.12.+' //Dep of TerminaalConsoleeAppender
installer 'org.jline:jline-terminal:3.12.+' //Dep of TerminaalConsoleeAppender
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 "net.minecraftforge:JarJar:${JARJAR_VERSION}"
/*
installer 'org.lwjgl:lwjgl:3.2.2'
@@ -387,8 +374,7 @@ def sharedFmlonlyForge = { Project prj ->
environment 'MOD_CLASSES', '{source_roots}'
environment 'MCP_MAPPINGS', '{mcp_mappings}'
// Natives are loaded from the classpath now in all cases
// property 'nativesDirectory', '{natives}'
property 'nativesDirectory', '{natives}'
if (prj.name == 'forge') {
property 'forge.enableGameTest', 'true'
}
@@ -453,7 +439,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'
run.property 'mergeModules', 'jna-5.10.0.jar,jna-platform-5.10.0.jar,java-objc-bridge-1.0.0.jar'
if (userdevRuns.contains(run)) {
run.property 'legacyClassPath.file', '{minecraft_classpath_file}'
run.jvmArgs '-p', '{modules}'
@@ -464,7 +450,6 @@ 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'
}
@@ -473,8 +458,7 @@ def sharedFmlonlyForge = { Project prj ->
run.workingDirectory prj.file('run')
if (run.name.contains('client') || run.name.contains('data')) {
// Natives are loaded from the classpath now in all cases
// run.property 'nativesDirectory', prj.extractNatives.output.get().asFile
run.property 'nativesDirectory', prj.extractNatives.output.get().asFile
run.args '--assetsDir', prj.downloadAssets.output
run.args '--assetIndex', "{asset_index}"
}
@@ -656,7 +640,6 @@ 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'
]
@@ -884,10 +867,6 @@ project(':forge') {
apply plugin: 'net.minecraftforge.gradle.patcher'
apply plugin: 'org.cadixdev.licenser'
applyPatches {
level 'WARNING'
}
sourceSets {
main {
java {
@@ -1080,40 +1059,11 @@ project(':forge') {
'--mod', 'tag_based_tool_types',
'--mod', 'custom_transformtype_test',
'--mod', 'data_pack_registries_test',
'--mod', 'biome_modifiers_test',
'--mod', 'structure_modifiers_test',
'--mod', 'custom_preset_editor_test',
'--output', rootProject.file('src/generated_test/resources/'),
'--existing', sourceSets.main.resources.srcDirs[0],
'--existing', sourceSets.test.resources.srcDirs[0]
}
}
forge_mdk_data {
parent runs.forge_data
taskName 'forge_mdk_data'
environment 'MOD_CLASSES', 'dummy' // Needed to work around FG limitation, FG will replace this!
ideaModule "${rootProject.name}.${project.name}.test"
mods {
tests { sources sourceSets.test }
}
afterEvaluate {
// Clear forge's main datagen args
args.removeAll {
forgeDataArgs.contains(it instanceof File ? (it as File).absolutePath : it.toString())
}
args '--flat', '--all', '--validate',
'--mod', 'mdk_datagen',
'--output', rootProject.file('src/generated_mdk/resources/'),
'--existing', sourceSets.main.resources.srcDirs[0],
'--existing', sourceSets.test.resources.srcDirs[0]
}
}
}
}
@@ -1206,31 +1156,12 @@ project(':forge') {
}
}
task findFinalizeSpawnTargets(type: BytecodePredicateFinder, dependsOn: ['createJoinedSRG']) {
jar = createJoinedSRG.output
output = rootProject.file('src/main/resources/coremods/finalize_spawn_targets.json')
predicate = {
parent, node, insn ->
'net/minecraft/world/level/BaseSpawner' != parent.name // Ignore this class as we special case it.
&& insn.getOpcode() == Opcodes.INVOKEVIRTUAL
&& insn.name.equals('m_6518_')
&& insn.desc.equals('(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/MobSpawnType;Lnet/minecraft/world/entity/SpawnGroupData;Lnet/minecraft/nbt/CompoundTag;)Lnet/minecraft/world/entity/SpawnGroupData;')
}
}
tasks.register('validateDeprecations', ValidateDeprecations) {
input = tasks.jar.archiveFile
mcVersion = MC_VERSION
}
tasks.jar.finalizedBy 'validateDeprecations'
tasks.register('checkAll') {
dependsOn 'checkLicenses'
group = 'checks'
}
tasks.register('checkAllAndFix') {
dependsOn 'findFinalizeSpawnTargets', 'findFieldInstanceChecks', 'checkLicenses'
dependsOn 'findFieldInstanceChecks', 'checkLicenses'
group = 'checks'
}
@@ -1269,7 +1200,7 @@ project(':forge') {
}
def baseForgeVersionProperty = project.objects.property(String)
baseForgeVersionProperty.set(project.provider { TeamcityRequests.attemptFindBase(rootDir) ?: getLatestForgeVersion(MC_VERSION) })
baseForgeVersionProperty.set(project.provider { getLatestForgeVersion(MC_VERSION) }.map { MC_VERSION + '-' + it })
baseForgeVersionProperty.finalizeValueOnRead()
def jarCompatibilityTaskSetup = { task ->
task.group = 'jar compatibility'
@@ -1346,7 +1277,6 @@ project(':forge') {
'--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'
]
@@ -1630,15 +1560,11 @@ project(':forge') {
MAPPING_CHANNEL: MAPPING_CHANNEL,
MAPPING_VERSION: MAPPING_VERSION,
FORGE_SPEC_VERSION: SPEC_VERSION.split("\\.")[0],
MC_NEXT_VERSION: MC_NEXT_VERSION
MC_NEXT_VERSION: "1."+((MC_VERSION.split("[\\.-]")[1] as int)+1)
])
rename 'gitignore\\.txt', '.gitignore'
rename 'gitattributes\\.txt', '.gitattributes'
}
from(rootProject.file('src/generated_mdk/resources/')) {
into('src/main/resources/')
exclude '**/.cache/'
}
}
license {
@@ -1675,83 +1601,6 @@ project(':forge') {
sourceSets.test.java.srcDirs.each { extractMappedNew.addTarget it }
}
tasks.named('javadoc', Javadoc).configure {
description 'Generates the combined javadocs for the FML projects and the main Forge project'
var includedProjects = [ ':fmlcore', ':fmlloader', ':javafmllanguage', ':mclanguage' ]
source includedProjects.collect { project(it).sourceSets.main.allJava }
classpath = classpath + files(includedProjects.collect { project(it).sourceSets.main.compileClasspath })
var docsDir = rootProject.file('src/docs/')
inputs.dir(docsDir)
.withPropertyName('docs resources directory')
.withPathSensitivity(PathSensitivity.RELATIVE)
.optional()
failOnError = false
// Exclude the Minecraft classes if not enabled
if (!project.hasProperty('generateAllDocumentation')) {
exclude 'net/minecraft/**'
exclude 'com/mojang/**'
}
exclude 'mcp/**'
options.addStringOption('Xdoclint:all,-missing', '-public')
options {
stylesheetFile = new File(docsDir, 'stylesheet.css')
tags = [
'apiNote:a:<em>API Note:</em>',
'implSpec:a:<em>Implementation Requirements:</em>',
'implNote:a:<em>Implementation Note:</em>'
]
groups = [
'Forge Mod Loader': [
'net.minecraftforge.fml.common.asm*',
'net.minecraftforge.fml.loading*',
'net.minecraftforge.fml.server*'
],
'FML Core': [
'net.minecraftforge.fml',
'net.minecraftforge.fml.config*',
'net.minecraftforge.fml.event*',
'net.minecraftforge.fml.util*'
],
'FML Common': [
'net.minecraftforge.fml.core',
'net.minecraftforge.fml.event.config',
'net.minecraftforge.fml.event.lifecycle'
],
'FML Java/MC Language Providers': [
'net.minecraftforge.fml.common',
'net.minecraftforge.fml.javafmlmod',
'net.minecraftforge.fml.mclanguageprovider'
],
'Minecraft Forge API': [
'net.minecraftforge*'
]
]
author = false
noSince = true
noHelp = true
bottom = "Minecraft Forge is an open source modding API for Minecraft: Java Edition, licensed under the Lesser GNU General Public License, version 2.1."
windowTitle = "Minecraft Forge API ${VERSION}"
docTitle = "Minecraft Forge API - ${FORGE_VERSION} for Minecraft ${MC_VERSION}"
header = "<div style=\"margin-top: 9px;padding: 5px 6px;\"><strong>${FORGE_VERSION} for Minecraft ${MC_VERSION}</strong></div>"
}
doLast {
project.copy {
from docsDir
exclude '/stylesheet.css'
into destinationDir
}
}
}
publishing {
publications {
mavenJava(MavenPublication) {
@@ -1790,4 +1639,4 @@ if (System.env.TEAMCITY_VERSION) {
println "##teamcity[setParameter name='env.PUBLISHED_JAVA_FML_ARTIFACT_VERSION' value='${project(':fmlonly').version}']"
}
}
}
}
+3 -5
View File
@@ -2,12 +2,10 @@ repositories {
maven { url = 'https://maven.minecraftforge.net/' }
mavenCentral()
}
dependencies {
implementation 'org.ow2.asm:asm:9.5'
implementation 'org.ow2.asm:asm-tree:9.5'
implementation 'net.minecraftforge:srgutils:0.5.+'
implementation 'org.ow2.asm:asm:9.1'
implementation 'org.ow2.asm:asm-tree:9.1'
implementation 'net.minecraftforge:srgutils:0.4.+'
implementation 'commons-io:commons-io:2.8.0'
implementation 'com.google.code.gson:gson:2.10'
implementation 'org.eclipse.jgit:org.eclipse.jgit:6.5.0.202303070854-r'
}
@@ -6,10 +6,15 @@ import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.objectweb.asm.ClassReader
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.FieldNode
import org.objectweb.asm.tree.MethodNode
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
abstract class BytecodeFinder extends DefaultTask {
@InputFile abstract RegularFileProperty getJar()
@OutputFile abstract RegularFileProperty getOutput()
@@ -28,7 +33,18 @@ abstract class BytecodeFinder extends DefaultTask {
pre()
Util.processClassNodes(jar.get().asFile, this::process)
jar.get().asFile.withInputStream { i ->
new ZipInputStream(i).withCloseable { zin ->
ZipEntry zein
while ((zein = zin.nextEntry) != null) {
if (zein.name.endsWith('.class')) {
def node = new ClassNode(Opcodes.ASM9)
new ClassReader(zin).accept(node, 0)
process(node)
}
}
}
}
post()
outputFile.text = new JsonBuilder(getData()).toPrettyString()
@@ -1,32 +0,0 @@
package net.minecraftforge.forge.tasks
import groovy.transform.CompileStatic
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Internal
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.MethodNode
@CompileStatic
abstract class BytecodePredicateFinder extends BytecodeFinder {
@Internal
abstract Property<Closure<Boolean>> getPredicate()
private final Set<String> matches = new HashSet()
@Override
protected process(ClassNode parent, MethodNode node) {
for (final current : node.instructions) {
if (predicate.get().call(parent, node, current)) {
matches.add(parent.name)
return
}
}
}
@Internal
@Override
protected Object getData() {
return matches.toSorted(Comparator.naturalOrder()) ?: {throw new RuntimeException('Failed to find any targets, please ensure that method names and descriptors are correct.')}()
}
}
@@ -6,7 +6,7 @@ import org.gradle.api.provider.SetProperty
abstract class InstallerJar extends Zip {
@Input @Optional abstract SetProperty<String> getPackedDependencies()
InstallerJar() {
archiveClassifier.set('installer')
archiveExtension.set('jar') // Needs to be Zip task to not override Manifest, so set extension
@@ -15,7 +15,7 @@ abstract class InstallerJar extends Zip {
def installerJson = project.tasks.installerJson
def launcherJson = project.tasks.launcherJson
def downloadInstaller = project.tasks.downloadInstaller
dependsOn(installerJson, launcherJson, downloadInstaller)
from(installerJson, launcherJson)
@@ -24,13 +24,13 @@ abstract class InstallerJar extends Zip {
from(project.zipTree(downloadInstaller.output)) {
duplicatesStrategy = 'exclude'
}
if (!System.env.TEAMCITY_VERSION) {
if (!System.env.MAVEN_USER) {
dependsOn(project.universalJar)
from(project.universalJar) {
into '/maven/' + project.group.replace('.', '/') + '/' + project.universalJar.archiveBaseName.get() + '/' + project.version + '/'
}
packedDependencies.get().forEach {
def jarTask = project.rootProject.getTasks().findByPath(it)
dependsOn(jarTask)
@@ -1,74 +0,0 @@
package net.minecraftforge.forge.tasks
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import groovy.transform.CompileStatic
import org.eclipse.jgit.api.Git
import javax.annotation.Nullable
import java.util.stream.Collectors
@CompileStatic
class TeamcityRequests {
public static final Gson GSON = new Gson()
@Nullable
static <T> T jsonRequest(TypeToken<T> clazz, String url) throws Exception {
final HttpURLConnection conn = (HttpURLConnection) URI.create(url).toURL().openConnection()
conn.setRequestProperty('Accept', 'application/json')
conn.setReadTimeout(5 * 1000)
conn.setConnectTimeout(5 * 1000)
conn.connect()
if (conn.responseCode !== 200) {
return null
}
try (final InputStream is = conn.getInputStream()) {
GSON.fromJson(new InputStreamReader(is), clazz.getType())
}
}
@Nullable
static Map<String, Build> buildsByCommit() throws IOException {
final Map<String, Build> builds = [:]
jsonRequest(new TypeToken<Builds>() {}, "https://teamcity.minecraftforge.net/guestAuth/app/rest/builds?fields=build:(revisions,number)&locator=buildType:(id:MinecraftForge_MinecraftForge_MinecraftForge_MinecraftForge__Build),count:300,status:SUCCESS")
?.build?.forEach {
it.revisions.revision.each { rev -> builds[rev.version] = it }
}
return builds
}
@Nullable
static String attemptFindBase(File gitPath) {
try (final git = Git.open(gitPath)) {
final Map<String, Build> buildByCommit = buildsByCommit()
for (final commit in git.log().setMaxCount(100).call()) {
// Find the first commit which was built on CI
final build = buildByCommit[commit.id.name]
if (build) {
return build.number
}
}
} catch (Exception ignored) {}
return null
}
static final class Builds {
public List<Build> build
}
static final class Build {
public String number
public Revisions revisions
}
static final class Revisions {
public int count
public List<Revision> revision
}
static final class Revision {
public String version
}
}
@@ -2,20 +2,13 @@ package net.minecraftforge.forge.tasks
import groovy.json.JsonBuilder
import groovy.json.JsonSlurper
import org.objectweb.asm.ClassReader
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.ClassNode
import java.io.File
import java.security.MessageDigest
import java.text.SimpleDateFormat
import java.util.Date
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
public class Util {
static final ASM_LEVEL = Opcodes.ASM9
static void init() {
File.metaClass.sha1 = { ->
MessageDigest md = MessageDigest.getInstance('SHA-1')
@@ -150,24 +143,8 @@ public class Util {
}
}
static String getLatestForgeVersion(mcVersion) {
final json = new JsonSlurper().parseText(new URL('https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json').getText('UTF-8'))
final ver = json.promos["$mcVersion-latest"]
ver === null ? null : (mcVersion + '-' + ver)
}
static void processClassNodes(File file, Closure process) {
file.withInputStream { i ->
new ZipInputStream(i).withCloseable { zin ->
ZipEntry zein
while ((zein = zin.nextEntry) != null) {
if (zein.name.endsWith('.class')) {
def node = new ClassNode(ASM_LEVEL)
new ClassReader(zin).accept(node, 0)
process(node)
}
}
}
}
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"]
}
}
@@ -1,92 +0,0 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.forge.tasks
import net.minecraftforge.srgutils.MinecraftVersion
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.InputFile
import org.gradle.api.tasks.TaskAction
import org.objectweb.asm.tree.AnnotationNode
import org.objectweb.asm.tree.ClassNode
abstract class ValidateDeprecations extends DefaultTask {
@InputFile
abstract RegularFileProperty getInput()
@Input
abstract Property<String> getMcVersion()
ValidateDeprecations() {
this.onlyIf { !System.env.TEAMCITY_VERSION }
}
@TaskAction
protected void exec() {
def mcVer = MinecraftVersion.from(mcVersion.get())
def errors = []
Util.processClassNodes(input.get().asFile) {
processNode(mcVer, errors, it)
}
if (!errors.isEmpty()) {
errors.forEach {
this.logger.error("Deprecated ${it[0]} is marked for removal in ${it[1]} but is not yet removed")
}
throw new IllegalStateException("Found deprecated members marked for removal but not yet removed in ${mcVer}; see log for details")
}
}
protected processNode(MinecraftVersion mcVer, List<String> errors, ClassNode node) {
node.visibleAnnotations?.each { annotation ->
ValidateDeprecations.processAnnotations(annotation, mcVer, errors) {
"class ${node.name}"
}
}
node.fields?.each { field ->
field.visibleAnnotations?.each { annotation ->
ValidateDeprecations.processAnnotations(annotation, mcVer, errors) {
"field ${node.name}#${field.name}"
}
}
}
node.methods?.each { method ->
method.visibleAnnotations?.each { annotation ->
ValidateDeprecations.processAnnotations(annotation, mcVer, errors) {
"method ${node.name}#${method.name}${method.desc}"
}
}
}
}
private static void processAnnotations(AnnotationNode annotation, MinecraftVersion mcVer, List<String> errors, Closure context) {
def values = annotation.values
if (values === null)
return
int forRemoval = values.indexOf('forRemoval')
int since = values.indexOf('since')
if (annotation.desc == 'Ljava/lang/Deprecated;' && forRemoval !== -1 && since !== -1 && values.size() >= 4 && values[forRemoval + 1] === true) {
def oldVersion = MinecraftVersion.from(values[since + 1])
def split = ValidateDeprecations.splitDots(oldVersion.toString())
if (split.length < 2)
return
def removeVersion = MinecraftVersion.from("${split[0]}.${split[1] + 1}")
if (removeVersion <= mcVer)
errors.add([context(), removeVersion])
}
}
private static int[] splitDots(String version) {
String[] pts = version.split('\\.')
int[] values = new int[pts.length]
for (int x = 0; x < pts.length; x++)
values[x] = Integer.parseInt(pts[x])
return values
}
}
@@ -1,6 +1,5 @@
package net.minecraftforge.forge.tasks.checks
import net.minecraftforge.forge.tasks.Util
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
@@ -8,6 +7,7 @@ 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
@@ -67,7 +67,7 @@ abstract class CheckExcs extends CheckTask {
private void collectKnown(Collection<String> known) {
binary.get().asFile.withInputStream { i ->
new ZipInputStream(i).withCloseable { zin ->
final visitor = new ClassVisitor(Util.ASM_LEVEL) {
final visitor = new ClassVisitor(Opcodes.ASM9) {
private String cls
@Override
void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
@@ -35,11 +35,6 @@ abstract class CheckPatches extends CheckTask {
}
}
}
def accessChange(previous, current) {
//return ACCESS_MAP[previous] < ACCESS_MAP[current]
return previous != current
}
void verifyPatch(Path patch, Reporter reporter, boolean fix, String patchPath, boolean hasS2SArtifact) {
final oldFixedErrors = reporter.fixed.size()
@@ -92,7 +87,7 @@ abstract class CheckPatches extends CheckTask {
pMatcher.group(6) == cMatcher.group(6) && // = ...
pMatcher.group(5) == cMatcher.group(5) && // field name
pMatcher.group(3) == cMatcher.group(3) && // static
(accessChange(pMatcher.group(2), cMatcher.group(2)) || pMatcher.group(4) != cMatcher.group(4))) {
(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)
}
@@ -103,7 +98,7 @@ abstract class CheckPatches extends CheckTask {
pMatcher.group(6) == cMatcher.group(6) && // params
pMatcher.group(5) == cMatcher.group(5) && // <T> void name
pMatcher.group(3) == cMatcher.group(3) && // static
(accessChange(pMatcher.group(2), cMatcher.group(2)) || pMatcher.group(4) != cMatcher.group(4))) {
(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)
}
@@ -114,7 +109,7 @@ abstract class CheckPatches extends CheckTask {
pMatcher.group(6) == cMatcher.group(6) && // ClassName<> extends ...
pMatcher.group(5) == cMatcher.group(5) && // class | interface
pMatcher.group(3) == cMatcher.group(3) && // static
(accessChange(pMatcher.group(2), cMatcher.group(2)) || pMatcher.group(4) != cMatcher.group(4))) {
(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)
}
}
+4 -6
View File
@@ -1,13 +1,13 @@
Contributing to Forge
=====================
1) Keep patches to Minecraft classes together. If you need a lot of things done, you may either add to relevant forge classes or make a new class. Try not to spread out your patches across multiple disjoint lines, as this makes maintenance of your patches difficult.
1) Keep patches to Minecraft classes minimal. If you need a lot of things done, you may either add to relevant forge classes or make a new class.
2) TODO: Test Mods
2) An example mod must be provided for all PRs adding events/hooks to Forge. Whether it's a full mod using your proposed hooks, or just a pastebin containing a simple example, an example mod is required. Please also explain in detail what your new hook does.
3) Follow the code style of the class you're working in (braces on newlines & spaces instead of tabs in Forge classes, inline brackets in patches, etc).
3) Follow Forge coding guidelines (braces on newlines, spaces instead of tabs, etc)
For more information, refer to [the wiki][Wiki].
For more information, refer to [the wiki](https://github.com/MinecraftForge/MinecraftForge/wiki/If-you-want-to-contribute-to-Forge)
Contributor License Agreement
@@ -16,5 +16,3 @@ Contributor License Agreement
- You assign copyright ownership of your contributions to the patches codebase (everything under patches) to Forge, where it will be licensed under the LGPLv2.1 license.
"Forge" is Forge Development LLC, a legally incorporated entity in Oregon, USA.
[Wiki]: https://github.com/MinecraftForge/MinecraftForge/wiki/If-you-want-to-contribute-to-Forge
+23 -35
View File
@@ -2,17 +2,16 @@
MinecraftForge
=============
[![Stable Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Stable&prefix=1.19.4-&query=$.promos["1.19.4-recommended"]&color=brightgreen&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtc3RhciBmYS13LTE4IiBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1pY29uPSJzdGFyIiBkYXRhLXByZWZpeD0iZmFzIiBmb2N1c2FibGU9ImZhbHNlIiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCA1NzYgNTEyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNMjU5LjMgMTcuOEwxOTQgMTUwLjIgNDcuOSAxNzEuNWMtMjYuMiAzLjgtMzYuNyAzNi4xLTE3LjcgNTQuNmwxMDUuNyAxMDMtMjUgMTQ1LjVjLTQuNSAyNi4zIDIzLjIgNDYgNDYuNCAzMy43TDI4OCA0MzkuNmwxMzAuNyA2OC43YzIzLjIgMTIuMiA1MC45LTcuNCA0Ni40LTMzLjdsLTI1LTE0NS41IDEwNS43LTEwM2MxOS0xOC41IDguNS01MC44LTE3LjctNTQuNkwzODIgMTUwLjIgMzE2LjcgMTcuOGMtMTEuNy0yMy42LTQ1LjYtMjMuOS01Ny40IDB6IiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K)][Download]
[![Latest Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Latest&prefix=1.19.4-&query=$.promos["1.19.4-latest"]&color=blue&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtYnVnIGZhLXctMTYiIGFyaWEtaGlkZGVuPSJ0cnVlIiBkYXRhLWljb249ImJ1ZyIgZGF0YS1wcmVmaXg9ImZhcyIgZm9jdXNhYmxlPSJmYWxzZSIgcm9sZT0iaW1nIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTUxMS45ODggMjg4LjljLS40NzggMTcuNDMtMTUuMjE3IDMxLjEtMzIuNjUzIDMxLjFINDI0djE2YzAgMjEuODY0LTQuODgyIDQyLjU4NC0xMy42IDYxLjE0NWw2MC4yMjggNjAuMjI4YzEyLjQ5NiAxMi40OTcgMTIuNDk2IDMyLjc1OCAwIDQ1LjI1NS0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDBsLTU0LjczNi01NC43MzZDMzQ1Ljg4NiA0NjcuOTY1IDMxNC4zNTEgNDgwIDI4MCA0ODBWMjM2YzAtNi42MjctNS4zNzMtMTItMTItMTJoLTI0Yy02LjYyNyAwLTEyIDUuMzczLTEyIDEydjI0NGMtMzQuMzUxIDAtNjUuODg2LTEyLjAzNS05MC42MzYtMzIuMTA4bC01NC43MzYgNTQuNzM2Yy0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDAtMTIuNDk2LTEyLjQ5Ny0xMi40OTYtMzIuNzU4IDAtNDUuMjU1bDYwLjIyOC02MC4yMjhDOTIuODgyIDM3OC41ODQgODggMzU3Ljg2NCA4OCAzMzZ2LTE2SDMyLjY2NkMxNS4yMyAzMjAgLjQ5MSAzMDYuMzMuMDEzIDI4OC45LS40ODQgMjcwLjgxNiAxNC4wMjggMjU2IDMyIDI1Nmg1NnYtNTguNzQ1bC00Ni42MjgtNDYuNjI4Yy0xMi40OTYtMTIuNDk3LTEyLjQ5Ni0zMi43NTggMC00NS4yNTUgMTIuNDk4LTEyLjQ5NyAzMi43NTgtMTIuNDk3IDQ1LjI1NiAwTDE0MS4yNTUgMTYwaDIyOS40ODlsNTQuNjI3LTU0LjYyN2MxMi40OTgtMTIuNDk3IDMyLjc1OC0xMi40OTcgNDUuMjU2IDAgMTIuNDk2IDEyLjQ5NyAxMi40OTYgMzIuNzU4IDAgNDUuMjU1TDQyNCAxOTcuMjU1VjI1Nmg1NmMxNy45NzIgMCAzMi40ODQgMTQuODE2IDMxLjk4OCAzMi45ek0yNTcgMGMtNjEuODU2IDAtMTEyIDUwLjE0NC0xMTIgMTEyaDIyNEMzNjkgNTAuMTQ0IDMxOC44NTYgMCAyNTcgMHoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=
)][Download] [![Discord](https://img.shields.io/discord/313125603924639766.svg?color=%237289da&label=Discord&logo=discord&logoColor=%237289da)][Discord] [![Support](https://img.shields.io/badge/Patreon-Support-orange.svg?logo=Patreon)][Patreon]
[![Stable Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Stable&prefix=1.18.2-&query=$.promos["1.18.2-recommended"]&color=brightgreen&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtc3RhciBmYS13LTE4IiBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1pY29uPSJzdGFyIiBkYXRhLXByZWZpeD0iZmFzIiBmb2N1c2FibGU9ImZhbHNlIiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCA1NzYgNTEyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNMjU5LjMgMTcuOEwxOTQgMTUwLjIgNDcuOSAxNzEuNWMtMjYuMiAzLjgtMzYuNyAzNi4xLTE3LjcgNTQuNmwxMDUuNyAxMDMtMjUgMTQ1LjVjLTQuNSAyNi4zIDIzLjIgNDYgNDYuNCAzMy43TDI4OCA0MzkuNmwxMzAuNyA2OC43YzIzLjIgMTIuMiA1MC45LTcuNCA0Ni40LTMzLjdsLTI1LTE0NS41IDEwNS43LTEwM2MxOS0xOC41IDguNS01MC44LTE3LjctNTQuNkwzODIgMTUwLjIgMzE2LjcgMTcuOGMtMTEuNy0yMy42LTQ1LjYtMjMuOS01Ny40IDB6IiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K)](https://files.minecraftforge.net)
[![Latest Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Latest&prefix=1.18.2-&query=$.promos["1.18.2-latest"]&color=blue&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtYnVnIGZhLXctMTYiIGFyaWEtaGlkZGVuPSJ0cnVlIiBkYXRhLWljb249ImJ1ZyIgZGF0YS1wcmVmaXg9ImZhcyIgZm9jdXNhYmxlPSJmYWxzZSIgcm9sZT0iaW1nIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTUxMS45ODggMjg4LjljLS40NzggMTcuNDMtMTUuMjE3IDMxLjEtMzIuNjUzIDMxLjFINDI0djE2YzAgMjEuODY0LTQuODgyIDQyLjU4NC0xMy42IDYxLjE0NWw2MC4yMjggNjAuMjI4YzEyLjQ5NiAxMi40OTcgMTIuNDk2IDMyLjc1OCAwIDQ1LjI1NS0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDBsLTU0LjczNi01NC43MzZDMzQ1Ljg4NiA0NjcuOTY1IDMxNC4zNTEgNDgwIDI4MCA0ODBWMjM2YzAtNi42MjctNS4zNzMtMTItMTItMTJoLTI0Yy02LjYyNyAwLTEyIDUuMzczLTEyIDEydjI0NGMtMzQuMzUxIDAtNjUuODg2LTEyLjAzNS05MC42MzYtMzIuMTA4bC01NC43MzYgNTQuNzM2Yy0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDAtMTIuNDk2LTEyLjQ5Ny0xMi40OTYtMzIuNzU4IDAtNDUuMjU1bDYwLjIyOC02MC4yMjhDOTIuODgyIDM3OC41ODQgODggMzU3Ljg2NCA4OCAzMzZ2LTE2SDMyLjY2NkMxNS4yMyAzMjAgLjQ5MSAzMDYuMzMuMDEzIDI4OC45LS40ODQgMjcwLjgxNiAxNC4wMjggMjU2IDMyIDI1Nmg1NnYtNTguNzQ1bC00Ni42MjgtNDYuNjI4Yy0xMi40OTYtMTIuNDk3LTEyLjQ5Ni0zMi43NTggMC00NS4yNTUgMTIuNDk4LTEyLjQ5NyAzMi43NTgtMTIuNDk3IDQ1LjI1NiAwTDE0MS4yNTUgMTYwaDIyOS40ODlsNTQuNjI3LTU0LjYyN2MxMi40OTgtMTIuNDk3IDMyLjc1OC0xMi40OTcgNDUuMjU2IDAgMTIuNDk2IDEyLjQ5NyAxMi40OTYgMzIuNzU4IDAgNDUuMjU1TDQyNCAxOTcuMjU1VjI1Nmg1NmMxNy45NzIgMCAzMi40ODQgMTQuODE2IDMxLjk4OCAzMi45ek0yNTcgMGMtNjEuODU2IDAtMTEyIDUwLjE0NC0xMTIgMTEyaDIyNEMzNjkgNTAuMTQ0IDMxOC44NTYgMCAyNTcgMHoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=
)](https://files.minecraftforge.net) [![Discord](https://img.shields.io/discord/313125603924639766.svg?color=%237289da&label=Discord&logo=discord&logoColor=%237289da)](https://discord.gg/UvedJ9m) [![Support](https://img.shields.io/badge/Patreon-Support-orange.svg?logo=Patreon)](https://www.patreon.com/LexManos)
Forge is a free, open-source modding API all of your favourite mods use!
| Version | Support |
|---------| ------------- |
| 1.20.x | Active |
| 1.19.4 | Legacy |
| 1.19.2 | Legacy |
| Version | Support |
| ------------- | ------------- |
| 1.18.x | Active |
| 1.16.x | LTS |
* [Download]
* [Forum]
@@ -21,7 +20,7 @@ Forge is a free, open-source modding API all of your favourite mods use!
# Installing Forge
Go to [the Forge website][Download]
Go to [the Forge website](https://files.minecraftforge.net)
and select the Minecraft version you wish to get Forge for from the list.
You can download the installer for the *Recommended Build* or the
@@ -30,48 +29,37 @@ You can download the installer for the *Recommended Build* or the
into your vanilla launcher environment, where you can then create a new
profile using that version and play the game!
For support and questions, visit [the Support Forum][Support-Forum] or [the Forge Discord server][Discord].
For support and questions, visit [the Support Forum](https://www.minecraftforge.net/forum/forum/18-support-bug-reports/).
[Here is a short video from Rorax showing how to install and setup Forge.](https://www.youtube.com/watch?v=lB3ArN_-3Oc)
# Creating Mods
[See the "Getting Started" section in the Forge Documentation][Getting-Started].
[See the "Getting Started" section in the Forge Documentation](https://mcforge.readthedocs.io/en/latest/gettingstarted/).
# Contribute to Forge
If you wish to actually inspect Forge, submit PRs or otherwise work
with Forge itself, you're in the right place!
[See the guide to setting up a Forge workspace][ForgeDev].
[See the guide to setting up a Forge workspace](http://mcforge.readthedocs.io/en/latest/forgedev/).
### Pull requests
[See the "Making Changes and Pull Requests" section in the Forge documentation][Pull-Requests].
[See the "Making Changes and Pull Requests" section in the Forge documentation](https://mcforge.readthedocs.io/en/latest/forgedev/#making-changes-and-pull-requests).
Please read the contributing guidelines found [here][Contributing] before making a pull request.
Please read the contributing guidelines found [here](CONTRIBUTING.md) before making a pull request.
### Contributor License Agreement
We require all contributors to acknowledge the [Forge Contributor License Agreement][CLA].
Please ensure you have a valid email address associated with your GitHub account to do this. If you have previously
We require all contributors to acknowledge the [Forge Contributor
License Agreement](https://cla-assistant.io/MinecraftForge/MinecraftForge). Please ensure you have a valid email address
associated with your GitHub account to do this. If you have previously
signed it, you should be OK.
#### Donate
*Forge is a large project with many collaborators working on it around the clock. Forge is and will always remain free
to use and modify. However, it costs money to run such a large project as this, so please consider
[becoming a patron][Patreon].*
[Contributing]: ./CONTRIBUTING.md
[CLA]: https://cla-assistant.io/MinecraftForge/MinecraftForge
*Forge is a large project with many collaborators working on it around the clock. Forge is and will always remain free to use and modify. However, it costs money to run such a large project as this, so please consider [becoming a patron](https://www.patreon.com/LexManos).*
[Download]: https://files.minecraftforge.net/
[Forum]: https://forums.minecraftforge.net/
[Support-Forum]: https://forums.minecraftforge.net/forum/18-support-bug-reports/
[Discord]: https://discord.minecraftforge.net/
[Documentation]: https://docs.minecraftforge.net/
[Getting-Started]: https://docs.minecraftforge.net/en/latest/gettingstarted/
[ForgeDev]: https://docs.minecraftforge.net/en/latest/forgedev/
[Pull-Requests]: https://docs.minecraftforge.net/en/latest/forgedev/#making-changes-and-pull-requests
[Patreon]: https://www.patreon.com/LexManos
[Forum]: https://www.minecraftforge.net/forum/
[Discord]: https://discord.gg/UvedJ9m
[Documentation]: https://mcforge.readthedocs.io
+9 -17
View File
@@ -1,7 +1,7 @@
Getting Support Surrounding Forge
=================================
**If you do not want to read the following wall of text, no matter its relevance for helping get your issue resolved quickly, please refer to the [Forge Forums][Bug-Reports] for support.**
**If you do not want to read the following wall of text, no matter its relevance for helping get your issue resolved quickly, please refer to the [Forge Forums](http://www.minecraftforge.net/forum/forum/18-support-bug-reports/) for support.**
Before reporting an issue on this repository's bug tracker, please carefully read this document. There are various resources that are more likely to be able to help you with certain kinds of issues. This document provides instructions on how to determine which place best to report to.
@@ -44,7 +44,7 @@ You can generally tell pretty quickly what caused the error at the head of your
1. If you can find the name of a mod in the trace, that mod most likely is the culprit.
- Be aware that you may find multiple mods as participants of a crash. In that case, you can either report to both or try to reason (using the names in the stacktrace) about which mod is at fault.
2. If you can only find Forge (usually starting with `net.minecraftforge`) alongside Vanilla (usually starting with `net.minecraft`) references, chances are that you have found a bug in Forge. In this case, use one of the various [support channels](#forge-support) listed below.
- The occurrence of a single call to Forge somewhere in the stacktrace *does not* warrant a report to Forge if there are mods present in the crash report.
- The occurence of a single call to Forge somewhere in the stacktrace *does not* warrant a report to Forge if there are mods present in the crash report.
3. If there's no mention of Forge or a mod, but any Vanilla class, you might have found a Vanilla bug. You should probably still report it via the Forge channels first, however, due the nature of modded environments and the rarity of Vanilla crash bugs.
4. Always check whether there's a mention of coremods at the beginning of a crash report. If there is and you can't find any clear culprit, consider reporting to these coremods first, since they are free to change any of the code executed in your environment.
@@ -74,18 +74,17 @@ Please note that this list has to be considered incomplete as there is a large n
If you're certain you've found an issue that is directly caused by Forge, you have the following three platforms available for dealing with it, with decreasing relevance for end-users:
1. **Forge Forums:** There's a dedicated [Support & Bug Report][Bug-Reports] section on the Forge forums, which is led by team of moderators and volunteers who will try to deal with your reports as quickly as possible. *As a player you should probably report here first.*
- For modders, there also is a separate [Modder Support][Modder-Support] section where you can ask all your development-related questions. *You should prefer this section for coding help.*
2. **Forge Discord Server:** You can find a lot of help on the official [Discord Server][Discord], especially smaller issues that you quickly need help with. *Be aware that the channel is more suited towards development questions, so while you may get support there as a player, you should almost always prefer the forums.*
3. **Forge Issue Tracker:** If you're absolutely certain that you've found an issue in the Forge codebase (e.g. you're a modder yourself and have analyzed the stacktrace and relevant code), don't refrain from reporting it on this repository's [issue tracker](https://github.com/MinecraftForge/MinecraftForge/issues). *Please do not use the issues as a means of getting coding help. For developers, the IRC and the [official documentation][Documentation] are the best sources of information.*
1. **Forge Forums:** There's a dedicated [Support & Bug Report](http://www.minecraftforge.net/forum/forum/18-support-bug-reports/) section on the Forge forums, which is led by team of moderators and volunteers who will try to deal with your reports as quickly as possible. *As a player you should probably report here first.*
- For modders, there also is a separate [Modder Support](http://www.minecraftforge.net/forum/forum/70-modder-support/) section where you can ask all your development-related questions. *You should prefer this section for coding help.*
2. **Forge IRC Channel:** You can find a lot of help on the official [`#minecraftforge`](irc://irc.esper.net:6667/minecraftforge) channel on EsperNet. Especially smaller issues that you quickly need help with could be reported there. *Be aware that the channel is more suited towards development questions, so while you may get support there as a player, you should almost always prefer the forums.*
3. **Forge Issue Tracker:** If you're absolutely certain that you've found an issue in the Forge codebase (e.g. you're a modder yourself and have analyzed the stacktrace and relevant code), don't refrain from reporting it on this repository's [issue tracker](https://github.com/MinecraftForge/MinecraftForge/issues). *Please do not use the issues as a means of getting coding help. For developers, the IRC and the [official documentation](https://mcforge.readthedocs.io) are the best sources of information.*
### Mod Support
A lot of mods also use GitHub or other source management systems and their integrated issue trackers. The following options will give you the best chances of reporting your issue in the right place:
- **CurseForge:** Many mods are hosted on the [CurseForge platform](https://minecraft.curseforge.com) nowadays. Chances are you already downloaded them from there or as a part of a modpack hosted there. Projects may specify an issue reporting link on their own page, so simply look for it on the mod's project page and you should be directed to the preferred means of error reporting.
- **Modrinth:** Some modders use [Modrinth](https://modrinth.com/mods) to host their mods, but be mindful that not all mods on this platform are for the Forge modloader.
- **Searching for a repository directly:** You may need to try and find a repository directly on source control sites. The most common ones are [GitHub](https://github.com), [GitLab](https://gitlab.com), and [BitBucket](https://bitbucket.org). Both come with search capabilities, so simply try to search for the mod's name.
- **Searching for a repository directly:** You may need to try and find a repository directly on source control sites. The most common ones are [GitHub](https://github.com) and [BitBucket](https://bitbucket.org). Both come with search capabilities, so simply try to search for the mod's name.
### Vanilla Support
@@ -99,7 +98,7 @@ Now that you've identified the source of your issue and decided on the correct s
### Preventing Duplicates and Unnecessary Reports
Use the target issue tracker's search functionality to look for issues which have descriptions similar to yours, but make sure to also search for already closed issues. If you can find one, verify it is in fact the same (or a very similar issue) and simply comment on it if you have anything else to contribute to the discussion, such as other steps to reproduce it. *Do not create duplicates of an existing issue, since it only puts extra effort on maintainers to recognize and close/mark them.*
Additionally, always check the *latest* changelogs of Forge and participating mods to find out if the bug was already noticed and fixed by the developer. Often the maintainer may only support newer versions of the game, so also check the changelogs for versions of Minecraft you don't actually actively play. If the issue was addressed already, *do not report it*, even if the fix only exists for a newer version of the game, since the developer probably had a good reason to do so. If your issue only exists with versions of a mod that are for very old versions of the game, also refrain from reporting them, considering most modders only support the two latest versions of Minecraft, if at all.
Additionally, always check the *latest* changelogs of Forge and participating mods to find out if the bug was already noticed and fixed by the developer. Often the maintainer may only support newer versions of the game, so also check the changelogs for versions of Minecraft you don't actually actively play. If the issue was adressed already, *do not report it*, even if the fix only exists for a newer version of the game, since the developer probably had a good reason to do so. If your issue only exists with versions of a mod that are for very old versions of the game, also refrain from reporting them, considering most modders only support the two latest versions of Minecraft, if at all.
### Required Information
If you're certain that your issue went unnoticed up until now, you can get to actually writing up the report. Below you can find a list of the *required* information any bug report should contain:
@@ -144,11 +143,4 @@ After you have written your bug report, it is time to *wait*. Please be *patient
Every project has a different way of dealing with issues, so don't wonder if there's no comment on it, but somebody "labels" it with certain markers. This helps developers to more easily keep track of bugs and priorities. When any further input from you is required, you will be asked specifically. Do not bug anyone through other channels about resolving your issue.
Once your issue is closed, you will know whether it was valid or turned out to be unreproducible. If you included all of the data from above in your report, chances are that the latter won't be the case.
[Bug-Reports]: https://forums.minecraftforge.net/forum/18-support-bug-reports/
[Modder-Support]: https://forums.minecraftforge.net/forum/70-modder-support/
[Discord]: https://discord.minecraftforge.net/
[Documentation]: https://docs.minecraftforge.net/
Once your issue is closed, you will know whether it was valid or turned out to be irreproducible. If you included all of the data from above in your report, chances are that the latter won't be the case.
+3 -5
View File
@@ -10,12 +10,10 @@ apply plugin: 'org.javamodularity.moduleplugin'
dependencyUpdates.rejectVersionIf { isNonStable(it.candidate.version) }
dependencies {
compileOnly("org.jetbrains:annotations:${JETBRAINS_ANNOTATIONS_VERSION}")
api("net.minecraftforge:eventbus:${EVENTBUS_VERSION}")
compileOnly('org.jetbrains:annotations:21.0.1')
implementation(project(':fmlloader'))
implementation("commons-io:commons-io:${COMMONS_IO_VERSION}")
api('net.minecraftforge:eventbus:5.0.+')
implementation('commons-io:commons-io:2.5')
}
task sourcesJar(type: Jar) {
@@ -5,93 +5,33 @@
package net.minecraftforge.fml;
import com.mojang.logging.LogUtils;
import org.slf4j.Logger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.BooleanSupplier;
import java.util.function.Supplier;
public class CrashReportCallables
{
private static final Logger LOGGER = LogUtils.getLogger();
public class CrashReportCallables {
private static final List<ISystemReportExtender> crashCallables = Collections.synchronizedList(new ArrayList<>());
/**
* Register a custom {@link ISystemReportExtender}
*/
public static void registerCrashCallable(ISystemReportExtender callable)
{
crashCallables.add(callable);
}
/**
* Register a {@link ISystemReportExtender system report extender} with the given header name and content
* generator, which will always be appended to the system report
* @param headerName The name of the system report entry
* @param reportGenerator The report generator to be called when a crash report is built
*/
public static void registerCrashCallable(String headerName, Supplier<String> reportGenerator)
{
registerCrashCallable(new ISystemReportExtender()
{
public static void registerCrashCallable(String headerName, Supplier<String> reportGenerator) {
registerCrashCallable(new ISystemReportExtender() {
@Override
public String getLabel()
{
public String getLabel() {
return headerName;
}
@Override
public String get()
{
public String get() {
return reportGenerator.get();
}
});
}
/**
* Register a {@link ISystemReportExtender system report extender} with the given header name and content
* generator, which will only be appended to the system report when the given {@link BooleanSupplier} returns true
* @param headerName The name of the system report entry
* @param reportGenerator The report generator to be called when a crash report is built
* @param active The supplier of the flag to be checked when a crash report is built
*/
public static void registerCrashCallable(String headerName, Supplier<String> reportGenerator, BooleanSupplier active)
{
registerCrashCallable(new ISystemReportExtender()
{
@Override
public String getLabel()
{
return headerName;
}
@Override
public String get()
{
return reportGenerator.get();
}
@Override
public boolean isActive()
{
try
{
return active.getAsBoolean();
}
catch (Throwable t)
{
LOGGER.warn("CrashCallable '{}' threw an exception while checking the active flag, disabling", headerName, t);
return false;
}
}
});
}
public static List<ISystemReportExtender> allCrashCallables()
{
public static List<ISystemReportExtender> allCrashCallables() {
return List.copyOf(crashCallables);
}
}
@@ -11,11 +11,9 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Supplier;
import com.google.common.base.Stopwatch;
@@ -55,29 +53,17 @@ public class DeferredWorkQueue
public void runTasks() {
if (tasks.isEmpty()) return;
LOGGER.debug(LOADING, "Dispatching synchronous work for work queue {}: {} jobs", modLoadingStage, tasks.size());
RuntimeException aggregate = new RuntimeException();
Stopwatch timer = Stopwatch.createStarted();
tasks.forEach(t -> makeRunnable(t, Runnable::run, aggregate));
tasks.forEach(t->makeRunnable(t, Runnable::run));
timer.stop();
if (aggregate.getSuppressed().length > 0) {
LOGGER.fatal(
LOADING,
"Synchronous work queue completed exceptionally in {}, see suppressed exceptions for details:",
timer,
aggregate
);
//TODO 1.20: throw aggregate exception
} else {
LOGGER.debug(LOADING, "Synchronous work queue completed in {}", timer);
}
LOGGER.debug(LOADING, "Synchronous work queue completed in {}", timer);
}
private static void makeRunnable(TaskInfo ti, Executor executor, RuntimeException aggregate) {
private static void makeRunnable(TaskInfo ti, Executor executor) {
executor.execute(() -> {
Stopwatch timer = Stopwatch.createStarted();
ModLoadingContext.get().setActiveContainer(ti.owner);
try {
ti.future.exceptionally(t -> captureException(ti.owner.getModId(), aggregate, t));
ti.task.run();
} finally {
ModLoadingContext.get().setActiveContainer(null);
@@ -89,38 +75,13 @@ public class DeferredWorkQueue
});
}
private static <T> T captureException(String modId, RuntimeException aggregate, Throwable throwable) {
if (throwable instanceof CompletionException ce) {
throwable = ce.getCause();
}
aggregate.addSuppressed(throwable);
LOGGER.error("Mod '{}' encountered an error in a deferred task:", modId, throwable);
return null;
}
public CompletableFuture<Void> enqueueWork(final ModContainer modInfo, final Runnable work) {
return enqueueWork(modInfo, taskInfo -> CompletableFuture.runAsync(work, r -> taskInfo.task = r));
return CompletableFuture.runAsync(work, r->tasks.add(new TaskInfo(modInfo, r)));
}
public <T> CompletableFuture<T> enqueueWork(final ModContainer modInfo, final Supplier<T> work) {
return enqueueWork(modInfo, taskInfo -> CompletableFuture.supplyAsync(work, r -> taskInfo.task = r));
return CompletableFuture.supplyAsync(work, r->tasks.add(new TaskInfo(modInfo, r)));
}
private <T> CompletableFuture<T> enqueueWork(final ModContainer modInfo, Function<TaskInfo, CompletableFuture<T>> futureGen) {
TaskInfo taskInfo = new TaskInfo(modInfo);
CompletableFuture<T> future = futureGen.apply(taskInfo);
taskInfo.future = future;
tasks.add(taskInfo);
return future;
}
private static class TaskInfo {
private final ModContainer owner;
private Runnable task;
private CompletableFuture<?> future;
private TaskInfo(ModContainer owner) {
this.owner = owner;
}
}
record TaskInfo(ModContainer owner, Runnable task) {}
}
@@ -8,92 +8,41 @@ package net.minecraftforge.fml;
import java.util.function.BiPredicate;
import java.util.function.Supplier;
/**
* An extension point for a mod container.
*
* <p>An extension point can be registered for a mod container using {@link ModContainer#registerExtensionPoint(Class, Supplier)}
* and retrieved (if present) using {@link ModContainer#getCustomExtension(Class)}. An extension point allows a mod to
* supply an arbitrary value as a record class to another mod or framework through their mod container class, avoiding
* the use of {@link InterModComms} or other external frameworks to pass around these values.</p>
*
* <p>The usual way to declare an extension point is to implement this interface on a record class, with the type
* parameter being a reference to the class itself. For example, {@code record MyExtension(...) extends
* IExtensionPoint<MyExtension>} would declare an extension point which supplies a {@code MyExtension} object. However,
* there is no hard requirement that an extension point's type parameter must be in reference to itself; the type
* parameter may reference another record class instead.</p>
*
* @param <T> the type of the record which is held by the extension point
*/
@SuppressWarnings("unused") // Type parameter T
public interface IExtensionPoint<T extends Record>
{
/**
* Extension point for the compatibility display test used on the server selection screen.
* Compatibility display test for the mod.
* Used for displaying compatibility with remote servers with the same mod, and on disk saves.
*
* <em>Note: "server" and "client" refers to the {@linkplain net.minecraftforge.api.distmarker.Dist#DEDICATED_SERVER dedicated server}
* and {@linkplain net.minecraftforge.api.distmarker.Dist#CLIENT game client} physical distributions, rather than the
* {@linkplain LogicalSide logical server and client}.</em>
* The supplier provides my "local" version for sending across the impl or writing to disk
* The predicate tests the version from a remote instance or save for acceptability (Boolean is true for impl, false for local save)
* and returns true if the version is compatible.
*
* <p>The {@link Supplier} provides the local compatibility version, which is sent from the server to the client
* for multiplayer connections or stored to disk for the world save. The {@link BiPredicate} accepts the remote
* compatibility version and a boolean indicating whether the remote version is from the server or a world save,
* where {@code true} means it is from the server and {@code false} means it is from the world save. The return
* value of the predicate determines whether the remote version is "compatible" for the purposes of the display test.</p>
* <p>Return {@code net.minecraftforge.network.NetworkConstants#IGNORESERVERONLY} in the supplier, if you wish to be ignored
* as a server side only mod.</p>
* <p>Return true in the predicate for all values of the input string (when impl boolean is true) if you are client side,
* and don't care about matching any potential server version.</p>
*
* <p>The local compatibility version may be of the value {@link net.minecraftforge.network.NetworkConstants#IGNORESERVERONLY},
* in which case clients will ignore the mod's presence if it is present on the server but not on the client.
* However, the remote version test predicate must still accept this value as a remote version in order to display
* as compatible if the mod is present on the client.</p>
*
* <p><strong>The compatibility display test does not necessarily indicate the success or failure of an actual
* connection attempt.</strong> Factors such as display test extension misconfiguration, truncation of ping data,
* difference of registry data or network channels between server and client, and others may cause the result of the
* compatibility test to not reflect the actual likelihood of a connection successfully being established between
* the server and the client.</p>
*
* <p>An example declaration of a display test extension registration for a regular mod (requires to be present on
* server and client) is as follows:</p>
* <p>
* Examples: A server only mod
* </p>
* <pre>{@code
* String compatibilityVersion = "1"; // Could be linked with a network channel version or mod version
* ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class,
* () -> new IExtensionPoint.DisplayTest(
* () -> compatibilityVersion,
* (remoteVersion, isFromServer) -> remoteVersion.equals(compatibilityVersion)
* )
* );
* registerExtensionPoint(DisplayTest.class, ()->new DisplayTest(
* ()->NetworkConstants.IGNORESERVERONLY, // ignore me, I'm a server only mod
* (s,b)->true // i accept anything from the server or the save, if I'm asked
* )
* }</pre>
*
* <p>An example declaration of a display test extension registration for a <em>server-side-only</em> mod (does not
* require to be present on the client) is as follows:</p>
* <p>
* Examples: A client only mod
* </p>
* <pre>{@code
* ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class,
* () -> new IExtensionPoint.DisplayTest(
* // Ignore this mod if not present on the client
* () -> NetworkConstants.IGNORESERVERONLY,
* // If present on the client, accept any version if from a server
* (remoteVersion, isFromServer) -> isFromServer
* )
* );
* registerExtensionPoint(DisplayTest.class, ()->new DisplayTest(
* ()->"anything. i don't care", // if i'm actually on the server, this string is sent but i'm a client only mod, so it won't be
* (remoteversionstring,networkbool)->networkbool // i accept anything from the server, by returning true if it's asking about the server
* )
* }</pre>
*
* <p>An example declaration of a display test extension registration for a <em>client-side-only</em> mod (does not
* require to be present on the server) is as follows:</p>
* <pre>{@code
* ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class,
* () -> new IExtensionPoint.DisplayTest(
* // Send any version from server to client, since we will be accepting any version as well
* () -> "dQw4w9WgXcQ",
* // Accept any version on the client, from server or from save
* (remoteVersion, isFromServer) -> true
* )
* );
* }</pre>
*
* @see net.minecraftforge.network.ServerStatusPing
* @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> {
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";
}
record DisplayTest(Supplier<String> suppliedVersion, BiPredicate<String, Boolean> remoteVersionTest) implements IExtensionPoint<DisplayTest> {}
}
@@ -8,79 +8,23 @@ package net.minecraftforge.fml;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.event.IModBusEvent;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* A mod loading state. During mod loading, the mod loader transitions between states in a defined sorted list of states,
* grouped into various {@link ModLoadingPhase phases}.
*
* @see IModStateProvider
*/
public interface IModLoadingState {
/**
* {@return the name of this state}
*/
String name();
/**
* {@return the name of the state immediately previous to this state} This may be a blank name, which indicates this
* is either the first mod loading state or an exceptional mod loading state (such as a situation where errors
* prevent the loading process from continuing normally).
*/
String previous();
/**
* {@return the mod loading phase this state belongs to} For exceptional mod loading states, this should be
* {@link ModLoadingPhase#ERROR}.
*/
ModLoadingPhase phase();
/**
* {@return a function returning a human-friendly message for this state}
*/
Function<ModList, String> message();
/**
* {@return an optional runnable, which runs before starting the transition from this state to the next}
* @see #buildTransition(Executor, Executor, Function, Function)
*/
Optional<Consumer<ModList>> inlineRunnable();
/**
* Builds the transition task for this state with a blank pre-sync and post-sync task.
*
* @param syncExecutor a synchronous executor
* @param parallelExecutor a parallel executor
* @param <T> a type of event fired on the mod-specific event bus
* @return a transition task for this state
* @see #buildTransition(Executor, Executor, Function, Function)
*/
default <T extends Event & IModBusEvent>
Optional<CompletableFuture<Void>> buildTransition(final Executor syncExecutor,
final Executor parallelExecutor) {
return buildTransition(syncExecutor, parallelExecutor,
e -> CompletableFuture.runAsync(() -> {}, e),
e -> CompletableFuture.runAsync(() -> {}, e));
default <T extends Event & IModBusEvent> Optional<CompletableFuture<List<Throwable>>> buildTransition(final Executor syncExecutor, final Executor parallelExecutor) {
return buildTransition(syncExecutor, parallelExecutor, e->CompletableFuture.runAsync(()->{}, e), e->CompletableFuture.runAsync(()->{}, e));
}
/**
* Builds the transition task for this state. The pre-sync and post-sync task functions allow the transition builder
* to run these tasks on the same executor as the actual event dispatch and pre/post hooks.
*
* @param syncExecutor a synchronous executor
* @param parallelExecutor a parallel executor
* @param preSyncTask a function which returns a task to run before event pre-dispatch hook
* @param postSyncTask a function which returns a task to run after event post-dispatch hook
* @param <T> a type of event fired on the mod-specific event bus
* @return a transition task for this state
*/
<T extends Event & IModBusEvent>
Optional<CompletableFuture<Void>> buildTransition(final Executor syncExecutor,
final Executor parallelExecutor,
final Function<Executor, CompletableFuture<Void>> preSyncTask,
final Function<Executor, CompletableFuture<Void>> postSyncTask);
<T extends Event & IModBusEvent> Optional<CompletableFuture<List<Throwable>>> buildTransition(Executor syncExecutor, Executor parallelExecutor, Function<Executor, CompletableFuture<Void>> preSyncTask, Function<Executor, CompletableFuture<Void>> postSyncTask);
}
@@ -7,15 +7,6 @@ package net.minecraftforge.fml;
import java.util.List;
/**
* Provides a list of mod loading states which the mod loader may transition between.
*
* <p>There may be multiple mod state providers in a single application, where all states from each provider is
* combined into a single list and ordered.</p>
*/
public interface IModStateProvider {
/**
* {@return the list of mod loading states known to this provider}
*/
List<IModLoadingState> getAllStates();
}
@@ -9,6 +9,7 @@ import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.event.IModBusEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
@@ -23,26 +24,18 @@ public interface IModStateTransition {
return new NoopTransition();
}
default <T extends Event & IModBusEvent>
CompletableFuture<Void> build(final Executor syncExecutor,
final Executor parallelExecutor,
final Function<Executor, CompletableFuture<Void>> preSyncTask,
final Function<Executor, CompletableFuture<Void>> postSyncTask) {
List<CompletableFuture<Void>> futures = new ArrayList<>();
default <T extends Event & IModBusEvent> CompletableFuture<List<Throwable>> build(Executor syncExecutor, Executor parallelExecutor, Function<Executor, CompletableFuture<Void>> preSyncTask, Function<Executor, CompletableFuture<Void>> postSyncTask) {
List<CompletableFuture<List<Throwable>>> cfs = new ArrayList<>();
this.eventFunctionStream().get()
.map(f -> (EventGenerator<T>) f)
.reduce((head, tail) -> addCompletableFutureTaskForModDispatch(syncExecutor, parallelExecutor, futures, head, ModLoadingStage::currentState, tail))
.ifPresent(last -> addCompletableFutureTaskForModDispatch(syncExecutor, parallelExecutor, futures, last, nextModLoadingStage(), null));
.map(f->(EventGenerator<T>)f)
.reduce((head, tail)-> addCompletableFutureTaskForModDispatch(syncExecutor, parallelExecutor, cfs, head, ModLoadingStage::currentState, tail))
.ifPresent(last-> addCompletableFutureTaskForModDispatch(syncExecutor, parallelExecutor, cfs, last, nextModLoadingStage(), null));
final CompletableFuture<Void> preSyncTaskCF = preSyncTask.apply(syncExecutor);
final CompletableFuture<Void> eventDispatchCF = ModList.gather(futures).thenCompose(ModList::completableFutureFromExceptionList);
final CompletableFuture<Void> postEventDispatchCF = preSyncTaskCF
.thenComposeAsync(n -> eventDispatchCF, parallelExecutor)
.thenApply(r -> {
postSyncTask.apply(syncExecutor);
return null;
});
final CompletableFuture<List<Throwable>> eventDispatchCF = ModList.gather(cfs).thenCompose(ModList::completableFutureFromExceptionList);
final CompletableFuture<List<Throwable>> postEventDispatchCF = preSyncTaskCF.thenComposeAsync(n -> eventDispatchCF, parallelExecutor).thenApply(r -> {
postSyncTask.apply(syncExecutor);
return r;
});
return this.finalActivityGenerator().apply(syncExecutor, postEventDispatchCF);
}
@@ -50,31 +43,18 @@ public interface IModStateTransition {
return ModLoadingStage::nextState;
}
private <T extends Event & IModBusEvent>
EventGenerator<T> addCompletableFutureTaskForModDispatch(final Executor syncExecutor,
final Executor parallelExecutor,
final List<CompletableFuture<Void>> completableFutures,
final EventGenerator<T> eventGenerator,
final BiFunction<ModLoadingStage, Throwable, ModLoadingStage> nextState,
final EventGenerator<T> nextGenerator) {
final Executor selectedExecutor = threadSelector().apply(syncExecutor, parallelExecutor);
var preDispatchHook = (BiFunction<Executor, EventGenerator<T>, CompletableFuture<Void>>) preDispatchHook();
completableFutures.add(preDispatchHook.apply(selectedExecutor, eventGenerator));
completableFutures.add(ModList.get().futureVisitor(eventGenerator, nextState).apply(threadSelector().apply(syncExecutor, parallelExecutor)));
var postDispatchHook = (BiFunction<Executor, EventGenerator<T>, CompletableFuture<Void>>) postDispatchHook();
completableFutures.add(postDispatchHook.apply(selectedExecutor, eventGenerator));
private <T extends Event & IModBusEvent> EventGenerator<T> addCompletableFutureTaskForModDispatch(final Executor syncExecutor, final Executor parallelExecutor, final List<CompletableFuture<List<Throwable>>> completeableFutures, final EventGenerator<T> eventGenerator, BiFunction<ModLoadingStage, Throwable, ModLoadingStage> nextState, final EventGenerator<T> nextGenerator) {
completeableFutures.add(((BiFunction<Executor, EventGenerator<T>, CompletableFuture<List<Throwable>>>)preDispatchHook()).apply(threadSelector().apply(syncExecutor, parallelExecutor), eventGenerator));
completeableFutures.add(ModList.get().futureVisitor(eventGenerator, nextState).apply(threadSelector().apply(syncExecutor, parallelExecutor)));
completeableFutures.add(((BiFunction<Executor, EventGenerator<T>, CompletableFuture<List<Throwable>>>)postDispatchHook()).apply(threadSelector().apply(syncExecutor, parallelExecutor), eventGenerator));
return nextGenerator;
}
Supplier<Stream<EventGenerator<?>>> eventFunctionStream();
ThreadSelector threadSelector();
BiFunction<Executor, CompletableFuture<Void>, CompletableFuture<Void>> finalActivityGenerator();
BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> preDispatchHook();
BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> postDispatchHook();
BiFunction<Executor, CompletableFuture<List<Throwable>>, CompletableFuture<List<Throwable>>> finalActivityGenerator();
BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<List<Throwable>>> preDispatchHook();
BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<List<Throwable>>> postDispatchHook();
interface EventGenerator<T extends Event & IModBusEvent> extends Function<ModContainer, T> {
static <FN extends Event & IModBusEvent> EventGenerator<FN> fromFunction(Function<ModContainer, FN> fn) {
@@ -96,17 +76,17 @@ record NoopTransition() implements IModStateTransition {
}
@Override
public BiFunction<Executor, CompletableFuture<Void>, CompletableFuture<Void>> finalActivityGenerator() {
public BiFunction<Executor, CompletableFuture<List<Throwable>>, CompletableFuture<List<Throwable>>> finalActivityGenerator() {
return (e, t) -> t.thenApplyAsync(Function.identity(), e);
}
@Override
public BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> preDispatchHook() {
return (t, f)-> CompletableFuture.completedFuture(null);
public BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<List<Throwable>>> preDispatchHook() {
return (t, f)-> CompletableFuture.completedFuture(Collections.emptyList());
}
@Override
public BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> postDispatchHook() {
return (t, f)-> CompletableFuture.completedFuture(null);
public BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<List<Throwable>>> postDispatchHook() {
return (t, f)-> CompletableFuture.completedFuture(Collections.emptyList());
}
}
@@ -10,9 +10,4 @@ import java.util.function.Supplier;
public interface ISystemReportExtender extends Supplier<String>
{
String getLabel();
default boolean isActive()
{
return true;
}
}
@@ -5,41 +5,12 @@
package net.minecraftforge.fml;
import net.minecraftforge.api.distmarker.Dist;
/**
* A logical side of the Minecraft game.
* <p>
* The {@linkplain Dist#CLIENT client distribution} has a copy of the logical client and the logical server, while the
* {@linkplain Dist#DEDICATED_SERVER dedicated server distribution} only holds
* the logical server.
*
* @see Dist
*/
public enum LogicalSide
{
/**
* The logical client of the Minecraft game, which interfaces with the player's inputs and renders the player's
* viewpoint.
* <p>
* The logical client is only shipped with the client distribution of the game.
*
* @see Dist#CLIENT
*/
CLIENT,
/**
* The logical server of the Minecraft game, responsible for connecting to clients and running the simulation logic
* on the level.
* <p>
* The logical server is shipped with both client and dedicated server distributions. The client distribution runs
* the logical server for singleplayer mode and LAN play.
*
* @see Dist#DEDICATED_SERVER
*/
SERVER;
CLIENT, SERVER;
/**
* {@return if this logical side is the server}
* @return if the logical side is a server.
*/
public boolean isServer()
{
@@ -47,7 +18,7 @@ public enum LogicalSide
}
/**
* {@return if the logical side is the client}
* @return if the logical side is a client.
*/
public boolean isClient()
{
@@ -56,24 +56,9 @@ public abstract class ModContainer
this.namespace = this.modId;
this.modInfo = info;
this.modLoadingStage = ModLoadingStage.CONSTRUCT;
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);
// 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())));
}
/**
@@ -21,7 +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.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -54,7 +54,6 @@ 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)
{
@@ -112,7 +111,7 @@ public class ModList
return this.fileById.get(modid);
}
<T extends Event & IModBusEvent> Function<Executor, CompletableFuture<Void>> futureVisitor(
<T extends Event & IModBusEvent> Function<Executor, CompletableFuture<List<Throwable>>> futureVisitor(
final IModStateTransition.EventGenerator<T> eventGenerator,
final BiFunction<ModLoadingStage, Throwable, ModLoadingStage> stateChange) {
return executor -> gather(
@@ -121,12 +120,12 @@ public class ModList
.collect(Collectors.toList()))
.thenComposeAsync(ModList::completableFutureFromExceptionList, executor);
}
static CompletionStage<Void> completableFutureFromExceptionList(List<? extends Map.Entry<?, Throwable>> t) {
static CompletionStage<List<Throwable>> completableFutureFromExceptionList(List<? extends Map.Entry<?, Throwable>> t) {
if (t.stream().noneMatch(e->e.getValue()!=null)) {
return CompletableFuture.completedFuture(null);
return CompletableFuture.completedFuture(Collections.emptyList());
} else {
final List<Throwable> throwables = t.stream().filter(e -> e.getValue() != null).map(Map.Entry::getValue).collect(Collectors.toList());
CompletableFuture<Void> cf = new CompletableFuture<>();
CompletableFuture<List<Throwable>> cf = new CompletableFuture<>();
final RuntimeException accumulator = new RuntimeException();
cf.completeExceptionally(accumulator);
throwables.forEach(exception -> {
@@ -157,7 +156,6 @@ 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()));
}
@@ -221,10 +219,6 @@ 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,21 +8,23 @@ 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.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.*;
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.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.Executor;
@@ -82,7 +84,6 @@ public class ModLoader
private boolean loadingStateValid;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private final Optional<Consumer<String>> statusConsumer = StartupMessageManager.modLoaderConsumer();
private final Set<IModLoadingState> completedStates = new HashSet<>();
private ModList modList;
private ModLoader()
@@ -96,7 +97,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)
.filter(ModFileInfo::missingLicense) //Search for files with missing licenses
.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,7 +134,6 @@ 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);
@@ -146,22 +146,6 @@ 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)
@@ -201,7 +185,6 @@ public class ModLoader
statusConsumer.ifPresent(c->c.accept(state.message().apply(this.modList)));
state.inlineRunnable().ifPresent(a->a.accept(this.modList));
state.buildTransition(syncExecutor, parallelExecutor).ifPresent(t->waitForTransition(state, syncExecutor, ticker, t));
completedStates.add(state);
}
private void dispatchAndHandleError(IModLoadingState state, ModWorkManager.DrivenExecutor syncExecutor, Executor parallelExecutor, final Runnable ticker, Function<Executor, CompletableFuture<Void>> preSyncTask, Function<Executor, CompletableFuture<Void>> postSyncTask) {
@@ -212,10 +195,9 @@ public class ModLoader
statusConsumer.ifPresent(c->c.accept(state.message().apply(this.modList)));
state.inlineRunnable().ifPresent(a->a.accept(this.modList));
state.buildTransition(syncExecutor, parallelExecutor, preSyncTask, postSyncTask).ifPresent(t->waitForTransition(state, syncExecutor, ticker, t));
completedStates.add(state);
}
private void waitForTransition(final IModLoadingState state, final ModWorkManager.DrivenExecutor syncExecutor, final Runnable ticker, final CompletableFuture<Void> transition) {
private void waitForTransition(final IModLoadingState state, final ModWorkManager.DrivenExecutor syncExecutor, final Runnable ticker, final CompletableFuture<List<Throwable>> transition) {
while (!transition.isDone()) {
syncExecutor.drive(ticker);
}
@@ -253,22 +235,10 @@ 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(), 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);
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()));
loadingExceptions.add(new ModLoadingException(null, ModLoadingStage.CONSTRUCT, "fml.modloading.missingclasses", null, modFile.getFilePath()));
}
// remove errored mod containers
@@ -299,17 +269,12 @@ public class ModLoader
return get().loadingStateValid;
}
public boolean hasCompletedState(final String stateName) {
IModLoadingState state = stateManager.findState(stateName);
return completedStates.contains(state);
}
public <T extends Event & IModBusEvent> void runEventGenerator(Function<ModContainer, T> generator) {
if (!loadingStateValid) {
LOGGER.error("Cowardly refusing to send event generator to a broken mod state");
return;
}
ModList.get().forEachModInOrder(mc -> mc.acceptEvent(generator.apply(mc)));
ModList.get().forEachModContainer((id, mc) -> mc.acceptEvent(generator.apply(mc)));
}
public <T extends Event & IModBusEvent> void postEvent(T e) {
@@ -317,29 +282,7 @@ public class ModLoader
LOGGER.error("Cowardly refusing to send event {} to a broken mod state", e.getClass().getName());
return;
}
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 postEventWrapContainerInModOrder(T event) {
postEventWithWrapInModOrder(event, (mc, e) -> ModLoadingContext.get().setActiveContainer(mc), (mc, e) -> ModLoadingContext.get().setActiveContainer(null));
}
public <T extends Event & IModBusEvent> void postEventWithWrapInModOrder(T e, BiConsumer<ModContainer, T> pre, BiConsumer<ModContainer, T> post) {
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);
});
ModList.get().forEachModContainer((id, mc) -> mc.acceptEvent(e));
}
public List<ModLoadingWarning> getWarnings()
@@ -5,18 +5,13 @@
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;
@@ -51,24 +46,10 @@ 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));
}
@@ -5,34 +5,10 @@
package net.minecraftforge.fml;
/**
* Phases of mod loading, for grouping mod loading states.
*/
public enum ModLoadingPhase {
/**
* Special phase for exceptional situations and error handling, where mod loading cannot continue normally.
*
* <p>There is conventionally only one state with this phase.</p>
*/
ERROR, // Special state for error handling
/**
* Phase for discovering and gathering mods for loading.
*/
GATHER,
/**
* Phase for the loading of mods found from mod discovery.
*
* @see #GATHER
*/
LOAD,
/**
* Phase after mod loading has completed, for post-loading tasks.
*
* @see #LOAD
*/
COMPLETE,
/**
* Marker phase for the last state in the full mod loading process.
*/
DONE
DONE;
}
@@ -5,53 +5,16 @@
package net.minecraftforge.fml;
/**
* Mod loading stage of mod containers during the mod loading process. These will have a corresponding {@link ModLoadingState}
* in the basic mod loading process provided by FML.
*
* <p>Each mod loading stage has a global {@link DeferredWorkQueue}, which is populated during the execution of the state
* associated with this stage and emptied at the end of the state's execution.</p>
*/
public enum ModLoadingStage
{
/**
* Special stage for exceptional situations and error handling.
*/
ERROR,
/**
* Validation of the mod list.
* TODO: figure out where this is used and why this exists instead of CONSTRUCT being the first normal stage
*/
VALIDATE,
/**
* Default stage of mod containers after construction.
*/
CONSTRUCT,
/**
* Common (non-side-specific) setup and initialization.
*/
COMMON_SETUP,
/**
* Side-specific setup and initialization.
*
* @see net.minecraftforge.api.distmarker.Dist
*/
SIDED_SETUP,
/**
* Stage for enqueuing {@link net.minecraftforge.fml.InterModComms} messages for later processing.
*/
ENQUEUE_IMC,
/**
* Stage for processing received messages though {@link net.minecraftforge.fml.InterModComms}.
*/
PROCESS_IMC,
/**
* Marks the completion of mod loading for this container.
*/
COMPLETE,
/**
* Marks the completion of the full mod loading process.
*/
DONE;
private final DeferredWorkQueue deferredWorkQueue;
@@ -60,27 +23,14 @@ public enum ModLoadingStage
deferredWorkQueue = new DeferredWorkQueue(this);
}
/**
* {@return the next stage after this stage, or {@link #ERROR} if the exception is not {@code null}}
*
* @param exception the exception that occurred during this stage, may be {@code null}
*/
ModLoadingStage nextState(Throwable exception) {
return exception != null ? ERROR : values()[this.ordinal()+1];
}
/**
* {@return this stage, or {@link #ERROR} if the exception is not {@code null}}
*
* @param exception the exception that occurred during this stage, may be {@code null}
*/
public ModLoadingStage currentState(Throwable exception) {
return exception != null ? ERROR : this;
}
/**
* {@return the deferred work queue for this stage}
*/
public DeferredWorkQueue getDeferredWorkQueue() {
return deferredWorkQueue;
}
@@ -8,91 +8,31 @@ package net.minecraftforge.fml;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.event.IModBusEvent;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* Implementation of the {@link IModLoadingState} interface.
*
* @param name the name of this state
* @param previous the name of the state immediately previous to this state
* @param message a function returning a human-friendly message for this state
* @param phase the mod loading phase this state belongs to
* @param inlineRunnable an optional runnable, which runs before starting the transition from this state to the next
* @param transition optional state transition information
*/
public record ModLoadingState(String name, String previous,
Function<ModList, String> message,
ModLoadingPhase phase,
Optional<Consumer<ModList>> inlineRunnable,
Optional<IModStateTransition> transition) implements IModLoadingState {
public record ModLoadingState(String name, String previous, Function<ModList, String> message, ModLoadingPhase phase, Optional<Consumer<ModList>> inlineRunnable, Optional<IModStateTransition> transition) implements IModLoadingState {
@Override
public <T extends Event & IModBusEvent>
Optional<CompletableFuture<Void>> buildTransition(final Executor syncExecutor,
final Executor parallelExecutor,
final Function<Executor, CompletableFuture<Void>> preSyncTask,
final Function<Executor, CompletableFuture<Void>> postSyncTask) {
return transition.map(t -> t.build(syncExecutor, parallelExecutor, preSyncTask, postSyncTask));
public <T extends Event & IModBusEvent> Optional<CompletableFuture<List<Throwable>>> buildTransition(final Executor syncExecutor, final Executor parallelExecutor, Function<Executor, CompletableFuture<Void>> preSyncTask, Function<Executor, CompletableFuture<Void>> postSyncTask) {
return transition.map(t->t.build(syncExecutor, parallelExecutor, preSyncTask, postSyncTask));
}
/**
* {@return an empty mod loading state} The mod loading state has a blank human-readable message, no inline runnable,
* and no state transition information.
*
* @param name the name of the state
* @param previous the name of the immediately previous state to this state
* @param phase the mod loading phase the state belongs to
*/
public static ModLoadingState empty(final String name, final String previous, final ModLoadingPhase phase) {
return new ModLoadingState(name, previous, ml -> "", phase, Optional.empty(), Optional.empty());
return new ModLoadingState(name, previous, ml->"", phase, Optional.empty(), Optional.empty());
}
/**
* Returns a mod loading state with state transition information and a default human-friendly message of
* {@code Processing transition [name]}.
*
* @param name the name of the state
* @param previous the name of the immediately previous state to this state
* @param phase the mod loading phase the state belongs to
* @param transition the state transition information
* @return a mod loading state with state transition information and a default message
*/
public static ModLoadingState withTransition(final String name, final String previous, final ModLoadingPhase phase,
final IModStateTransition transition) {
return new ModLoadingState(name, previous, ml -> "Processing transition " + name, phase, Optional.empty(), Optional.of(transition));
public static ModLoadingState withTransition(final String name, final String previous, final ModLoadingPhase phase, IModStateTransition transition) {
return new ModLoadingState(name, previous, ml->"Processing transition "+name, phase, Optional.empty(), Optional.of(transition));
}
/**
* Returns a mod loading state with state transition information and a custom human-friendly message function.
*
* @param name the name of the state
* @param previous the name of the immediately previous state to this state
* @param message a function returning a human-friendly message for this state
* @param phase the mod loading phase the state belongs to
* @param transition the state transition information
* @return a mod loading state with state transition information and a custom message
*/
public static ModLoadingState withTransition(final String name, final String previous,
final Function<ModList, String> message, final ModLoadingPhase phase,
final IModStateTransition transition) {
public static ModLoadingState withTransition(final String name, final String previous, final Function<ModList, String> message, final ModLoadingPhase phase, IModStateTransition transition) {
return new ModLoadingState(name, previous, message, phase, Optional.empty(), Optional.of(transition));
}
/**
* Returns a mod loading state with an inline runnable and a default human-friendly message of {@code Processing
* work [name]}.
*
* @param name the name of the state
* @param previous the name of the immediately previous state to this state
* @param phase the mod loading phase the state belongs to
* @param inline an optional runnable, which runs before starting the transition from this state to the next
* @return a mod loading state with an inline runnable and default message
*/
public static ModLoadingState withInline(final String name, final String previous, final ModLoadingPhase phase,
final Consumer<ModList> inline) {
return new ModLoadingState(name, previous, ml -> "Processing work " + name, phase, Optional.of(inline), Optional.empty());
public static ModLoadingState withInline(final String name, final String previous, final ModLoadingPhase phase, Consumer<ModList> inline) {
return new ModLoadingState(name, previous, ml->"Processing work "+name, phase, Optional.of(inline), Optional.empty());
}
}
@@ -39,13 +39,4 @@ public class ModStateManager {
nodes.forEach(n->graph.putEdge(lookup.getOrDefault(n.previous(), dummy), n));
return TopologicalSort.topologicalSort(graph, Comparator.comparingInt(nodes::indexOf)).stream().filter(st->st!=dummy).toList();
}
public IModLoadingState findState(final String stateName) {
return stateMap.values()
.stream()
.flatMap(Collection::stream)
.filter(mls -> mls.name().equals(stateName))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Unknown IModLoadingState: " + stateName));
}
}
@@ -10,11 +10,11 @@ import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import com.electronwill.nightconfig.core.file.FileWatcher;
import com.electronwill.nightconfig.core.io.ParsingException;
import com.electronwill.nightconfig.core.io.WritingMode;
import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.loading.FMLConfig;
import net.minecraftforge.fml.loading.FMLPaths;
import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.nio.file.Files;
@@ -24,7 +24,7 @@ import java.util.function.Function;
import static net.minecraftforge.fml.config.ConfigTracker.CONFIG;
public class ConfigFileTypeHandler {
private static final Logger LOGGER = LogUtils.getLogger();
private static final Logger LOGGER = LogManager.getLogger();
static ConfigFileTypeHandler TOML = new ConfigFileTypeHandler();
private static final Path defaultConfigPath = FMLPaths.GAMEDIR.get().resolve(FMLConfig.defaultConfigPath());
@@ -7,18 +7,18 @@ package net.minecraftforge.fml.config;
import com.electronwill.nightconfig.core.CommentedConfig;
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import com.mojang.logging.LogUtils;
import org.slf4j.Logger;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class ConfigTracker {
private static final Logger LOGGER = LogUtils.getLogger();
static final Marker CONFIG = MarkerFactory.getMarker("CONFIG");
private static final Logger LOGGER = LogManager.getLogger();
static final Marker CONFIG = MarkerManager.getMarker("CONFIG");
public static final ConfigTracker INSTANCE = new ConfigTracker();
private final ConcurrentHashMap<String, ModConfig> fileMap;
private final EnumMap<ModConfig.Type, Set<ModConfig>> configSets;
@@ -66,12 +66,8 @@ 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());
// 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.getHandler().unload(configBasePath, config);
config.setConfigData(null);
}
}
@@ -10,11 +10,8 @@ import net.minecraftforge.fml.Bindings;
import java.util.function.Function;
import org.jetbrains.annotations.Nullable;
public interface IConfigEvent {
record ConfigConfig(Function<ModConfig, IConfigEvent> loading, Function<ModConfig, IConfigEvent> reloading, @Nullable Function<ModConfig, IConfigEvent> unloading) {}
record ConfigConfig(Function<ModConfig, IConfigEvent> loading, Function<ModConfig, IConfigEvent> reloading) {}
ConfigConfig CONFIGCONFIG = Bindings.getConfigConfiguration().get();
static IConfigEvent reloading(ModConfig modConfig) {
@@ -23,9 +20,6 @@ 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")
+19 -25
View File
@@ -11,36 +11,30 @@ dependencyUpdates.rejectVersionIf { isNonStable(it.candidate.version) }
configurations.forEach{ it.transitive = false }
dependencies {
compileOnly("org.jetbrains:annotations:${JETBRAINS_ANNOTATIONS_VERSION}")
implementation('net.sf.jopt-simple:jopt-simple:5.0.4')
compileOnly('org.jetbrains:annotations:23.0.0')
api("org.ow2.asm:asm:${ASM_VERSION}")
api("org.ow2.asm:asm-tree:${ASM_VERSION}")
api("org.ow2.asm:asm-commons:${ASM_VERSION}")
api("net.minecraftforge:forgespi:${SPI_VERSION}")
api("org.apache.logging.log4j:log4j-api:${LOG4J_VERSION}")
api("org.slf4j:slf4j-api:${SLF4J_API_VERSION}")
api("com.google.guava:guava:${GUAVA_VERSION}")
api("com.google.code.gson:gson:${GSON_VERSION}")
api("org.apache.maven:maven-artifact:${APACHE_MAVEN_ARTIFACT_VERSION}")
api("org.apache.commons:commons-lang3:${APACHE_COMMONS_LANG3_VERSION}")
api("com.electronwill.night-config:core:${NIGHTCONFIG_VERSION}")
api("com.electronwill.night-config:toml:${NIGHTCONFIG_VERSION}")
api("cpw.mods:modlauncher:${MODLAUNCHER_VERSION}")
api("net.minecraftforge:coremods:${COREMODS_VERSION}")
api("com.mojang:logging:${MOJANG_LOGGING_VERSION}")
api "net.minecraftforge:JarJarSelector:${JARJAR_VERSION}"
api "net.minecraftforge:JarJarMetadata:${JARJAR_VERSION}"
implementation("net.sf.jopt-simple:jopt-simple:${JOPT_SIMPLE_VERSION}")
api('org.apache.logging.log4j:log4j-api:2.17.0')
api('org.slf4j:slf4j-api:1.8.0-beta4')
api('com.google.guava:guava:21.0')
api('com.google.code.gson:gson:2.8.9')
api('org.apache.maven:maven-artifact:3.8.1')
api('org.apache.commons:commons-lang3:3.12.0')
api('com.electronwill.night-config:core:3.6.4')
api('com.electronwill.night-config:toml:3.6.4')
api('cpw.mods:modlauncher:9.0.+')
api('net.minecraftforge:coremods:5.0.+')
api "net.minecraftforge:JarJar:${JARJAR_VERSION}"
implementation("cpw.mods:securejarhandler:${SECUREJARHANDLER_VERSION}")
implementation("net.minecraftforge:accesstransformers:${ACCESSTRANSFORMERS_VERSION}")
implementation("net.minecrell:terminalconsoleappender:${TERMINALCONSOLEAPPENDER_VERSION}")
// implementation("org.jline:jline:${JLINE_VERSION}")
implementation("org.apache.logging.log4j:log4j-core:${LOG4J_VERSION}")
annotationProcessor("org.apache.logging.log4j:log4j-core:${LOG4J_VERSION}")
testCompileOnly("org.jetbrains:annotations:${JETBRAINS_ANNOTATIONS_VERSION}")
implementation('org.apache.logging.log4j:log4j-core:2.17.0')
annotationProcessor('org.apache.logging.log4j:log4j-core:2.17.0')
implementation('net.minecraftforge:accesstransformers:8.0.4')
implementation('net.minecrell:terminalconsoleappender:1.2.0')
// implementation('org.jline:jline:3.12.+')
testCompileOnly('org.jetbrains:annotations:21.0.0')
testRuntimeOnly("cpw.mods:bootstraplauncher:${BOOTSTRAPLAUNCHER_VERSION}")
}
@@ -7,11 +7,7 @@ package net.minecraftforge.fml.common.asm;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
@@ -24,17 +20,7 @@ import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
* Will also de-finalize all fields in on class level annotations.
*/
public class ObjectHolderDefinalize implements ILaunchPluginService {
// Hardcoded map of vanilla classes that should have object holders for each field of the given registry type.
// IMPORTANT: Updates to this collection must be reflected in ObjectHolderRegistry. Duplicated cuz classloaders, yay!
// Classnames are validated in ObjectHolderRegistry.
private static final Map<String, VanillaObjectHolderData> VANILLA_OBJECT_HOLDERS = Stream.of(
new VanillaObjectHolderData("net.minecraft.world.level.block.Blocks", "block", "net.minecraft.world.level.block.Block"),
new VanillaObjectHolderData("net.minecraft.world.item.Items", "item", "net.minecraft.world.item.Item"),
new VanillaObjectHolderData("net.minecraft.world.item.enchantment.Enchantments", "enchantment", "net.minecraft.world.item.enchantment.Enchantment"),
new VanillaObjectHolderData("net.minecraft.world.effect.MobEffects", "mob_effect", "net.minecraft.world.effect.MobEffect"),
new VanillaObjectHolderData("net.minecraft.core.particles.ParticleTypes", "particle_type", "net.minecraft.core.particles.ParticleType"),
new VanillaObjectHolderData("net.minecraft.sounds.SoundEvents", "sound_event", "net.minecraft.sounds.SoundEvent")
).collect(Collectors.toMap(VanillaObjectHolderData::holderClass, Function.identity()));
private final String OBJECT_HOLDER = "Lnet/minecraftforge/registries/ObjectHolder;"; //Don't directly reference this to prevent class loading.
@Override
@@ -42,6 +28,7 @@ public class ObjectHolderDefinalize implements ILaunchPluginService {
return "object_holder_definalize";
}
private static final EnumSet<Phase> YAY = EnumSet.of(Phase.AFTER);
private static final EnumSet<Phase> NAY = EnumSet.noneOf(Phase.class);
@@ -86,8 +73,10 @@ public class ObjectHolderDefinalize implements ILaunchPluginService {
changes.compareAndSet(false, prev != f.access);
});
if (VANILLA_OBJECT_HOLDERS.containsKey(classType.getClassName())) //Class level, de-finalize all fields and add @ObjectHolder to them!
if (hasHolder(classNode.visibleAnnotations)) //Class level, de-finalize all fields and add @ObjectHolder to them!
{
@SuppressWarnings("unused")
String value = getValue(classNode.visibleAnnotations);
classNode.fields.stream().filter(f -> ((f.access & flags) == flags) && f.desc.startsWith("L")).forEach(f ->
{
int prev = f.access;
@@ -106,5 +95,4 @@ public class ObjectHolderDefinalize implements ILaunchPluginService {
return changes.get() ? ComputeFlags.SIMPLE_REWRITE : ComputeFlags.NO_REWRITE;
}
private record VanillaObjectHolderData(String holderClass, String registryName, String registryType) {}
}
@@ -9,7 +9,9 @@ import java.util.EnumSet;
import java.util.List;
import java.util.stream.Collectors;
import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.loading.AdvancedLogMessageAdapter;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
@@ -19,9 +21,6 @@ import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.MethodNode;
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
import org.slf4j.Logger;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
/**
* Modifies specified enums to allow runtime extension by making the $VALUES field non-final and
@@ -29,7 +28,7 @@ import org.slf4j.MarkerFactory;
*/
public class RuntimeEnumExtender implements ILaunchPluginService {
private static final Logger LOGGER = LogUtils.getLogger();
private static final Logger LOGGER = LogManager.getLogger();
private final Type STRING = Type.getType(String.class);
private final Type ENUM = Type.getType(Enum.class);
private final Type MARKER_IFACE = Type.getType("Lnet/minecraftforge/common/IExtensibleEnum;");
@@ -64,49 +63,43 @@ public class RuntimeEnumExtender implements ILaunchPluginService {
final int flags = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC;
FieldNode values = classNode.fields.stream().filter(f -> f.desc.contentEquals(array.getDescriptor()) && ((f.access & flags) == flags)).findFirst().orElse(null);
if (!classNode.interfaces.contains(MARKER_IFACE.getInternalName())) {
return ComputeFlags.NO_REWRITE;
}
//Static methods named "create" with first argument as a string
List<MethodNode> candidates = classNode.methods.stream()
.filter(m -> ((m.access & Opcodes.ACC_STATIC) != 0) && m.name.equals("create"))
.collect(Collectors.toList());
if (candidates.isEmpty()) {
throw new IllegalStateException("IExtensibleEnum has no candidate factory methods: " + classType.getClassName());
}
candidates.forEach(mtd ->
{
Type[] args = Type.getArgumentTypes(mtd.desc);
if (args.length == 0 || !args[0].equals(STRING)) {
if (LOGGER.isErrorEnabled(LogUtils.FATAL_MARKER))
{
StringBuilder sb = new StringBuilder();
LOGGER.fatal(()->new AdvancedLogMessageAdapter(sb-> {
sb.append("Enum has create method without String as first parameter:\n");
sb.append(" Enum: ").append(classType.getDescriptor()).append("\n");
sb.append(" Target: ").append(mtd.name).append(mtd.desc).append("\n");
LOGGER.error(LogUtils.FATAL_MARKER, sb.toString());
}
}));
throw new IllegalStateException("Enum has create method without String as first parameter: " + mtd.name + mtd.desc);
}
Type ret = Type.getReturnType(mtd.desc);
if (!ret.equals(classType)) {
if (LOGGER.isErrorEnabled(LogUtils.FATAL_MARKER))
{
StringBuilder sb = new StringBuilder();
LOGGER.fatal(()->new AdvancedLogMessageAdapter(sb-> {
sb.append("Enum has create method with incorrect return type:\n");
sb.append(" Enum: ").append(classType.getDescriptor()).append("\n");
sb.append(" Target: ").append(mtd.name).append(mtd.desc).append("\n");
sb.append(" Found: ").append(ret.getClassName()).append(", Expected: ").append(classType.getClassName());
LOGGER.error(LogUtils.FATAL_MARKER, sb.toString());
}
}));
throw new IllegalStateException("Enum has create method with incorrect return type: " + mtd.name + mtd.desc);
}
Type[] ctrArgs = new Type[args.length + 1];
ctrArgs[0] = STRING;
ctrArgs[1] = Type.INT_TYPE;
@@ -118,29 +111,23 @@ public class RuntimeEnumExtender implements ILaunchPluginService {
MethodNode ctr = classNode.methods.stream().filter(m -> m.name.equals("<init>") && m.desc.equals(desc)).findFirst().orElse(null);
if (ctr == null)
{
if (LOGGER.isErrorEnabled(LogUtils.FATAL_MARKER))
{
StringBuilder sb = new StringBuilder();
LOGGER.fatal(()->new AdvancedLogMessageAdapter(sb-> {
sb.append("Enum has create method with no matching constructor:\n");
sb.append(" Enum: ").append(classType.getDescriptor()).append("\n");
sb.append(" Candidate: ").append(mtd.desc).append("\n");
sb.append(" Target: ").append(desc).append("\n");
classNode.methods.stream().filter(m -> m.name.equals("<init>")).forEach(m -> sb.append(" : ").append(m.desc).append("\n"));
LOGGER.error(LogUtils.FATAL_MARKER, sb.toString());
}
}));
throw new IllegalStateException("Enum has create method with no matching constructor: " + desc);
}
if (values == null)
{
if (LOGGER.isErrorEnabled(LogUtils.FATAL_MARKER))
{
StringBuilder sb = new StringBuilder();
LOGGER.fatal(()->new AdvancedLogMessageAdapter(sb-> {
sb.append("Enum has create method but we could not find $VALUES. Found:\n");
classNode.fields.stream().filter(f -> (f.access & Opcodes.ACC_STATIC) != 0).
forEach(m -> sb.append(" ").append(m.name).append(" ").append(m.desc).append("\n"));
LOGGER.error(LogUtils.FATAL_MARKER, sb.toString());
}
}));
throw new IllegalStateException("Enum has create method but we could not find $VALUES");
}
@@ -0,0 +1,48 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.util.StringBuilderFormattable;
import java.util.function.Consumer;
public class AdvancedLogMessageAdapter implements Message, StringBuilderFormattable {
private static final long serialVersionUID = -8291754833965087436L;
private static final Object[] EMPTY = new Object[0];
private final Consumer<StringBuilder> logMessageBuilder;
public AdvancedLogMessageAdapter(final Consumer<StringBuilder> logMessageBuilder) {
this.logMessageBuilder = logMessageBuilder;
}
@Override
public String getFormattedMessage() {
return "";
}
@Override
public String getFormat() {
return "";
}
@Override
public Object[] getParameters() {
return EMPTY;
}
@Override
public Throwable getThrowable() {
return null;
}
@Override
public void formatTo(final StringBuilder buffer) {
logMessageBuilder.accept(buffer);
}
}
@@ -5,8 +5,8 @@
package net.minecraftforge.fml.loading;
import com.mojang.logging.LogUtils;
import org.slf4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.net.URI;
import java.net.URISyntaxException;
@@ -16,7 +16,7 @@ import java.nio.file.Paths;
public class ClasspathLocatorUtils {
private static final Logger LOGGER = LogUtils.getLogger();
private static final Logger LOGGER = LogManager.getLogger();
public static Path findJarPathFor(final String resourceName, final String jarName, final URL resource) {
try {
@@ -31,7 +31,7 @@ public class ClasspathLocatorUtils {
//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);
LOGGER.fatal(LogMarkers.SCAN, "Failed to find JAR for class {} - {}", resourceName, jarName);
throw new RuntimeException("Unable to locate "+resourceName+" - "+jarName, e);
}
}
@@ -10,8 +10,8 @@ import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import com.electronwill.nightconfig.core.file.FileNotFoundAction;
import com.electronwill.nightconfig.core.io.ParsingException;
import com.electronwill.nightconfig.core.io.WritingMode;
import com.mojang.logging.LogUtils;
import org.slf4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -21,7 +21,7 @@ import static net.minecraftforge.fml.loading.LogMarkers.CORE;
public class FMLConfig
{
private static final Logger LOGGER = LogUtils.getLogger();
private static final Logger LOGGER = LogManager.getLogger();
private static FMLConfig INSTANCE = new FMLConfig();
private static ConfigSpec configSpec = new ConfigSpec();
static {
@@ -29,7 +29,6 @@ public class FMLConfig
configSpec.define("maxThreads", -1);
configSpec.define("versionCheck", Boolean.TRUE);
configSpec.define("defaultConfigPath", "defaultconfigs");
configSpec.define("disableOptimizedDFU", Boolean.TRUE);
}
private CommentedFileConfig configData;
@@ -61,15 +60,12 @@ public class FMLConfig
{
final Path configFile = FMLPaths.FMLCONFIG.get();
INSTANCE.loadFrom(configFile);
if (LOGGER.isTraceEnabled(CORE))
{
LOGGER.trace(CORE, "Loaded FML config from {}", FMLPaths.FMLCONFIG.get());
LOGGER.trace(CORE, "Splash screen is {}", FMLConfig.splashScreenEnabled());
LOGGER.trace(CORE, "Max threads for mod loading computed at {}", FMLConfig.loadingThreadCount());
LOGGER.trace(CORE, "Version check is {}", FMLConfig.runVersionCheck());
LOGGER.trace(CORE, "Default config paths at {}", FMLConfig.defaultConfigPath());
}
FMLPaths.getOrCreateGameRelativePath(Paths.get(FMLConfig.defaultConfigPath()));
LOGGER.trace(CORE, "Loaded FML config from {}", FMLPaths.FMLCONFIG.get());
LOGGER.trace(CORE, "Splash screen is {}", FMLConfig::splashScreenEnabled);
LOGGER.trace(CORE, "Max threads for mod loading computed at {}", FMLConfig::loadingThreadCount);
LOGGER.trace(CORE, "Version check is {}", FMLConfig::runVersionCheck);
LOGGER.trace(CORE, "Default config paths at {}", FMLConfig::defaultConfigPath);
FMLPaths.getOrCreateGameRelativePath(Paths.get(FMLConfig.defaultConfigPath()), "default config directory");
}
public static boolean splashScreenEnabled() {
@@ -89,8 +85,4 @@ 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);
}
}
@@ -5,7 +5,6 @@
package net.minecraftforge.fml.loading;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.Launcher;
import cpw.mods.modlauncher.api.*;
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
@@ -22,7 +21,8 @@ import net.minecraftforge.fml.loading.targets.CommonLaunchHandler;
import net.minecraftforge.forgespi.Environment;
import net.minecraftforge.forgespi.coremod.ICoreModProvider;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -35,7 +35,7 @@ import static net.minecraftforge.fml.loading.LogMarkers.SCAN;
public class FMLLoader
{
private static final Logger LOGGER = LogUtils.getLogger();
private static final Logger LOGGER = LogManager.getLogger();
private static AccessTransformerService accessTransformer;
private static ModDiscoverer modDiscoverer;
private static ICoreModProvider coreModProvider;
@@ -62,47 +62,47 @@ public class FMLLoader
final Package modLauncherPackage = ITransformationService.class.getPackage();
LOGGER.debug(CORE,"FML found ModLauncher version : {}", modLauncherPackage.getImplementationVersion());
if (!modLauncherPackage.isCompatibleWith("4.0")) {
LOGGER.error(CORE, "Found incompatible ModLauncher specification : {}, version {} from {}", modLauncherPackage.getSpecificationVersion(), modLauncherPackage.getImplementationVersion(), modLauncherPackage.getImplementationVendor());
LOGGER.fatal(CORE,"Found incompatible ModLauncher specification : {}, version {} from {}", modLauncherPackage.getSpecificationVersion(), modLauncherPackage.getImplementationVersion(), modLauncherPackage.getImplementationVendor());
throw new IncompatibleEnvironmentException("Incompatible modlauncher found "+modLauncherPackage.getSpecificationVersion());
}
accessTransformer = (AccessTransformerService) environment.findLaunchPlugin("accesstransformer").orElseThrow(()-> {
LOGGER.error(CORE, "Access Transformer library is missing, we need this to run");
LOGGER.fatal(CORE,"Access Transformer library is missing, we need this to run");
return new IncompatibleEnvironmentException("Missing AccessTransformer, cannot run");
});
final Package atPackage = accessTransformer.getClass().getPackage();
LOGGER.debug(CORE,"FML found AccessTransformer version : {}", atPackage.getImplementationVersion());
if (!atPackage.isCompatibleWith("1.0")) {
LOGGER.error(CORE, "Found incompatible AccessTransformer specification : {}, version {} from {}", atPackage.getSpecificationVersion(), atPackage.getImplementationVersion(), atPackage.getImplementationVendor());
LOGGER.fatal(CORE,"Found incompatible AccessTransformer specification : {}, version {} from {}", atPackage.getSpecificationVersion(), atPackage.getImplementationVersion(), atPackage.getImplementationVendor());
throw new IncompatibleEnvironmentException("Incompatible accesstransformer found "+atPackage.getSpecificationVersion());
}
eventBus = environment.findLaunchPlugin("eventbus").orElseThrow(()-> {
LOGGER.error(CORE, "Event Bus library is missing, we need this to run");
LOGGER.fatal(CORE,"Event Bus library is missing, we need this to run");
return new IncompatibleEnvironmentException("Missing EventBus, cannot run");
});
final Package eventBusPackage = eventBus.getClass().getPackage();
LOGGER.debug(CORE,"FML found EventBus version : {}", eventBusPackage.getImplementationVersion());
if (!eventBusPackage.isCompatibleWith("1.0")) {
LOGGER.error(CORE, "Found incompatible EventBus specification : {}, version {} from {}", eventBusPackage.getSpecificationVersion(), eventBusPackage.getImplementationVersion(), eventBusPackage.getImplementationVendor());
LOGGER.fatal(CORE,"Found incompatible EventBus specification : {}, version {} from {}", eventBusPackage.getSpecificationVersion(), eventBusPackage.getImplementationVersion(), eventBusPackage.getImplementationVendor());
throw new IncompatibleEnvironmentException("Incompatible eventbus found "+eventBusPackage.getSpecificationVersion());
}
runtimeDistCleaner = (RuntimeDistCleaner)environment.findLaunchPlugin("runtimedistcleaner").orElseThrow(()-> {
LOGGER.error(CORE, "Dist Cleaner is missing, we need this to run");
LOGGER.fatal(CORE,"Dist Cleaner is missing, we need this to run");
return new IncompatibleEnvironmentException("Missing DistCleaner, cannot run!");
});
LOGGER.debug(CORE, "Found Runtime Dist Cleaner");
var coreModProviders = ServiceLoaderUtils.streamWithErrorHandling(ServiceLoader.load(FMLLoader.class.getModule().getLayer(), ICoreModProvider.class), sce -> LOGGER.error(CORE, "Failed to load a coremod library, expect problems", sce)).toList();
var coreModProviders = ServiceLoaderUtils.streamWithErrorHandling(ServiceLoader.load(FMLLoader.class.getModule().getLayer(), ICoreModProvider.class), sce -> LOGGER.fatal(CORE, "Failed to load a coremod library, expect problems", sce)).toList();
if (coreModProviders.isEmpty()) {
LOGGER.error(CORE, "Found no coremod provider. Cannot run");
LOGGER.fatal(CORE, "Found no coremod provider. Cannot run");
throw new IncompatibleEnvironmentException("No coremod library found");
} else if (coreModProviders.size() > 1) {
LOGGER.error(CORE, "Found multiple coremod providers : {}. Cannot run", coreModProviders.stream().map(p -> p.getClass().getName()).collect(Collectors.toList()));
LOGGER.fatal(CORE, "Found multiple coremod providers : {}. Cannot run", coreModProviders.stream().map(p -> p.getClass().getName()).collect(Collectors.toList()));
throw new IncompatibleEnvironmentException("Multiple coremod libraries found");
}
@@ -114,7 +114,7 @@ public class FMLLoader
LOGGER.debug(CORE, "Found ForgeSPI package implementation version {}", Environment.class.getPackage().getImplementationVersion());
LOGGER.debug(CORE, "Found ForgeSPI package specification {}", Environment.class.getPackage().getSpecificationVersion());
if (Integer.parseInt(Environment.class.getPackage().getSpecificationVersion()) < 2) {
LOGGER.error(CORE, "Found an out of date ForgeSPI implementation: {}, loading cannot continue", Environment.class.getPackage().getSpecificationVersion());
LOGGER.fatal(CORE, "Found an out of date ForgeSPI implementation: {}, loading cannot continue", Environment.class.getPackage().getSpecificationVersion());
throw new IncompatibleEnvironmentException("ForgeSPI is out of date, we cannot continue");
}
@@ -122,7 +122,7 @@ public class FMLLoader
Class.forName("com.electronwill.nightconfig.core.Config", false, environment.getClass().getClassLoader());
Class.forName("com.electronwill.nightconfig.toml.TomlFormat", false, environment.getClass().getClassLoader());
} catch (ClassNotFoundException e) {
LOGGER.error(CORE, "Failed to load NightConfig");
LOGGER.fatal(CORE, "Failed to load NightConfig");
throw new IncompatibleEnvironmentException("Missing NightConfig");
}
}
@@ -134,12 +134,12 @@ public class FMLLoader
final Optional<ILaunchHandlerService> launchHandler = environment.findLaunchHandler(launchTarget);
LOGGER.debug(CORE, "Using {} as launch service", launchTarget);
if (launchHandler.isEmpty()) {
LOGGER.error(CORE, "Missing LaunchHandler {}, cannot continue", launchTarget);
LOGGER.fatal(CORE,"Missing LaunchHandler {}, cannot continue", launchTarget);
throw new RuntimeException("Missing launch handler: " + launchTarget);
}
if (!(launchHandler.get() instanceof CommonLaunchHandler)) {
LOGGER.error(CORE, "Incompatible Launch handler found - type {}, cannot continue", launchHandler.get().getClass().getName());
LOGGER.fatal(CORE, "Incompatible Launch handler found - type {}, cannot continue", launchHandler.get().getClass().getName());
throw new RuntimeException("Incompatible launch handler found");
}
commonLaunchHandler = (CommonLaunchHandler)launchHandler.get();
@@ -5,12 +5,10 @@
package net.minecraftforge.fml.loading;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.api.IEnvironment;
import org.slf4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
@@ -24,7 +22,7 @@ public enum FMLPaths
CONFIGDIR("config"),
FMLCONFIG(false, CONFIGDIR, "fml.toml");
private static final Logger LOGGER = LogUtils.getLogger();
private static final Logger LOGGER = LogManager.getLogger();
private final Path relativePath;
private final boolean isDirectory;
private Path absolutePath;
@@ -59,41 +57,16 @@ public enum FMLPaths
for (FMLPaths path : FMLPaths.values())
{
path.absolutePath = rootPath.resolve(path.relativePath).toAbsolutePath().normalize();
if (path.isDirectory && !Files.isDirectory(path.absolutePath))
if (path.isDirectory)
{
try {
Files.createDirectories(path.absolutePath);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (LOGGER.isDebugEnabled(CORE))
{
LOGGER.debug(CORE, "Path {} is {}", path, path.absolutePath);
FileUtils.getOrCreateDirectory(path.absolutePath, path.name());
}
LOGGER.debug(CORE,"Path {} is {}", ()-> path, ()-> path.absolutePath);
}
}
/**
* @deprecated Use {@link #getOrCreateGameRelativePath(Path)} instead.
*/
@Deprecated(forRemoval = true, since = "1.19.4")
public static Path getOrCreateGameRelativePath(Path path, String name) {
return getOrCreateGameRelativePath(path);
}
public static Path getOrCreateGameRelativePath(Path path) {
Path gameFolderPath = FMLPaths.GAMEDIR.get().resolve(path);
if (!Files.isDirectory(gameFolderPath)) {
try {
Files.createDirectories(gameFolderPath);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return gameFolderPath;
return FileUtils.getOrCreateDirectory(FMLPaths.GAMEDIR.get().resolve(path), name);
}
public Path relative() {
@@ -5,14 +5,14 @@
package net.minecraftforge.fml.loading;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.api.*;
import joptsimple.ArgumentAcceptingOptionSpec;
import joptsimple.OptionSpecBuilder;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import net.minecraftforge.forgespi.Environment;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import java.util.ArrayList;
import java.util.Arrays;
@@ -26,7 +26,7 @@ import static net.minecraftforge.fml.loading.LogMarkers.CORE;
public class FMLServiceProvider implements ITransformationService
{
private static final Logger LOGGER = LogUtils.getLogger();
private static final Logger LOGGER = LogManager.getLogger();
private ArgumentAcceptingOptionSpec<String> modsOption;
private ArgumentAcceptingOptionSpec<String> modListsOption;
private ArgumentAcceptingOptionSpec<String> mavenRootsOption;
@@ -5,32 +5,41 @@
package net.minecraftforge.fml.loading;
import com.mojang.logging.LogUtils;
import org.slf4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.Path;
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
public class FileUtils
{
private static final Logger LOGGER = LogUtils.getLogger();
private static final Logger LOGGER = LogManager.getLogger();
/**
* @deprecated Use normal Java NIO methods instead
*/
@Deprecated(forRemoval = true, since = "1.19.4")
public static Path getOrCreateDirectory(Path dirPath, String dirLabel) {
if (!Files.isDirectory(dirPath.getParent())) {
getOrCreateDirectory(dirPath.getParent(), "parent of "+dirLabel);
}
if (!Files.isDirectory(dirPath))
{
LOGGER.debug(CORE,"Making {} directory : {}", dirLabel, dirPath);
try {
Files.createDirectories(dirPath);
Files.createDirectory(dirPath);
} catch (IOException e) {
if (e instanceof FileAlreadyExistsException) {
LOGGER.fatal(CORE,"Failed to create {} directory - there is a file in the way", dirLabel);
} else {
LOGGER.fatal(CORE,"Problem with creating {} directory (Permissions?)", dirLabel, e);
}
throw new RuntimeException("Problem creating directory", e);
}
LOGGER.debug(CORE,"Created {} directory : {}", dirLabel, dirPath);
} else {
LOGGER.debug(CORE,"Found existing {} directory : {}", dirLabel, dirPath);
}
return dirPath;
}
@@ -44,16 +53,4 @@ 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;
}
}
@@ -5,7 +5,6 @@
package net.minecraftforge.fml.loading;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.Launcher;
import cpw.mods.modlauncher.api.IModuleLayerManager;
import cpw.mods.modlauncher.util.ServiceLoaderUtils;
@@ -13,10 +12,11 @@ import net.minecraftforge.fml.loading.progress.StartupMessageManager;
import net.minecraftforge.forgespi.language.IModLanguageProvider;
import net.minecraftforge.fml.loading.moddiscovery.ExplodedDirectoryLocator;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.artifact.versioning.VersionRange;
import org.slf4j.Logger;
import java.net.URISyntaxException;
import java.nio.file.Files;
@@ -38,7 +38,7 @@ import static net.minecraftforge.fml.loading.LogMarkers.LOADING;
public class LanguageLoadingProvider
{
private static final Logger LOGGER = LogUtils.getLogger();
private static final Logger LOGGER = LogManager.getLogger();
private final List<IModLanguageProvider> languageProviders = new ArrayList<>();
private final ServiceLoader<IModLanguageProvider> serviceLoader;
private final Map<String, ModLanguageWrapper> languageProviderMap = new HashMap<>();
@@ -81,7 +81,7 @@ public class LanguageLoadingProvider
loadLanguageProviders();
}
private void loadLanguageProviders() {
LOGGER.debug(CORE, "Found {} language providers", ServiceLoaderUtils.streamServiceLoader(()->serviceLoader, sce->LOGGER.error("Problem with language loaders")).count());
LOGGER.debug(CORE, "Found {} language providers", ServiceLoaderUtils.streamServiceLoader(()->serviceLoader, sce->LOGGER.fatal("Problem with language loaders")).count());
serviceLoader.forEach(languageProviders::add);
languageProviders.forEach(lp -> {
@@ -94,7 +94,7 @@ public class LanguageLoadingProvider
Optional<String> implementationVersion = JarVersionLookupHandler.getImplementationVersion(lp.getClass());
String impl = implementationVersion.orElse(Files.isDirectory(lpPath) ? FMLLoader.versionInfo().forgeVersion().split("\\.")[0] : null);
if (impl == null) {
LOGGER.error(CORE, "Found unversioned language provider {}", lp.name());
LOGGER.fatal(CORE, "Found unversioned language provider {}", lp.name());
throw new RuntimeException("Failed to find implementation version for language provider "+ lp.name());
}
LOGGER.debug(CORE, "Found language provider {}, version {}", lp.name(), impl);
@@ -134,7 +134,7 @@ public class LanguageLoadingProvider
}
public IModLanguageProvider findLanguage(ModFile mf, String modLoader, VersionRange modLoaderVersion) {
final String languageFileName = mf.getProvider() instanceof ExplodedDirectoryLocator ? "in-development" : mf.getFileName();
final String languageFileName = mf.getLocator() 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);
@@ -5,13 +5,13 @@
package net.minecraftforge.fml.loading;
import com.mojang.logging.LogUtils;
import org.slf4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
public class LauncherVersion {
private static final Logger LOGGER = LogUtils.getLogger();
private static final Logger LOGGER = LogManager.getLogger();
private static final String launcherVersion;
static {
@@ -5,14 +5,14 @@
package net.minecraftforge.fml.loading;
import com.mojang.logging.LogUtils;
import org.slf4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.nio.file.Files;
import java.nio.file.Path;
public class LibraryFinder {
private static final Logger LOGGER = LogUtils.getLogger();
private static final Logger LOGGER = LogManager.getLogger();
private static Path libsPath;
static Path findLibsPath() {
if (libsPath == null) {
@@ -10,18 +10,11 @@ import net.minecraftforge.fml.loading.moddiscovery.BackgroundScanHandler;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
import net.minecraftforge.forgespi.locating.IModFile;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.*;
import java.util.stream.Collectors;
/**
@@ -35,7 +28,7 @@ public class LoadingModList
private final List<ModInfo> sortedList;
private final Map<String, ModFileInfo> fileById;
private final List<EarlyLoadingException> preLoadErrors;
private List<IModFile> brokenFiles;
private List<ModFile> brokenFiles;
private LoadingModList(final List<ModFile> modFiles, final List<ModInfo> sortedList)
{
@@ -161,11 +154,11 @@ public class LoadingModList
return preLoadErrors;
}
public void setBrokenFiles(final List<IModFile> brokenFiles) {
public void setBrokenFiles(final List<ModFile> brokenFiles) {
this.brokenFiles = brokenFiles;
}
public List<IModFile> getBrokenFiles() {
public List<ModFile> getBrokenFiles() {
return this.brokenFiles;
}
}
@@ -5,12 +5,12 @@
package net.minecraftforge.fml.loading;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
public class LogMarkers {
public static final Marker CORE = MarkerFactory.getMarker("CORE");
public static final Marker LOADING = MarkerFactory.getMarker("LOADING");
public static final Marker SCAN = MarkerFactory.getMarker("SCAN");
public static final Marker SPLASH = MarkerFactory.getMarker("SPLASH");
public static final Marker CORE = MarkerManager.getMarker("CORE");
public static final Marker LOADING = MarkerManager.getMarker("LOADING");
public static final Marker SCAN = MarkerManager.getMarker("SCAN");
public static final Marker SPLASH = MarkerManager.getMarker("SPLASH");
}
@@ -5,10 +5,10 @@
package net.minecraftforge.fml.loading;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.api.INameMappingService;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.BufferedReader;
import java.io.IOException;
@@ -20,7 +20,7 @@ import java.util.function.BiConsumer;
import java.util.function.BiFunction;
public class MCPNamingService implements INameMappingService {
private static final Logger LOGGER = LogUtils.getLogger();
private static final Logger LOGGER = LogManager.getLogger();
private HashMap<String, String> methods;
private HashMap<String, String> fields;
@@ -5,29 +5,20 @@
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 org.apache.logging.log4j.LogManager;
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.Set;
import java.util.zip.ZipFile;
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) {
ModDirTransformerDiscoverer.scan(gameDirectory);
@@ -49,7 +40,7 @@ public class ModDirTransformerDiscoverer implements ITransformerDiscoveryService
try (var walk = Files.walk(modsDir, 1)){
walk.forEach(ModDirTransformerDiscoverer::visitFile);
} catch (IOException | IllegalStateException ioe) {
LOGGER.error("Error during early discovery", ioe);
LogManager.getLogger().error("Error during early discovery", ioe);
}
}
@@ -57,11 +48,14 @@ public class ModDirTransformerDiscoverer implements ITransformerDiscoveryService
if (!Files.isRegularFile(path)) return;
if (!path.toString().endsWith(".jar")) return;
if (LamdbaExceptionUtils.uncheck(() -> Files.size(path)) == 0) return;
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)));
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) {
LogManager.getLogger().error("Zip Error when loading jar file {}", path, ioe);
}
}
}
@@ -5,9 +5,9 @@
package net.minecraftforge.fml.loading;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
import org.slf4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.io.InputStream;
@@ -24,7 +24,7 @@ import static net.minecraftforge.fml.loading.LogMarkers.CORE;
public class ModJarURLHandler extends URLStreamHandler
{
private static final Logger LOGGER = LogUtils.getLogger();
private static final Logger LOGGER = LogManager.getLogger();
// modjar://modid/path/to/file
@Override
protected URLConnection openConnection(URL url) {
@@ -7,10 +7,8 @@ package net.minecraftforge.fml.loading;
import com.google.common.graph.GraphBuilder;
import com.google.common.graph.MutableGraph;
import com.mojang.logging.LogUtils;
import cpw.mods.jarhandling.SecureJar;
import net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator;
import net.minecraftforge.forgespi.language.IModFileInfo;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.fml.loading.EarlyLoadingException.ExceptionData;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
@@ -18,9 +16,10 @@ 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 org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.slf4j.Logger;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
@@ -34,7 +33,7 @@ import static net.minecraftforge.fml.loading.LogMarkers.LOADING;
public class ModSorter
{
private static final Logger LOGGER = LogUtils.getLogger();
private static final Logger LOGGER = LogManager.getLogger();
private final UniqueModListBuilder uniqueModListBuilder;
private List<ModFile> modFiles;
private List<ModInfo> sortedList;
@@ -99,10 +98,11 @@ public class ModSorter
catch (CyclePresentException e)
{
Set<Set<ModFileInfo>> cycles = e.getCycles();
if (LOGGER.isErrorEnabled(LOADING))
{
LOGGER.error(LOADING, "Mod Sorting failed.\nDetected Cycles: {}\n", cycles);
}
LOGGER.error(LOADING, () -> new AdvancedLogMessageAdapter(buffer ->
buffer.append("Mod Sorting failed.\n")
.append("Detected Cycles: ")
.append(cycles)
.append('\n')));
var dataList = cycles.stream()
.<ModFileInfo>mapMulti(Iterable::forEach)
.<IModInfo>mapMulti((mf,c)->mf.getMods().forEach(c))
@@ -160,10 +160,9 @@ public class ModSorter
systemMods.add("minecraft");
// Find mod file from MinecraftLocator to define the system mods
modFiles.stream()
.filter(modFile -> modFile.getProvider().getClass() == MinecraftLocator.class)
.filter(modFile -> modFile.getLocator().getClass() == MinecraftLocator.class)
.map(ModFile::getSecureJar)
.map(SecureJar::moduleDataProvider)
.map(SecureJar.ModuleDataProvider::getManifest)
.map(SecureJar::getManifest)
.map(Manifest::getMainAttributes)
.map(mf -> mf.getValue("FML-System-Mods"))
.filter(Objects::nonNull)
@@ -16,11 +16,14 @@ import java.util.function.Consumer;
import java.util.stream.Collectors;
import com.google.common.collect.Streams;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.api.distmarker.OnlyIns;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
import org.objectweb.asm.Handle;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -30,14 +33,11 @@ import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.MethodNode;
import org.slf4j.Logger;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
public class RuntimeDistCleaner implements ILaunchPluginService
{
private static final Logger LOGGER = LogUtils.getLogger();
private static final Marker DISTXFORM = MarkerFactory.getMarker("DISTXFORM");
private static final Logger LOGGER = LogManager.getLogger();
private static final Marker DISTXFORM = MarkerManager.getMarker("DISTXFORM");
private static String DIST;
private static final String ONLYIN = Type.getDescriptor(OnlyIn.class);
private static final String ONLYINS = Type.getDescriptor(OnlyIns.class);
@@ -53,7 +53,7 @@ public class RuntimeDistCleaner implements ILaunchPluginService
AtomicBoolean changes = new AtomicBoolean();
if (remove(classNode.visibleAnnotations, DIST))
{
LOGGER.error(DISTXFORM, "Attempted to load class {} for invalid dist {}", classNode.name, DIST);
LOGGER.fatal(DISTXFORM, "Attempted to load class {} for invalid dist {}", classNode.name, DIST);
throw new RuntimeException("Attempted to load class "+ classNode.name + " for invalid dist "+ DIST);
}
@@ -5,7 +5,8 @@
package net.minecraftforge.fml.loading;
import org.slf4j.Logger;
import org.apache.logging.log4j.Logger;
import java.io.PrintStream;
/**
@@ -5,27 +5,21 @@
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.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.slf4j.Logger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.joining;
import static net.minecraftforge.fml.loading.LogMarkers.LOADING;
import static java.util.stream.Collectors.*;
public class UniqueModListBuilder
{
private final static Logger LOGGER = LogUtils.getLogger();
private final static Logger LOGGER = LogManager.getLogger();
private final List<ModFile> modFiles;
@@ -72,35 +66,28 @@ public class UniqueModListBuilder
// Its theoretically possible that some mod has somehow moved an id to a secondary place, thus causing a dupe.
// We can't handle this
final List<String> dupedModErrors = modIds.values().stream()
final List<IModInfo> dupedMods = modIds.values().stream()
.filter(modInfos -> modInfos.size() > 1)
.map(mods -> String.format("\tMod ID: '%s' from mod files: %s",
mods.get(0).getModId(),
mods.stream()
.map(modInfo -> modInfo.getOwningFile().getFile().getFileName()).collect(joining(", "))
)).toList();
.map(modInfos -> modInfos.get(0))
.toList();
if (!dupedModErrors.isEmpty()) {
LOGGER.error(LOADING, "Found duplicate mods:\n{}", dupedModErrors.stream().collect(joining("\n")));
throw new EarlyLoadingException("Duplicate mods found", null, dupedModErrors.stream()
.map(s -> new EarlyLoadingException.ExceptionData(s))
.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<String> dupedLibErrors = versionedLibIds.values().stream()
final List<ModFile> dupedLibs = versionedLibIds.values().stream()
.filter(modFiles -> modFiles.size() > 1)
.map(mods -> String.format("\tLibrary: '%s' from files: %s",
getModId(mods.get(0)),
mods.stream()
.map(modFile -> modFile.getFileName()).collect(joining(", "))
)).toList();
.map(modFiles -> modFiles.get(0))
.toList();
if (!dupedLibErrors.isEmpty()) {
LOGGER.error(LOADING, "Found duplicate plugins or libraries:\n{}", dupedLibErrors.stream().collect(joining("\n")));
throw new EarlyLoadingException("Duplicate plugins or libraries found", null, dupedLibErrors.stream()
.map(s -> new EarlyLoadingException.ExceptionData(s))
.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
@@ -119,17 +106,16 @@ public class UniqueModListBuilder
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)));
LOGGER.debug("Selected file {} for modid {} with version {}", modInfoList.get(0).getFileName(), fullList.getKey(), modInfoList.get(0).getModInfos().get(0).getVersion());
}
return modInfoList.get(0);
}
private ArtifactVersion getVersion(final ModFile mf)
{
if (mf.getModFileInfo() == null || mf.getModInfos() == null || mf.getModInfos().isEmpty()) {
if (mf.getModFileInfo() == null) {
return mf.getJarVersion();
}
return mf.getModInfos().get(0).getVersion();
}
@@ -5,7 +5,6 @@
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;
@@ -19,12 +18,16 @@ 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.19.2]").containsVersion(version)) {
// 1.19.2 is Compatible with 1.19.1
add("languageloader.javafml", "42");
add("mod.minecraft", "1.19.1");
add("mod.forge", "42.0.9");
}
// if (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");
// }
}
private static void add(String key, String value) {
overrideVersions.computeIfAbsent(key, k -> new ArrayList<>()).add(new DefaultArtifactVersion(value));
@@ -5,11 +5,9 @@
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 org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.FileNotFoundException;
import java.io.InputStream;
@@ -17,9 +15,9 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
public abstract class AbstractJarFileDependencyLocator extends AbstractJarFileModProvider implements IDependencyLocator
public abstract class AbstractJarFileDependencyLocator extends AbstractJarFileLocator
{
private static final Logger LOGGER = LogUtils.getLogger();
private static final Logger LOGGER = LogManager.getLogger();
protected Optional<InputStream> loadResourceFromModFile(final IModFile modFile, final Path path) {
try {
@@ -38,11 +36,11 @@ public abstract class AbstractJarFileDependencyLocator extends AbstractJarFileMo
protected Optional<IModFile> loadModFileFrom(final IModFile file, final Path path) {
try {
final Path pathInModFile = file.findResource(path.toString());
return Optional.of(createMod(pathInModFile).file());
return createMod(pathInModFile);
}
catch (Exception e) {
LOGGER.error("Failed to load mod file {} from {}", path, file.getFileName());
throw new ModFileLoadingException("Failed to load mod file "+file.getFileName());
return Optional.empty();
}
}
@@ -5,11 +5,11 @@
package net.minecraftforge.fml.loading.moddiscovery;
import com.mojang.logging.LogUtils;
import cpw.mods.jarhandling.SecureJar;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.forgespi.locating.IModFile;
import org.slf4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.nio.file.Files;
@@ -18,9 +18,9 @@ import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream;
public abstract class AbstractJarFileModProvider extends AbstractModProvider
public abstract class AbstractJarFileLocator extends AbstractModLocator
{
private static final Logger LOGGER = LogUtils.getLogger();
private static final Logger LOGGER = LogManager.getLogger();
@Override
public void scanFile(final IModFile file, final Consumer<Path> pathConsumer) {
@@ -5,18 +5,25 @@
package net.minecraftforge.fml.loading.moddiscovery;
import net.minecraftforge.forgespi.locating.IModFile;
import net.minecraftforge.forgespi.locating.IModLocator;
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public abstract class AbstractJarFileModLocator extends AbstractJarFileModProvider implements IModLocator
public abstract class AbstractJarFileModLocator extends AbstractJarFileLocator implements IModLocator
{
@Override
public List<IModLocator.ModFileOrException> scanMods()
public List<IModFile> scanMods()
{
return scanCandidates().map(this::createMod).toList();
return scanCandidates()
.map(this::createMod)
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
}
public abstract Stream<Path> scanCandidates();
@@ -5,7 +5,6 @@
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;
@@ -14,12 +13,10 @@ 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 org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -27,42 +24,42 @@ import java.util.Optional;
import java.util.function.Function;
import java.util.jar.Manifest;
public abstract class AbstractModProvider implements IModProvider
public abstract class AbstractModLocator implements IModLocator
{
private static final Logger LOGGER = LogUtils.getLogger();
private static final Logger LOGGER = LogManager.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) {
protected Optional<IModFile> 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),
jar -> jar.findFile(MODS_TOML).isPresent() ? mjm : JarMetadata.from(jar, path),
(root, p) -> true,
path
);
IModFile mod;
var type = sj.moduleDataProvider().getManifest().getMainAttributes().getValue(ModFile.TYPE);
var type = sj.getManifest().getMainAttributes().getValue(ModFile.TYPE);
if (type == null) {
type = getDefaultJarModType();
}
if (sj.moduleDataProvider().findFile(MODS_TOML).isPresent()) {
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, type);
} else {
return new IModLocator.ModFileOrException(null, new ModFileLoadingException("Invalid mod file found "+ Arrays.toString(path)));
return Optional.empty();
}
mjm.setModFile(mod);
return new IModLocator.ModFileOrException(mod, null);
return Optional.of(mod);
}
protected IModFileInfo manifestParser(final IModFile mod) {
Function<String, Optional<String>> cfg = name -> Optional.ofNullable(mod.getSecureJar().moduleDataProvider().getManifest().getMainAttributes().getValue(name));
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
@@ -5,11 +5,11 @@
package net.minecraftforge.fml.loading.moddiscovery;
import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.loading.LoadingModList;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.forgespi.language.ModFileScanData;
import org.slf4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.time.Duration;
import java.time.Instant;
@@ -28,7 +28,7 @@ public class BackgroundScanHandler
ERRORED
}
private static final Logger LOGGER = LogUtils.getLogger();
private static final Logger LOGGER = LogManager.getLogger();
private final ExecutorService modContentScanner;
private final List<ModFile> pendingFiles;
private final List<ModFile> scannedFiles;
@@ -6,18 +6,15 @@
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;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
@@ -25,9 +22,8 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
public class ClasspathLocator extends AbstractJarFileModLocator
{
private static final Logger LOGGER = LogUtils.getLogger();
public class ClasspathLocator extends AbstractJarFileModLocator {
private static final Logger LOGGER = LogManager.getLogger();
private final List<Path> legacyClasspath = Arrays.stream(System.getProperty("legacyClassPath", "").split(File.pathSeparator)).map(Path::of).toList();
private boolean enabled = false;
@@ -50,7 +46,7 @@ public class ClasspathLocator extends AbstractJarFileModLocator
return paths.build();
} catch (IOException e) {
LOGGER.error(LogMarkers.SCAN, "Error trying to find resources", e);
LOGGER.fatal(LogMarkers.SCAN, "Error trying to find resources", e);
throw new RuntimeException(e);
}
}
@@ -73,22 +69,4 @@ public class ClasspathLocator extends AbstractJarFileModLocator
var launchTarget = (String) arguments.get("launchTarget");
enabled = launchTarget != null && launchTarget.contains("dev");
}
private 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);
}
}
}
@@ -5,11 +5,11 @@
package net.minecraftforge.fml.loading.moddiscovery;
import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.forgespi.locating.IModFile;
import net.minecraftforge.forgespi.locating.IModLocator;
import org.slf4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.nio.file.Files;
@@ -22,7 +22,7 @@ import java.util.function.Consumer;
import java.util.stream.Stream;
public class ExplodedDirectoryLocator implements IModLocator {
private static final Logger LOGGER = LogUtils.getLogger();
private static final Logger LOGGER = LogManager.getLogger();
public record ExplodedMod(String modid, List<Path> paths) {}
@@ -30,14 +30,14 @@ public class ExplodedDirectoryLocator implements IModLocator {
private final Map<ExplodedMod, IModFile> mods = new HashMap<>();
@Override
public List<IModLocator.ModFileOrException> scanMods() {
public List<IModFile> scanMods() {
explodedMods.forEach(explodedMod ->
ModJarMetadata.buildFile(this,
jar->jar.moduleDataProvider().findFile("/META-INF/mods.toml").isPresent(),
jar->jar.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 mods.values().stream().map(mf->new IModLocator.ModFileOrException(mf, null)).toList();
return List.copyOf(mods.values());
}
@Override
@@ -6,22 +6,16 @@
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 ModFileLoadingException
public class InvalidModFileException extends RuntimeException
{
private final IModFileInfo modFileInfo;
public InvalidModFileException(String message, IModFileInfo modFileInfo)
{
super(String.format(Locale.ROOT, "%s (%s)", message, Optional.ofNullable(modFileInfo).map(mf->mf.getFile().getFileName()).orElse("MISSING FILE NAME")));
super(String.format(Locale.ENGLISH, "%s (%s)", message, ((ModFileInfo)modFileInfo).getFile().getFileName()));
this.modFileInfo = modFileInfo;
}
public IModFileInfo getBrokenFile() {
return modFileInfo;
}
}
@@ -5,34 +5,27 @@
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.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
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();
private static final Logger LOGGER = LogManager.getLogger();
@Override
public String name()
@@ -41,15 +34,20 @@ public class JarInJarDependencyLocator extends AbstractJarFileDependencyLocator
}
@Override
public List<IModFile> scanMods(final Iterable<IModFile> loadedMods)
{
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())
{
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();
}
@@ -70,89 +68,46 @@ public class JarInJarDependencyLocator extends AbstractJarFileDependencyLocator
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();
.filter(entry -> !entry.sources().isEmpty()) //Should never be the case, but just to be sure
.map(entry -> new EarlyLoadingException.ExceptionData(
getErrorTranslationKey(entry),
entry.identifier().group() + ":" + entry.identifier().artifact(),
entry.sources()
.stream()
.flatMap(this::getModWithVersionRangeStream)
.map(this::formatError)
.collect(Collectors.joining(", ")))).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";
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()));
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";
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)
{}
private record ModWithVersionRange(IModInfo modInfo, VersionRange versionRange, ArtifactVersion artifactVersion) {}
}
@@ -15,8 +15,7 @@ import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class MavenDirectoryLocator extends AbstractJarFileModLocator
{
public class MavenDirectoryLocator extends AbstractJarFileModLocator {
private List<Path> modCoords;
@Override
@@ -6,7 +6,6 @@
package net.minecraftforge.fml.loading.moddiscovery;
import com.electronwill.nightconfig.core.Config;
import com.mojang.logging.LogUtils;
import cpw.mods.jarhandling.SecureJar;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.fml.loading.LogMarkers;
@@ -14,24 +13,21 @@ 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 org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.nio.file.*;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class MinecraftLocator extends AbstractModProvider implements IModLocator
{
private static final Logger LOGGER = LogUtils.getLogger();
public class MinecraftLocator extends AbstractModLocator implements IModLocator {
private static final Logger LOGGER = LogManager.getLogger();
@Override
public List<IModLocator.ModFileOrException> scanMods() {
public List<IModFile> 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();
@@ -40,11 +36,12 @@ public class MinecraftLocator extends AbstractModProvider implements IModLocator
.map(sj -> new ModFile(sj, this, ModFileParser::modsTomlParser))
.collect(Collectors.<IModFile>toList());
var othermods = baseMC.otherModPaths().stream()
.map(p -> createMod(p.toArray(Path[]::new)))
.filter(Objects::nonNull);
.map(p -> createMod(p.toArray(Path[]::new)).orElse(null))
.filter(Objects::nonNull)
.toList();
artifacts.add(mcjar);
return Stream.concat(artifacts.stream().map(f -> new ModFileOrException(f, null)), othermods).toList();
artifacts.addAll(othermods);
return artifacts;
}
private IModFileInfo buildMinecraftTOML(final IModFile iModFile) {
@@ -7,7 +7,6 @@ package net.minecraftforge.fml.loading.moddiscovery;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.Launcher;
import cpw.mods.modlauncher.api.IModuleLayerManager;
import cpw.mods.modlauncher.util.ServiceLoaderUtils;
@@ -16,48 +15,35 @@ 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;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.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 static final Logger LOGGER = LogManager.getLogger();
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();
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 : {}", 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(",")));
}
}
public ModValidator discoverMods() {
@@ -65,33 +51,18 @@ public class ModDiscoverer {
List<ModFile> loadedFiles = new ArrayList<>();
List<EarlyLoadingException.ExceptionData> discoveryErrorData = new ArrayList<>();
boolean successfullyLoadedMods = true;
List<IModFileInfo> brokenFiles = new ArrayList<>();
//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());
LOGGER.debug(LogMarkers.SCAN,"Trying locator {}", locator);
var locatedFiles = locator.scanMods();
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());
if (locatedFiles.stream().anyMatch(file -> !(file instanceof ModFile))) {
LOGGER.error(LogMarkers.SCAN, "A mod locator returned a file which is not a ModFile instance!. They will be skipped!");
}
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());
@@ -119,7 +90,7 @@ public class ModDiscoverer {
//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) {
for (IModLocator locator : modLocatorList) {
try {
LOGGER.debug(LogMarkers.SCAN,"Trying locator {}", locator);
final List<IModFile> locatedMods = ImmutableList.copyOf(loadedFiles);
@@ -158,7 +129,7 @@ public class ModDiscoverer {
//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);
var validator = new ModValidator(modFilesMap, discoveryErrorData);
validator.stage1Validation();
return validator;
}
@@ -167,7 +138,7 @@ public class ModDiscoverer {
{
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());
LOGGER.info(LogMarkers.SCAN, "Found mod file {} of type {} with provider {}", mf.getFileName(), mf.getType(), mf.getLocator());
StartupMessageManager.modLoaderConsumer().ifPresent(c->c.accept("Found mod file "+mf.getFileName()+" of type "+mf.getType()));
}
loadedFiles.addAll(locatedModFiles);
@@ -6,7 +6,6 @@
package net.minecraftforge.fml.loading.moddiscovery;
import com.google.common.collect.ImmutableMap;
import com.mojang.logging.LogUtils;
import cpw.mods.jarhandling.SecureJar;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.fml.loading.LogMarkers;
@@ -16,11 +15,12 @@ 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.IModProvider;
import net.minecraftforge.forgespi.locating.IModLocator;
import net.minecraftforge.forgespi.locating.ModFileFactory;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.slf4j.Logger;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -39,7 +39,7 @@ public class ModFile implements IModFile {
// Mods either must have a mods.toml or a manifest. We can no longer just put any jar on the classpath.
@Deprecated(forRemoval = true, since = "1.18")
public static final Manifest DEFAULTMANIFEST;
private static final Logger LOGGER = LogUtils.getLogger();
private static final Logger LOGGER = LogManager.getLogger();
static {
DEFAULTMANIFEST = new Manifest();
@@ -54,7 +54,7 @@ public class ModFile implements IModFile {
private final SecureJar jar;
private final Type modFileType;
private final Manifest manifest;
private final IModProvider provider;
private final IModLocator locator;
private IModFileInfo modFileInfo;
private ModFileScanData fileModFileScanData;
private CompletableFuture<ModFileScanData> futureScanResult;
@@ -64,16 +64,16 @@ public class ModFile implements IModFile {
static final Attributes.Name TYPE = new Attributes.Name("FMLModType");
private SecureJar.Status securityStatus;
public ModFile(final SecureJar jar, final IModProvider provider, final ModFileFactory.ModFileInfoParser parser) {
this(jar, provider, parser, parseType(jar));
public ModFile(final SecureJar jar, final IModLocator locator, final ModFileFactory.ModFileInfoParser parser) {
this(jar, locator, parser, parseType(jar));
}
public ModFile(final SecureJar jar, final IModProvider provider, final ModFileFactory.ModFileInfoParser parser, String type) {
this.provider = provider;
public ModFile(final SecureJar jar, final IModLocator locator, final ModFileFactory.ModFileInfoParser parser, String type) {
this.locator = locator;
this.jar = jar;
this.parser = parser;
manifest = this.jar.moduleDataProvider().getManifest();
manifest = this.jar.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);
@@ -129,7 +129,7 @@ public class ModFile implements IModFile {
}
public void scanFile(Consumer<Path> pathConsumer) {
provider.scanFile(this, pathConsumer);
locator.scanFile(this, pathConsumer);
}
public void setFutureScanResult(CompletableFuture<ModFileScanData> future) {
@@ -194,8 +194,8 @@ public class ModFile implements IModFile {
}
@Override
public IModProvider getProvider() {
return provider;
public IModLocator getLocator() {
return locator;
}
@Override
@@ -214,7 +214,7 @@ public class ModFile implements IModFile {
}
private static String parseType(final SecureJar jar) {
final Manifest m = jar.moduleDataProvider().getManifest();
final Manifest m = jar.getManifest();
final Optional<String> value = Optional.ofNullable(m.getMainAttributes().getValue(TYPE));
return value.orElse("MOD");
}
@@ -6,7 +6,6 @@
package net.minecraftforge.fml.loading.moddiscovery;
import com.google.common.base.Strings;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.fml.loading.StringUtils;
@@ -14,7 +13,8 @@ import net.minecraftforge.forgespi.language.IConfigurable;
import net.minecraftforge.forgespi.language.IModFileInfo;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.forgespi.language.MavenVersionAdapter;
import org.slf4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.security.auth.x500.X500Principal;
import java.net.URL;
import java.security.CodeSigner;
@@ -32,7 +32,7 @@ import java.util.stream.Stream;
public class ModFileInfo implements IModFileInfo, IConfigurable
{
private static final Logger LOGGER = LogUtils.getLogger();
private static final Logger LOGGER = LogManager.getLogger();
private final IConfigurable config;
private final ModFile modFile;
private final URL issueURL;
@@ -68,13 +68,10 @@ public class ModFileInfo implements IModFileInfo, IConfigurable
this.mods = modConfigs.stream()
.map(mi-> new ModInfo(this, mi))
.collect(Collectors.toList());
if (LOGGER.isDebugEnabled(LogMarkers.LOADING))
{
LOGGER.debug(LogMarkers.LOADING, "Found valid mod file {} with {} mods - versions {}",
this.modFile.getFileName(),
this.mods.stream().map(IModInfo::getModId).collect(Collectors.joining(",", "{", "}")),
this.mods.stream().map(IModInfo::getVersion).map(Objects::toString).collect(Collectors.joining(",", "{", "}")));
}
LOGGER.debug(LogMarkers.LOADING, "Found valid mod file {} with {} mods - versions {}",
this.modFile::getFileName,
() -> this.mods.stream().map(IModInfo::getModId).collect(Collectors.joining(",", "{", "}")),
() -> this.mods.stream().map(IModInfo::getVersion).map(Objects::toString).collect(Collectors.joining(",", "{", "}")));
}
public ModFileInfo(final ModFile file, final IConfigurable config, final List<LanguageSpec> languageSpecs) {
@@ -8,12 +8,12 @@ package net.minecraftforge.fml.loading.moddiscovery;
import com.electronwill.nightconfig.core.file.FileConfig;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.forgespi.language.IModFileInfo;
import net.minecraftforge.forgespi.locating.IModFile;
import net.minecraftforge.forgespi.locating.ModFileFactory;
import org.slf4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.lang.reflect.Type;
@@ -25,7 +25,7 @@ import java.util.Map;
import java.util.stream.Collectors;
public class ModFileParser {
private static final Logger LOGGER = LogUtils.getLogger();
private static final Logger LOGGER = LogManager.getLogger();
public static IModFileInfo readModList(final ModFile modFile, final ModFileFactory.ModFileInfoParser parser) {
return parser.build(modFile);
@@ -5,17 +5,16 @@
package net.minecraftforge.fml.loading.moddiscovery;
import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.loading.StringSubstitutor;
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.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.artifact.versioning.VersionRange;
import org.slf4j.Logger;
import java.net.URL;
import java.util.Collections;
@@ -27,11 +26,10 @@ import java.util.stream.Collectors;
public class ModInfo implements IModInfo, IConfigurable
{
private static final Logger LOGGER = LogUtils.getLogger();
private static final Logger LOGGER = LogManager.getLogger();
private static final DefaultArtifactVersion DEFAULT_VERSION = new DefaultArtifactVersion("1");
private static final Pattern VALID_MODID = Pattern.compile("^[a-z][a-z0-9_]{1,63}$");
private static final Pattern VALID_NAMESPACE = Pattern.compile("^[a-z][a-z0-9_.-]{1,63}$");
private static final Pattern VALID_VERSION = Pattern.compile("^\\d+.*");
private final ModFileInfo owningFile;
private final String modId;
@@ -43,11 +41,8 @@ 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)
{
@@ -57,12 +52,12 @@ public class ModInfo implements IModInfo, IConfigurable
this.modId = config.<String>getConfigElement("modId")
.orElseThrow(() -> new InvalidModFileException("Missing modId", owningFile));
if (!VALID_MODID.matcher(this.modId).matches()) {
LOGGER.error(LogUtils.FATAL_MARKER, "Invalid modId found in file {} - {} does not match the standard: {}", this.owningFile.getFile().getFilePath(), this.modId, VALID_MODID.pattern());
LOGGER.fatal("Invalid modId found in file {} - {} does not match the standard: {}", this.owningFile.getFile().getFilePath(), this.modId, VALID_MODID.pattern());
throw new InvalidModFileException("Invalid modId found : " + this.modId, owningFile);
}
this.namespace = config.<String>getConfigElement("namespace").orElse(this.modId);
if (!VALID_NAMESPACE.matcher(this.namespace).matches()) {
LOGGER.error(LogUtils.FATAL_MARKER, "Invalid override namespace found in file {} - {} does not match the standard: {}", this.owningFile.getFile().getFilePath(), this.namespace, VALID_NAMESPACE.pattern());
LOGGER.fatal("Invalid override namespace found in file {} - {} does not match the standard: {}", this.owningFile.getFile().getFilePath(), this.namespace, VALID_NAMESPACE.pattern());
throw new InvalidModFileException("Invalid override namespace found : " + this.namespace, owningFile);
}
this.version = config.<String>getConfigElement("version")
@@ -86,23 +81,9 @@ 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
@@ -169,11 +150,6 @@ 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);
@@ -184,11 +160,6 @@ 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;
@@ -196,7 +167,6 @@ 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;
@@ -213,8 +183,6 @@ 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);
}
@@ -259,11 +227,6 @@ public class ModInfo implements IModInfo, IConfigurable
{
return owner;
}
@Override
public Optional<URL> getReferralURL() {
return referralUrl;
}
}
}
@@ -5,13 +5,13 @@
package net.minecraftforge.fml.loading.moddiscovery;
import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.loading.FMLPaths;
import net.minecraftforge.fml.loading.FileUtils;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.fml.loading.MavenCoordinateResolver;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.nio.file.Files;
@@ -23,7 +23,7 @@ import java.util.Objects;
import java.util.stream.Collectors;
public class ModListHandler {
private static final Logger LOGGER = LogUtils.getLogger();
private static final Logger LOGGER = LogManager.getLogger();
/**
* Reads the modList paths specified, and searches each maven root for mods matching. Returns a list of mods
* found.
@@ -5,7 +5,6 @@
package net.minecraftforge.fml.loading.moddiscovery;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.api.IModuleLayerManager;
import cpw.mods.modlauncher.api.ITransformationService;
import net.minecraftforge.fml.loading.EarlyLoadingException;
@@ -13,34 +12,29 @@ 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.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.*;
public class ModValidator {
private static final Logger LOGGER = LogUtils.getLogger();
private static final Logger LOGGER = LogManager.getLogger();
private final Map<IModFile.Type, List<ModFile>> modFiles;
private final List<ModFile> candidatePlugins;
private final List<ModFile> candidateMods;
private LoadingModList loadingModList;
private List<IModFile> brokenFiles;
private List<ModFile> brokenFiles;
private final List<EarlyLoadingException.ExceptionData> discoveryErrorData;
public ModValidator(final Map<IModFile.Type, List<ModFile>> modFiles, final List<IModFileInfo> brokenFiles, final List<EarlyLoadingException.ExceptionData> discoveryErrorData) {
public ModValidator(final Map<IModFile.Type, List<ModFile>> modFiles, 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) {
@@ -48,10 +42,8 @@ public class ModValidator {
}
public void stage1Validation() {
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());
}
brokenFiles = validateFiles(candidateMods);
LOGGER.debug(LogMarkers.SCAN,"Found {} mod files with {} mods", candidateMods::size, ()-> candidateMods.stream().mapToInt(mf -> mf.getModInfos().size()).sum());
StartupMessageManager.modLoaderConsumer().ifPresent(c->c.accept("Found "+ candidateMods.size()+" modfiles to load"));
}
@@ -61,7 +53,7 @@ public class ModValidator {
for (Iterator<ModFile> iterator = mods.iterator(); iterator.hasNext(); )
{
ModFile modFile = iterator.next();
if (!modFile.getProvider().isValid(modFile) || !modFile.identifyMods()) {
if (!modFile.getLocator().isValid(modFile) || !modFile.identifyMods()) {
LOGGER.warn(LogMarkers.SCAN, "File {} has been ignored - it is invalid", modFile.getFilePath());
iterator.remove();
brokenFiles.add(modFile);
@@ -5,12 +5,12 @@
package net.minecraftforge.fml.loading.moddiscovery;
import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.loading.FMLPaths;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.fml.loading.ModDirTransformerDiscoverer;
import net.minecraftforge.fml.loading.StringUtils;
import org.slf4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -23,10 +23,9 @@ import static cpw.mods.modlauncher.api.LamdbaExceptionUtils.uncheck;
/**
* Support loading mods located in JAR files in the mods folder
*/
public class ModsFolderLocator extends AbstractJarFileModLocator
{
public class ModsFolderLocator extends AbstractJarFileModLocator {
private static final String SUFFIX = ".jar";
private static final Logger LOGGER = LogUtils.getLogger();
private static final Logger LOGGER = LogManager.getLogger();
private final Path modFolder;
private final String customName;
@@ -5,12 +5,12 @@
package net.minecraftforge.fml.loading.moddiscovery;
import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.forgespi.language.IModLanguageProvider;
import net.minecraftforge.forgespi.language.ModFileScanData;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.objectweb.asm.ClassReader;
import org.slf4j.Logger;
import java.io.IOException;
import java.io.InputStream;
@@ -19,7 +19,7 @@ import java.nio.file.Path;
import java.util.List;
public class Scanner {
private static final Logger LOGGER = LogUtils.getLogger();
private static final Logger LOGGER = LogManager.getLogger();
private final ModFile fileToScan;
public Scanner(final ModFile fileToScan) {
@@ -34,6 +34,8 @@ import java.util.function.IntSupplier;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
import javax.annotation.Nullable;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.glfw.GLFW.glfwCreateWindow;
import static org.lwjgl.opengl.GL11.*;
@@ -5,8 +5,8 @@
package net.minecraftforge.fml.loading.targets;
import com.mojang.logging.LogUtils;
import org.slf4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.ArrayList;
import java.util.Arrays;
@@ -20,7 +20,7 @@ import java.util.function.Supplier;
* Can not use JOptSimple as that doesn't parse out the values for keys unless the spec says it has a value.
*/
class ArgumentList {
private static final Logger LOGGER = LogUtils.getLogger();
private static final Logger LOGGER = LogManager.getLogger();
private List<Supplier<String[]>> entries = new ArrayList<>();
private Map<String, EntryValue> values = new HashMap<>();
@@ -5,26 +5,31 @@
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;
import net.minecraftforge.api.distmarker.Dist;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.nio.file.Path;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.stream.Stream;
public abstract class CommonClientLaunchHandler extends CommonLaunchHandler {
private static final Logger LOGGER = LogManager.getLogger();
@Override public Dist getDist() { return Dist.CLIENT; }
@Override public String getNaming() { return "srg"; }
@Override public boolean isProduction() { return true; }
@Override
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
public Callable<Void> 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;
};
}
@@ -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,7 +30,8 @@ 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 = findJarOnClasspath(legacyCP, "client-extra");
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);
// The MC code/Patcher edits are in exploded directories
final var modstream = Stream.<List<Path>>builder();
@@ -41,7 +42,6 @@ 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,6 +50,8 @@ public abstract class CommonDevLaunchHandler extends CommonLaunchHandler {
if (getDist().isDedicatedServer())
return arguments;
fixNatives();
if (isData())
return arguments;
@@ -78,18 +80,11 @@ 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 -> FileUtils.matchFileName(e, "fmlcore", "javafmllanguage", "lowcodelanguage", "mclanguage"))
.filter(e -> e.contains("fmlcore") || e.contains("javafmllanguage") || e.contains("lowcodelanguage") || e.contains("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('\\', '/');
@@ -124,4 +119,18 @@ 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);
}
}
@@ -5,12 +5,12 @@
package net.minecraftforge.fml.loading.targets;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.api.ILaunchHandlerService;
import cpw.mods.modlauncher.api.ITransformingClassLoaderBuilder;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.api.distmarker.Dist;
import org.slf4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.File;
import java.nio.file.Path;
@@ -25,7 +25,7 @@ import java.util.stream.Collectors;
public abstract class CommonLaunchHandler implements ILaunchHandlerService {
public record LocatedPaths(List<Path> minecraftPaths, BiPredicate<String, String> minecraftFilter, List<List<Path>> otherModPaths, List<Path> otherArtifacts) {}
protected static final Logger LOGGER = LogUtils.getLogger();
protected static final Logger LOGGER = LogManager.getLogger();
public abstract Dist getDist();
@@ -6,11 +6,12 @@
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;
import net.minecraftforge.api.distmarker.Dist;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.nio.file.Path;
import java.util.List;
@@ -19,14 +20,17 @@ import java.util.function.BiPredicate;
import java.util.stream.Stream;
public abstract class CommonServerLaunchHandler extends CommonLaunchHandler {
protected static final Logger LOGGER = LogManager.getLogger();
@Override public Dist getDist() { return Dist.DEDICATED_SERVER; }
@Override public String getNaming() { return "srg"; }
@Override public boolean isProduction() { return true; }
@Override
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
public Callable<Void> 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;
};
}
@@ -10,6 +10,8 @@ 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;
@@ -39,4 +41,8 @@ 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")));
}
}
@@ -5,7 +5,6 @@
package net.minecraftforge.fml.loading.targets;
import cpw.mods.modlauncher.api.ServiceRunner;
import net.minecraftforge.api.distmarker.Dist;
import java.util.concurrent.Callable;
@@ -14,11 +13,12 @@ public class FMLClientDevLaunchHandler extends CommonDevLaunchHandler {
@Override public Dist getDist() { return Dist.CLIENT; }
@Override
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
public Callable<Void> 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;
};
}
}
@@ -5,7 +5,6 @@
package net.minecraftforge.fml.loading.targets;
import cpw.mods.modlauncher.api.ServiceRunner;
import net.minecraftforge.api.distmarker.Dist;
import java.util.concurrent.Callable;
@@ -18,11 +17,12 @@ public class FMLClientUserdevLaunchHandler extends FMLUserdevLaunchHandler {
public Dist getDist() { return Dist.CLIENT; }
@Override
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
public Callable<Void> 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;
};
}
}
@@ -5,7 +5,6 @@
package net.minecraftforge.fml.loading.targets;
import cpw.mods.modlauncher.api.ServiceRunner;
import net.minecraftforge.api.distmarker.Dist;
import java.util.concurrent.Callable;
@@ -21,11 +20,12 @@ public class FMLDataUserdevLaunchHandler extends FMLUserdevLaunchHandler {
public boolean isData() { return true; }
@Override
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
public Callable<Void> 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;
};
}
}
@@ -5,7 +5,6 @@
package net.minecraftforge.fml.loading.targets;
import cpw.mods.modlauncher.api.ServiceRunner;
import net.minecraftforge.api.distmarker.Dist;
import java.util.concurrent.Callable;
@@ -14,11 +13,12 @@ public class FMLServerDevLaunchHandler extends CommonDevLaunchHandler {
@Override public Dist getDist() { return Dist.DEDICATED_SERVER; }
@Override
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
public Callable<Void> 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;
};
}
}
@@ -5,7 +5,6 @@
package net.minecraftforge.fml.loading.targets;
import cpw.mods.modlauncher.api.ServiceRunner;
import net.minecraftforge.api.distmarker.Dist;
import java.util.concurrent.Callable;
@@ -15,11 +14,12 @@ public class FMLServerUserdevLaunchHandler extends FMLUserdevLaunchHandler {
@Override public Dist getDist() { return Dist.DEDICATED_SERVER; }
@Override
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
public Callable<Void> 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;
};
}
}
@@ -5,7 +5,6 @@
package net.minecraftforge.fml.loading.targets;
import cpw.mods.modlauncher.api.ServiceRunner;
import net.minecraftforge.api.distmarker.Dist;
import java.util.concurrent.Callable;
@@ -14,11 +13,12 @@ public class ForgeClientDevLaunchHandler extends CommonDevLaunchHandler {
@Override public Dist getDist() { return Dist.CLIENT; }
@Override
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
public Callable<Void> 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;
};
}
}
Loaded 100 of 2211 files, more files were not shown because too many files have changed in this diff. Show more