Compare commits

..
1 Commits
Author SHA1 Message Date
LexManos 2fb2bbeed0 1.20.1 RB 2023-09-21 10:17:41 -07:00
3102 changed files with 57237 additions and 56637 deletions

No files matched your search

-3
View File
@@ -21,6 +21,3 @@ src/generated/**/*.json text eol=lf
src/generated_test/**/.cache/cache text eol=lf
src/generated_test/**/*.json text eol=lf
# We force eclipse to use unix line endings
ide/eclipse/template/.settings/*.prefs text eol=lf
-28
View File
@@ -1,28 +0,0 @@
name: Publish
on:
push:
branches: [ '1.21.1' ]
permissions:
contents: read
jobs:
build:
uses: MinecraftForge/SharedActions/.github/workflows/gradle.yml@main
with:
java: 21
gradle_setup: './gradlew setup'
gradle_tasks: 'build publish'
artifact_name: 'forge'
secrets:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
PROMOTE_ARTIFACT_WEBHOOK: ${{ secrets.PROMOTE_ARTIFACT_WEBHOOK }}
PROMOTE_ARTIFACT_USERNAME: ${{ secrets.PROMOTE_ARTIFACT_USERNAME }}
PROMOTE_ARTIFACT_PASSWORD: ${{ secrets.PROMOTE_ARTIFACT_PASSWORD }}
MAVEN_USER: ${{ secrets.MAVEN_USER }}
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
SIGN_KEY_ALIAS: ${{ secrets.SIGN_KEY_ALIAS }}
SIGN_KEY_PASSWORD: ${{ secrets.SIGN_KEY_PASSWORD }}
SIGN_KEYSTORE_PASSWORD: ${{ secrets.SIGN_KEYSTORE_PASSWORD }}
SIGN_KEYSTORE_DATA: ${{ secrets.SIGN_KEYSTORE_DATA }}
+1 -1
View File
@@ -7,7 +7,7 @@ jobs:
name: Upload payload
runs-on: ubuntu-latest
steps:
- uses: actions/upload-artifact@v4 # Payload artifact is consumed in Actionable
- uses: actions/upload-artifact@v3 # Payload artifact is consumed in Actionable
with:
name: payload
path: ${{ github.event_path }}
@@ -7,7 +7,7 @@ jobs:
name: Upload payload
runs-on: ubuntu-latest
steps:
- uses: actions/upload-artifact@v4 # Payload artifact is consumed in Actionable
- uses: actions/upload-artifact@v3 # Payload artifact is consumed in Actionable
with:
name: payload
path: ${{ github.event_path }}
+4 -9
View File
@@ -13,9 +13,6 @@
/classes
*.iml
# We however WANT the ide folder as it has tempaltes/configs for developing Forge.
!ide/**
#gradle
/build
/.gradle
@@ -26,6 +23,7 @@
/projects/mcp/
/projects/clean/
/projects/forge/
/projects/fmlonly/
#/patches/*
#occupational hazards
@@ -34,10 +32,9 @@
/projects/**/run/
/projects/**/*.launch
/repo/
src/*/generated/**/.cache/
# Generated by gradle every import
**/src/main/resources/META-INF/MANIFEST.MF
/fmlloader/src/main/resources/forge_version.json
src/generated/resources/.cache/
src/generated_test/resources/.cache/
src/generated_mdk/resources/.cache/
#Patch rejects
#/patches-/
@@ -50,5 +47,3 @@ changelog.txt
*.7z
/logs/
/ForgeRoot.ipr
/ForgeRoot.iws
+104
View File
@@ -0,0 +1,104 @@
<?xml version="1.0"?>
<project>
<modelVersion>4.0.0</modelVersion>
<name>MinecraftForge_Forge Config DSL Script</name>
<groupId>MinecraftForge_Forge</groupId>
<artifactId>MinecraftForge_Forge_dsl</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.jetbrains.teamcity</groupId>
<artifactId>configs-dsl-kotlin-parent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<repositories>
<repository>
<id>jetbrains-all</id>
<url>https://download.jetbrains.com/teamcity-repository</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>teamcity-server</id>
<url>https://teamcity.minecraftforge.net/app/dsl-plugins-repository</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>JetBrains</id>
<url>https://download.jetbrains.com/teamcity-repository</url>
</pluginRepository>
</pluginRepositories>
<build>
<sourceDirectory>${basedir}</sourceDirectory>
<plugins>
<plugin>
<artifactId>kotlin-maven-plugin</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
<version>${kotlin.version}</version>
<configuration/>
<executions>
<execution>
<id>compile</id>
<phase>process-sources</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>process-test-sources</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jetbrains.teamcity</groupId>
<artifactId>teamcity-configs-maven-plugin</artifactId>
<version>${teamcity.dsl.version}</version>
<configuration>
<format>kotlin</format>
<dstDir>target/generated-configs</dstDir>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.jetbrains.teamcity</groupId>
<artifactId>configs-dsl-kotlin</artifactId>
<version>${teamcity.dsl.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.teamcity</groupId>
<artifactId>configs-dsl-kotlin-plugins</artifactId>
<version>1.0-SNAPSHOT</version>
<type>pom</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>${kotlin.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-script-runtime</artifactId>
<version>${kotlin.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
+208
View File
@@ -0,0 +1,208 @@
import jetbrains.buildServer.configs.kotlin.v2019_2.*
import jetbrains.buildServer.configs.kotlin.v2019_2.projectFeatures.githubIssues
import jetbrains.buildServer.configs.kotlin.*
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.gradle
/*
The settings script is an entry point for defining a TeamCity
project hierarchy. The script should contain a single call to the
project() function with a Project instance or an init function as
an argument.
VcsRoots, BuildTypes, Templates, and subprojects can be
registered inside the project using the vcsRoot(), buildType(),
template(), and subProject() methods respectively.
To debug settings scripts in command-line, run the
mvnDebug org.jetbrains.teamcity:teamcity-configs-maven-plugin:generate
command and attach your debugger to the port 8000.
To debug in IntelliJ Idea, open the 'Maven Projects' tool window (View
-> Tool Windows -> Maven Projects), find the generate task node
(Plugins -> teamcity-configs -> teamcity-configs:generate), the
'Debug' option is available in the context menu for the task.
*/
version = "2021.2"
project {
buildType(Build)
buildType(BuildSecondaryBranches)
buildType(PullRequests)
buildType(PullRequestChecks)
buildType(PullRequestCompatibility)
params {
text("docker_jdk_version", "17", label = "Gradle version", description = "The version of the JDK to use during execution of tasks in a JDK.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
text("docker_gradle_version", "7.5.1", label = "Gradle version", description = "The version of Gradle to use during execution of Gradle tasks.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
text("git_main_branch", "1.20.x", label = "Git Main Branch", description = "The git main or default branch to use in VCS operations.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
text("git_branch_spec", """
+:refs/heads/(main*)
+:refs/heads/(master*)
+:refs/heads/(develop|release|staging|main|master)
+:refs/heads/(1.*)
""".trimIndent(), label = "The branch specification of the repository", description = "By default all main branches are build by the configuration. Modify this value to adapt the branches build.", display = ParameterDisplay.HIDDEN, allowEmpty = true)
text("github_repository_name", "MinecraftForge", label = "The github repository name. Used to connect to it in VCS Roots.", description = "This is the repository slug on github. So for example `MinecraftForge` or `MinecraftForge`. It is interpolated into the global VCS Roots.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
text("env.PUBLISHED_JAVA_ARTIFACT_ID", "forge", label = "Published artifact id", description = "The maven coordinate artifact id that has been published by this build. Can not be empty.", allowEmpty = false)
text("env.PUBLISHED_JAVA_FML_ARTIFACT_ID", "fmlonly", label = "Published fmlonly artifact id", description = "The maven coordinate artifact id for fml only that has been published by this build. Can not be empty.", allowEmpty = false)
text("env.PUBLISHED_JAVA_FML_ARTIFACT_VERSION", "0.0.0-SNAPSHOT", label = "Published fmlonly artifact version", description = "The version for fml only that has been published by this build. Can not be empty.", allowEmpty = false)
text("env.PUBLISHED_JAVA_GROUP", "net.minecraftforge", label = "Published group", description = "The maven coordinate group that has been published by this build. Can not be empty.", allowEmpty = false)
//These are references and not actually keys
password("env.CROWDIN_KEY", "credentialsJSON:a3102dbe-805d-4177-9f54-3d2c2eb08fd5", display = ParameterDisplay.HIDDEN)
password("env.KEYSTORE_URL", "credentialsJSON:a7ae1c82-8058-4061-8d12-7f6bc2618d2e", display = ParameterDisplay.HIDDEN)
password("env.KEYSTORE_PASSWORD", "credentialsJSON:d7b964e3-a1fd-47a8-b892-6f601fe47479", display = ParameterDisplay.HIDDEN)
text("additional_publishing_gradle_parameters", "-PcrowdinKey=%env.CROWDIN_KEY% -PkeystoreKeyPass=%env.KEYSTORE_PASSWORD% -PkeystoreStorePass=%env.KEYSTORE_PASSWORD% -Pkeystore=%env.KEYSTORE_URL%", label = "Additional gradle parameters for publish", description = "Contains the additional gradle parameters used during publishing.", display = ParameterDisplay.HIDDEN, allowEmpty = true)
checkbox("should_execute_build", "false", label = "Should build", description = "Indicates if the build task should be executed.", display = ParameterDisplay.HIDDEN,
checked = "true", unchecked = "false")
checkbox("should_execute_test", "false", label = "Should build", description = "Indicates if the build task should be executed.", display = ParameterDisplay.HIDDEN,
checked = "true", unchecked = "false")
}
features {
githubIssues {
id = "MinecraftForge_MinecraftForge__IssueTracker"
displayName = "MinecraftForge/MinecraftForge"
repositoryURL = "https://github.com/MinecraftForge/MinecraftForge"
}
}
}
object Build : BuildType({
templates(AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_BuildMainBranches"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"), AbsoluteId("MinecraftForge_BuildUsingGradle"), AbsoluteId("MinecraftForge_PublishProjectUsingGradle"), AbsoluteId("MinecraftForge_TriggersStaticFilesWebpageGenerator"))
id("MinecraftForge_MinecraftForge__Build")
name = "Build"
description = "Builds and Publishes the main branches of the project."
features {
feature {
id = "trigger_fml_only_files_generator"
type = "triggerBuildFeature"
param("triggers", "MinecraftForge_FilesGenerator_GeneratePages")
param("parameters", """
env.PUBLISHED_JAVA_FML_ARTIFACT_ID~env.PUBLISHED_JAVA_ARTIFACT_ID
env.PUBLISHED_JAVA_FML_ARTIFACT_VERSION~env.PUBLISHED_JAVA_ARTIFACT_VERSION
env.PUBLISHED_JAVA_GROUP
""".trimIndent())
}
}
})
object BuildSecondaryBranches : BuildType({
templates(AbsoluteId("MinecraftForge_ExcludesBuildingDefaultBranch"), AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_BuildMainBranches"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"), AbsoluteId("MinecraftForge_BuildUsingGradle"))
id("MinecraftForge_MinecraftForge__BuildSecondaryBranches")
name = "Build - Secondary Branches"
description = "Builds and Publishes the secondary branches of the project."
params {
text("git_branch_spec", """
+:refs/heads/(*)
-:refs/heads/noci*
""".trimIndent(), label = "The branch specification of the repository", description = "By default all main branches are build by the configuration. Modify this value to adapt the branches build.", display = ParameterDisplay.HIDDEN, allowEmpty = true)
}
vcs {
branchFilter = """
+:*
-:1.*
-:<default>
""".trimIndent()
}
})
object PullRequests : BuildType({
templates(AbsoluteId("MinecraftForge_BuildPullRequests"), AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"), AbsoluteId("MinecraftForge_BuildUsingGradle"))
id("MinecraftForge_MinecraftForge__PullRequests")
name = "Pull Requests"
description = "Builds pull requests for the project"
params {
checkbox("should_execute_build", "true", label = "Should build", description = "Indicates if the build task should be executed.", display = ParameterDisplay.HIDDEN,
checked = "true", unchecked = "false")
text(
"gradle_build_task",
"assemble",
label = "Gradle build task to execute during build",
description = "Determines the build task that is executed to build the project.",
display = ParameterDisplay.HIDDEN,
allowEmpty = false
)
}
vcs {
branchFilter = """
+:*
-:1.*
-:<default>
""".trimIndent()
}
})
object PullRequestChecks : BuildType({
templates(AbsoluteId("MinecraftForge_BuildPullRequests"), AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"))
id("MinecraftForge_MinecraftForge__PullRequestChecks")
name = "Pull Requests (Checks)"
description = "Checks pull requests for the project"
steps {
gradle {
name = "Check"
id = "RUNNER_10_Check"
tasks = "checkAll"
gradleParams = "--continue %gradle_custom_args%"
enableStacktrace = true
dockerImage = "%docker_gradle_image%"
dockerRunParameters = """
-v "/opt/cache/agent/gradle:/home/gradle/.gradle"
-v "/opt/cache/shared/gradle:/home/gradle/rocache:ro"
--network=host
-u 1000:1000
%docker_additional_args%
""".trimIndent()
}
}
vcs {
branchFilter = """
+:*
-:1.*
-:<default>
""".trimIndent()
}
})
object PullRequestCompatibility : BuildType({
templates(AbsoluteId("MinecraftForge_BuildPullRequests"), AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"))
id("MinecraftForge_MinecraftForge__PullRequestCompatibility")
name = "Pull Requests (Compatibility)"
description = "Validates binary compatibility for pull requests made to the project"
steps {
gradle {
name = "Validate"
id = "RUNNER_10_Compatibility"
tasks = "checkJarCompatibility"
gradleParams = "--continue %gradle_custom_args%"
enableStacktrace = true
dockerImage = "%docker_gradle_image%"
dockerRunParameters = """
-v "/opt/cache/agent/gradle:/home/gradle/.gradle"
-v "/opt/cache/shared/gradle:/home/gradle/rocache:ro"
--network=host
-u 1000:1000
%docker_additional_args%
""".trimIndent()
}
}
vcs {
branchFilter = """
+:*
-:1.*
-:<default>
""".trimIndent()
}
})
+65
View File
@@ -0,0 +1,65 @@
Minecraft Forge: Credits/Thank You
Forge is a set of tools and modifications to the Minecraft base game code to assist
mod developers in creating new and exciting content. It has been in development for
several years now, but I would like to take this time thank a few people who have
helped it along it's way.
First, the people who originally created the Forge projects way back in Minecraft
alpha. Eloraam of RedPower, and SpaceToad of Buildcraft, without their acceptiance
of me taking over the project, who knows what Minecraft modding would be today.
Secondly, someone who has worked with me, and developed some of the core features
that allow modding to be as functional, and as simple as it is, cpw. For developing
FML, which stabelized the client and server modding ecosystem. As well as the base
loading system that allows us to modify Minecraft's code as elegently as possible.
Mezz, who has stepped up as the issue and pull request manager. Helping to keep me
sane as well as guiding the community into creating better additions to Forge.
Searge, Bspks, Fesh0r, ProfMobious, and all the rest over on the MCP team {of which
I am a part}. For creating some of the core tools needed to make Minecraft modding
both possible, and as stable as can be.
On that note, here is some specific information of the MCP data we use:
* Minecraft Coder Pack (MCP) *
Forge Mod Loader and Minecraft Forge have permission to distribute and automatically
download components of MCP and distribute MCP data files. This permission is not
transitive and others wishing to redistribute the Minecraft Forge source independently
should seek permission of MCP or remove the MCP data files and request their users
to download MCP separately.
And lastly, the countless community members who have spent time submitting bug reports,
pull requests, and just helping out the community in general. Thank you.
--LexManos
=========================================================================
This is Forge Mod Loader.
You can find the source code at all times at https://github.com/MinecraftForge/MinecraftForge/tree/1.12.x/src/main/java/net/minecraftforge/fml
This minecraft mod is a clean open source implementation of a mod loader for minecraft servers
and minecraft clients.
The code is authored by cpw.
It began by partially implementing an API defined by the client side ModLoader, authored by Risugami.
http://www.minecraftforum.net/topic/75440-
This support has been dropped as of Minecraft release 1.7, as Risugami no longer maintains ModLoader.
It also contains suggestions and hints and generous helpings of code from LexManos, author of MinecraftForge.
http://www.minecraftforge.net/
Additionally, it contains an implementation of topological sort based on that
published at http://keithschwarz.com/interesting/code/?dir=topological-sort
It also contains code from the Maven project for performing versioned dependency
resolution. http://maven.apache.org/
It also contains a partial repackaging of the javaxdelta library from http://sourceforge.net/projects/javaxdelta/
with credit to it's authors.
Forge Mod Loader downloads components from the Minecraft Coder Pack
(http://mcp.ocean-labs.de/index.php/Main_Page) with kind permission from the MCP team.
Vendored
+125
View File
@@ -0,0 +1,125 @@
@Library('forge-shared-library')_
pipeline {
options {
disableConcurrentBuilds()
}
agent {
docker {
image 'gradle:7-jdk16'
//args '-v forgegc:/home/gradle/.gradle_cache/'
}
}
environment {
GRADLE_ARGS = '--no-daemon --console=plain' // No daemon for now as FG3 kinda derps. //'-Dorg.gradle.daemon.idletimeout=5000'
DISCORD_WEBHOOK = credentials('forge-discord-jenkins-webhook')
DISCORD_PREFIX = "Job: Forge Branch: ${BRANCH_NAME} Build: #${BUILD_NUMBER}"
JENKINS_HEAD = 'https://wiki.jenkins-ci.org/download/attachments/2916393/headshot.png'
}
stages {
stage('notify_start') {
when {
not {
changeRequest()
}
}
steps {
discordSend(
title: "${DISCORD_PREFIX} Started",
successful: true,
result: 'ABORTED', //White border
thumbnail: JENKINS_HEAD,
webhookURL: DISCORD_WEBHOOK
)
}
}
/*
stage('pull_cache') {
steps {
sh 'cp -r /home/gradle/.gradle_cache/jdks/ /home/gradle/.gradle/jdks/'
sh 'cp -r /home/gradle/.gradle_cache/wrapper/ /home/gradle/.gradle/wrapper/'
sh 'cp -r /home/gradle/.gradle_cache/caches/ /home/gradle/.gradle/caches/'
}
}
*/
stage('setup') {
steps {
withGradle {
sh './gradlew ${GRADLE_ARGS} --refresh-dependencies --continue setup'
}
script {
env.MYVERSION = sh(returnStdout: true, script: './gradlew :forge:properties -q | grep "^version:" | cut -d" " -f2').trim()
env.FMLONLY_VERSION = sh(returnStdout: true, script: './gradlew :fmlonly:properties -q | grep "^version:" | cut -d" " -f2').trim()
}
}
}
stage('changelog') {
when {
not {
changeRequest()
}
}
steps {
writeChangelog(currentBuild, 'build/changelog.txt')
}
}
stage('publish') {
when {
not {
changeRequest()
}
}
environment {
CROWDIN = credentials('forge-crowdin')
KEYSTORE = credentials('forge-jenkins-keystore-old')
KEYSTORE_KEYPASS = credentials('forge-jenkins-keystore-old-keypass')
KEYSTORE_STOREPASS = credentials('forge-jenkins-keystore-old-keypass')
}
steps {
withCredentials([usernamePassword(credentialsId: 'maven-forge-user', usernameVariable: 'MAVEN_USER', passwordVariable: 'MAVEN_PASSWORD')]) {
withGradle {
sh './gradlew ${GRADLE_ARGS} publish -PkeystoreKeyPass=${KEYSTORE_KEYPASS} -PkeystoreStorePass=${KEYSTORE_STOREPASS} -Pkeystore=${KEYSTORE} -PcrowdinKey=${CROWDIN}'
}
}
}
post {
success {
build job: 'filegenerator', parameters: [string(name: 'COMMAND', value: "promote net.minecraftforge:forge ${env.MYVERSION} latest")], propagate: false, wait: false
build job: 'filegenerator', parameters: [string(name: 'COMMAND', value: "promote net.minecraftforge:fmlonly ${env.FMLONLY_VERSION} latest")], propagate: false, wait: false
}
}
}
stage('test_publish_pr') { //Publish to local repo to test full process, but don't include credentials so it can't sign/publish to maven
when {
changeRequest()
}
environment {
CROWDIN = credentials('forge-crowdin')
}
steps {
withGradle {
sh './gradlew ${GRADLE_ARGS} publish -PcrowdinKey=${CROWDIN}'
}
}
}
}
post {
always {
script {
archiveArtifacts artifacts: 'projects/forge/build/libs/**/*.*', fingerprint: true, onlyIfSuccessful: true, allowEmptyArchive: true
if (env.CHANGE_ID == null) { // This is unset for non-PRs
discordSend(
title: "${DISCORD_PREFIX} Finished ${currentBuild.currentResult}",
description: '```\n' + getChanges(currentBuild) + '\n```',
successful: currentBuild.resultIsBetterOrEqualTo("SUCCESS"),
result: currentBuild.currentResult,
thumbnail: JENKINS_HEAD,
webhookURL: DISCORD_WEBHOOK
)
}
}
}
}
}
+1781 -21
View File
File diff suppressed because it is too large. Load diff
+7 -7
View File
@@ -1,13 +1,13 @@
repositories {
maven { url = 'https://maven.minecraftforge.net/' }
mavenCentral()
mavenCentral()
}
dependencies {
implementation 'org.ow2.asm:asm:9.9.1'
implementation 'org.ow2.asm:asm-tree:9.9.1'
implementation 'net.minecraftforge:srgutils:0.6.2'
implementation 'commons-io:commons-io:2.13.0'
implementation 'org.ow2.asm:asm:9.5'
implementation 'org.ow2.asm:asm-tree:9.5'
implementation 'net.minecraftforge:srgutils:0.5.+'
implementation 'commons-io:commons-io:2.12.0'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'org.eclipse.jgit:org.eclipse.jgit:6.7.0.202309050840-r'
}
implementation 'org.eclipse.jgit:org.eclipse.jgit:6.5.0.202303070854-r'
}
@@ -1,72 +0,0 @@
package net.minecraftforge.forge.tasks
import org.gradle.api.tasks.*
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.bundling.AbstractArchiveTask
import java.util.zip.ZipFile
abstract class BundleList extends DefaultTask {
@InputFiles abstract ConfigurableFileCollection getConfig()
@InputFiles abstract ConfigurableFileCollection getConfigExtra()
@InputFile abstract RegularFileProperty getServerBundle()
@OutputFile abstract RegularFileProperty getOutput()
BundleList() {
config.setFrom(project.configurations.installer)
configExtra.setFrom(project.configurations.installerextra)
output.convention(project.layout.buildDirectory.file("$name/output.list"))
configure {
dependsOn(project.tasks.universalJar)
inputs.file(project.tasks.universalJar.archiveFile)
dependsOn(project.tasks.applyServerBinPatches)
inputs.file(project.tasks.applyServerBinPatches.output)
}
}
@TaskAction
void run() {
def entries = [:] as TreeMap
def resolved = project.configurations.installer.resolvedConfiguration.resolvedArtifacts
for (def dep : resolved) {
def info = Util.getMavenInfoFromDep(dep)
//println("$dep.file.sha1\t$info.name\t$info.path")
entries.put("$info.art.group:$info.art.name", "$dep.file.sha256\t$info.name\t$info.path")
}
resolved = project.configurations.installerextra.resolvedConfiguration.resolvedArtifacts
for (def dep : resolved) {
def info = Util.getMavenInfoFromDep(dep)
//println("$dep.file.sha1\t$info.name\t$info.path")
entries.put("$info.art.group:$info.art.name", "$dep.file.sha256\t$info.name\t$info.path")
}
var packed = (AbstractArchiveTask) project.tasks.universalJar
var info = Util.getMavenInfoFromTask(packed)
def file = packed.archiveFile.get().asFile
entries.put("$info.art.group:$info.art.name:$info.art.classifier", "$file.sha256\t$info.name\t$info.path")
var classifier = 'server'
var genned = project.tasks.applyServerBinPatches
info = Util.getMavenInfoFromTask(genned, classifier)
file = genned.output.get().asFile
entries.put("$info.art.group:$info.art.name:$info.art.classifier", "$file.sha256\t$info.name\t$info.path")
try (def zip = new ZipFile(serverBundle.get().asFile)) {
def entry = zip.getEntry('META-INF/libraries.list')
def data = zip.getInputStream(entry).text.split('\n')
for (def line : data) {
def (sha, artifact, path) = line.split('\t')
def (group, name, other) = artifact.split(':', 3)
//println("Group: $group Name: $name")
def key = "$group:$name"
if (!entries.containsKey(key))
entries.put(key, line)
}
}
output.get().asFile.text = entries.values().join('\n')
}
}
@@ -1,7 +1,6 @@
package net.minecraftforge.forge.tasks
import groovy.json.JsonBuilder
import groovy.transform.CompileStatic
import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
@@ -11,7 +10,6 @@ import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.FieldNode
import org.objectweb.asm.tree.MethodNode
@CompileStatic
abstract class BytecodeFinder extends DefaultTask {
@InputFile abstract RegularFileProperty getJar()
// It should be fine to mark the output as internal as we want to control when we run it anyways.
@@ -26,13 +24,13 @@ abstract class BytecodeFinder extends DefaultTask {
protected void exec() {
Util.init()
var outputFile = output.get().asFile
def outputFile = output.get().asFile
if (outputFile.exists())
outputFile.delete()
pre()
Util.processClassNodes(jar.get().asFile, this.&process)
Util.processClassNodes(jar.get().asFile, this::process)
post()
outputFile.text = new JsonBuilder(getData()).toPrettyString()
@@ -40,8 +38,8 @@ abstract class BytecodeFinder extends DefaultTask {
protected process(ClassNode node) {
if (node.fields !== null) node.fields.each { process(node, it) }
if (node.methods !== null) node.methods.each { process(node, it) }
if (node.fields != null) node.fields.each { process(node, it) }
if (node.methods != null) node.methods.each { process(node, it) }
}
protected pre() {}
@@ -12,7 +12,7 @@ abstract class BytecodePredicateFinder extends BytecodeFinder {
@Internal
abstract Property<Closure<Boolean>> getPredicate()
private final Set<String> matches = new HashSet<>()
private final Set<String> matches = new HashSet()
@Override
protected process(ClassNode parent, MethodNode node) {
@@ -27,6 +27,6 @@ abstract class BytecodePredicateFinder extends BytecodeFinder {
@Internal
@Override
protected Object getData() {
return matches.toSorted(Comparator.<String>naturalOrder()) ?: {throw new RuntimeException('Failed to find any targets, please ensure that method names and descriptors are correct.')}()
return matches.toSorted(Comparator.naturalOrder()) ?: {throw new RuntimeException('Failed to find any targets, please ensure that method names and descriptors are correct.')}()
}
}
@@ -1,97 +0,0 @@
package net.minecraftforge.forge.tasks;
import groovy.transform.CompileDynamic;
import groovy.transform.CompileStatic;
import java.util.Properties;
import java.util.Enumeration;
import java.util.Map;
import java.util.TreeSet;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.Reader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Set;
/**
* Eclipse config files are literally just java properties, with the header cleaned up.
* https://github.com/eclipse/buildship/blob/5b2c7fca7fa86cd74d71b3c099c7b1559eba038e/org.eclipse.buildship.core/src/main/java/org/eclipse/buildship/core/internal/configuration/PreferenceStore.java#L238
*
* This does the same thing, as well as sorting alphabetically.
* It also ignores all comments. We can add them latter if someone cares.
*/
@CompileStatic
public class CleanProperties extends Properties {
private static final long serialVersionUID = 1L;
private static final String LINE_SEP = System.getProperty("line.separator");
private static final String UNIX_LINE_SEP = "\n";
private static final Charset ENCODING = StandardCharsets.UTF_8;
public CleanProperties load(File input) throws IOException {
if (input.exists()) {
try (Reader is = new InputStreamReader(new FileInputStream(input), ENCODING)) {
super.load(is);
}
}
return this;
}
public void store(File out) throws IOException {
if (!out.getParentFile().exists())
out.getParentFile().mkdirs();
try (OutputStream os = new FileOutputStream(out)) {
store(os, null);
}
}
@Override
public synchronized Enumeration<Object> keys() {
Set<Object> ret = new TreeSet<>();
for (Enumeration<?> e = super.keys(); e.hasMoreElements();)
ret.add(e.nextElement());
return Collections.enumeration(ret);
}
@CompileDynamic
@Override
public Set<Map.Entry<Object, Object>> entrySet() {
Set<Map.Entry<Object, Object>> ret = new TreeSet<>((l, r) -> ((String)l.getKey()).compareTo((String)r.getKey()));
ret.addAll(super.entrySet());
return ret;
}
@Override
public void store(OutputStream out, String comments) throws IOException {
out.write(clean().getBytes(ENCODING));
out.flush();
}
@Override
public void store(Writer out, String comments) throws IOException {
out.write(clean());
out.flush();
}
private String clean() throws IOException {
ByteArrayOutputStream tmp = new ByteArrayOutputStream();
try {
super.store(tmp, null);
} finally {
tmp.close();
}
String ret = tmp.toString(ENCODING).replace(LINE_SEP, UNIX_LINE_SEP);
ret = ret.substring(ret.indexOf(UNIX_LINE_SEP) + 1);
return ret;
}
}
@@ -1,7 +1,5 @@
package net.minecraftforge.forge.tasks
import groovy.transform.stc.FirstParam
import java.util.function.BiConsumer
public class ClosureHelper {
@@ -24,7 +22,7 @@ public class ClosureHelper {
}
}
static <T> T apply(T obj, @DelegatesTo(value = FirstParam, strategy = Closure.DELEGATE_FIRST) Closure cl) {
static <T> T apply(T obj, Closure cl) {
cl.delegate = obj
cl.resolveStrategy = Closure.DELEGATE_FIRST
cl()
@@ -0,0 +1,73 @@
package net.minecraftforge.forge.tasks
import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
abstract class Crowdin extends DefaultTask {
@Input abstract Property<String> getId()
@Input @Optional abstract Property<String> getKey()
@Input boolean json = true
@OutputFile abstract RegularFileProperty getOutput()
@OutputFile abstract RegularFileProperty getExport()
Crowdin() {
outputs.upToDateWhen{ false }
id.convention('minecraft-forge')
output.convention(project.layout.buildDirectory.dir(name).map {it.file("output.zip") })
export.convention(project.layout.buildDirectory.dir(name).map {it.file("export.json") })
}
@TaskAction
def run() {
File outputFile = output.get().asFile
File exportFile = export.get().asFile
if (outputFile.exists())
outputFile.delete()
if (!key.isPresent())
return
String key = this.key.get()
String id = this.id.get()
// Force an export
new URL("https://api.crowdin.com/api/project/${id}/export?key=${key}").withInputStream { i ->
exportFile.withOutputStream { it << i }
}
if (!exportFile.text.contains('success')) {
throw new RuntimeException("Crowdin export failed, see ${exportFile} for more info")
}
new URL("https://api.crowdin.com/api/project/${id}/download/all.zip?key=${key}").withInputStream { i ->
new ZipInputStream(i).withCloseable { zin ->
outputFile.withOutputStream { out ->
new ZipOutputStream(out).withCloseable { zout ->
ZipEntry zein
while ((zein = zin.nextEntry) != null) {
if (zein.isDirectory()) {
zout.putNextEntry(new ZipEntry(zein.name))
} else {
// 1.13+ uses json
if (zein.name.endsWith('.json') == json) {
ZipEntry zeout = new ZipEntry(json ? zein.name.toLowerCase() : zein.name)
zeout.time = 1
zout.putNextEntry(zeout)
zout << zin
}
}
}
}
}
}
}
}
}
@@ -27,7 +27,7 @@ abstract class DownloadLibraries extends DefaultTask {
def run() {
Util.init()
File outputDir = output.get().asFile
var libraries = new ArrayList<String>()
def libraries = new ArrayList()
def json = input.get().asFile.json().libraries.each { lib ->
//TODO: Thread?
@@ -35,7 +35,7 @@ abstract class DownloadLibraries extends DefaultTask {
artifacts.each{ art ->
def target = new File(outputDir, art.path)
libraries.add(target.absolutePath)
if (!target.exists() || art.sha1 != target.sha1()) {
if (!target.exists() || !art.sha1.equals(target.sha1())) {
project.logger.lifecycle("Downloading ${art.url}")
if (!target.parentFile.exists()) {
target.parentFile.mkdirs()
@@ -43,7 +43,7 @@ abstract class DownloadLibraries extends DefaultTask {
new URL(art.url).withInputStream { i ->
target.withOutputStream { it << i }
}
if (art.sha1 != target.sha1()) {
if (!art.sha1.equals(target.sha1())) {
throw new IllegalStateException("Failed to download ${art.url} to ${target.canonicalPath} SHA Mismatch")
}
}
@@ -1,28 +0,0 @@
package net.minecraftforge.forge.tasks
import groovy.transform.CompileStatic
import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
import java.util.zip.ZipFile
@CompileStatic
abstract class ExtractFile extends DefaultTask {
@InputFile abstract RegularFileProperty getInput()
@Input abstract Property<String> getTarget()
@OutputFile abstract RegularFileProperty getOutput()
@TaskAction
protected void exec() {
var zip = new ZipFile(input.get().asFile);
var entry = zip.getEntry(target.get())
if (entry == null)
throw new IllegalStateException(input.get().asFile.absolutePath + " does not contain " + target.get())
var stream = zip.getInputStream(entry)
output.get().asFile.withOutputStream { out ->
stream.transferTo(out)
}
}
}
@@ -1,11 +1,20 @@
package net.minecraftforge.forge.tasks
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import java.util.ArrayList
import java.util.HashMap
import java.util.TreeMap
import java.util.TreeSet
import java.util.function.BiConsumer
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.*
import org.objectweb.asm.tree.AbstractInsnNode
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Type
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.FieldNode
import org.objectweb.asm.tree.MethodNode
import static org.objectweb.asm.Opcodes.*
@@ -25,17 +34,17 @@ abstract class FieldCompareFinder extends BytecodeFinder {
@Override
protected process(ClassNode parent, MethodNode node) {
AbstractInsnNode last = null
def last = null
def parentInstance = new ObjectTarget(owner: parent.name, name: '', desc: '')
for (int x = 0; x < node.instructions.size(); x++) {
def current = node.instructions.get(x)
if (current.opcode === IF_ACMPEQ || current.opcode === IF_ACMPNE) {
if (last !== null && (last.opcode === GETSTATIC || last.opcode === GETFIELD)) {
if (current.opcode == IF_ACMPEQ || current.opcode == IF_ACMPNE) {
if (last != null && (last.opcode == GETSTATIC || last.opcode == GETFIELD)) {
def target = new Search(cls: last.owner, name: last.name)
def wanted = fieldsReverse.get(target)
def original = fields.get(wanted)
def instance = new ObjectTarget(owner: parent.name, name: node.name, desc: node.desc)
if (wanted !== null && (original.blacklist === null || (!original.blacklist.contains(instance) && !original.blacklist.contains(parentInstance)))) {
if (wanted != null && (original.blacklist == null || (!original.blacklist.contains(instance) && !original.blacklist.contains(parentInstance)))) {
targets.computeIfAbsent(wanted, { k -> new TreeSet() }).add(instance)
}
}
@@ -59,10 +68,9 @@ abstract class FieldCompareFinder extends BytecodeFinder {
}
return ret
}
@CompileStatic
@EqualsAndHashCode(excludes = ['replacement', 'blacklist'])
static class Search {
public static class Search {
@Input
String cls
@@ -81,17 +89,17 @@ abstract class FieldCompareFinder extends BytecodeFinder {
return cls + '.' + name
}
def blacklist(String owner, String name, String desc) {
if (blacklist === null)
blacklist = new HashSet<>()
def blacklist(def owner, def name, def desc) {
if (blacklist == null)
blacklist = new HashSet()
blacklist.add(new ObjectTarget(owner: owner, name: name, desc: desc))
}
def blacklist(String owner) {
def blacklist(def owner) {
blacklist(owner, '', '')
}
}
void fields(Closure cl) {
def fields(Closure cl) {
new ClosureHelper(cl, {name, ccl ->
def search = ClosureHelper.apply(new Search(), ccl)
this.fields.put(name, search)
@@ -0,0 +1,40 @@
package net.minecraftforge.forge.tasks
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.TaskAction
import java.nio.file.Files
abstract class FixPatchImports extends DefaultTask {
@InputDirectory abstract DirectoryProperty getClean()
@InputDirectory abstract DirectoryProperty getPatched()
@TaskAction
void run() {
final patchedPath = getPatched().get().asFile.toPath()
final cleanPath = getClean().get().asFile.toPath()
try (final var stream = Files.find(patchedPath, Integer.MAX_VALUE, (path, attr) -> path.toString().endsWith('.java'))) {
final itr = stream.iterator()
while (itr.hasNext()) {
final next = itr.next()
final clean = cleanPath.resolve(patchedPath.relativize(next).toString())
final patchedLines = Files.readAllLines(next)
final cleanLines = Files.readAllLines(clean)
final patchedImport = patchedLines.findLastIndexOf(2) { it.startsWith('import ') }
final cleanImport = cleanLines.findLastIndexOf(2) { it.startsWith('import ') }
if (cleanImport !== patchedImport) {
patchedLines.removeIf { it.startsWith('import ') }
patchedLines.addAll(2, cleanLines.subList(2, cleanImport + 1))
Files.write(next, patchedLines)
}
}
}
}
}
@@ -1,158 +1,44 @@
package net.minecraftforge.forge.tasks
import org.gradle.api.file.DuplicatesStrategy
import org.gradle.api.tasks.bundling.AbstractArchiveTask
import org.gradle.api.tasks.bundling.Zip
import org.gradle.api.tasks.*
import org.gradle.api.DefaultTask
import org.gradle.api.provider.Property
import groovy.json.JsonSlurper
import org.gradle.api.provider.SetProperty
abstract class InstallerJar extends Zip {
@Input @Optional abstract Property<Boolean> getFat()
@Input @Optional abstract Property<Boolean> getOffline()
@Input @Optional abstract SetProperty<String> getPackedDependencies()
InstallerJar() {
archiveClassifier.set('installer')
archiveExtension.set('jar') // Needs to be Zip task to not override Manifest, so set extension
destinationDirectory.set(project.layout.buildDirectory.dir('libs'))
fat.convention(false)
offline.convention(false)
def installerJson = project.tasks.installerJson
def launcherJson = project.tasks.launcherJson
def downloadInstaller = project.tasks.downloadInstaller
dependsOn(installerJson, launcherJson, downloadInstaller, project.configurations.installer)
dependsOn(installerJson, launcherJson, downloadInstaller)
from(installerJson, launcherJson)
from(project.rootProject.file('/src/main/resources/url.png'))
project.afterEvaluate {
from(project.zipTree(downloadInstaller.output)) {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
duplicatesStrategy = 'exclude'
}
if (fat.get() || offline.get()) {
def cfg = project.tasks.register(name + "Config", Configure)
cfg.get().configure {
parent = this
dependsOn(project.tasks.installerJson, project.tasks.launcherJson)
if (!System.env.TEAMCITY_VERSION) {
dependsOn(project.universalJar)
from(project.universalJar) {
into '/maven/' + project.group.replace('.', '/') + '/' + project.universalJar.archiveBaseName.get() + '/' + project.version + '/'
}
dependsOn(cfg)
} else {
// Things we ALWAYS bundle, this just the server shim jar, because the installer spec only says to extract the file.
// I should make it allow downloads but thats a spec break, and this is just a ~14KB jar
[
project.tasks.serverShimJar // Server bootstrap executable jar
].forEach { AbstractArchiveTask packed ->
def path = Util.getMavenInfoFromTask(packed).path
from(packed) {
rename { "maven/$path" }
packedDependencies.get().forEach {
def jarTask = project.rootProject.getTasks().findByPath(it)
dependsOn(jarTask)
from(jarTask) {
into '/maven/' + jarTask.project.group.replace('.', '/') + '/' + jarTask.archiveBaseName.get() + '/' + jarTask.project.version + '/'
}
}
}
}
}
static abstract class Configure extends DefaultTask {
public Zip parent
private int count = 0;
@TaskAction
protected void exec() {
def deps = [:] as java.util.TreeMap
// Gather all things that need downloading
[
project.tasks.installerJson,
project.tasks.launcherJson
].each { task ->
def json = task.output.get().asFile.json
json.libraries.each { lib ->
if (lib.downloads?.artifact?.url !== null && !lib.downloads.artifact.url.isEmpty())
deps.put(lib.name, lib.downloads.artifact)
}
}
// First find things we build in this project.
[
project.tasks.universalJar, // Forge runtime code
project.tasks.serverShimJar // Shim jar for dedicated server
].forEach { AbstractArchiveTask packed ->
def name = Util.getMavenInfoFromTask(packed).name
def info = deps.remove(name)
if (info !== null) {
println("Adding: $packed.path $name")
parent.from(packed) {
rename { "maven/$info.path" }
}
}
}
// Find any artifacts from the 'installer' config
// This config specifies the runtime files we intend for the interaller to have.
// And are typically what we would be developing and testing alongside Forge.
// So we may have local modified versions
def cfg = project.configurations.installer
while (cfg != null) {
//println('')
def resolved = cfg.resolvedConfiguration.resolvedArtifacts
int found = 0
for (def dep : resolved) {
def name = Util.getMavenInfoFromDep(dep).name
def info = deps.remove(name)
if (info == null) {
//println("Skipping: $name")
continue
}
//println("-$name")
found++
addFile(dep.file, info)
}
if (deps.isEmpty()) {
cfg = null
continue
}
// Prevent infinite loops if something fucky happens
if (found == 0)
throw new IllegalStateException("Failed to find any installer dependencies")
def seen = [] as Set
cfg = project.configurations.detachedConfiguration()
cfg.transitive = false
for (def key : deps.keySet()) {
def (group, artifact, other) = key.split(':', 3)
// Only resolve unique group:artifact so we don't get version overrides
if (seen.add(group + ':' + artifact)) {
//println("+$key")
cfg.dependencies.add(project.dependencies.create(key))
}
}
}
}
void addFile(file, info) {
boolean pack = parent.offline.get() || info.url.isEmpty()
// If it's a offline jar just always pack
if (!pack) {
try {
var remote = new URL("${info.url}.sha1").getText('UTF-8')
pack = info.sha1 != remote
} catch (FileNotFoundException e) {
pack = !info.url.startsWith('https://libraries.minecraft.net/')
// Oh noes its not there!
}
}
if (pack) {
println("Adding: $file.absolutePath")
parent.from(file) {
rename { "maven/$info.path" }
}
}
}
}
}
@@ -7,13 +7,13 @@ import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.SetProperty
import org.gradle.api.tasks.*
import org.gradle.api.tasks.bundling.AbstractArchiveTask
import java.nio.file.Files
abstract class InstallerJson extends DefaultTask {
@OutputFile abstract RegularFileProperty getOutput()
@InputFiles abstract ConfigurableFileCollection getInput()
@Input @Optional abstract SetProperty<String> getPackedDependencies()
@Input @Optional final Map<String, Object> libraries = new LinkedHashMap<>()
@Input Map<String, Object> json = new LinkedHashMap<>()
@InputFile abstract RegularFileProperty getIcon()
@@ -23,19 +23,29 @@ abstract class InstallerJson extends DefaultTask {
@Input abstract Property<String> getWelcome()
InstallerJson() {
launcherJsonName.convention('/version.json')
logo.convention('/big_logo.png')
mirrors.convention('https://files.minecraftforge.net/mirrors-2.0.json')
welcome.convention("Welcome to the ${project.name.capitalize()} installer.")
output.convention(project.layout.buildDirectory.file('libs/install_profile.json'))
getLauncherJsonName().convention('/version.json')
getLogo().convention('/big_logo.png')
getMirrors().convention('https://files.minecraftforge.net/mirrors-2.0.json')
getWelcome().convention("Welcome to the simple ${project.name.capitalize()} installer.")
getOutput().convention(project.layout.buildDirectory.file('install_profile.json'))
['client', 'server'].each { side ->
['slim', 'extra'].each { type ->
def tsk = project.tasks.getByName("download${side.capitalize()}${type.capitalize()}")
dependsOn(tsk)
input.from(tsk.output)
}
def tsk = project.tasks.getByName("create${side.capitalize()}SRG")
dependsOn(tsk)
input.from(tsk.output)
}
project.afterEvaluate {
[
project.tasks.universalJar,
project.tasks.serverShimJar
].forEach { packed ->
dependsOn(packed)
input.from packed.archiveFile
(packedDependencies.get().collect{ project.rootProject.tasks.findByPath(it) } + [project.universalJar]).forEach {
dependsOn(it)
input.from it.archiveFile
}
}
}
@@ -43,19 +53,17 @@ abstract class InstallerJson extends DefaultTask {
@TaskAction
protected void exec() {
def libs = libraries
[
project.tasks.universalJar,
project.tasks.serverShimJar
].forEach { AbstractArchiveTask packed ->
def info = Util.getMavenInfoFromTask(packed)
libs.put(info.name, [
name: info.name,
for (def child : packedDependencies.get().collect{ project.rootProject.tasks.findByPath(it) } + [project.universalJar]) {
def dep = Util.getMavenDep(child)
def path = Util.getMavenPath(child)
libs.put(dep.toString(), [
name: dep,
downloads: [
artifact: [
path: info.path,
url: "https://maven.minecraftforge.net/$info.path",
sha1: packed.archiveFile.get().asFile.sha1(),
size: packed.archiveFile.get().asFile.length()
path: path,
url: "https://maven.minecraftforge.net/${path}",
sha1: child.archiveFile.get().asFile.sha1(),
size: child.archiveFile.get().asFile.length()
]
]
])
@@ -6,7 +6,6 @@ import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.SetProperty
import org.gradle.api.tasks.*
import org.gradle.api.tasks.bundling.AbstractArchiveTask
import java.nio.file.Files
@@ -17,80 +16,59 @@ abstract class LauncherJson extends DefaultTask {
@OutputFile abstract RegularFileProperty getOutput()
@InputFiles abstract ConfigurableFileCollection getInput()
@Input Map<String, Object> json = new LinkedHashMap<>()
@Input @Optional abstract SetProperty<String> getPackedDependencies()
@Internal final vanilla = project.project(':mcp').file('build/mcp/downloadJson/version.json')
@Internal final timestamp = iso8601Now()
@Internal final comment = [
"Please do not automate the download and installation of Forge.",
"Our efforts are supported by ads from the download page.",
"If you MUST automate this, please consider supporting the project through https://www.patreon.com/LexManos/"
]
@Internal final id = "${project.rootProject.ext.MC_VERSION}-${project.name}${project.version.substring(project.rootProject.ext.MC_VERSION.length())}"
LauncherJson() {
output.convention(project.layout.buildDirectory.file('libs/version.json'))
getOutput().convention(project.layout.buildDirectory.file('version.json'))
dependsOn(project.tasks.universalJar)
input.from(project.tasks.universalJar.archiveFile)
input.from(project.configurations.installer)
input.from(project.configurations.installerextra)
configure {
def mc = project.rootProject.ext.MC_VERSION
def forge = project.rootProject.ext.FORGE_VERSION
def timestamp = iso8601Now()
json.putAll([
_comment: [
"Please do not automate the download and installation of Forge.",
"Our efforts are supported by ads from the download page.",
"If you MUST automate this, please consider supporting the project through https://www.patreon.com/LexManos/"
],
id: "$mc-$project.name-$forge",
time: timestamp,
releaseTime: timestamp,
inheritsFrom: mc,
type: 'release',
logging: [:],
mainClass: '',
libraries: []
] as LinkedHashMap)
[
project.tasks.universalJar
].forEach { packed ->
dependsOn(packed)
input.from packed.archiveFile
dependsOn(':fmlloader:jar', 'universalJar')
getInput().from(project.tasks.universalJar.archiveFile,
project.project(':fmlloader').jar.archiveFile,
vanilla)
project.afterEvaluate {
packedDependencies.get().forEach {
def jarTask = project.rootProject.tasks.findByPath(it)
dependsOn(jarTask)
input.from jarTask.archiveFile
}
def patched = project.tasks.applyClientBinPatches
dependsOn(patched)
input.from patched.output
}
}
@TaskAction
protected void exec() {
var packed = (AbstractArchiveTask) project.tasks.universalJar
def info = Util.getMavenInfoFromTask(packed)
json.libraries.add([
name: info.name,
downloads: [
artifact: [
path: info.path,
url: "https://maven.minecraftforge.net/$info.path",
sha1: packed.archiveFile.get().asFile.sha1(),
size: packed.archiveFile.get().asFile.length()
if (!json.libraries)
json.libraries = []
def libs = [:]
getArtifacts(project, project.configurations.installer, false).each { key, lib -> libs[key] = lib }
getArtifacts(project, project.configurations.moduleonly, false).each { key, lib -> libs[key] = lib }
packedDependencies.get().collect{ project.rootProject.tasks.findByPath(it) }.forEach {
def path = Util.getMavenPath(it)
def key = Util.getMavenDep(it)
libs[key] = [
name: key,
downloads: [
artifact: [
path: path,
url: "https://maven.minecraftforge.net/${path}",
sha1: it.archiveFile.get().asFile.sha1(),
size: it.archiveFile.get().asFile.length()
]
]
]
])
var classifier = 'client'
var genned = project.tasks.applyClientBinPatches
info = Util.getMavenInfoFromTask(genned, classifier)
json.libraries.add([
name: info.name,
downloads: [
artifact: [
path: info.path,
url: "",
sha1: genned.output.get().asFile.sha1(),
size: genned.output.get().asFile.length()
]
]
])
json.libraries.addAll(getArtifacts(project.configurations.installerextra).values())
json.libraries.addAll(getArtifacts(project.configurations.installer).values())
}
libs.each { key, lib -> json.libraries.add(lib) }
Files.writeString(output.get().asFile.toPath(), new JsonBuilder(json).toPrettyString())
}
}
@@ -1,6 +1,5 @@
package net.minecraftforge.forge.tasks
import groovy.transform.CompileStatic
import org.apache.commons.io.IOUtils
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
@@ -13,7 +12,6 @@ import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
@CompileStatic
abstract class MergeJars extends DefaultTask {
MergeJars() {
output.convention(project.layout.buildDirectory.dir(name).map { it.file('output.jar') })
@@ -26,8 +24,8 @@ abstract class MergeJars extends DefaultTask {
try (ZipOutputStream zout = new ZipOutputStream(new FileOutputStream(output.get().asFile))) {
for (def jar : jars) {
try (ZipInputStream zin = new ZipInputStream(new FileInputStream(jar))) {
ZipEntry entry
while ((entry = zin.getNextEntry()) !== null) {
def entry
while ((entry = zin.getNextEntry()) != null) {
ZipEntry _new = new ZipEntry(entry.getName())
_new.setTime(0) //SHOULD be the same time as the main entry, but NOOOO _new.setTime(entry.getTime()) throws DateTimeException, so you get 0, screw you!
zout.putNextEntry(_new)
@@ -27,6 +27,6 @@ public class ObjectTarget implements Comparable<ObjectTarget> {
@Override
int compareTo(ObjectTarget o) {
return toString() <=> o.toString()
return toString().compareTo(o.toString())
}
}
@@ -16,7 +16,7 @@ abstract class SetupCheckJarCompatibility extends DefaultTask {
SetupCheckJarCompatibility() {
group = 'jar compatibility'
onlyIf {
inputVersion.getOrNull() !== null
inputVersion.getOrNull() != null
}
outputs.upToDateWhen { false } // Never up to date, because this setup task should always run
@@ -26,6 +26,16 @@ abstract class SetupCheckJarCompatibility extends DefaultTask {
@TaskAction
void run() {
def inputVersion = inputVersion.get()
def fmlLibs = project.configurations.detachedConfiguration(project.PACKED_DEPS.collect {
def artifactId = it.split(':')[1]
return project.dependencies.create("net.minecraftforge:${artifactId}:${inputVersion}")
}.toArray(Dependency[]::new))
project.tasks.named('checkJarCompatibility') {
baseLibraries.from(project.provider {
fmlLibs.resolvedConfiguration.lenientConfiguration.files
})
}
def baseForgeUserdev = project.layout.buildDirectory.dir(name).map { it.file("forge-${inputVersion}-userdev.jar") }.get().asFile
project.rootProject.extensions.download.run {
@@ -2,224 +2,159 @@ package net.minecraftforge.forge.tasks
import groovy.json.JsonBuilder
import groovy.json.JsonSlurper
import groovy.transform.CompileStatic
import groovy.transform.stc.ClosureParams
import groovy.transform.stc.SimpleType
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ResolvedArtifact
import org.gradle.api.tasks.bundling.AbstractArchiveTask
import org.objectweb.asm.ClassReader
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.ClassNode
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.io.File
import java.security.MessageDigest
import java.text.SimpleDateFormat
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Semaphore
import java.util.Date
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
final class Util {
public static final int ASM_LEVEL = Opcodes.ASM9
private static final HttpClient HTTP = HttpClient.newBuilder().build()
public class Util {
static final ASM_LEVEL = Opcodes.ASM9
static void init() {
File.metaClass.sha1 = { ->
MessageDigest md = MessageDigest.getInstance('SHA-1')
delegate.eachByte(4096) { byte[] bytes, int size ->
md.update(bytes, 0, size)
}
return md.digest().collect(this.&toHex).join('')
}
static void init() {
File.metaClass.sha1 = { ->
MessageDigest md = MessageDigest.getInstance('SHA-1')
delegate.eachByte 4096, {bytes, size ->
md.update(bytes, 0, size)
}
return md.digest().collect {String.format "%02x", it}.join()
}
File.metaClass.getSha1 = { !delegate.exists() ? null : delegate.sha1() }
File.metaClass.sha256 = { ->
MessageDigest md = MessageDigest.getInstance('SHA-256')
delegate.eachByte(4096) { byte[] bytes, int size ->
md.update(bytes, 0, size)
}
return md.digest().collect(this.&toHex).join('')
}
File.metaClass.getSha256 = { !delegate.exists() ? null : delegate.sha256() }
File.metaClass.json = { -> new JsonSlurper().parseText(delegate.text) }
File.metaClass.json = { -> new JsonSlurper().parseText(delegate.text) }
File.metaClass.getJson = { return delegate.exists() ? new JsonSlurper().parse(delegate) : [:] }
File.metaClass.setJson = { json -> delegate.text = new JsonBuilder(json).toPrettyString() }
Date.metaClass.iso8601 = { ->
var format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
var result = format.format(delegate)
return result[0..21] + ':' + result[22..-1]
}
Date.metaClass.iso8601 = { ->
def format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
def result = format.format(delegate)
return result[0..21] + ':' + result[22..-1]
}
String.metaClass.rsplit = { String del, int limit = -1 ->
var lst = new ArrayList<String>()
int x = 0
int idx
String tmp = delegate
while ((idx = tmp.lastIndexOf(del)) != -1 && (limit === -1 || x++ < limit)) {
def lst = new ArrayList()
def x = 0, idx
def tmp = delegate
while ((idx = tmp.lastIndexOf(del)) != -1 && (limit == -1 || x++ < limit)) {
lst.add(0, tmp.substring(idx + del.length(), tmp.length()))
tmp = tmp.substring(0, idx)
}
lst.add(0, tmp)
return lst
}
}
public static String[] getClasspath(project, libs, artifact) {
def ret = []
artifactTree(project, artifact).each { key, lib ->
libs[lib.name] = lib
if (lib.name != artifact)
ret.add(lib.name)
}
return ret
}
public static def getArtifacts(project, config, classifiers) {
def ret = [:]
config.resolvedConfiguration.resolvedArtifacts.each {
def art = [
group: it.moduleVersion.id.group,
name: it.moduleVersion.id.name,
version: it.moduleVersion.id.version,
classifier: it.classifier,
extension: it.extension,
file: it.file
]
def key = art.group + ':' + art.name
def folder = "${art.group.replace('.', '/')}/${art.name}/${art.version}/"
def filename = "${art.name}-${art.version}"
if (art.classifier != null)
filename += "-${art.classifier}"
filename += ".${art.extension}"
def path = "${folder}${filename}"
def url = "https://libraries.minecraft.net/${path}"
if (!checkExists(url)) {
url = "https://maven.minecraftforge.net/${path}"
}
ret[key] = [
name: "${art.group}:${art.name}:${art.version}" + (art.classifier == null ? '' : ":${art.classifier}") + (art.extension == 'jar' ? '' : "@${art.extension}"),
downloads: [
artifact: [
path: path,
url: url,
sha1: sha1(art.file),
size: art.file.length()
]
]
]
}
return ret
}
public static def getMavenPath(task) {
def classifier = task.archiveClassifier.get()
def dep = "${task.project.group}:${task.project.name}:${task.project.version}" + (classifier == '' ? '' : ':' + classifier)
return "${task.project.group.replace('.', '/')}/${task.project.name}/${task.project.version}/${task.project.name}-${task.project.version}".toString() + (classifier == '' ? '' : '-' + classifier) + '.jar'
}
static String[] getClasspath(Project project, Map libs, String artifact) {
def ret = []
artifactTree(project, artifact).each { key, lib ->
libs[lib.name] = lib
if (lib.name != artifact)
ret.add(lib.name)
}
return ret
public static def getMavenDep(task) {
def classifier = task.archiveClassifier.get()
return "${task.project.group}:${task.project.name}:${task.project.version}" + (classifier == '' ? '' : ':' + classifier)
}
@CompileStatic
static Map getArtifacts(Configuration config) {
var ret = [:]
var semaphore = new Semaphore(1, true)
config.resolvedConfiguration.resolvedArtifacts.parallelStream().forEachOrdered(dep -> {
var info = getMavenInfoFromDep(dep)
var domain = 'libraries.minecraft.net'
var url = "https://$domain/$info.path"
if (!checkExists(url))
url.values[0] = 'maven.minecraftforge.net'
public static def iso8601Now() { new Date().iso8601() }
var sha1 = sha1(dep.file)
public static def sha1(file) {
MessageDigest md = MessageDigest.getInstance('SHA-1')
file.eachByte 4096, {bytes, size ->
md.update(bytes, 0, size)
}
return md.digest().collect {String.format "%02x", it}.join()
}
semaphore.acquire()
ret[info.key] = [
name: info.name,
downloads: [
artifact: [
path: info.path,
url: url.toString(),
sha1: sha1,
size: dep.file.length()
]
]
]
semaphore.release()
})
return ret
}
private static def artifactTree(project, artifact) {
if (!project.ext.has('tree_resolver'))
project.ext.tree_resolver = 1
def cfg = project.configurations.create('tree_resolver_' + project.ext.tree_resolver++)
def dep = project.dependencies.create(artifact)
cfg.dependencies.add(dep)
def files = cfg.resolve()
return getArtifacts(project, cfg, true)
}
@CompileStatic
static Map getMavenInfoFromDep(ResolvedArtifact dep) {
return getMavenInfoFromMap([
group: dep.moduleVersion.id.group,
name: dep.moduleVersion.id.name,
version: dep.moduleVersion.id.version,
classifier: dep.classifier,
extension: dep.extension
])
}
private static boolean checkExists(url) {
try {
def code = new URL(url).openConnection().with {
requestMethod = 'HEAD'
connect()
responseCode
}
return code == 200
} catch (Exception e) {
if (e.toString().contains('unable to find valid certification path to requested target'))
throw new RuntimeException('Failed to connect to ' + url + ': Missing certificate root authority, try updating java')
throw e
}
}
@CompileStatic
static Map getMavenInfoFromTask(AbstractArchiveTask task) {
return getMavenInfoFromMap([
group: task.project.group.toString(),
name: task.project.name,
version: task.project.version.toString(),
classifier: task.archiveClassifier.get(),
extension: task.archiveExtension.get()
])
}
@CompileStatic
static Map getMavenInfoFromTask(Task task, String classifier) {
return getMavenInfoFromMap([
group: task.project.group.toString(),
name: task.project.name,
version: task.project.version.toString(),
classifier: classifier,
extension: 'jar'
])
}
@CompileStatic
private static Map getMavenInfoFromMap(Map<String, String> art) {
var key = "$art.group:$art.name"
var name = "$art.group:$art.name:$art.version"
var path = "${art.group.replace('.', '/')}/$art.name/$art.version/$art.name-$art.version"
if (art.classifier !== null) {
name += ":$art.classifier"
path += "-$art.classifier"
}
if ('jar' != art.extension) {
name += "@$art.extension"
path += ".$art.extension"
} else {
path += ".jar"
}
return [
key: key.toString(),
name: name.toString(),
path: path.toString(),
art: art
]
}
static String iso8601Now() { new Date().iso8601() }
@CompileStatic
static String sha1(File file) {
MessageDigest md = MessageDigest.getInstance('SHA-1')
file.eachByte(4096) { byte[] bytes, int size ->
md.update(bytes, 0, size)
}
return md.digest().collect(this.&toHex).join('')
}
@CompileStatic
private static String toHex(byte bite) {
return String.format('%02x', bite)
}
private static Map artifactTree(Project project, String artifact, boolean transitive = true) {
if (!project.ext.has('tree_resolver'))
project.ext.tree_resolver = 1
def cfg = project.configurations.create('tree_resolver_' + project.ext.tree_resolver++)
cfg.transitive = transitive
def dep = project.dependencies.create(artifact)
cfg.dependencies.add(dep)
def files = cfg.resolve()
return getArtifacts(cfg)
}
@CompileStatic
static boolean checkExists(String url) {
try {
return HTTP.send(HttpRequest.newBuilder(new URI(url))
.method('HEAD', HttpRequest.BodyPublishers.noBody()).build(), HttpResponse.BodyHandlers.discarding()
).statusCode() === 200
} catch (Exception e) {
if (e.toString().contains('unable to find valid certification path to requested target'))
throw new RuntimeException("Failed to connect to $url: Missing certificate root authority, try updating Java")
throw e
}
}
static String getLatestForgeVersion(String mcVersion) {
static String getLatestForgeVersion(mcVersion) {
final json = new JsonSlurper().parseText(new URL('https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json').getText('UTF-8'))
final ver = json.promos["$mcVersion-latest"]
ver === null ? null : (mcVersion + '-' + ver)
}
@CompileStatic
static void processClassNodes(File file, @ClosureParams(value = SimpleType, options = 'org.objectweb.asm.tree.ClassNode') Closure process) {
static void processClassNodes(File file, Closure process) {
file.withInputStream { i ->
new ZipInputStream(i).withCloseable { zin ->
ZipEntry zein
while ((zein = zin.nextEntry) !== null) {
while ((zein = zin.nextEntry) != null) {
if (zein.name.endsWith('.class')) {
var node = new ClassNode(ASM_LEVEL)
def node = new ClassNode(ASM_LEVEL)
new ClassReader(zin).accept(node, 0)
process(node)
}
@@ -227,4 +162,4 @@ final class Util {
}
}
}
}
}
@@ -5,7 +5,6 @@
package net.minecraftforge.forge.tasks
import groovy.transform.CompileStatic
import net.minecraftforge.srgutils.MinecraftVersion
import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFileProperty
@@ -29,8 +28,8 @@ abstract class ValidateDeprecations extends DefaultTask {
@TaskAction
protected void exec() {
var mcVer = MinecraftVersion.from(mcVersion.get())
List<String> errors = []
def mcVer = MinecraftVersion.from(mcVersion.get())
def errors = []
Util.processClassNodes(input.get().asFile) {
processNode(mcVer, errors, it)
@@ -66,7 +65,7 @@ abstract class ValidateDeprecations extends DefaultTask {
}
}
private static void processAnnotations(AnnotationNode annotation, MinecraftVersion mcVer, List<String> errors, Closure<String> context) {
private static void processAnnotations(AnnotationNode annotation, MinecraftVersion mcVer, List<String> errors, Closure context) {
def values = annotation.values
if (values === null)
return
@@ -74,7 +73,7 @@ abstract class ValidateDeprecations extends DefaultTask {
int since = values.indexOf('since')
if (annotation.desc == 'Ljava/lang/Deprecated;' && forRemoval !== -1 && since !== -1 && values.size() >= 4 && values[forRemoval + 1] === true) {
def oldVersion = MinecraftVersion.from(values[since + 1])
int[] split = ValidateDeprecations.splitDots(oldVersion.toString())
def split = ValidateDeprecations.splitDots(oldVersion.toString())
if (split.length < 2)
return
def removeVersion = MinecraftVersion.from("${split[0]}.${split[1] + 1}")
@@ -83,7 +82,6 @@ abstract class ValidateDeprecations extends DefaultTask {
}
}
@CompileStatic
private static int[] splitDots(String version) {
String[] pts = version.split('\\.')
int[] values = new int[pts.length]
@@ -26,12 +26,12 @@ abstract class CheckExcs extends CheckTask {
excs.each { f ->
final lines = []
f.eachLine { line ->
int idx = line.indexOf('#')
if (idx === 0 || line.isEmpty()) {
def idx = line.indexOf('#')
if (idx == 0 || line.isEmpty()) {
return
}
if (idx !== -1) line = line.substring(0, idx - 1)
if (idx != -1) line = line.substring(0, idx - 1)
if (!line.contains('=')) {
reporter.report("Invalid: $line")
@@ -58,8 +58,6 @@ abstract class CheckExcs extends CheckTask {
return
}
lines.add(line)
return
}
if (fix) f.text = lines.sort().join('\n')
@@ -6,6 +6,4 @@ import groovy.transform.CompileStatic
enum CheckMode {
CHECK,
FIX
CheckMode() {}
}
@@ -36,13 +36,13 @@ abstract class CheckPatches extends CheckTask {
}
}
static boolean accessChange(String previous, String current) {
def accessChange(previous, current) {
//return ACCESS_MAP[previous] < ACCESS_MAP[current]
return previous != current
}
void verifyPatch(Path patch, Reporter reporter, boolean fix, String patchPath, boolean hasS2SArtifact) {
int oldFixedErrors = reporter.fixed.size()
final oldFixedErrors = reporter.fixed.size()
final lines = Files.readAllLines(patch)
@@ -140,7 +140,7 @@ abstract class CheckPatches extends CheckTask {
final nextLineChange = i + 1 < lines.size() && lines[i + 1].startsWithAny('+','-')
if (!prevLineChange && !nextLineChange) {
reporter.report("Patch contains white space change in valid hunk at line ${i + 1}, file: $patchPath \n$prevLine\n$line\n${lines[i+1]}", false)
reporter.report("Patch contains white space change in valid hunk at line ${i + 1}, file: $patchPath", false)
}
}
@@ -94,7 +94,7 @@ abstract class CheckSAS extends CheckTask {
}
}
protected static boolean isSided(Annotatable annotatable) {
protected static isSided(Annotatable annotatable) {
if (annotatable === null) return false
for (ann in annotatable.annotations) {
if ('Lnet/minecraftforge/api/distmarker/OnlyIn;' == ann.desc)
@@ -104,7 +104,7 @@ abstract class CheckSAS extends CheckTask {
}
protected static findChildMethods(Map<String, InheritanceData> json, String cls, String desc) {
return json.values().findAll{ it.methods !== null && it.methods[desc] !== null && it.methods[desc].override == cls && isSided(it.methods[desc]) }
return json.values().findAll{ it.methods != null && it.methods[desc] != null && it.methods[desc].override == cls && isSided(it.methods[desc]) }
.collect { it.name + ' ' + desc.replace(' ', '') } as TreeSet
}
}
@@ -1,8 +1,6 @@
package net.minecraftforge.forge.tasks.checks
import groovy.transform.CompileStatic
import groovy.transform.stc.ClosureParams
import groovy.transform.stc.ThirdParam
import net.minecraftforge.forge.tasks.Util
import org.gradle.api.Action
import org.gradle.api.DefaultTask
@@ -35,7 +33,7 @@ abstract class CheckTask extends DefaultTask implements VerificationTask {
void run() {
Util.init()
boolean doFix = getMode().get() === CheckMode.FIX
final doFix = getMode().get() === CheckMode.FIX
final Reporter reporter = new Reporter(doFix)
check(reporter, doFix)
@@ -90,8 +88,7 @@ abstract class CheckTask extends DefaultTask implements VerificationTask {
}
static <T extends CheckTask> void registerTask(TaskContainer tasks, String taskName, @DelegatesTo.Target('type') Class<T> clazz,
@DelegatesTo(genericTypeIndex = 0, target = 'type')
@ClosureParams(ThirdParam.FirstGenericType) Closure configuration) {
@DelegatesTo(genericTypeIndex = 0, target = 'type') Closure configuration) {
taskName = taskName.capitalize()
tasks.register("check$taskName", clazz) { CheckTask task ->
def castedTask = task as T
-73
View File
@@ -1,73 +0,0 @@
plugins {
id 'java-library'
id 'net.minecraftforge.gradle.patcher'
}
evaluationDependsOn(':mcp')
repositories {
mavenCentral()
maven gradleutils.forgeMaven
maven gradleutils.minecraftLibsMaven
}
java.toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION)
dependencies {
implementation(libs.forgespi)
}
patcher {
parent = project(':mcp')
mcVersion = MC_VERSION
patchedSrc = file('src/main/java')
mappings channel: MAPPING_CHANNEL, version: MAPPING_VERSION
runs {
clean_client {
client true
taskName 'clean_client'
ideaModule "${rootProject.name}.${project.name}.main"
main 'net.minecraft.client.main.Main'
workingDirectory project.file('run/client')
args '--gameDir', '.'
args '--version', MC_VERSION
args '--assetsDir', downloadAssets.output
args '--assetIndex', '{asset_index}'
args '--accessToken', '0'
}
clean_server {
client false
taskName 'clean_server'
ideaModule "${rootProject.name}.${project.name}.main"
main 'net.minecraft.server.Main'
workingDirectory project.file('run/server')
}
}
}
tasks.withType(JavaCompile).configureEach {
options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation
options.warnings = false // Shutup deprecated for removal warnings
}
eclipse.classpath.file.whenMerged {
// Disable optional warnings on the minecraft decompiled source code. It just muddies the warning window and hides warnings in our own codebase
def src = entries.find { it.path == 'src/main/java' }
if (src == null) throw new IllegalStateException("You must run `setup` task before importing")
src.entryAttributes['ignore_optional_problems'] = 'true'
}
tasks.named('genPatches').configure {
onlyIf { false }
}
tasks.named('extractRangeMap').configure {
onlyIf { false }
tool = 'net.minecraftforge:Srg2Source:8.1.0:fatjar'
}
-1073
View File
File diff suppressed because it is too large. Load diff
-85
View File
@@ -1,85 +0,0 @@
import net.minecraftforge.forge.tasks.CleanProperties
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'net.minecraftforge.gradleutils'
group = 'net.minecraftforge'
version = VERSION
println("Version: $version")
repositories {
mavenCentral()
maven gradleutils.forgeMaven
maven gradleutils.minecraftLibsMaven
//mavenLocal()
}
tasks.withType(Javadoc).configureEach {
options.tags = [
'apiNote:a:<em>API Note:</em>',
'implSpec:a:<em>Implementation Requirements:</em>',
'implNote:a:<em>Implementation Note:</em>'
]
options.addStringOption('Xdoclint:all,-missing', '-public')
}
// We need to write the manifest to the binary file so we have properly versioned packaged at dev time.
tasks.register('writeManifest') {
doLast {
if (plugins.findPlugin('net.minecraftforge.gradle.patcher')) // Forge project
universalJar.manifest.writeTo(rootProject.file('src/main/resources/META-INF/MANIFEST.MF'))
else
jar.manifest.writeTo(project.file('src/main/resources/META-INF/MANIFEST.MF'))
}
}
tasks.register('generateResources') {
dependsOn('writeManifest')
}
// Make sure out manifests get written before compiling the code, IDEA calls this task if you tell it to use the gradle build.
tasks.withType(JavaCompile).configureEach {
dependsOn 'generateResources'
dependsOn 'processResources' // Needed because we merge the output of this with the output of the compile task. And gradle detects downstream tasks using the output without a hard dep
options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation
options.warnings = false // Shutup deprecated for removal warnings
options.forkOptions.jvmArgs += '-Xmx3G' // Needed to make compiling faster, and not run out of heap space in some cases.
}
// Merge the resources and classes into the same directory. We'll need to split them at runtime because
// Minecraft and Forge are in the same sourceSet as they are inter dependent.. for now..
sourceSets.each {
def dir = layout.buildDirectory.dir("classes/java/$it.name")
it.output.resourcesDir = dir
it.java.destinationDirectory = dir
}
tasks.register('copyEclipseSettings') {
doLast {
rootProject.fileTree('ide/eclipse/template/.settings/').matching { include '**/*.prefs' }.each { file ->
def target = project.file('.settings/' + file.name)
def temp = new CleanProperties().load(file)
def exst = new CleanProperties().load(target)
exst.put('eclipse.preferences.version', '1')
exst.putAll(temp)
exst.store(target)
}
}
}
// TODO: [Gradle][IntelliJ] Auto trigger these tasks on import.
eclipse {
// Run everytime eclipse builds the code
//autoBuildTasks writeManifest
// Run when importing the project
synchronizationTasks generateResources, copyEclipseSettings, eclipseClasspath, eclipseProject
}
idea {
module {
// IntelliJ IDEA does not do this by itself anymore...
downloadJavadoc = true
downloadSources = true
}
}
+8 -27
View File
@@ -2,15 +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.21.1-&query=$.promos["1.21.1-recommended"]&color=brightgreen&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtc3RhciBmYS13LTE4IiBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1pY29uPSJzdGFyIiBkYXRhLXByZWZpeD0iZmFzIiBmb2N1c2FibGU9ImZhbHNlIiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCA1NzYgNTEyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNMjU5LjMgMTcuOEwxOTQgMTUwLjIgNDcuOSAxNzEuNWMtMjYuMiAzLjgtMzYuNyAzNi4xLTE3LjcgNTQuNmwxMDUuNyAxMDMtMjUgMTQ1LjVjLTQuNSAyNi4zIDIzLjIgNDYgNDYuNCAzMy43TDI4OCA0MzkuNmwxMzAuNyA2OC43YzIzLjIgMTIuMiA1MC45LTcuNCA0Ni40LTMzLjdsLTI1LTE0NS41IDEwNS43LTEwM2MxOS0xOC41IDguNS01MC44LTE3LjctNTQuNkwzODIgMTUwLjIgMzE2LjcgMTcuOGMtMTEuNy0yMy42LTQ1LjYtMjMuOS01Ny40IDB6IiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K)][Download]
[![Latest Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Latest&prefix=1.21.1-&query=$.promos["1.21.1-latest"]&color=blue&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtYnVnIGZhLXctMTYiIGFyaWEtaGlkZGVuPSJ0cnVlIiBkYXRhLWljb249ImJ1ZyIgZGF0YS1wcmVmaXg9ImZhcyIgZm9jdXNhYmxlPSJmYWxzZSIgcm9sZT0iaW1nIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTUxMS45ODggMjg4LjljLS40NzggMTcuNDMtMTUuMjE3IDMxLjEtMzIuNjUzIDMxLjFINDI0djE2YzAgMjEuODY0LTQuODgyIDQyLjU4NC0xMy42IDYxLjE0NWw2MC4yMjggNjAuMjI4YzEyLjQ5NiAxMi40OTcgMTIuNDk2IDMyLjc1OCAwIDQ1LjI1NS0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDBsLTU0LjczNi01NC43MzZDMzQ1Ljg4NiA0NjcuOTY1IDMxNC4zNTEgNDgwIDI4MCA0ODBWMjM2YzAtNi42MjctNS4zNzMtMTItMTItMTJoLTI0Yy02LjYyNyAwLTEyIDUuMzczLTEyIDEydjI0NGMtMzQuMzUxIDAtNjUuODg2LTEyLjAzNS05MC42MzYtMzIuMTA4bC01NC43MzYgNTQuNzM2Yy0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDAtMTIuNDk2LTEyLjQ5Ny0xMi40OTYtMzIuNzU4IDAtNDUuMjU1bDYwLjIyOC02MC4yMjhDOTIuODgyIDM3OC41ODQgODggMzU3Ljg2NCA4OCAzMzZ2LTE2SDMyLjY2NkMxNS4yMyAzMjAgLjQ5MSAzMDYuMzMuMDEzIDI4OC45LS40ODQgMjcwLjgxNiAxNC4wMjggMjU2IDMyIDI1Nmg1NnYtNTguNzQ1bC00Ni42MjgtNDYuNjI4Yy0xMi40OTYtMTIuNDk3LTEyLjQ5Ni0zMi43NTggMC00NS4yNTUgMTIuNDk4LTEyLjQ5NyAzMi43NTgtMTIuNDk3IDQ1LjI1NiAwTDE0MS4yNTUgMTYwaDIyOS40ODlsNTQuNjI3LTU0LjYyN2MxMi40OTgtMTIuNDk3IDMyLjc1OC0xMi40OTcgNDUuMjU2IDAgMTIuNDk2IDEyLjQ5NyAxMi40OTYgMzIuNzU4IDAgNDUuMjU1TDQyNCAxOTcuMjU1VjI1Nmg1NmMxNy45NzIgMCAzMi40ODQgMTQuODE2IDMxLjk4OCAzMi45ek0yNTcgMGMtNjEuODU2IDAtMTEyIDUwLjE0NC0xMTIgMTEyaDIyNEMzNjkgNTAuMTQ0IDMxOC44NTYgMCAyNTcgMHoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=
[![Stable Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Stable&prefix=1.20.1&query=$.promos["1.20.1-recommended"]&color=brightgreen&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtc3RhciBmYS13LTE4IiBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1pY29uPSJzdGFyIiBkYXRhLXByZWZpeD0iZmFzIiBmb2N1c2FibGU9ImZhbHNlIiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCA1NzYgNTEyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNMjU5LjMgMTcuOEwxOTQgMTUwLjIgNDcuOSAxNzEuNWMtMjYuMiAzLjgtMzYuNyAzNi4xLTE3LjcgNTQuNmwxMDUuNyAxMDMtMjUgMTQ1LjVjLTQuNSAyNi4zIDIzLjIgNDYgNDYuNCAzMy43TDI4OCA0MzkuNmwxMzAuNyA2OC43YzIzLjIgMTIuMiA1MC45LTcuNCA0Ni40LTMzLjdsLTI1LTE0NS41IDEwNS43LTEwM2MxOS0xOC41IDguNS01MC44LTE3LjctNTQuNkwzODIgMTUwLjIgMzE2LjcgMTcuOGMtMTEuNy0yMy42LTQ1LjYtMjMuOS01Ny40IDB6IiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K)][Download]
[![Latest Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Latest&prefix=1.20.1-&query=$.promos["1.20.1-latest"]&color=blue&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtYnVnIGZhLXctMTYiIGFyaWEtaGlkZGVuPSJ0cnVlIiBkYXRhLWljb249ImJ1ZyIgZGF0YS1wcmVmaXg9ImZhcyIgZm9jdXNhYmxlPSJmYWxzZSIgcm9sZT0iaW1nIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTUxMS45ODggMjg4LjljLS40NzggMTcuNDMtMTUuMjE3IDMxLjEtMzIuNjUzIDMxLjFINDI0djE2YzAgMjEuODY0LTQuODgyIDQyLjU4NC0xMy42IDYxLjE0NWw2MC4yMjggNjAuMjI4YzEyLjQ5NiAxMi40OTcgMTIuNDk2IDMyLjc1OCAwIDQ1LjI1NS0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDBsLTU0LjczNi01NC43MzZDMzQ1Ljg4NiA0NjcuOTY1IDMxNC4zNTEgNDgwIDI4MCA0ODBWMjM2YzAtNi42MjctNS4zNzMtMTItMTItMTJoLTI0Yy02LjYyNyAwLTEyIDUuMzczLTEyIDEydjI0NGMtMzQuMzUxIDAtNjUuODg2LTEyLjAzNS05MC42MzYtMzIuMTA4bC01NC43MzYgNTQuNzM2Yy0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDAtMTIuNDk2LTEyLjQ5Ny0xMi40OTYtMzIuNzU4IDAtNDUuMjU1bDYwLjIyOC02MC4yMjhDOTIuODgyIDM3OC41ODQgODggMzU3Ljg2NCA4OCAzMzZ2LTE2SDMyLjY2NkMxNS4yMyAzMjAgLjQ5MSAzMDYuMzMuMDEzIDI4OC45LS40ODQgMjcwLjgxNiAxNC4wMjggMjU2IDMyIDI1Nmg1NnYtNTguNzQ1bC00Ni42MjgtNDYuNjI4Yy0xMi40OTYtMTIuNDk3LTEyLjQ5Ni0zMi43NTggMC00NS4yNTUgMTIuNDk4LTEyLjQ5NyAzMi43NTgtMTIuNDk3IDQ1LjI1NiAwTDE0MS4yNTUgMTYwaDIyOS40ODlsNTQuNjI3LTU0LjYyN2MxMi40OTgtMTIuNDk3IDMyLjc1OC0xMi40OTcgNDUuMjU2IDAgMTIuNDk2IDEyLjQ5NyAxMi40OTYgMzIuNzU4IDAgNDUuMjU1TDQyNCAxOTcuMjU1VjI1Nmg1NmMxNy45NzIgMCAzMi40ODQgMTQuODE2IDMxLjk4OCAzMi45ek0yNTcgMGMtNjEuODU2IDAtMTEyIDUwLjE0NC0xMTIgMTEyaDIyNEMzNjkgNTAuMTQ0IDMxOC44NTYgMCAyNTcgMHoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=
)][Download] [![Discord](https://img.shields.io/discord/313125603924639766.svg?color=%237289da&label=Discord&logo=discord&logoColor=%237289da)][Discord] [![Support](https://img.shields.io/badge/Patreon-Support-orange.svg?logo=Patreon)][Patreon]
Forge is a free, open-source modding API all of your favourite mods use!
| Version | Support |
|--------------|-----------------------------|
| All versions | [Supported][Support-Policy] |
| Version | Support |
|---------| ------------- |
| 1.20.x | Active |
| 1.19.x | Legacy |
* [Download]
* [Forum]
@@ -26,23 +27,10 @@ You can download the installer for the *Recommended Build* or the
*Latest build* there. Latest builds may have newer features but may be
more unstable as a result. The installer will attempt to install Forge
into your vanilla launcher environment, where you can then create a new
profile using that version and play the game!
profile using that version and play the game!
For support and questions, visit [the Support Forum][Support-Forum] or [the Forge Discord server][Discord].
## Required Java Versions
We require the version of Java that Minecraft requires, which is currently:
| Minecraft | Java |
|---------------|-------------------|
| 1.20.6+ | [21][Adoptium-21] |
| 1.18 - 1.20.4 | [17][Adoptium-17] |
| 1.17.1 | [16][Adoptium-16] |
| 1.16.5- | [8][Adoptium-8] |
**If you are on Windows and have difficulty using Java to launch JAR files, consider using [Jarfix by Johann Löfflmann][Jarfix].**
# Creating Mods
[See the "Getting Started" section in the Forge Documentation][Getting-Started].
@@ -75,15 +63,8 @@ Please ensure you have a valid email address associated with your GitHub account
[Download]: https://files.minecraftforge.net/
[Adoptium-21]: https://adoptium.net/temurin/releases/?version=21
[Adoptium-17]: https://adoptium.net/temurin/releases/?version=17
[Adoptium-16]: https://adoptium.net/temurin/releases/?version=16
[Adoptium-8]: https://adoptium.net/temurin/releases/?version=8
[Jarfix]: https://johann.loefflmann.net/en/software/jarfix/index.html
[Forum]: https://forums.minecraftforge.net/
[Support-Forum]: https://forums.minecraftforge.net/forum/18-support-bug-reports/
[Support-Policy]: https://forums.minecraftforge.net/topic/144690-new-tiered-support-policy
[Discord]: https://discord.minecraftforge.net/
@@ -92,4 +73,4 @@ Please ensure you have a valid email address associated with your GitHub account
[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
[Patreon]: https://www.patreon.com/MinecraftForge
-24
View File
@@ -1,24 +0,0 @@
#!/usr/bin/env bash
for file in $(find repo | grep --invert-match -e md5 -e sha | grep -e .jar -e .zip); do
file_path=$(realpath $file)
file_name=$(basename $file)
echo "$file_name"
files=$(unzip -l $file | awk '{print $4}')
for f in $files; do
out="_actual/$file_name/$f"
parent=$(dirname "$out")
echo $out
if [ ! -d "$parent" ]; then
mkdir -p "$parent"
fi
if [[ $f == *.class ]]; then
javap -v -p jar:file://$file_path!/$f | tail -n +4 >$out.txt
elif [[ $f != */ && $f != "Name" && $f != "----" ]]; then
unzip -o -q $file $f -d _actual/$file_name
fi
done
done
+49 -42
View File
@@ -1,67 +1,74 @@
import net.minecraftforge.gradleutils.PomUtils
plugins {
id 'java-library'
id 'maven-publish'
id 'net.minecraftforge.licenser'
id 'net.minecraftforge.gradleutils'
id 'com.github.ben-manes.versions'
id 'org.javamodularity.moduleplugin' version '1.8.3' apply false
id 'org.cadixdev.licenser'
}
apply plugin: 'java-library'
apply plugin: 'jacoco'
apply plugin: 'org.javamodularity.moduleplugin'
apply from: rootProject.file('build_shared.gradle')
dependencyUpdates.rejectVersionIf { isNonStable(it.candidate.version) }
dependencies {
compileOnly(libs.nulls)
compileOnly("org.jetbrains:annotations:${JETBRAINS_ANNOTATIONS_VERSION}")
api(libs.eventbus)
api("net.minecraftforge:eventbus:${EVENTBUS_VERSION}")
implementation(project(':fmlloader'))
implementation(libs.commons.io)
implementation("commons-io:commons-io:${COMMONS_IO_VERSION}")
}
java {
toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION)
withSourcesJar()
task sourcesJar(type: Jar) {
archiveClassifier = 'sources'
from sourceSets.main.allSource
}
tasks.named('jar', Jar).configure {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.fmlcore',
'FMLModType': 'LIBRARY'
] as LinkedHashMap)
attributes([
'Specification-Title': 'FML',
ext {
MANIFESTS = [
'': [
'FMLModType': 'LIBRARY',
'Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"),
'Git-Commit': GIT_INFO.abbreviatedId,
'Git-Branch': GIT_INFO.branch,
'Build-Number': "${System.getenv('BUILD_NUMBER')?:0}"
] as LinkedHashMap,
'net/minecraftforge/fml/loading/': [
'Specification-Title': 'FMLLoader',
'Specification-Vendor': 'Forge Development LLC',
'Specification-Version': '1',
'Implementation-Title': 'FML',
'Implementation-Title': 'FML Loader',
'Implementation-Version': '1.0',
'Implementation-Vendor': 'Forge Development LLC'
] as LinkedHashMap, 'net/minecraftforge/fml/')
'Implementation-Vendor': 'Forge'
] as LinkedHashMap
]
}
jar.doFirst {
manifest.attributes['Automatic-Module-Name'] = project.name
MANIFESTS.each { pkg, values ->
if (pkg == '')
manifest.attributes(values)
else
manifest.attributes(values, pkg)
}
}
tasks.withType(JavaCompile).configureEach {
options.compilerArgs << '-Xlint:-unchecked'
tasks.withType(JavaCompile) {
options.compilerArgs << '-Xlint:unchecked'
}
license {
header = rootProject.file('LICENSE-header.txt')
include 'net/minecraftforge/'
}
publishing {
publications.register('mavenJava', MavenPublication).configure {
from components.java
artifactId = 'fmlcore'
pom {
name = project.name
description = 'Modifactions to Minecraft to enable mod developers.'
url = 'https://github.com/MinecraftForge/MinecraftForge'
PomUtils.setGitHubDetails(pom, 'MinecraftForge')
license PomUtils.Licenses.LGPLv2_1
}
}
repositories {
maven gradleutils.publishingForgeMaven
}
artifacts {
archives jar
archives sourcesJar
}
publishing.publications.mavenJava(MavenPublication) {
from components.java
artifact sourcesJar
}
@@ -5,39 +5,36 @@
package net.minecraftforge.fml;
import cpw.mods.modlauncher.util.ServiceLoaderUtils;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.fml.config.IConfigEvent;
import net.minecraftforge.fml.loading.FMLLoader;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
import java.util.function.Supplier;
/**
* Used to allow access to certain things from the game layer
*/
public class Bindings {
private Bindings() {}
private static final Bindings INSTANCE = new Bindings();
/**
* The provider of the bindings
* @implNote May also throw a {@link ServiceConfigurationError}
*/
private static final IBindingsProvider PROVIDER = ServiceLoader.load(FMLLoader.getGameLayer(), IBindingsProvider.class)
.findFirst().orElseThrow(() -> new IllegalStateException("Could not find bindings provider"));
private final IBindingsProvider provider;
private Bindings() {
final var providers = ServiceLoaderUtils.streamServiceLoader(()->ServiceLoader.load(FMLLoader.getGameLayer(), IBindingsProvider.class), sce->{}).toList();
if (providers.size() != 1) {
throw new IllegalStateException("Could not find bindings provider");
}
this.provider = providers.get(0);
}
/**
* @return A supplier of net.minecraftforge.common.MinecraftForge#EVENT_BUS
*/
public static Supplier<IEventBus> getForgeBus() {
return PROVIDER.getForgeBusSupplier();
return INSTANCE.provider.getForgeBusSupplier();
}
public static Supplier<I18NParser> getMessageParser() {
return PROVIDER.getMessageParser();
return INSTANCE.provider.getMessageParser();
}
public static Supplier<IConfigEvent.ConfigConfig> getConfigConfiguration() {
return PROVIDER.getConfigConfiguration();
return INSTANCE.provider.getConfigConfiguration();
}
}
@@ -57,9 +57,7 @@ public class DeferredWorkQueue
LOGGER.debug(LOADING, "Dispatching synchronous work for work queue {}: {} jobs", modLoadingStage, tasks.size());
RuntimeException aggregate = new RuntimeException();
Stopwatch timer = Stopwatch.createStarted();
for (TaskInfo t : tasks) {
makeRunnable(t, Runnable::run, aggregate);
}
tasks.forEach(t -> makeRunnable(t, Runnable::run, aggregate));
timer.stop();
if (aggregate.getSuppressed().length > 0) {
LOGGER.fatal(
@@ -116,7 +114,7 @@ public class DeferredWorkQueue
return future;
}
private static final class TaskInfo {
private static class TaskInfo {
private final ModContainer owner;
private Runnable task;
private CompletableFuture<?> future;
@@ -25,7 +25,6 @@ import java.util.function.Supplier;
* @param <T> the type of the record which is held by the extension point
*/
@SuppressWarnings("unused") // Type parameter T
// TODO: Complete re-write, this hasnt actually be used in a generic way by mods and we could make it a lot simpler.
public interface IExtensionPoint<T extends Record>
{
/**
@@ -41,7 +40,7 @@ public interface IExtensionPoint<T extends Record>
* where {@code true} means it is from the server and {@code false} means it is from the world save. The return
* value of the predicate determines whether the remote version is "compatible" for the purposes of the display test.</p>
*
* <p>The local compatibility version may be of the value {@link DisplayTest#IGNORESERVERONLY},
* <p>The local compatibility version may be of the value {@link net.minecraftforge.network.NetworkConstants#IGNORESERVERONLY},
* in which case clients will ignore the mod's presence if it is present on the server but not on the client.
* However, the remote version test predicate must still accept this value as a remote version in order to display
* as compatible if the mod is present on the client.</p>
@@ -95,27 +94,6 @@ public interface IExtensionPoint<T extends Record>
*/
@SuppressWarnings("JavadocReference") // reference to NetworkConstants, ForgeHooksClient
record DisplayTest(Supplier<String> suppliedVersion, BiPredicate<String, Boolean> remoteVersionTest) implements IExtensionPoint<DisplayTest> {
public static final String IGNORESERVERONLY = "SERVER_ONLY";
/**
* Ignores any version information coming from the server - use for server only mods
*/
public static final Supplier<DisplayTest> IGNORE_SERVER_VERSION = () -> new DisplayTest(IGNORESERVERONLY, (remoteVersion, isFromServer) -> true);
/**
* Ignores all information and provides no information
* <p>Note: If your mod is purely client-side and has no multiplayer functionality (be it dedicated servers or
* Open to LAN), consider setting {@code clientSideOnly=true} in the root of your mods.toml.</p>
*/
public static final Supplier<DisplayTest> IGNORE_ALL_VERSION = () -> new DisplayTest("", (remoteVersion, isFromServer) -> true);
/**
* An optional alternative to {@link #DisplayTest(Supplier, BiPredicate)} which accepts a constant version string
* instead of a {@link Supplier}.
* <p>Internally, the provided version string is wrapped in a Supplier for you.</p>
*/
public DisplayTest(String version, BiPredicate<String, Boolean> remoteVersionTest) {
this(() -> version, remoteVersionTest);
}
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";
}
}
@@ -61,7 +61,7 @@ public class InterModComms
}
}
private static final ConcurrentMap<String, ConcurrentLinkedQueue<IMCMessage>> containerQueues = new ConcurrentHashMap<>();
private static ConcurrentMap<String, ConcurrentLinkedQueue<IMCMessage>> containerQueues = new ConcurrentHashMap<>();
/**
* Send IMC to remote. Sender will default to the active modcontainer, or minecraft if not.
@@ -115,14 +115,16 @@ public class InterModComms
return getMessages(modId, s->Boolean.TRUE);
}
private record QueueFilteringSpliterator(
ConcurrentLinkedQueue<IMCMessage> queue,
Predicate<String> methodFilter,
Iterator<IMCMessage> iterator
) implements Spliterator<IMCMessage> {
private static class QueueFilteringSpliterator implements Spliterator<IMCMessage>
{
private final ConcurrentLinkedQueue<IMCMessage> queue;
private final Predicate<String> methodFilter;
private final Iterator<IMCMessage> iterator;
public QueueFilteringSpliterator(final ConcurrentLinkedQueue<IMCMessage> queue, final Predicate<String> methodFilter) {
this(queue, methodFilter, queue.iterator());
this.queue = queue;
this.iterator = queue.iterator();
this.methodFilter = methodFilter;
}
@Override
@@ -16,7 +16,8 @@ import net.minecraftforge.api.distmarker.Dist;
*
* @see Dist
*/
public enum LogicalSide {
public enum LogicalSide
{
/**
* The logical client of the Minecraft game, which interfaces with the player's inputs and renders the player's
* viewpoint.
@@ -26,7 +27,6 @@ public enum LogicalSide {
* @see Dist#CLIENT
*/
CLIENT,
/**
* The logical server of the Minecraft game, responsible for connecting to clients and running the simulation logic
* on the level.
@@ -41,14 +41,16 @@ public enum LogicalSide {
/**
* {@return if this logical side is the server}
*/
public boolean isServer() {
public boolean isServer()
{
return !isClient();
}
/**
* {@return if the logical side is the client}
*/
public boolean isClient() {
public boolean isClient()
{
return this == CLIENT;
}
}
@@ -21,7 +21,6 @@ import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -45,7 +44,7 @@ public abstract class ModContainer
protected final IModInfo modInfo;
protected ModLoadingStage modLoadingStage;
protected Supplier<?> contextExtension;
protected final Map<ModLoadingStage, Runnable> activityMap = new EnumMap<>(ModLoadingStage.class);
protected final Map<ModLoadingStage, Runnable> activityMap = new HashMap<>();
protected final Map<Class<? extends IExtensionPoint<?>>, Supplier<?>> extensionPoints = new IdentityHashMap<>();
protected final EnumMap<ModConfig.Type, ModConfig> configs = new EnumMap<>(ModConfig.Type.class);
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@@ -59,16 +58,15 @@ public abstract class ModContainer
this.modInfo = info;
this.modLoadingStage = ModLoadingStage.CONSTRUCT;
final String displayTestString = info.getConfig().<String>getConfigElement("displayTest")
.orElseGet(() -> info.getOwningFile().isClientSideOnly() ? "IGNORE_ALL_VERSION" : "MATCH_VERSION");
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
IExtensionPoint.DisplayTest.IGNORE_SERVER_VERSION;
() -> new IExtensionPoint.DisplayTest(() -> IExtensionPoint.DisplayTest.IGNORESERVERONLY, (incoming, isNetwork) -> true);
case "IGNORE_ALL_VERSION" -> // Ignores all information and provides no information
IExtensionPoint.DisplayTest.IGNORE_ALL_VERSION;
() -> 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");
@@ -147,31 +145,12 @@ public abstract class ModContainer
extensionPoints.put(point, extension);
}
public void registerDisplayTest(IExtensionPoint.DisplayTest displayTest) {
registerExtensionPoint(IExtensionPoint.DisplayTest.class, () -> displayTest);
}
public void registerDisplayTest(Supplier<IExtensionPoint.DisplayTest> displayTest) {
registerExtensionPoint(IExtensionPoint.DisplayTest.class, displayTest);
}
public void registerDisplayTest(String version, BiPredicate<String, Boolean> remoteVersionTest) {
registerDisplayTest(new IExtensionPoint.DisplayTest(version, remoteVersionTest));
}
public void registerDisplayTest(Supplier<String> suppliedVersion, BiPredicate<String, Boolean> remoteVersionTest) {
registerDisplayTest(new IExtensionPoint.DisplayTest(suppliedVersion, remoteVersionTest));
}
public void addConfig(final ModConfig modConfig) {
configs.put(modConfig.getType(), modConfig);
}
public void dispatchConfigEvent(IConfigEvent event) {
var handler = configHandler.orElse(null);
if (handler != null) {
handler.accept(event);
}
configHandler.ifPresent(configHandler->configHandler.accept(event));
}
/**
@@ -47,7 +47,7 @@ import java.util.stream.Stream;
*/
public class ModList
{
//private static final Logger LOGGER = LogManager.getLogger();
private static Logger LOGGER = LogManager.getLogger();
private static ModList INSTANCE;
private final List<IModFileInfo> modFiles;
private final List<IModInfo> sortedList;
@@ -74,12 +74,11 @@ public class ModList
}
private String fileToLine(IModFile mf) {
var mainMod = mf.getModInfos().getFirst();
return String.format(Locale.ENGLISH, "%-50.50s|%-30.30s|%-30.30s|%-20.20s|%-10.10s|Manifest: %s", mf.getFileName(),
mainMod.getDisplayName(),
mainMod.getModId(),
mainMod.getVersion(),
getModContainerState(mainMod.getModId()),
mf.getModInfos().get(0).getDisplayName(),
mf.getModInfos().get(0).getModId(),
mf.getModInfos().get(0).getVersion(),
getModContainerState(mf.getModInfos().get(0).getModId()),
((ModFileInfo)mf.getModFileInfo()).getCodeSigningFingerprint().orElse("NOSIGNATURE"));
}
private String crashReport() {
@@ -96,6 +95,14 @@ public class ModList
return INSTANCE;
}
private static ForkJoinWorkerThread newForkJoinWorkerThread(ForkJoinPool pool) {
ForkJoinWorkerThread thread = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool);
thread.setName("modloading-worker-" + thread.getPoolIndex());
// The default sets it to the SystemClassloader, so copy the current one.
thread.setContextClassLoader(Thread.currentThread().getContextClassLoader());
return thread;
}
public List<IModFileInfo> getModFiles()
{
return modFiles;
@@ -113,29 +120,27 @@ public class ModList
return executor -> gather(
this.mods.stream()
.map(mod -> ModContainer.buildTransitionHandler(mod, eventGenerator, progressBar, stateChange, executor))
.toList()
).thenComposeAsync(ModList::completableFutureFromExceptionList, executor);
.collect(Collectors.toList()))
.thenComposeAsync(ModList::completableFutureFromExceptionList, executor);
}
static CompletionStage<Void> completableFutureFromExceptionList(List<? extends Map.Entry<?, Throwable>> t) {
if (t.stream().noneMatch(e->e.getValue()!=null)) {
return CompletableFuture.completedFuture(null);
} else {
final List<Throwable> throwables = t.stream().filter(e -> e.getValue() != null).map(Map.Entry::getValue).toList();
final List<Throwable> throwables = t.stream().filter(e -> e.getValue() != null).map(Map.Entry::getValue).collect(Collectors.toList());
CompletableFuture<Void> cf = new CompletableFuture<>();
final RuntimeException accumulator = new RuntimeException();
cf.completeExceptionally(accumulator);
for (Throwable exception : throwables) {
throwables.forEach(exception -> {
if (exception instanceof CompletionException) {
exception = exception.getCause();
}
if (exception.getSuppressed().length != 0) {
for (Throwable throwable : exception.getSuppressed()) {
accumulator.addSuppressed(throwable);
}
if (exception.getSuppressed().length!=0) {
Arrays.stream(exception.getSuppressed()).forEach(accumulator::addSuppressed);
} else {
accumulator.addSuppressed(exception);
}
}
});
return cf;
}
}
@@ -143,11 +148,11 @@ public class ModList
static <V> CompletableFuture<List<Map.Entry<V, Throwable>>> gather(List<? extends CompletableFuture<? extends V>> futures) {
List<Map.Entry<V, Throwable>> list = new ArrayList<>(futures.size());
CompletableFuture<?>[] results = new CompletableFuture[futures.size()];
for (var future : futures) {
futures.forEach(future -> {
int i = list.size();
list.add(null);
results[i] = future.whenComplete((result, exception) -> list.set(i, new AbstractMap.SimpleImmutableEntry<>(result, exception)));
}
});
return CompletableFuture.allOf(results).handle((r, th)->null).thenApply(res -> list);
}
@@ -6,7 +6,6 @@
package net.minecraftforge.fml;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Streams;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.loading.FMLEnvironment;
@@ -33,7 +32,6 @@ import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static net.minecraftforge.fml.Logging.CORE;
import static net.minecraftforge.fml.Logging.LOADING;
@@ -77,13 +75,13 @@ import static net.minecraftforge.fml.Logging.LOADING;
public class ModLoader
{
private static final Logger LOGGER = LogManager.getLogger();
private static ModLoader INSTANCE;
private final LoadingModList loadingModList;
private final List<ModLoadingException> loadingExceptions;
private final Set<IModInfo> erroredModInfos;
private final List<ModLoadingWarning> loadingWarnings;
private final ModStateManager stateManager;
private static boolean loadingStateValid;
private boolean loadingStateValid;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private final Optional<Consumer<String>> statusConsumer = StartupNotificationManager.modLoaderConsumer();
private final Set<IModLoadingState> completedStates = new HashSet<>();
@@ -91,39 +89,32 @@ public class ModLoader
private ModLoader()
{
INSTANCE = this;
this.loadingModList = FMLLoader.getLoadingModList();
this.loadingExceptions = this.loadingModList.getErrors().stream()
this.loadingExceptions = FMLLoader.getLoadingModList().getErrors().stream()
.flatMap(ModLoadingException::fromEarlyException)
.collect(Collectors.toList());
this.loadingWarnings = this.loadingModList.getBrokenFiles().stream()
this.loadingWarnings = FMLLoader.getLoadingModList().getBrokenFiles().stream()
.map(file -> new ModLoadingWarning(null, ModLoadingStage.VALIDATE, InvalidModIdentifier.identifyJarProblem(file.getFilePath()).orElse("fml.modloading.brokenfile"), file.getFileName()))
.collect(Collectors.toList());
if (this.loadingExceptions.isEmpty()) {
this.erroredModInfos = Collections.emptySet();
} else {
this.erroredModInfos = Collections.newSetFromMap(new IdentityHashMap<>());
this.erroredModInfos.addAll(this.loadingExceptions.stream().map(ModLoadingException::getModInfo).toList());
}
this.loadingModList.getModFiles().stream()
FMLLoader.getLoadingModList().getModFiles().stream()
.filter(ModFileInfo::missingLicense)
.filter(modFileInfo -> modFileInfo.getMods().stream().noneMatch(this.erroredModInfos::contains)) //Ignore files where any other mod already encountered an error
.filter(modFileInfo -> modFileInfo.getMods().stream().noneMatch(thisModInfo -> this.loadingExceptions.stream().map(ModLoadingException::getModInfo).anyMatch(otherInfo -> otherInfo == thisModInfo))) //Ignore files where any other mod already encountered an error
.map(modFileInfo -> new ModLoadingException(null, ModLoadingStage.VALIDATE, "fml.modloading.missinglicense", null, modFileInfo.getFile()))
.forEach(this.loadingExceptions::add);
this.stateManager = new ModStateManager();
CrashReportCallables.registerCrashCallable("ModLauncher", FMLLoader::getLauncherInfo);
CrashReportCallables.registerCrashCallable("ModLauncher launch target", FMLLoader::launcherHandlerName);
CrashReportCallables.registerCrashCallable("ModLauncher naming", FMLLoader::getNaming);
CrashReportCallables.registerCrashCallable("ModLauncher services", ModLoader::computeModLauncherServiceList);
CrashReportCallables.registerCrashCallable("FML Language Providers", ModLoader::computeLanguageList);
CrashReportCallables.registerCrashCallable("ModLauncher services", this::computeModLauncherServiceList);
CrashReportCallables.registerCrashCallable("FML Language Providers", this::computeLanguageList);
}
private static String computeLanguageList() {
return "\n" + FMLLoader.getLanguageLoadingProvider()
.applyForEach(lp -> lp.name() + "@" + lp.getClass().getPackage().getImplementationVersion())
.collect(Collectors.joining("\n\t\t", "\t\t", ""));
private String computeLanguageList() {
return "\n"+FMLLoader.getLanguageLoadingProvider().applyForEach(lp->lp.name() +"@"+ lp.getClass().getPackage().getImplementationVersion()).collect(Collectors.joining("\n\t\t", "\t\t", ""));
}
private static String computeModLauncherServiceList() {
private String computeModLauncherServiceList() {
final List<Map<String, String>> mods = FMLLoader.modLauncherModList();
return "\n"+mods.stream().map(mod->mod.getOrDefault("file","nofile")+
" "+mod.getOrDefault("name", "missing")+
@@ -132,13 +123,9 @@ public class ModLoader
collect(Collectors.joining("\n\t\t","\t\t",""));
}
public static ModLoader get() {
return LazyInit.INSTANCE;
}
private static final class LazyInit {
private static final ModLoader INSTANCE = new ModLoader();
private LazyInit() {}
public static ModLoader get()
{
return INSTANCE == null ? INSTANCE = new ModLoader() : INSTANCE;
}
/**
@@ -155,7 +142,7 @@ public class ModLoader
final ModList modList = ModList.of(loadingModList.getModFiles().stream().map(ModFileInfo::getFile).toList(),
loadingModList.getMods());
if (!this.loadingExceptions.isEmpty()) {
LOGGER.fatal(CORE, "Error during pre-loading phase", loadingExceptions.getFirst());
LOGGER.fatal(CORE, "Error during pre-loading phase", loadingExceptions.get(0));
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
modList.setLoadedMods(Collections.emptyList());
loadingStateValid = false;
@@ -163,7 +150,7 @@ public class ModLoader
}
List<? extends ForgeFeature.Bound> failedBounds = loadingModList.getMods().stream()
.map(ModInfo::getForgeFeatures)
.flatMap(List::stream)
.flatMap(Collection::stream)
.filter(bound -> !ForgeFeature.testFeature(FMLEnvironment.dist, bound))
.toList();
@@ -180,10 +167,10 @@ public class ModLoader
final List<ModContainer> modContainers = loadingModList.getModFiles().stream()
.map(ModFileInfo::getFile)
.map(this::buildMods)
.flatMap(List::stream)
.<ModContainer>mapMulti(Iterable::forEach)
.toList();
if (!loadingExceptions.isEmpty()) {
LOGGER.fatal(CORE, "Failed to initialize mod containers", loadingExceptions.getFirst());
LOGGER.fatal(CORE, "Failed to initialize mod containers", loadingExceptions.get(0));
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
modList.setLoadedMods(Collections.emptyList());
loadingStateValid = false;
@@ -192,28 +179,22 @@ public class ModLoader
modList.setLoadedMods(modContainers);
this.modList = modList;
var stateList = stateManager.getStates(ModLoadingPhase.GATHER);
var progress = StartupMessageManager.addProgressBar("Mod Gather", stateList.stream().mapToInt(mls -> mls.size().applyAsInt(this.modList)).sum());
for (IModLoadingState mls : stateList) {
dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress);
}
var progress = StartupMessageManager.addProgressBar("Mod Gather", stateList.stream().mapToInt(mls -> mls.size().applyAsInt(modList)).sum());
stateList.forEach(mls->dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress));
progress.complete();
}
public void loadMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
var stateList = stateManager.getStates(ModLoadingPhase.LOAD);
var progress = StartupMessageManager.addProgressBar("Mod Loading", stateList.stream().mapToInt(mls -> mls.size().applyAsInt(modList)).sum());
for (IModLoadingState mls : stateList) {
dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress);
}
stateList.forEach(mls->dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress));
progress.complete();
}
public void finishMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
var stateList = stateManager.getStates(ModLoadingPhase.COMPLETE);
var progress = StartupMessageManager.addProgressBar("Mod Complete", stateList.stream().mapToInt(mls -> mls.size().applyAsInt(modList)).sum());
for (IModLoadingState mls : stateList) {
dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress);
}
stateList.forEach(mls->dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress));
statusConsumer.ifPresent(c->c.accept(String.format("Mod loading complete - %d mods loaded", this.modList.size())));
progress.complete();
}
@@ -223,15 +204,10 @@ public class ModLoader
LOGGER.error("Cowardly refusing to process mod state change request from {}", state);
return;
}
progressBar.label(progressBar.name() + " working");
progressBar.label(progressBar.name()+ " working");
syncExecutor.drive(ticker);
var inlineRunnable = state.inlineRunnable().orElse(null);
if (inlineRunnable != null) handleInlineTransition(inlineRunnable, state, syncExecutor, ticker);
var transition = state.buildTransition(syncExecutor, parallelExecutor, progressBar).orElse(null);
if (transition != null) waitForTransition(state, syncExecutor, ticker, transition);
state.inlineRunnable().ifPresent(a->this.handleInlineTransition(a, state, syncExecutor, ticker));
state.buildTransition(syncExecutor, parallelExecutor, progressBar).ifPresent(t->waitForTransition(state, syncExecutor, ticker, t));
completedStates.add(state);
}
@@ -252,9 +228,10 @@ public class ModLoader
} catch (CompletionException e) {
loadingStateValid = false;
Throwable t = e.getCause();
boolean hasNotModLoadingEx = Arrays.stream(t.getSuppressed())
.anyMatch(obj -> !(obj instanceof ModLoadingException));
if (hasNotModLoadingEx) {
final List<Throwable> notModLoading = Arrays.stream(t.getSuppressed())
.filter(obj -> !(obj instanceof ModLoadingException))
.collect(Collectors.toList());
if (!notModLoading.isEmpty()) {
LOGGER.fatal("Encountered non-modloading exceptions!", e);
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
throw e;
@@ -280,10 +257,10 @@ public class ModLoader
.stream()
.map(e -> buildModContainerFromTOML(modFile, modInfoMap, e))
.filter(Objects::nonNull)
.toList();
.collect(Collectors.toList());
if (containers.size() != modInfoMap.size()) {
var modIds = modInfoMap.values().stream().map(IModInfo::getModId).sorted().toList();
var containerIds = containers.stream().map(c -> c != null ? c.getModId() : "(null)").sorted().toList();
var modIds = modInfoMap.values().stream().map(IModInfo::getModId).sorted().collect(Collectors.toList());
var containerIds = containers.stream().map(c -> c != null ? c.getModId() : "(null)").sorted().collect(Collectors.toList());
LOGGER.fatal(LOADING,"File {} constructed {} mods: {}, but had {} mods specified: {}",
modFile.getFilePath(),
@@ -301,19 +278,16 @@ public class ModLoader
loadingExceptions.add(new ModLoadingException(null, ModLoadingStage.CONSTRUCT, "fml.modloading.missingclasses", null, modFile.getFilePath()));
}
// remove errored mod containers
return containers.stream().filter(mc -> mc.modLoadingStage != ModLoadingStage.ERROR).toList();
return containers.stream().filter(mc -> mc.modLoadingStage != ModLoadingStage.ERROR).collect(Collectors.toList());
}
private ModContainer buildModContainerFromTOML(final IModFile modFile, final Map<String, IModInfo> modInfoMap, final Map.Entry<String, ? extends IModLanguageProvider.IModLanguageLoader> idToProviderEntry) {
try {
final String modId = idToProviderEntry.getKey();
final IModLanguageProvider.IModLanguageLoader languageLoader = idToProviderEntry.getValue();
IModInfo info = modInfoMap.get(modId);
// throw a missing metadata error if there is no matching modid in the modInfoMap from the mods.toml file
if (info == null)
throw new ModLoadingException(null, ModLoadingStage.CONSTRUCT, "fml.modloading.missingmetadata", null, modId);
IModInfo info = Optional.ofNullable(modInfoMap.get(modId)).
// throw a missing metadata error if there is no matching modid in the modInfoMap from the mods.toml file
orElseThrow(()->new ModLoadingException(null, ModLoadingStage.CONSTRUCT, "fml.modloading.missingmetadata", null, modId));
return languageLoader.loadMod(info, modFile.getScanResult(), FMLLoader.getGameLayer());
} catch (ModLoadingException mle) {
// exceptions are caught and added to the error list for later handling
@@ -328,7 +302,7 @@ public class ModLoader
* and don't want to cause extraneous crashes due to trying to do things that aren't possible in a "broken load"
*/
public static boolean isLoadingStateValid() {
return loadingStateValid;
return get().loadingStateValid;
}
public boolean hasCompletedState(final String stateName) {
@@ -12,58 +12,34 @@ import net.minecraftforge.fml.loading.FMLLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.function.BiPredicate;
import java.util.function.Supplier;
public class ModLoadingContext
{
private static final Logger LOGGER = LogUtils.getLogger();
private static final ThreadLocal<ModLoadingContext> context = ThreadLocal.withInitial(ModLoadingContext::new);
private ModContainer activeContainer;
private Object languageExtension;
private ModLoadingStage stage;
/**
* @deprecated Use the context provided by your language loader in your mod's constructor
*/
@Deprecated(forRemoval = true, since="1.21.1")
public static ModLoadingContext get() {
return context.get();
}
/**
* @deprecated Going to be moved to ForgeHooks for Internal use.
*/
@Deprecated(forRemoval = true, since="1.21.1")
private ModContainer activeContainer;
public void setActiveContainer(final ModContainer container) {
this.activeContainer = container;
this.languageExtension = container == null ? null : container.contextExtension.get();
}
/**
* Going to be moved to ForgeHooks for Internal use.
* @deprecated Override/Use {@link ModLoadingContext#getContainer()}
*/
@Deprecated(forRemoval = true, since="1.21.1")
public ModContainer getActiveContainer() {
return activeContainer == null ? ModList.get().getModContainerById("minecraft").orElseThrow(()->new RuntimeException("Where is minecraft???!")) : activeContainer;
}
/**
* @deprecated Going to be moved to ForgeHooks for Internal use.
*/
@Deprecated(forRemoval = true, since="1.21.1")
public String getActiveNamespace() {
return activeContainer == null ? "minecraft" : activeContainer.getNamespace();
}
/**
* @return {@link ModLoadingContext#getActiveContainer()} by default.
*/
public ModContainer getContainer() {
return getActiveContainer();
}
/**
* Register an {@link IExtensionPoint} with the mod container.
* @param point The extension point to register
@@ -71,67 +47,29 @@ public class ModLoadingContext
* @param <T> The type signature of the extension operator
*/
public <T extends Record & IExtensionPoint<T>> void registerExtensionPoint(Class<? extends IExtensionPoint<T>> point, Supplier<T> extension) {
getContainer().registerExtensionPoint(point, extension);
}
/**
* Register a {@link IExtensionPoint.DisplayTest} with the mod container.
* <p>A shorthand for registering a DisplayTest with {@link #registerExtensionPoint(Class, Supplier)}.</p>
* @param displayTest The {@link IExtensionPoint.DisplayTest} to register
*/
public void registerDisplayTest(IExtensionPoint.DisplayTest displayTest) {
getContainer().registerDisplayTest(() -> displayTest);
}
/**
* Register a {@link IExtensionPoint.DisplayTest} with the mod container.
* <p>A shorthand for registering a DisplayTest supplier with {@link #registerExtensionPoint(Class, Supplier)}.</p>
* @param displayTest The {@link Supplier<IExtensionPoint.DisplayTest>} to register
*/
public void registerDisplayTest(Supplier<IExtensionPoint.DisplayTest> displayTest) {
getContainer().registerDisplayTest(displayTest);
}
/**
* Register a {@link IExtensionPoint.DisplayTest} with the mod container.
* <p>A shorthand for registering a DisplayTest with {@link #registerExtensionPoint(Class, Supplier)} that also
* creates the DisplayTest instance for you using the provided parameters.</p>
* @see IExtensionPoint.DisplayTest#DisplayTest(String, BiPredicate)
*/
public void registerDisplayTest(String version, BiPredicate<String, Boolean> remoteVersionTest) {
getContainer().registerDisplayTest(new IExtensionPoint.DisplayTest(version, remoteVersionTest));
}
/**
* Register a {@link IExtensionPoint.DisplayTest} with the mod container.
* <p>A shorthand for registering a DisplayTest with {@link #registerExtensionPoint(Class, Supplier)} that also
* creates the DisplayTest instance for you using the provided parameters.</p>
* @see IExtensionPoint.DisplayTest#DisplayTest(Supplier, BiPredicate)
*/
public void registerDisplayTest(Supplier<String> suppliedVersion, BiPredicate<String, Boolean> remoteVersionTest) {
getContainer().registerDisplayTest(new IExtensionPoint.DisplayTest(suppliedVersion, remoteVersionTest));
getActiveContainer().registerExtensionPoint(point, extension);
}
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, getContainer().getModId());
LOGGER.debug("Attempted to register an empty config for type {} on mod {}", type, getActiveContainer().getModId());
return;
}
getContainer().addConfig(new ModConfig(type, spec, getContainer()));
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, getContainer().getModId(), fileName);
LOGGER.debug("Attempted to register an empty config for type {} on mod {} using file name {}", type, getActiveContainer().getModId(), fileName);
return;
}
getContainer().addConfig(new ModConfig(type, spec, getContainer(), fileName));
getActiveContainer().addConfig(new ModConfig(type, spec, getActiveContainer(), fileName));
}
@@ -39,10 +39,7 @@ public record ModLoadingState(String name, String previous,
final ProgressMeter progressBar,
final Function<Executor, CompletableFuture<Void>> preSyncTask,
final Function<Executor, CompletableFuture<Void>> postSyncTask) {
var transition = this.transition.orElse(null);
return transition == null
? Optional.empty()
: Optional.ofNullable(transition.build(name, syncExecutor, parallelExecutor, progressBar, preSyncTask, postSyncTask));
return transition.map(t -> t.build(name, syncExecutor, parallelExecutor, progressBar, preSyncTask, postSyncTask));
}
/**
@@ -12,7 +12,8 @@ import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class ModLoadingWarning {
public class ModLoadingWarning
{
/**
* Mod Info for mod with warning
*/
@@ -16,13 +16,15 @@ import java.util.stream.Collectors;
@SuppressWarnings("UnstableApiUsage")
public class ModStateManager {
static ModStateManager INSTANCE;
private final EnumMap<ModLoadingPhase, List<IModLoadingState>> stateMap;
public ModStateManager() {
INSTANCE = this;
final var sp = ServiceLoader.load(FMLLoader.getGameLayer(), IModStateProvider.class);
this.stateMap = ServiceLoaderUtils.streamWithErrorHandling(sp, sce->{})
.map(IModStateProvider::getAllStates)
.flatMap(List::stream)
.<IModLoadingState>mapMulti(Iterable::forEach)
.collect(Collectors.groupingBy(IModLoadingState::phase, ()->new EnumMap<>(ModLoadingPhase.class), Collectors.toUnmodifiableList()));
}
@@ -34,16 +36,14 @@ public class ModStateManager {
var dummy = ModLoadingState.empty("", "", phase);
nodes.forEach(graph::addNode);
graph.addNode(dummy);
for (IModLoadingState node : nodes) {
graph.putEdge(lookup.getOrDefault(node.previous(), dummy), node);
}
nodes.forEach(n->graph.putEdge(lookup.getOrDefault(n.previous(), dummy), n));
return TopologicalSort.topologicalSort(graph, Comparator.comparingInt(nodes::indexOf)).stream().filter(st->st!=dummy).toList();
}
public IModLoadingState findState(final String stateName) {
return stateMap.values()
.stream()
.flatMap(List::stream)
.flatMap(Collection::stream)
.filter(mls -> mls.name().equals(stateName))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Unknown IModLoadingState: " + stateName));
@@ -17,7 +17,6 @@ import static net.minecraftforge.fml.Logging.LOADING;
public class ModWorkManager {
private static final Logger LOGGER = LogManager.getLogger();
private static final long PARK_TIME = TimeUnit.MILLISECONDS.toNanos(1);
public interface DrivenExecutor extends Executor {
boolean selfDriven();
boolean driveOne();
@@ -34,11 +33,8 @@ public class ModWorkManager {
}
}
}
private record SyncExecutor(ConcurrentLinkedDeque<Runnable> tasks) implements DrivenExecutor {
public SyncExecutor() {
this(new ConcurrentLinkedDeque<>());
}
private static class SyncExecutor implements DrivenExecutor {
private ConcurrentLinkedDeque<Runnable> tasks = new ConcurrentLinkedDeque<>();
@Override
public boolean driveOne() {
@@ -60,7 +56,13 @@ public class ModWorkManager {
}
}
private record WrappingExecutor(Executor wrapped) implements DrivenExecutor {
private static class WrappingExecutor implements DrivenExecutor {
private final Executor wrapped;
public WrappingExecutor(final Executor executor) {
this.wrapped = executor;
}
@Override
public boolean selfDriven() {
return true;
@@ -77,9 +79,11 @@ public class ModWorkManager {
}
}
private static final SyncExecutor syncExecutor = new SyncExecutor();
private static SyncExecutor syncExecutor;
public static DrivenExecutor syncExecutor() {
if (syncExecutor == null)
syncExecutor = new SyncExecutor();
return syncExecutor;
}
@@ -87,8 +91,14 @@ public class ModWorkManager {
return new WrappingExecutor(executor);
}
private static ForkJoinPool parallelThreadPool;
public static Executor parallelExecutor() {
return LazyInit.PARALLEL_EXECUTOR;
if (parallelThreadPool == null) {
final int loadingThreadCount = FMLConfig.getIntConfigValue(FMLConfig.ConfigValue.MAX_THREADS);
LOGGER.debug(LOADING, "Using {} threads for parallel mod-loading", loadingThreadCount);
parallelThreadPool = new ForkJoinPool(loadingThreadCount, ModWorkManager::newForkJoinWorkerThread, null, false);
}
return parallelThreadPool;
}
private static ForkJoinWorkerThread newForkJoinWorkerThread(ForkJoinPool pool) {
@@ -99,14 +109,4 @@ public class ModWorkManager {
return thread;
}
private static final class LazyInit {
private LazyInit() {}
private static final ForkJoinPool PARALLEL_EXECUTOR;
static {
final int loadingThreadCount = FMLConfig.getIntConfigValue(FMLConfig.ConfigValue.MAX_THREADS);
LOGGER.debug(LOADING, "Using {} threads for parallel mod-loading", loadingThreadCount);
PARALLEL_EXECUTOR = new ForkJoinPool(loadingThreadCount, ModWorkManager::newForkJoinWorkerThread, null, false);
}
}
}
@@ -23,7 +23,7 @@ public final class OptionalMod<T>
return new OptionalMod<>(modId);
}
private static final OptionalMod<?> EMPTY = new OptionalMod<>(true);
private static OptionalMod<?> EMPTY = new OptionalMod<>(true);
private static <T> OptionalMod<T> empty() {
@SuppressWarnings("unchecked")
OptionalMod<T> t = (OptionalMod<T>) EMPTY;
@@ -213,8 +213,8 @@ public final class OptionalMod<T>
public boolean equals(Object obj)
{
if (this == obj) return true;
if (obj instanceof OptionalMod<?> optionalMod) {
return Objects.equals(optionalMod.modId, modId);
if (obj instanceof OptionalMod) {
return Objects.equals(((OptionalMod)obj).modId, modId);
}
return false;
}
@@ -7,6 +7,7 @@ package net.minecraftforge.fml;
import com.google.gson.Gson;
import net.minecraftforge.fml.loading.FMLConfig;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.forgespi.language.IModInfo;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -34,72 +35,81 @@ import java.util.zip.GZIPInputStream;
import static net.minecraftforge.fml.VersionChecker.Status.*;
public class VersionChecker {
public class VersionChecker
{
private static final Logger LOGGER = LogManager.getLogger();
private static final int MAX_HTTP_REDIRECTS = Integer.getInteger("http.maxRedirects", 20);
private static final int HTTP_TIMEOUT_SECS = Integer.getInteger("http.timeoutSecs", 15);
public enum Status {
public enum Status
{
PENDING(),
FAILED(),
UP_TO_DATE(),
OUTDATED(3, true, true),
OUTDATED(3, true),
AHEAD(),
BETA(),
BETA_OUTDATED(6, true, true);
BETA_OUTDATED(6, true);
private final int sheetOffset;
private final boolean draw, animated;
final int sheetOffset;
final boolean draw, animated;
Status() {
Status()
{
this(0, false, false);
}
Status(int sheetOffset, boolean draw, boolean animated) {
Status(int sheetOffset)
{
this(sheetOffset, true, false);
}
Status(int sheetOffset, boolean animated)
{
this(sheetOffset, true, animated);
}
Status(int sheetOffset, boolean draw, boolean animated)
{
this.sheetOffset = sheetOffset;
this.draw = draw;
this.animated = animated;
}
public int getSheetOffset() {
public int getSheetOffset()
{
return sheetOffset;
}
public boolean shouldDraw() {
public boolean shouldDraw()
{
return draw;
}
public boolean isAnimated() {
public boolean isAnimated()
{
return animated;
}
public boolean isOutdated() {
return this == OUTDATED || this == BETA_OUTDATED;
}
}
public record CheckResult(VersionChecker.Status status, ComparableVersion target, Map<ComparableVersion, String> changes, String url) {}
public static void startVersionCheck() {
new Thread("Forge Version Check") {
public static void startVersionCheck()
{
new Thread("Forge Version Check")
{
private HttpClient client;
private String mcVersion;
private String agent;
@Override
public void run() {
if (!FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.VERSION_CHECK)) {
public void run()
{
if (!FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.VERSION_CHECK))
{
LOGGER.info("Global Forge version check system disabled, no further processing.");
return;
}
var mc = ModList.get().getModContainerById("minecraft");
var forge = ModList.get().getModContainerById("forge");
this.mcVersion = mc.get().getModInfo().getVersion().toString();
this.agent = "Java-http-client/" + System.getProperty("java.version")
+ " MinecraftForge/" + mcVersion + '-' + forge.get().getModInfo().getVersion();
client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(HTTP_TIMEOUT_SECS)).build();
gatherMods().forEach(this::process);
}
@@ -109,9 +119,15 @@ public class VersionChecker {
*/
private String openUrlString(URL url, IModInfo mod) throws IOException, URISyntaxException, InterruptedException {
URL currentUrl = url;
var userAgent = this.agent + ' ' + mod.getModId() + '/' + mod.getVersion();
for (int redirects = 0; redirects < MAX_HTTP_REDIRECTS; redirects++) {
final StringBuilder sb = new StringBuilder();
sb.append("Java-http-client/").append(System.getProperty("java.version")).append(' ');
sb.append("MinecraftForge/").append(FMLLoader.versionInfo().mcAndForgeVersion()).append(' ');
sb.append(mod.getModId()).append('/').append(mod.getVersion());
final String userAgent = sb.toString();
for (int redirects = 0; redirects < MAX_HTTP_REDIRECTS; redirects++)
{
var request = HttpRequest.newBuilder()
.uri(currentUrl.toURI())
.timeout(Duration.ofSeconds(HTTP_TIMEOUT_SECS))
@@ -123,7 +139,8 @@ public class VersionChecker {
final HttpResponse<InputStream> response = client.send(request, HttpResponse.BodyHandlers.ofInputStream());
int responseCode = response.statusCode();
if (responseCode >= 300 && responseCode <= 399) {
if (responseCode >= 300 && responseCode <= 399)
{
String newLocation = response.headers().firstValue("Location")
.orElseThrow(() -> new IOException("Got a 3xx response code but Location header was null while trying to fetch " + url));
currentUrl = new URL(currentUrl, newLocation);
@@ -133,22 +150,26 @@ public class VersionChecker {
final boolean isGzipEncoded = response.headers().firstValue("Content-Encoding").orElse("").equals("gzip");
final String bodyStr;
try (var inStream = isGzipEncoded ? new GZIPInputStream(response.body()) : response.body();
var bufferedReader = new BufferedReader(new InputStreamReader(inStream))
) {
bodyStr = bufferedReader.lines().collect(Collectors.joining("\n"));
try (InputStream inStream = isGzipEncoded ? new GZIPInputStream(response.body()) : response.body())
{
try (var bufferedReader = new BufferedReader(new InputStreamReader(inStream)))
{
bodyStr = bufferedReader.lines().collect(Collectors.joining("\n"));
}
}
return bodyStr;
}
throw new IOException("Too many redirects while trying to fetch " + url);
}
private void process(IModInfo mod) {
private void process(IModInfo mod)
{
Status status = PENDING;
ComparableVersion target = null;
Map<ComparableVersion, String> changes = null;
String display_url = null;
try {
try
{
if (mod.getUpdateURL().isEmpty()) return;
URL url = mod.getUpdateURL().get();
LOGGER.info("[{}] Starting version check at {}", mod.getModId(), url.toString());
@@ -157,40 +178,54 @@ public class VersionChecker {
LOGGER.debug("[{}] Received version check data:\n{}", mod.getModId(), data);
@SuppressWarnings("unchecked")
Map<String, Object> json = new Gson().fromJson(data, Map.class);
@SuppressWarnings("unchecked")
Map<String, String> promos = (Map<String, String>)json.get("promos");
display_url = (String)json.get("homepage");
var mcVersion = FMLLoader.versionInfo().mcVersion();
String rec = promos.get(mcVersion + "-recommended");
String lat = promos.get(mcVersion + "-latest");
ComparableVersion current = new ComparableVersion(mod.getVersion().toString());
if (rec != null) {
if (rec != null)
{
ComparableVersion recommended = new ComparableVersion(rec);
int diff = recommended.compareTo(current);
if (diff == 0)
status = UP_TO_DATE;
else if (diff < 0) {
else if (diff < 0)
{
status = AHEAD;
if (lat != null) {
if (lat != null)
{
ComparableVersion latest = new ComparableVersion(lat);
if (current.compareTo(latest) < 0) {
if (current.compareTo(latest) < 0)
{
status = OUTDATED;
target = latest;
}
}
} else {
}
else
{
status = OUTDATED;
target = recommended;
}
} else if (lat != null) {
}
else if (lat != null)
{
ComparableVersion latest = new ComparableVersion(lat);
status = current.compareTo(latest) < 0 ? BETA_OUTDATED : BETA;
if (current.compareTo(latest) < 0)
status = BETA_OUTDATED;
else
status = BETA;
target = latest;
} else
}
else
status = BETA;
LOGGER.info("[{}] Found status: {} Current: {} Target: {}", mod.getModId(), status, current, target);
@@ -198,30 +233,38 @@ public class VersionChecker {
changes = new LinkedHashMap<>();
@SuppressWarnings("unchecked")
Map<String, String> tmp = (Map<String, String>)json.get(mcVersion);
if (tmp != null) {
if (tmp != null)
{
List<ComparableVersion> ordered = new ArrayList<>();
for (String key : tmp.keySet()) {
for (String key : tmp.keySet())
{
ComparableVersion ver = new ComparableVersion(key);
if (ver.compareTo(current) > 0 && (target == null || ver.compareTo(target) < 1))
{
ordered.add(ver);
}
}
Collections.sort(ordered);
for (ComparableVersion ver : ordered) {
for (ComparableVersion ver : ordered)
{
changes.put(ver, tmp.get(ver.toString()));
}
}
} catch (Exception e) {
}
catch (Exception e)
{
LOGGER.warn("Failed to process update information", e);
status = FAILED;
}
RESULTS.put(mod, new CheckResult(status, target, changes, display_url));
results.put(mod, new CheckResult(status, target, changes, display_url));
}
}.start();
}
// Gather a list of mods that have opted in to this update system by providing a URL.
private static List<IModInfo> gatherMods() {
private static List<IModInfo> gatherMods()
{
List<IModInfo> ret = new LinkedList<>();
for (IModInfo info : ModList.get().getMods()) {
if (info.getUpdateURL().isPresent())
@@ -230,10 +273,12 @@ public class VersionChecker {
return ret;
}
private static final Map<IModInfo, CheckResult> RESULTS = new ConcurrentHashMap<>();
private static Map<IModInfo, CheckResult> results = new ConcurrentHashMap<>();
private static final CheckResult PENDING_CHECK = new CheckResult(PENDING, null, null, null);
public static CheckResult getResult(IModInfo mod) {
return RESULTS.getOrDefault(mod, PENDING_CHECK);
public static CheckResult getResult(IModInfo mod)
{
return results.getOrDefault(mod, PENDING_CHECK);
}
}
@@ -14,7 +14,6 @@ import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.loading.FMLConfig;
import net.minecraftforge.fml.loading.FMLPaths;
import org.apache.commons.io.FilenameUtils;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import java.io.IOException;
@@ -26,66 +25,9 @@ import static net.minecraftforge.fml.config.ConfigTracker.CONFIG;
public class ConfigFileTypeHandler {
private static final Logger LOGGER = LogUtils.getLogger();
static ConfigFileTypeHandler TOML = new ConfigFileTypeHandler();
private static final Path defaultConfigPath = FMLPaths.GAMEDIR.get().resolve(FMLConfig.getConfigValue(FMLConfig.ConfigValue.DEFAULT_CONFIG_PATH));
private static final ConfigFileTypeHandler CLIENT = new ConfigFileTypeHandler(ModConfig.Type.CLIENT);
private static final ConfigFileTypeHandler COMMON = new ConfigFileTypeHandler(ModConfig.Type.COMMON);
private static final ConfigFileTypeHandler SERVER = new ConfigFileTypeHandler(ModConfig.Type.SERVER);
private final @Nullable ModConfig.Type type;
private @Nullable FileWatcher watcher;
// exists for bin compat
public ConfigFileTypeHandler() {
this(null);
}
private ConfigFileTypeHandler(@Nullable ModConfig.Type type) {
this.type = type;
}
/**
* Gets the handler for the given {@link ModConfig.Type}.
*
* @param type The type to get the handler for
* @return The handler
*/
static ConfigFileTypeHandler get(ModConfig.Type type) {
return switch (type) {
case CLIENT -> CLIENT;
case COMMON -> COMMON;
case SERVER -> SERVER;
};
}
/**
* Gets the {@link FileWatcher} for this handler, creating it if it doesn't exist and/or has been stopped.
*
* @return The watcher
*
* @apiNote This is package-private so modders can't just call {@link FileWatcher#stop()} and fuck up everything.
*/
FileWatcher getWatcher() {
if (this.watcher == null) {
LOGGER.debug(CONFIG, "Starting watcher for handler: {}", this);
this.watcher = new FileWatcher();
}
return this.watcher;
}
/**
* Stops the {@link FileWatcher} for this handler, and sets it to null afterward. Use this instead of
* {@link FileWatcher#stop()}.
*/
void stopWatcher() {
if (this.watcher == null) return;
LOGGER.debug(CONFIG, "Stopping watcher for hander: {}", this);
this.watcher.stop();
this.watcher = null;
}
public Function<ModConfig, CommentedFileConfig> reader(Path configBasePath) {
return (c) -> {
final Path configPath = configBasePath.resolve(c.getFileName());
@@ -95,7 +37,7 @@ public class ConfigFileTypeHandler {
onFileNotFound((newfile, configFormat)-> setupConfigFile(c, newfile, configFormat)).
writingMode(WritingMode.REPLACE).
build();
LOGGER.debug(CONFIG, "Built TOML config for {}", configPath);
LOGGER.debug(CONFIG, "Built TOML config for {}", configPath.toString());
try
{
configData.load();
@@ -104,9 +46,13 @@ public class ConfigFileTypeHandler {
{
throw new ConfigLoadingException(c, ex);
}
LOGGER.debug(CONFIG, "Loaded TOML config file {}", configPath);
this.getWatcher().addWatch(configPath, new ConfigWatcher(c, configData, Thread.currentThread().getContextClassLoader()));
LOGGER.debug(CONFIG, "Watching TOML config file {} for changes", configPath);
LOGGER.debug(CONFIG, "Loaded TOML config file {}", configPath.toString());
try {
FileWatcher.defaultInstance().addWatch(configPath, new ConfigWatcher(c, configData, Thread.currentThread().getContextClassLoader()));
LOGGER.debug(CONFIG, "Watching TOML config file {} for changes", configPath.toString());
} catch (IOException e) {
throw new RuntimeException("Couldn't watch config file", e);
}
return configData;
};
}
@@ -114,16 +60,14 @@ public class ConfigFileTypeHandler {
public void unload(Path configBasePath, ModConfig config) {
Path configPath = configBasePath.resolve(config.getFileName());
try {
this.getWatcher().removeWatch(configPath);
FileWatcher.defaultInstance().removeWatch(configBasePath.resolve(config.getFileName()));
} catch (RuntimeException e) {
LOGGER.error("Failed to remove config {} from tracker!", configPath, e);
LOGGER.error("Failed to remove config {} from tracker!", configPath.toString(), e);
}
}
private static boolean setupConfigFile(final ModConfig modConfig, final Path file, final ConfigFormat<?> conf) throws IOException {
if (!Files.isDirectory(file.getParent())) {
Files.createDirectories(file.getParent());
}
private boolean setupConfigFile(final ModConfig modConfig, final Path file, final ConfigFormat<?> conf) throws IOException {
Files.createDirectories(file.getParent());
Path p = defaultConfigPath.resolve(modConfig.getFileName());
if (Files.exists(p)) {
LOGGER.info(CONFIG, "Loading default config file from path {}", p);
@@ -167,16 +111,17 @@ public class ConfigFileTypeHandler {
}
}
@Override
public String toString() {
return "ConfigFileTypeHandler[" + (type != null ? type : "UNKNOWN") + "]";
}
private static class ConfigWatcher implements Runnable {
private final ModConfig modConfig;
private final CommentedFileConfig commentedFileConfig;
private final ClassLoader realClassLoader;
ConfigWatcher(final ModConfig modConfig, final CommentedFileConfig commentedFileConfig, final ClassLoader classLoader) {
this.modConfig = modConfig;
this.commentedFileConfig = commentedFileConfig;
this.realClassLoader = classLoader;
}
private record ConfigWatcher(
ModConfig modConfig,
CommentedFileConfig commentedFileConfig,
ClassLoader realClassLoader
) implements Runnable {
@Override
public void run() {
// Force the regular classloader onto the special thread
@@ -8,9 +8,6 @@ package net.minecraftforge.fml.config;
import com.electronwill.nightconfig.core.CommentedConfig;
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import com.mojang.logging.LogUtils;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.fml.DistExecutor;
import net.minecraftforge.fml.loading.FMLPaths;
import org.slf4j.Logger;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
@@ -50,32 +47,15 @@ public class ConfigTracker {
public void loadConfigs(ModConfig.Type type, Path configBasePath) {
LOGGER.debug(CONFIG, "Loading configs type {}", type);
for (ModConfig config : this.configSets.get(type)) {
openConfig(config, configBasePath);
}
this.configSets.get(type).forEach(config -> openConfig(config, configBasePath));
}
// TODO: [FML] This is only called for the server (outside of forceUnload)
// rethink config implementation for eventual FML rewrite
public void unloadConfigs(ModConfig.Type type, Path configBasePath) {
LOGGER.debug(CONFIG, "Unloading configs type {}", type);
for (ModConfig config : this.configSets.get(type)) {
closeConfig(config, configBasePath);
}
ConfigFileTypeHandler.get(type).stopWatcher();
this.configSets.get(type).forEach(config -> closeConfig(config, configBasePath));
}
// If there is a better way to do this, please tell me. Because this is FUCKED
public void forceUnload() {
// This is how ModStateProvider handles loading configs. So we're doing the same but with unloadConfigs instead
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> this.unloadConfigs(ModConfig.Type.CLIENT, FMLPaths.CONFIGDIR.get()));
this.unloadConfigs(ModConfig.Type.COMMON, FMLPaths.CONFIGDIR.get());
// just in case server watcher is still alive somehow...
ConfigFileTypeHandler.get(ModConfig.Type.SERVER).stopWatcher();
}
private static void openConfig(final ModConfig config, final Path configBasePath) {
private void openConfig(final ModConfig config, final Path configBasePath) {
LOGGER.trace(CONFIG, "Loading config file type {} at {} for {}", config.getType(), config.getFileName(), config.getModId());
final CommentedFileConfig configData = config.getHandler().reader(configBasePath).apply(config);
config.setConfigData(configData);
@@ -83,7 +63,7 @@ public class ConfigTracker {
config.save();
}
private static void closeConfig(final ModConfig config, final Path configBasePath) {
private void closeConfig(final ModConfig config, final Path configBasePath) {
if (config.getConfigData() != null) {
LOGGER.trace(CONFIG, "Closing config file type {} at {} for {}", config.getType(), config.getFileName(), config.getModId());
// stop the filewatcher before we save the file and close it, so reload doesn't fire
@@ -24,13 +24,14 @@ public class ModConfig
private final ModContainer container;
private final ConfigFileTypeHandler configHandler;
private CommentedConfig configData;
private Callable<Void> saveHandler;
public ModConfig(final Type type, final IConfigSpec<?> spec, final ModContainer container, final String fileName) {
this.type = type;
this.spec = spec;
this.fileName = fileName;
this.container = container;
this.configHandler = ConfigFileTypeHandler.get(type);
this.configHandler = ConfigFileTypeHandler.TOML;
ConfigTracker.INSTANCE.trackConfig(this);
}
+46 -45
View File
@@ -1,15 +1,14 @@
import net.minecraftforge.gradleutils.PomUtils
plugins {
id 'java-library'
id 'maven-publish'
id 'net.minecraftforge.licenser'
id 'net.minecraftforge.gradleutils'
id 'com.github.ben-manes.versions'
id 'org.javamodularity.moduleplugin' version '1.8.7' apply false
id 'org.cadixdev.licenser'
}
apply from: rootProject.file('build_shared.gradle')
apply plugin: 'java-library'
apply plugin: 'jacoco'
apply plugin: 'org.javamodularity.moduleplugin'
import org.gradle.internal.os.OperatingSystem
switch (OperatingSystem.current()) {
case OperatingSystem.LINUX:
project.ext.lwjglNatives = "natives-linux"
@@ -22,18 +21,22 @@ switch (OperatingSystem.current()) {
break
}
java {
toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION)
withSourcesJar()
}
dependencyUpdates.rejectVersionIf { isNonStable(it.candidate.version) }
java.withSourcesJar()
dependencies {
compileOnly(libs.nulls)
compileOnly('org.jetbrains:annotations:23.0.0')
implementation(project(':fmlloader'))
implementation(project(':fmlcore'))
implementation(libs.bundles.lwjgl)
implementation('org.lwjgl:lwjgl:3.3.1')
implementation('org.lwjgl:lwjgl-glfw:3.3.1')
implementation('org.lwjgl:lwjgl-opengl:3.3.1')
implementation('org.lwjgl:lwjgl-stb:3.3.1')
implementation('org.lwjgl:lwjgl-tinyfd:3.3.1')
implementation('org.slf4j:slf4j-api:1.8.0-beta4')
implementation(libs.jopt.simple)
implementation("net.sf.jopt-simple:jopt-simple:${JOPT_SIMPLE_VERSION}")
testImplementation('org.junit.jupiter:junit-jupiter-api:5.8.2')
testImplementation('org.powermock:powermock-core:2.0.9')
testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine:5.8.2')
@@ -44,49 +47,47 @@ dependencies {
testRuntimeOnly("org.lwjgl:lwjgl-stb::$lwjglNatives")
}
tasks.named('test', Test).configure {
test {
useJUnitPlatform()
}
tasks.named('jar', Jar).configure {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.earlydisplay',
'Forge-Module-Layer': 'boot'
] as LinkedHashMap)
attributes([
'Specification-Title': 'FML Early Display',
ext {
MANIFESTS = [
'': [
'Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"),
'Git-Commit': GIT_INFO.abbreviatedId,
'Git-Branch': GIT_INFO.branch,
'Build-Number': "${System.getenv('BUILD_NUMBER')?:0}",
] as LinkedHashMap,
'net/minecraftforge/fml/earlydisplay/': [
'Specification-Title': 'FMLEarlyDisplay',
'Specification-Vendor': 'Forge Development LLC',
'Specification-Version': '1',
'Implementation-Title': 'FML Early Display',
'Implementation-Vendor': 'Forge Development LLC',
'Implementation-Version': '1.0'
] as LinkedHashMap, 'net/minecraftforge/fml/earlydisplay/')
'Implementation-Version': '1.0',
'Implementation-Vendor': 'Forge'
] as LinkedHashMap
]
}
jar.doFirst {
MANIFESTS.each { pkg, values ->
if (pkg == '')
manifest.attributes(values)
else
manifest.attributes(values, pkg)
}
}
tasks.withType(JavaCompile).configureEach {
tasks.withType(JavaCompile) {
options.compilerArgs << '-Xlint:unchecked'
}
license {
header = rootProject.file('LICENSE-header.txt')
include 'net/minecraftforge/'
}
publishing {
publications.register('mavenJava', MavenPublication).configure {
from components.java
artifactId = 'fmlearlydisplay'
pom {
name = project.name
description = 'A pretty looking, but prone to erroring screen displayed during Forge loading.'
url = 'https://github.com/MinecraftForge/MinecraftForge'
PomUtils.setGitHubDetails(pom, 'MinecraftForge')
license PomUtils.Licenses.LGPLv2_1
}
}
repositories {
maven gradleutils.publishingForgeMaven
}
}
publishing.publications.mavenJava(MavenPublication) {
from components.java
}
@@ -14,7 +14,9 @@ import net.minecraftforge.fml.loading.ImmediateWindowProvider;
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.PointerBuffer;
import org.lwjgl.glfw.GLFWImage;
import org.lwjgl.glfw.GLFWVidMode;
import org.lwjgl.stb.STBImage;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import org.lwjgl.util.tinyfd.TinyFileDialogs;
@@ -22,12 +24,15 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.Desktop;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URI;
import java.nio.ByteBuffer;
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.Calendar;
@@ -35,16 +40,15 @@ import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.StringJoiner;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.IntConsumer;
import java.util.function.IntSupplier;
import java.util.function.LongSupplier;
@@ -74,7 +78,7 @@ public class DisplayWindow implements ImmediateWindowProvider {
private static final Logger LOGGER = LoggerFactory.getLogger("EARLYDISPLAY");
private final AtomicBoolean animationTimerTrigger = new AtomicBoolean(true);
private ColourScheme colourScheme = ColourScheme.RED;
private ColourScheme colourScheme;
private ElementShader elementShader;
private RenderElement.DisplayContext context;
@@ -82,7 +86,6 @@ public class DisplayWindow implements ImmediateWindowProvider {
private int framecount;
private EarlyFramebuffer framebuffer;
private ScheduledFuture<?> windowTick;
private ScheduledFuture<?> initializationFuture;
private PerformanceInfo performanceInfo;
private ScheduledFuture<?> performanceTick;
@@ -108,13 +111,11 @@ public class DisplayWindow implements ImmediateWindowProvider {
public String name() {
return "fmlearlywindow";
}
@Override
public Runnable initialize(String[] arguments) {
String mcVersion = FMLLoader.versionInfo().mcVersion();
String forgeVersion = FMLLoader.versionInfo().forgeVersion();
final OptionParser parser = new OptionParser();
var mcversionopt = parser.accepts("fml.mcVersion").withRequiredArg().ofType(String.class);
var forgeversionopt = parser.accepts("fml.forgeVersion").withRequiredArg().ofType(String.class);
var widthopt = parser.accepts("width")
.withRequiredArg().ofType(Integer.class)
.defaultsTo(FMLConfig.getIntConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_WIDTH));
@@ -129,28 +130,25 @@ public class DisplayWindow implements ImmediateWindowProvider {
FMLConfig.updateConfig(FMLConfig.ConfigValue.EARLY_WINDOW_WIDTH, winWidth);
FMLConfig.updateConfig(FMLConfig.ConfigValue.EARLY_WINDOW_HEIGHT, winHeight);
fbScale = FMLConfig.getIntConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_FBSCALE);
if (System.getenv("FML_EARLY_WINDOW_DARK") != null) {
if (System.getenv("FML_EARLY_WINDOW_DARK")!= null) {
this.colourScheme = ColourScheme.BLACK;
} else {
try {
// check the options file for the colour scheme
var optionLines = Files.readAllLines(FMLPaths.GAMEDIR.get().resolve(Path.of("options.txt")));
var keyName = "darkMojangStudiosBackground:";
for (String line : optionLines) {
if (line.startsWith(keyName)) {
this.colourScheme = line.startsWith("true", keyName.length()) ? ColourScheme.BLACK : ColourScheme.RED;
break;
}
}
} catch (IOException e) {
this.colourScheme = ColourScheme.RED; // fallback to red colourScheme
var optionLines = Files.readAllLines(FMLPaths.GAMEDIR.get().resolve(Paths.get("options.txt")));
var options = optionLines.stream().map(l -> l.split(":")).filter(a -> a.length == 2).collect(Collectors.toMap(a -> a[0], a -> a[1]));
var colourScheme = Boolean.parseBoolean(options.getOrDefault("darkMojangStudiosBackground", "false"));
this.colourScheme = colourScheme ? ColourScheme.BLACK : ColourScheme.RED;
} catch (IOException ioe) {
// No options
this.colourScheme = ColourScheme.RED; // default to red colourscheme
}
}
this.maximized = parsed.has(maximizedopt) || FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_MAXIMIZED);
StartupNotificationManager.modLoaderConsumer().ifPresent(c->c.accept("Forge loading " + forgeVersion));
var forgeVersion = parsed.valueOf(forgeversionopt);
StartupNotificationManager.modLoaderConsumer().ifPresent(c->c.accept("Forge loading "+ forgeVersion));
performanceInfo = new PerformanceInfo();
return start(mcVersion, forgeVersion);
return start(parsed.valueOf(mcversionopt), forgeVersion);
}
private static final long MINFRAMETIME = TimeUnit.MILLISECONDS.toNanos(10); // This is the FPS cap on the window - note animation is capped at 20FPS via the tickTimer
@@ -227,12 +225,12 @@ public class DisplayWindow implements ImmediateWindowProvider {
LOGGER.error("Crash during font initialization", t);
crashElegantly("An error occurred initializing a font for rendering. "+t.getMessage());
}
this.elements = new ArrayList<>(List.of(
RenderElement.anvil(font),
RenderElement.logMessageOverlay(font),
RenderElement.forgeVersionOverlay(font, mcVersion + "-" + forgeVersion),
RenderElement.performanceBar(font),
RenderElement.progressBars(font)
this.elements = new ArrayList<>(Arrays.asList(
RenderElement.anvil(font),
RenderElement.logMessageOverlay(font),
RenderElement.forgeVersionOverlay(font, mcVersion+"-"+forgeVersion.split("-")[0]),
RenderElement.performanceBar(font),
RenderElement.progressBars(font)
));
var date = Calendar.getInstance();
@@ -266,6 +264,7 @@ public class DisplayWindow implements ImmediateWindowProvider {
public void render(int alpha) {
var currentVAO = glGetInteger(GL_VERTEX_ARRAY_BINDING);
var currentFB = glGetInteger(GL_READ_FRAMEBUFFER_BINDING);
glfwSwapInterval(0);
glViewport(0, 0, this.context.scaledWidth(), this.context.scaledHeight());
RenderElement.globalAlpha = alpha;
framebuffer.activate();
@@ -284,12 +283,11 @@ public class DisplayWindow implements ImmediateWindowProvider {
public Runnable start(@Nullable String mcVersion, final String forgeVersion) {
renderScheduler = Executors.newSingleThreadScheduledExecutor(r -> {
final var thread = Executors.defaultThreadFactory().newThread(r);
thread.setName("EarlyDisplay");
thread.setDaemon(true);
return thread;
});
initWindow(mcVersion);
this.initializationFuture = renderScheduler.schedule(() -> initRender(mcVersion, forgeVersion), 1, TimeUnit.MILLISECONDS);
renderScheduler.schedule(() -> initRender(mcVersion, forgeVersion), 1, TimeUnit.MILLISECONDS);
return this::periodicTick;
}
@@ -300,6 +298,13 @@ public class DisplayWindow implements ImmediateWindowProvider {
}
private void crashElegantly(String errorDetails) {
String qrText;
try (var is = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream("/glfailure.txt")))) {
qrText = is.lines().collect(Collectors.joining("\n"));
} catch (IOException ioe) {
qrText = "";
}
StringBuilder msgBuilder = new StringBuilder(2000);
msgBuilder.append("Failed to initialize graphics window with current settings.\n");
msgBuilder.append("\n\n");
@@ -355,15 +360,14 @@ public class DisplayWindow implements ImmediateWindowProvider {
glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_NATIVE_CONTEXT_API);
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
String vanillaWindowTitle = "Minecraft* ";
if (mcVersion != null) vanillaWindowTitle += mcVersion;
// this emulates what we would get without early progress window
// as vanilla never sets these, so GLFW uses the first window title
// set them explicitly to avoid it using "FML early loading progress" as the class
glfwWindowHintString(GLFW_X11_CLASS_NAME, vanillaWindowTitle);
glfwWindowHintString(GLFW_X11_INSTANCE_NAME, vanillaWindowTitle);
if (mcVersion != null) {
// this emulates what we would get without early progress window
// as vanilla never sets these, so GLFW uses the first window title
// set them explicitly to avoid it using "FML early loading progress" as the class
String vanillaWindowTitle = "Minecraft* " + mcVersion;
glfwWindowHintString(GLFW_X11_CLASS_NAME, vanillaWindowTitle);
glfwWindowHintString(GLFW_X11_INSTANCE_NAME, vanillaWindowTitle);
}
long primaryMonitor = glfwGetPrimaryMonitor();
if (primaryMonitor == 0) {
@@ -385,8 +389,7 @@ public class DisplayWindow implements ImmediateWindowProvider {
}, 10, TimeUnit.SECONDS);
int versidx = 0;
var skipVersions = FMLConfig.<String>getListConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_SKIP_GL_VERSIONS);
boolean showHelpLog = FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_LOG_HELP_MSG);
final String[] lastGLError = new String[GL_VERSIONS.length];
final String[] lastGLError=new String[GL_VERSIONS.length];
do {
final var glVersionToTry = GL_VERSIONS[versidx][0] + "." + GL_VERSIONS[versidx][1];
if (skipVersions.contains(glVersionToTry)) {
@@ -395,23 +398,11 @@ public class DisplayWindow implements ImmediateWindowProvider {
continue;
}
LOGGER.info("Trying GL version " + glVersionToTry);
if (showHelpLog && versidx == 0) {
LOGGER.info("""
If this message is the only thing at the bottom of your log before a crash, you probably have a driver issue.
Possible solutions:
A) Make sure Minecraft is set to prefer high performance graphics in the OS and/or driver control panel
B) Check for driver updates on the graphics brand's website
C) Try reinstalling your graphics drivers
D) If still not working after trying all of the above, ask for further help on the Forge forums or Discord
You can safely ignore this message if the game starts up successfully.""");
}
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, GL_VERSIONS[versidx][0]); // we try our versions one at a time
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, GL_VERSIONS[versidx][1]);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
window = glfwCreateWindow(winWidth, winHeight, vanillaWindowTitle, 0L, 0L);
window = glfwCreateWindow(winWidth, winHeight, "Minecraft: Forge Loading...", 0L, 0L);
var erridx = versidx;
handleLastGLFWError((error, description) -> lastGLError[erridx] = String.format("Trying %d.%d: GLFW error: [0x%X]%s", GL_VERSIONS[erridx][0], GL_VERSIONS[erridx][1], error, description));
if (lastGLError[versidx] != null) {
@@ -439,9 +430,6 @@ public class DisplayWindow implements ImmediateWindowProvider {
this.glVersion = gotVersion;
this.window = window;
if (showHelpLog)
FMLConfig.updateConfig(FMLConfig.ConfigValue.EARLY_WINDOW_LOG_HELP_MSG, false);
int[] x = new int[1];
int[] y = new int[1];
glfwGetMonitorPos(primaryMonitor, x, y);
@@ -459,19 +447,19 @@ public class DisplayWindow implements ImmediateWindowProvider {
glfwSetWindowPos(window, (vidmode.width() - this.winWidth) / 2 + monitorX, (vidmode.height() - this.winHeight) / 2 + monitorY);
// Attempt setting the icon
// int[] channels = new int[1];
// try (var glfwImgBuffer = GLFWImage.create(MemoryUtil.getAllocator().malloc(GLFWImage.SIZEOF), 1)) {
// final ByteBuffer imgBuffer;
// try (GLFWImage glfwImages = GLFWImage.malloc()) {
// imgBuffer = STBHelper.loadImageFromClasspath("forge_logo.png", 20000, x, y, channels);
// glfwImgBuffer.put(glfwImages.set(x[0], y[0], imgBuffer));
// glfwSetWindowIcon(window, glfwImgBuffer);
// STBImage.stbi_image_free(imgBuffer);
// }
// } catch (NullPointerException e) {
// System.err.println("Failed to load forge logo");
// }
// handleLastGLFWError((error, description) -> LOGGER.debug(String.format("Suppressing GLFW icon error: [0x%X]%s", error, description)));
int[] channels = new int[1];
try (var glfwImgBuffer = GLFWImage.create(MemoryUtil.getAllocator().malloc(GLFWImage.SIZEOF), 1)) {
final ByteBuffer imgBuffer;
try (GLFWImage glfwImages = GLFWImage.malloc()) {
imgBuffer = STBHelper.loadImageFromClasspath("forge_logo.png", 20000, x, y, channels);
glfwImgBuffer.put(glfwImages.set(x[0], y[0], imgBuffer));
glfwSetWindowIcon(window, glfwImgBuffer);
STBImage.stbi_image_free(imgBuffer);
}
} catch (NullPointerException e) {
System.err.println("Failed to load forge logo");
}
handleLastGLFWError((error, description) -> LOGGER.debug(String.format("Suppressing GLFW icon error: [0x%X]%s", error, description)));
glfwSetFramebufferSizeCallback(window, this::fbResize);
glfwSetWindowPosCallback(window, this::winMove);
@@ -530,16 +518,6 @@ public class DisplayWindow implements ImmediateWindowProvider {
* @return the Window we own.
*/
public long setupMinecraftWindow(final IntSupplier width, final IntSupplier height, final Supplier<String> title, final LongSupplier monitorSupplier) {
// wait for the window to actually be initialized
try {
this.initializationFuture.get(30, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
} catch (TimeoutException e) {
Thread.dumpStack();
crashElegantly("We seem to be having trouble initializing the window, waited for 30 seconds");
}
// we have to spin wait for the window ticker
ImmediateWindowHandler.updateProgress("Initializing Game Graphics");
while (!this.windowTick.isDone()) {
@@ -603,25 +581,11 @@ public class DisplayWindow implements ImmediateWindowProvider {
@Override
public void updateModuleReads(final ModuleLayer layer) {
final var FORGE_MODULE = "net.minecraftforge.forge";
var forge_module = layer.findModule(FORGE_MODULE).orElse(null);
if (forge_module == null)
throw new IllegalStateException("Could not find " + FORGE_MODULE + " in " + layer);
getClass().getModule().addReads(forge_module);
var clz = Class.forName(forge_module, "net.minecraftforge.client.loading.ForgeLoadingOverlay");
for (var mtd : clz.getDeclaredMethods()) {
if (Modifier.isStatic(mtd.getModifiers()) && "newInstance".equals(mtd.getName())) {
this.loadingOverlay = mtd;
break;
}
}
if (loadingOverlay == null)
throw new IllegalStateException("Could not find static newInstace method in " + clz.getName());
var fm = layer.findModule("forge").orElseThrow();
getClass().getModule().addReads(fm);
var clz = FMLLoader.getGameLayer().findModule("forge").map(l->Class.forName(l, "net.minecraftforge.client.loading.ForgeLoadingOverlay")).orElseThrow();
var methods = Arrays.stream(clz.getMethods()).filter(m-> Modifier.isStatic(m.getModifiers())).collect(Collectors.toMap(Method::getName, Function.identity()));
loadingOverlay = methods.get("newInstance");
}
public int getFramebufferTextureId() {
@@ -6,44 +6,35 @@
package net.minecraftforge.fml.earlydisplay;
import com.sun.management.OperatingSystemMXBean;
import net.minecraftforge.fml.loading.FMLConfig;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
public class PerformanceInfo {
private static final MemoryMXBean MEMORY_BEAN = ManagementFactory.getMemoryMXBean();
private final boolean showCPUUsage;
private final OperatingSystemMXBean osBean;
private float memory;
private final MemoryMXBean memoryBean;
float memory;
private String text;
PerformanceInfo() {
showCPUUsage = FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_SHOW_CPU);
osBean = showCPUUsage ? ManagementFactory.getPlatformMXBean(OperatingSystemMXBean.class) : null;
osBean = ManagementFactory.getPlatformMXBean(OperatingSystemMXBean.class);
memoryBean = ManagementFactory.getMemoryMXBean();
}
void update() {
final MemoryUsage heapusage = MEMORY_BEAN.getHeapMemoryUsage();
final MemoryUsage heapusage = memoryBean.getHeapMemoryUsage();
memory = (float) heapusage.getUsed() / heapusage.getMax();
if (!showCPUUsage) {
text = "Memory: %d/%dMB (%.1f%%)".formatted(heapusage.getUsed() >>> 20, heapusage.getMax() >>> 20, memory * 100f);
return;
}
var cpuLoad = osBean.getProcessCpuLoad();
String cpuText;
if (cpuLoad == -1) {
cpuText = "*CPU: %.1f%%".formatted(osBean.getCpuLoad() * 100f);
cpuText = String.format("*CPU: %.1f%%", osBean.getCpuLoad() * 100f);
} else {
cpuText = "CPU: %.1f%%".formatted(cpuLoad * 100f);
cpuText = String.format("CPU: %.1f%%", cpuLoad * 100f);
}
text = "Memory: %d/%dMB (%.1f%%) %s".formatted(heapusage.getUsed() >>> 20, heapusage.getMax() >>> 20, memory * 100f, cpuText);
text = String.format("Heap: %d/%d MB (%.1f%%) OffHeap: %d MB %s", heapusage.getUsed() >> 20, heapusage.getMax() >> 20, memory * 100.0, memoryBean.getNonHeapMemoryUsage().getUsed() >> 20, cpuText);
}
String text() {
@@ -34,14 +34,11 @@ public class RenderElement {
};
}
}
@FunctionalInterface
interface TextureRenderer {
void accept(SimpleBufferBuilder bb, DisplayContext context, int[] size, int frame);
}
@FunctionalInterface
interface Initializer extends Supplier<Renderer> {}
@FunctionalInterface
interface TextGenerator {
void accept(SimpleBufferBuilder bb, SimpleFont fh, DisplayContext ctx);
}
@@ -204,17 +201,14 @@ public class RenderElement {
bar.then(label).accept(buffer, context, frameNumber);
}
@FunctionalInterface
interface ColourFunction {
int colour(int frame);
}
@FunctionalInterface
interface ProgressDisplay {
float[] progress(int frame);
}
@FunctionalInterface
interface BarPosition {
int[] location(DisplayContext context);
}
@@ -286,7 +280,7 @@ public class RenderElement {
if (num < min) {
return min;
} else {
return Math.min(num, max);
return num > max ? max : num;
}
}
@@ -294,7 +288,7 @@ public class RenderElement {
if (num < min) {
return min;
} else {
return Math.min(num, max);
return num > max ? max : num;
}
}
@@ -25,7 +25,7 @@ public class SimpleFont {
private final int lineSpacing;
private final int descent;
private final int GLYPH_COUNT = 127-32;
private final Glyph[] glyphs;
private Glyph[] glyphs;
private record Glyph(char c, int charwidth, int[] pos, float[] uv) {
Pos loadQuad(Pos pos, int colour, SimpleBufferBuilder bb) {
+71 -80
View File
@@ -1,106 +1,97 @@
import net.minecraftforge.gradleutils.PomUtils
plugins {
id 'java-library'
id 'maven-publish'
id 'net.minecraftforge.licenser'
id 'net.minecraftforge.gradleutils'
id 'com.github.ben-manes.versions'
id 'org.javamodularity.moduleplugin' version '1.8.3' apply false
id 'org.cadixdev.licenser'
}
apply plugin: 'java-library'
apply plugin: 'jacoco'
apply plugin: 'org.javamodularity.moduleplugin'
apply from: rootProject.file('build_shared.gradle')
dependencyUpdates.rejectVersionIf { isNonStable(it.candidate.version) }
configurations.forEach{ it.transitive = false }
dependencies {
compileOnly(libs.nulls)
compileOnly("org.jetbrains:annotations:${JETBRAINS_ANNOTATIONS_VERSION}")
api(libs.bundles.asm) // Needed by all the black magic
api(libs.forgespi)
api(libs.mergetool.api)
api(libs.log4j.api)
api(libs.slf4j.api)
api(libs.guava)
api(libs.gson)
api(libs.maven.artifact)
api(libs.apache.commons)
api(libs.bundles.night.config)
api(libs.modlauncher)
api(libs.coremods)
api(libs.mojang.logging)
api(libs.jarjar.selector)
api(libs.jarjar.meta)
api("org.ow2.asm:asm:${ASM_VERSION}")
api("org.ow2.asm:asm-tree:${ASM_VERSION}")
api("org.ow2.asm:asm-commons:${ASM_VERSION}")
api("net.minecraftforge:forgespi:${SPI_VERSION}")
api("net.minecraftforge:mergetool:${MERGETOOL_VERSION}:api")
api("org.apache.logging.log4j:log4j-api:${LOG4J_VERSION}")
api("org.slf4j:slf4j-api:${SLF4J_API_VERSION}")
api("com.google.guava:guava:${GUAVA_VERSION}")
api("com.google.code.gson:gson:${GSON_VERSION}")
api("org.apache.maven:maven-artifact:${APACHE_MAVEN_ARTIFACT_VERSION}")
api("org.apache.commons:commons-lang3:${APACHE_COMMONS_LANG3_VERSION}")
api("com.electronwill.night-config:core:${NIGHTCONFIG_VERSION}")
api("com.electronwill.night-config:toml:${NIGHTCONFIG_VERSION}")
api("cpw.mods:modlauncher:${MODLAUNCHER_VERSION}")
api("net.minecraftforge:coremods:${COREMODS_VERSION}")
api("com.mojang:logging:${MOJANG_LOGGING_VERSION}")
api "net.minecraftforge:JarJarSelector:${JARJAR_VERSION}"
api "net.minecraftforge:JarJarMetadata:${JARJAR_VERSION}"
implementation(libs.jopt.simple)
implementation(libs.securemodules)
implementation(libs.accesstransformers)
implementation(libs.terminalconsoleappender)
implementation(libs.jimfs)
implementation("net.sf.jopt-simple:jopt-simple:${JOPT_SIMPLE_VERSION}")
implementation("cpw.mods:securejarhandler:${SECUREJARHANDLER_VERSION}")
implementation("net.minecraftforge:accesstransformers:${ACCESSTRANSFORMERS_VERSION}")
implementation("net.minecrell:terminalconsoleappender:${TERMINALCONSOLEAPPENDER_VERSION}")
// implementation("org.jline:jline:${JLINE_VERSION}")
implementation(libs.log4j.core)
annotationProcessor(libs.bundles.log4j)
implementation("org.apache.logging.log4j:log4j-core:${LOG4J_VERSION}")
annotationProcessor("org.apache.logging.log4j:log4j-core:${LOG4J_VERSION}")
testCompileOnly(libs.nulls)
testRuntimeOnly(libs.bootstrap)
testCompileOnly("org.jetbrains:annotations:${JETBRAINS_ANNOTATIONS_VERSION}")
testRuntimeOnly("cpw.mods:bootstraplauncher:${BOOTSTRAPLAUNCHER_VERSION}")
}
java {
toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION)
withSourcesJar()
task sourcesJar(type: Jar) {
archiveClassifier = 'sources'
from sourceSets.main.allSource
}
tasks.named('jar', Jar).configure {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.fmlloader',
'Forge-Module-Layer': 'boot'
] as LinkedHashMap)
attributes([
'Specification-Title': 'FMLLoader',
'Specification-Vendor': 'Forge Development LLC',
'Specification-Version': '1',
'Implementation-Title': 'FMLLoader',
'Implementation-Vendor': 'Forge Development LLC',
'Implementation-Version': FORGE_VERSION
] as LinkedHashMap, 'net/minecraftforge/fml/loading/')
ext {
MANIFESTS = [
'/': [
'Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"),
'Git-Commit': GIT_INFO.abbreviatedId,
'Git-Branch': GIT_INFO.branch,
'Build-Number': "${System.getenv('BUILD_NUMBER')?:0}",
] as LinkedHashMap,
'net/minecraftforge/fml/loading/': [
'Specification-Title': 'FMLLoader',
'Specification-Vendor': 'Forge Development LLC',
'Specification-Version': '1',
'Implementation-Title': 'FML Loader',
'Implementation-Version': '1.0',
'Implementation-Vendor': 'Forge'
] as LinkedHashMap
]
}
jar.doFirst {
manifest.attributes['Automatic-Module-Name'] = project.name
MANIFESTS.each { pkg, values ->
manifest.attributes(values, pkg)
}
}
tasks.withType(JavaCompile).configureEach {
tasks.withType(JavaCompile) {
options.compilerArgs << '-Xlint:unchecked'
}
license {
header = rootProject.file('LICENSE-header.txt')
include 'net/minecraftforge/'
}
publishing {
publications.register('mavenJava', MavenPublication).configure {
from components.java
artifactId = 'fmlloader'
pom {
name = project.name
description = 'Modifactions to Minecraft to enable mod developers.'
url = 'https://github.com/MinecraftForge/MinecraftForge'
PomUtils.setGitHubDetails(pom, 'MinecraftForge')
license PomUtils.Licenses.LGPLv2_1
}
}
repositories {
maven gradleutils.publishingForgeMaven
}
artifacts {
archives jar
archives sourcesJar
}
tasks.register('writeForgeVersionJson') {
doLast {
file('src/main/resources/forge_version.json').json = [
forge: FORGE_VERSION,
mc: MC_VERSION,
mcp: MCP_VERSION
]
}
}
tasks.named('generateResources').configure {
dependsOn('writeForgeVersionJson')
}
publishing.publications.mavenJava(MavenPublication) {
from components.java
artifact sourcesJar
}
@@ -36,9 +36,9 @@ import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
*/
public class CapabilityTokenSubclass implements ILaunchPluginService {
private static final String FUNC_NAME = "getType";
private static final String FUNC_DESC = "()Ljava/lang/String;";
private static final String CAP_INJECT = "net/minecraftforge/common/capabilities/CapabilityToken"; //Don't directly reference this to prevent class loading.
private final String FUNC_NAME = "getType";
private final String FUNC_DESC = "()Ljava/lang/String;";
private final String CAP_INJECT = "net/minecraftforge/common/capabilities/CapabilityToken"; //Don't directly reference this to prevent class loading.
@Override
public String name() {
@@ -49,45 +49,49 @@ public class CapabilityTokenSubclass implements ILaunchPluginService {
private static final EnumSet<Phase> NAY = EnumSet.noneOf(Phase.class);
@Override
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty) {
if (isEmpty)
return NAY;
String internalName = classType.getInternalName();
if (internalName.startsWith("net/minecraft/") || internalName.startsWith("com/mojang/"))
return NAY;
return YAY;
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty)
{
return isEmpty ? NAY : YAY;
}
@Override
public int processClassWithFlags(final Phase phase, final ClassNode classNode, final Type classType, final String reason) {
if (CAP_INJECT.equals(classNode.name)) {
for (MethodNode mtd : classNode.methods) {
if (FUNC_NAME.equals(mtd.name) && FUNC_DESC.equals(mtd.desc)) {
public int processClassWithFlags(final Phase phase, final ClassNode classNode, final Type classType, final String reason)
{
if (CAP_INJECT.equals(classNode.name))
{
for (MethodNode mtd : classNode.methods)
{
if (FUNC_NAME.equals(mtd.name) && FUNC_DESC.equals(mtd.desc))
{
mtd.access &= ~Opcodes.ACC_FINAL; // We have it final in code so people don't override it, cuz that'd be stupid, and make our transformer more complicated.
}
}
return ComputeFlags.SIMPLE_REWRITE;
} else if (CAP_INJECT.equals(classNode.superName)) {
}
else if (CAP_INJECT.equals(classNode.superName))
{
Holder cls = new Holder();
SignatureReader reader = new SignatureReader(classNode.signature); // Having a node version of this would probably be useful.
reader.accept(new SignatureVisitor(Opcodes.ASM9) {
final Deque<String> stack = new ArrayDeque<>();
reader.accept(new SignatureVisitor(Opcodes.ASM9)
{
Deque<String> stack = new ArrayDeque<>();
@Override
public void visitClassType(final String name) {
public void visitClassType(final String name)
{
stack.push(name);
}
@Override
public void visitInnerClassType(final String name) {
public void visitInnerClassType(final String name)
{
stack.push(stack.pop() + '$' + name);
}
@Override
public void visitEnd() {
public void visitEnd()
{
var val = stack.pop();
if (!stack.isEmpty() && CAP_INJECT.equals(stack.peek()))
cls.value = val;
@@ -102,12 +106,15 @@ public class CapabilityTokenSubclass implements ILaunchPluginService {
mtd.visitInsn(Opcodes.ARETURN);
mtd.visitEnd();
return ComputeFlags.COMPUTE_MAXS;
} else {
}
else
{
return ComputeFlags.NO_REWRITE;
}
}
private static final class Holder {
private static class Holder {
String value;
}
}
@@ -8,7 +8,6 @@ package net.minecraftforge.fml.common.asm;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -35,12 +34,8 @@ public class ObjectHolderDefinalize implements ILaunchPluginService {
new VanillaObjectHolderData("net.minecraft.world.effect.MobEffects", "mob_effect", "net.minecraft.world.effect.MobEffect"),
new VanillaObjectHolderData("net.minecraft.core.particles.ParticleTypes", "particle_type", "net.minecraft.core.particles.ParticleType"),
new VanillaObjectHolderData("net.minecraft.sounds.SoundEvents", "sound_event", "net.minecraft.sounds.SoundEvent")
).collect(Collectors.toUnmodifiableMap(VanillaObjectHolderData::holderClass, Function.identity()));
private static final Set<String> VANILLA_OBJECT_HOLDER_CLASSES = VANILLA_OBJECT_HOLDERS.keySet().stream()
.map(it -> it.replace('.', '/'))
.collect(Collectors.toUnmodifiableSet());
private static final String OBJECT_HOLDER = "Lnet/minecraftforge/registries/ObjectHolder;"; //Don't directly reference this to prevent class loading.
private static final int PUBLIC_STATIC_FINAL_FLAGS = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL;
).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
public String name() {
@@ -51,33 +46,16 @@ public class ObjectHolderDefinalize implements ILaunchPluginService {
private static final EnumSet<Phase> NAY = EnumSet.noneOf(Phase.class);
@Override
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty) {
if (isEmpty)
return NAY;
// Let's skip processing classes that definitely don't have object holders
String internalName = classType.getInternalName();
// Forge classes that aren't in the debug package (that package is used for tests)
if (internalName.startsWith("net/minecraftforge/") && !internalName.substring(18).contains("debug"))
return NAY;
// Vanilla classes that don't have object holders
if (internalName.startsWith("com/mojang/"))
return NAY;
// ...except specific ones we added to the VANILLA_OBJECT_HOLDERS map
if (internalName.startsWith("net/minecraft/") && !VANILLA_OBJECT_HOLDER_CLASSES.contains(internalName))
return NAY;
return YAY;
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty)
{
return isEmpty ? NAY : YAY;
}
private static boolean hasHolder(List<AnnotationNode> lst) {
private boolean hasHolder(List<AnnotationNode> lst)
{
return lst != null && lst.stream().anyMatch(n -> n.desc.equals(OBJECT_HOLDER));
}
@SuppressWarnings("unused")
private String getValue(List<AnnotationNode> lst)
{
AnnotationNode ann = lst.stream().filter(n -> n.desc.equals(OBJECT_HOLDER)).findFirst().get();
@@ -97,9 +75,10 @@ public class ObjectHolderDefinalize implements ILaunchPluginService {
{
final AtomicBoolean changes = new AtomicBoolean();
//Must be public static finals, and non-array objects
final int flags = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL;
//Fix Annotated Fields before injecting from class level
classNode.fields.stream().filter(f -> ((f.access & PUBLIC_STATIC_FINAL_FLAGS) == PUBLIC_STATIC_FINAL_FLAGS) && f.desc.startsWith("L") && hasHolder(f.visibleAnnotations)).forEach(f ->
classNode.fields.stream().filter(f -> ((f.access & flags) == flags) && f.desc.startsWith("L") && hasHolder(f.visibleAnnotations)).forEach(f ->
{
int prev = f.access;
f.access &= ~Opcodes.ACC_FINAL; //Strip final
@@ -109,7 +88,7 @@ public class ObjectHolderDefinalize implements ILaunchPluginService {
if (VANILLA_OBJECT_HOLDERS.containsKey(classType.getClassName())) //Class level, de-finalize all fields and add @ObjectHolder to them!
{
classNode.fields.stream().filter(f -> ((f.access & PUBLIC_STATIC_FINAL_FLAGS) == PUBLIC_STATIC_FINAL_FLAGS) && f.desc.startsWith("L")).forEach(f ->
classNode.fields.stream().filter(f -> ((f.access & flags) == flags) && f.desc.startsWith("L")).forEach(f ->
{
int prev = f.access;
f.access &= ~Opcodes.ACC_FINAL;
@@ -20,6 +20,8 @@ import org.objectweb.asm.tree.MethodNode;
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
import org.slf4j.Logger;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
/**
* Modifies specified enums to allow runtime extension by making the $VALUES field non-final and
@@ -28,16 +30,15 @@ import org.slf4j.Logger;
public class RuntimeEnumExtender implements ILaunchPluginService {
private static final Logger LOGGER = LogUtils.getLogger();
private static final Type STRING = Type.getType(String.class);
private static final Type ENUM = Type.getType(Enum.class);
private static final Type MARKER_IFACE = Type.getType("Lnet/minecraftforge/common/IExtensibleEnum;");
private static final Type ARRAY_UTILS = Type.getType("Lorg/apache/commons/lang3/ArrayUtils;"); //Don't directly reference this to prevent class loading.
private static final String ADD_DESC = Type.getMethodDescriptor(Type.getType(Object[].class), Type.getType(Object[].class), Type.getType(Object.class));
private static final Type UNSAFE_HACKS = Type.getType("Lnet/minecraftforge/fml/unsafe/UnsafeHacks;"); //Again, not direct reference to prevent class loading.
private static final String CLEAN_DESC = Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Class.class));
private static final String NAME_DESC = Type.getMethodDescriptor(STRING);
private static final String EQUALS_DESC = Type.getMethodDescriptor(Type.BOOLEAN_TYPE, STRING);
private static final int FLAGS = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC;
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;");
private final Type ARRAY_UTILS = Type.getType("Lorg/apache/commons/lang3/ArrayUtils;"); //Don't directly reference this to prevent class loading.
private final String ADD_DESC = Type.getMethodDescriptor(Type.getType(Object[].class), Type.getType(Object[].class), Type.getType(Object.class));
private final Type UNSAFE_HACKS = Type.getType("Lnet/minecraftforge/fml/unsafe/UnsafeHacks;"); //Again, not direct reference to prevent class loading.
private final String CLEAN_DESC = Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Class.class));
private final String NAME_DESC = Type.getMethodDescriptor(STRING);
private final String EQUALS_DESC = Type.getMethodDescriptor(Type.BOOLEAN_TYPE, STRING);
@Override
public String name() {
@@ -48,15 +49,9 @@ public class RuntimeEnumExtender implements ILaunchPluginService {
private static final EnumSet<Phase> NAY = EnumSet.noneOf(Phase.class);
@Override
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty) {
if (isEmpty)
return NAY;
String internalName = classType.getInternalName();
if (internalName.startsWith("net/minecraftforge/") || internalName.startsWith("com/mojang/"))
return NAY;
return YAY;
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty)
{
return isEmpty ? NAY : YAY;
}
@Override
@@ -65,24 +60,25 @@ public class RuntimeEnumExtender implements ILaunchPluginService {
if ((classNode.access & Opcodes.ACC_ENUM) == 0)
return ComputeFlags.NO_REWRITE;
if (!classNode.interfaces.contains(MARKER_IFACE.getInternalName()))
return ComputeFlags.NO_REWRITE;
Type array = Type.getType("[" + classType.getDescriptor());
String arrayDesc = array.getDescriptor();
final int flags = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC;
FieldNode values = classNode.fields.stream().filter(f -> f.desc.equals(arrayDesc) && ((f.access & FLAGS) == FLAGS)).findFirst().orElse(null);
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"))
.toList();
.collect(Collectors.toList());
if (candidates.isEmpty()) {
throw new IllegalStateException("IExtensibleEnum has no candidate factory methods: " + classType.getClassName());
}
for (var mtd : candidates)
candidates.forEach(mtd ->
{
Type[] args = Type.getArgumentTypes(mtd.desc);
if (args.length == 0 || !args[0].equals(STRING)) {
@@ -114,7 +110,8 @@ public class RuntimeEnumExtender implements ILaunchPluginService {
Type[] ctrArgs = new Type[args.length + 1];
ctrArgs[0] = STRING;
ctrArgs[1] = Type.INT_TYPE;
System.arraycopy(args, 1, ctrArgs, 2, args.length - 1);
for (int x = 1; x < args.length; x++)
ctrArgs[1 + x] = args[x];
String desc = Type.getMethodDescriptor(Type.VOID_TYPE, ctrArgs);
@@ -236,7 +233,7 @@ public class RuntimeEnumExtender implements ILaunchPluginService {
ins.load(vars, classType);
ins.areturn(classType);
}
}
});
return ComputeFlags.COMPUTE_FRAMES;
}
@@ -5,13 +5,15 @@
package net.minecraftforge.fml.loading;
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class BackgroundWaiter {
private static final ExecutorService runner = Executors.newSingleThreadExecutor();
private static ExecutorService runner = Executors.newSingleThreadExecutor();
public static void runAndTick(Runnable r, Runnable tick) {
ImmediateWindowHandler.updateProgress("Loading bootstrap resources");
@@ -0,0 +1,61 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import cpw.mods.modlauncher.api.NamedPath;
import cpw.mods.modlauncher.serviceapi.ITransformerDiscoveryService;
import org.apache.logging.log4j.LogManager;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
public class ClasspathTransformerDiscoverer implements ITransformerDiscoveryService {
private final List<Path> legacyClasspath = Arrays.stream(System.getProperty("legacyClassPath", "").split(File.pathSeparator)).map(Path::of).toList();
@Override
public List<NamedPath> candidates(Path gameDirectory) {
return Collections.emptyList();
}
@Override
public List<NamedPath> candidates(final Path gameDirectory, final String launchTarget) {
if (launchTarget != null && launchTarget.contains("dev")) {
this.scan(gameDirectory);
}
return List.copyOf(found);
}
private final static List<NamedPath> found = new ArrayList<>();
public static List<Path> allExcluded() {
return found.stream().map(np->np.paths()[0]).toList();
}
private void scan(final Path gameDirectory) {
try {
locateTransformers("META-INF/services/cpw.mods.modlauncher.api.ITransformationService");
locateTransformers("META-INF/services/net.minecraftforge.forgespi.locating.IModLocator");
} catch (IOException e) {
LogManager.getLogger().error("Error during discovery of transform services from the classpath", e);
}
}
private void locateTransformers(String resource) throws IOException {
final Enumeration<URL> resources = ClassLoader.getSystemClassLoader().getResources(resource);
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
Path path = ClasspathLocatorUtils.findJarPathFor(resource, url.toString(), url);
if (legacyClasspath.stream().anyMatch(path::equals) || !Files.exists(path) || Files.isDirectory(path))
continue;
found.add(new NamedPath(path.toUri().toString(), path));
}
}
}
@@ -14,8 +14,6 @@ import java.util.List;
* or server.
*/
public class EarlyLoadingException extends RuntimeException {
private static final long serialVersionUID = 2302192216172508006L;
public static class ExceptionData {
private final IModInfo modInfo;
private final String i18message;
@@ -36,14 +36,13 @@ public class FMLConfig
EARLY_WINDOW_FBSCALE("earlyWindowFBScale", 1, "Early window framebuffer scale"),
EARLY_WINDOW_MAXIMIZED("earlyWindowMaximized", Boolean.FALSE, "Early window starts maximized"),
EARLY_WINDOW_SKIP_GL_VERSIONS("earlyWindowSkipGLVersions", List.of(), "Skip specific GL versions, may help with buggy graphics card drivers"),
EARLY_WINDOW_SQUIR("earlyWindowSquir", Boolean.FALSE, "Squir?"),
EARLY_WINDOW_SHOW_CPU("earlyWindowShowCPU", Boolean.FALSE, "Whether to show CPU usage stats in early window"),
EARLY_WINDOW_LOG_HELP_MSG("earlyWindowLogHelpMessage", Boolean.TRUE, "Whether to log a help message on first attempt, to aid troubleshooting. This setting should automatically disable itself after a successful launch"),
EARLY_WINDOW_SQUIR("earlyWindowSquir", Boolean.FALSE, "Squir?")
;
private final String entry;
private final Object defaultValue;
private final String comment;
private final Class<?> valueType;
private final Function<Object, Object> entryFunction;
ConfigValue(final String entry, final Object defaultValue, final String comment) {
@@ -53,6 +52,7 @@ public class FMLConfig
this.entry = entry;
this.defaultValue = defaultValue;
this.comment = comment;
this.valueType = defaultValue.getClass();
this.entryFunction = entryFunction;
}
@@ -8,40 +8,44 @@ package net.minecraftforge.fml.loading;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.Launcher;
import cpw.mods.modlauncher.api.*;
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
import cpw.mods.modlauncher.util.ServiceLoaderUtils;
import net.minecraftforge.fml.loading.moddiscovery.BackgroundScanHandler;
import net.minecraftforge.fml.loading.moddiscovery.ModDiscoverer;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import net.minecraftforge.fml.loading.moddiscovery.ModValidator;
import net.minecraftforge.accesstransformer.service.AccessTransformerService;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
import net.minecraftforge.fml.loading.targets.CommonLaunchHandler;
import net.minecraftforge.forgespi.Environment;
import net.minecraftforge.forgespi.coremod.ICoreModProvider;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
import java.util.Set;
import java.nio.file.Paths;
import java.util.*;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
import static net.minecraftforge.fml.loading.LogMarkers.SCAN;
public class FMLLoader {
public class FMLLoader
{
private static final Logger LOGGER = LogUtils.getLogger();
private static AccessTransformerService accessTransformer;
private static ModDiscoverer modDiscoverer;
private static ICoreModProvider coreModProvider;
private static ILaunchPluginService eventBus;
private static LanguageLoadingProvider languageLoadingProvider;
private static Dist dist;
private static String naming;
private static LoadingModList loadingModList;
private static RuntimeDistCleaner runtimeDistCleaner;
private static Path gamePath;
private static final VersionInfo versionInfo = VersionInfo.detect();
private static VersionInfo versionInfo;
private static String launchHandlerName;
private static CommonLaunchHandler commonLaunchHandler;
public static Runnable progressWindowTick;
@@ -50,84 +54,80 @@ public class FMLLoader {
private static boolean production;
private static IModuleLayerManager moduleLayerManager;
static void onInitialLoad(IEnvironment env, Set<String> otherServices) throws IncompatibleEnvironmentException {
LOGGER.debug(CORE, "Detected version data : {}", versionInfo);
LOGGER.debug(CORE, "FML {} loading", LauncherVersion.getVersion());
static void onInitialLoad(IEnvironment environment, Set<String> otherServices) throws IncompatibleEnvironmentException
{
final String version = LauncherVersion.getVersion();
LOGGER.debug(CORE,"FML {} loading", version);
final Package modLauncherPackage = ITransformationService.class.getPackage();
LOGGER.debug(CORE,"FML found ModLauncher version : {}", modLauncherPackage.getImplementationVersion());
if (!modLauncherPackage.isCompatibleWith("4.0")) {
LOGGER.error(CORE, "Found incompatible ModLauncher specification : {}, version {} from {}", modLauncherPackage.getSpecificationVersion(), modLauncherPackage.getImplementationVersion(), modLauncherPackage.getImplementationVendor());
throw new IncompatibleEnvironmentException("Incompatible modlauncher found "+modLauncherPackage.getSpecificationVersion());
}
// Allows us to communicate properties with other services through ModLauncher
setupBlackboardKeys();
accessTransformer = (AccessTransformerService) environment.findLaunchPlugin("accesstransformer").orElseThrow(()-> {
LOGGER.error(CORE, "Access Transformer library is missing, we need this to run");
return new IncompatibleEnvironmentException("Missing AccessTransformer, cannot run");
});
checkPackage(ITransformationService.class, "4.0", "ModLauncher");
accessTransformer = getPlugin(env, "accesstransformer", "1.0", "AccessTransformer");
/*eventBus =*/ getPlugin(env, "eventbus", "1.0", "EventBus");
runtimeDistCleaner = getPlugin(env, "runtimedistcleaner", "1.0", "RuntimeDistCleaner");
coreModProvider = getSingleService(ICoreModProvider.class, "CoreMod");
LOGGER.debug(CORE, "FML found CoreMod version : {}", JarVersionLookupHandler.getInfo(coreModProvider.getClass()).impl().version().orElse("MISSING"));
checkPackage(Environment.class, "2.0", "ForgeSPI");
final Package atPackage = accessTransformer.getClass().getPackage();
LOGGER.debug(CORE,"FML found AccessTransformer version : {}", atPackage.getImplementationVersion());
if (!atPackage.isCompatibleWith("1.0")) {
LOGGER.error(CORE, "Found incompatible AccessTransformer specification : {}, version {} from {}", atPackage.getSpecificationVersion(), atPackage.getImplementationVersion(), atPackage.getImplementationVendor());
throw new IncompatibleEnvironmentException("Incompatible accesstransformer found "+atPackage.getSpecificationVersion());
}
eventBus = environment.findLaunchPlugin("eventbus").orElseThrow(()-> {
LOGGER.error(CORE, "Event Bus library is missing, we need this to run");
return new IncompatibleEnvironmentException("Missing EventBus, cannot run");
});
final Package eventBusPackage = eventBus.getClass().getPackage();
LOGGER.debug(CORE,"FML found EventBus version : {}", eventBusPackage.getImplementationVersion());
if (!eventBusPackage.isCompatibleWith("1.0")) {
LOGGER.error(CORE, "Found incompatible EventBus specification : {}, version {} from {}", eventBusPackage.getSpecificationVersion(), eventBusPackage.getImplementationVersion(), eventBusPackage.getImplementationVendor());
throw new IncompatibleEnvironmentException("Incompatible eventbus found "+eventBusPackage.getSpecificationVersion());
}
runtimeDistCleaner = (RuntimeDistCleaner)environment.findLaunchPlugin("runtimedistcleaner").orElseThrow(()-> {
LOGGER.error(CORE, "Dist Cleaner is missing, we need this to run");
return new IncompatibleEnvironmentException("Missing DistCleaner, cannot run!");
});
LOGGER.debug(CORE, "Found Runtime Dist Cleaner");
var coreModProviders = ServiceLoaderUtils.streamWithErrorHandling(ServiceLoader.load(FMLLoader.class.getModule().getLayer(), ICoreModProvider.class), sce -> LOGGER.error(CORE, "Failed to load a coremod library, expect problems", sce)).toList();
if (coreModProviders.isEmpty()) {
LOGGER.error(CORE, "Found no coremod provider. Cannot run");
throw new IncompatibleEnvironmentException("No coremod library found");
} else if (coreModProviders.size() > 1) {
LOGGER.error(CORE, "Found multiple coremod providers : {}. Cannot run", coreModProviders.stream().map(p -> p.getClass().getName()).collect(Collectors.toList()));
throw new IncompatibleEnvironmentException("Multiple coremod libraries found");
}
coreModProvider = coreModProviders.get(0);
final Package coremodPackage = coreModProvider.getClass().getPackage();
LOGGER.debug(CORE,"FML found CoreMod version : {}", coremodPackage.getImplementationVersion());
LOGGER.debug(CORE, "Found ForgeSPI package implementation version {}", Environment.class.getPackage().getImplementationVersion());
LOGGER.debug(CORE, "Found ForgeSPI package specification {}", Environment.class.getPackage().getSpecificationVersion());
if (Integer.parseInt(Environment.class.getPackage().getSpecificationVersion()) < 2) {
LOGGER.error(CORE, "Found an out of date ForgeSPI implementation: {}, loading cannot continue", Environment.class.getPackage().getSpecificationVersion());
throw new IncompatibleEnvironmentException("ForgeSPI is out of date, we cannot continue");
}
try {
Class.forName("com.electronwill.nightconfig.core.Config", false, env.getClass().getClassLoader());
Class.forName("com.electronwill.nightconfig.toml.TomlFormat", false, env.getClass().getClassLoader());
Class.forName("com.electronwill.nightconfig.core.Config", false, environment.getClass().getClassLoader());
Class.forName("com.electronwill.nightconfig.toml.TomlFormat", false, environment.getClass().getClassLoader());
} catch (ClassNotFoundException e) {
LOGGER.error(CORE, "Failed to load NightConfig");
throw new IncompatibleEnvironmentException("Missing NightConfig");
}
}
@SuppressWarnings("unchecked")
private static <T> T getPlugin(IEnvironment env, String id, String version, String name) throws IncompatibleEnvironmentException {
var plugin = env.findLaunchPlugin(id).orElse(null);
if (plugin == null) {
LOGGER.error(CORE, "{} library is missing, we need this to run", name);
throw new IncompatibleEnvironmentException("Missing " + name + ", cannot run");
}
checkPackage(plugin.getClass(), version, name);
return (T) plugin;
}
private static void checkPackage(Class<?> cls, String version, String name) throws IncompatibleEnvironmentException {
var pkg = cls.getPackage();
var info = JarVersionLookupHandler.getInfo(pkg);
LOGGER.debug(CORE, "Found {} version: {}", name, info.impl().version().orElse("MISSING"));
if (!pkg.isCompatibleWith(version)) {
LOGGER.error(CORE, "Found incompatible {} specification: {}, version {} from {}", name,
info.spec().version().orElse("MISSING"),
info.impl().version().orElse("MISSING"),
info.impl().vendor().orElse("MISSING")
);
throw new IncompatibleEnvironmentException("Incompatible " + name + " found " + info.spec().version().orElse("MISSING"));
}
}
private static <T> T getSingleService(Class<T> clazz, String name) throws IncompatibleEnvironmentException {
var providers = new ArrayList<T>();
for (var itr = ServiceLoader.load(FMLLoader.class.getModule().getLayer(), clazz).iterator(); itr.hasNext(); ) {
try {
providers.add(itr.next());
} catch (ServiceConfigurationError e) {
LOGGER.error(CORE, "Failed to load a " + name + " library, expect problems", e);
}
}
if (providers.isEmpty()) {
LOGGER.error(CORE, "Found no {} provider. Cannot run", name);
throw new IncompatibleEnvironmentException("No " + name + " library found");
} else if (providers.size() > 1) {
LOGGER.error(CORE, "Found multiple {} providers: {}. Cannot run", name, providers.stream().map(p -> p.getClass().getName()).toList());
throw new IncompatibleEnvironmentException("Multiple " + name + " libraries found");
}
return providers.get(0);
}
private static void setupBlackboardKeys() {
LOGGER.debug(CORE, "Requesting CoreMods to not apply the fix for ASMAPI.findFirstInstructionBefore by default");
var blackboardKey = TypesafeMap.Key.getOrCreate(Launcher.INSTANCE.blackboard(), "coremods.use_old_findFirstInstructionBefore", Boolean.class);
Launcher.INSTANCE.blackboard().<Boolean>computeIfAbsent(blackboardKey, k -> true);
}
static void setupLaunchHandler(final IEnvironment environment, final Map<String, Object> arguments) {
static void setupLaunchHandler(final IEnvironment environment, final Map<String, Object> arguments)
{
final String launchTarget = environment.getProperty(IEnvironment.Keys.LAUNCHTARGET.get()).orElse("MISSING");
arguments.put("launchTarget", launchTarget);
final Optional<ILaunchHandlerService> launchHandler = environment.findLaunchHandler(launchTarget);
@@ -137,27 +137,30 @@ public class FMLLoader {
throw new RuntimeException("Missing launch handler: " + launchTarget);
}
// TODO: [FML][Loader] What the fuck is the point of using a service if you require a specific concrete class
if (!(launchHandler.get() instanceof CommonLaunchHandler)) {
LOGGER.error(CORE, "Incompatible Launch handler found - type {}, cannot continue", launchHandler.get().getClass().getName());
throw new RuntimeException("Incompatible launch handler found");
}
commonLaunchHandler = (CommonLaunchHandler)launchHandler.get();
launchHandlerName = launchHandler.get().name();
gamePath = environment.getProperty(IEnvironment.Keys.GAMEDIR.get()).orElse(Path.of(".").toAbsolutePath());
gamePath = environment.getProperty(IEnvironment.Keys.GAMEDIR.get()).orElse(Paths.get(".").toAbsolutePath());
naming = commonLaunchHandler.getNaming();
dist = commonLaunchHandler.getDist();
production = commonLaunchHandler.isProduction();
accessTransformer.getExtension().accept(Map.entry(naming, "srg"));
versionInfo = new VersionInfo(arguments);
accessTransformer.getExtension().accept(Pair.of(naming, "srg"));
LOGGER.debug(CORE,"Received command line version data : {}", versionInfo);
runtimeDistCleaner.getExtension().accept(dist);
}
public static List<ITransformationService.Resource> beginModScan(final Map<String,?> arguments) {
public static List<ITransformationService.Resource> beginModScan(final Map<String,?> arguments)
{
LOGGER.debug(SCAN,"Scanning for Mod Locators");
var modDiscoverer = new ModDiscoverer(arguments);
modDiscoverer = new ModDiscoverer(arguments);
modValidator = modDiscoverer.discoverMods();
var pluginResources = modValidator.getPluginResources();
return List.of(pluginResources);
@@ -175,34 +178,44 @@ public class FMLLoader {
return coreModProvider;
}
public static LanguageLoadingProvider getLanguageLoadingProvider() {
public static LanguageLoadingProvider getLanguageLoadingProvider()
{
return languageLoadingProvider;
}
static ModDiscoverer getModDiscoverer() {
return modDiscoverer;
}
public static CommonLaunchHandler getLaunchHandler() {
return commonLaunchHandler;
}
public static void addAccessTransformer(Path atPath, ModFile modName) {
LOGGER.debug(SCAN, "Adding Access Transformer {} in {}", atPath, modName.getFilePath());
public static void addAccessTransformer(Path atPath, ModFile modName)
{
LOGGER.debug(SCAN, "Adding Access Transformer in {}", modName.getFilePath());
accessTransformer.offerResource(atPath, modName.getFileName());
}
public static Dist getDist() {
public static Dist getDist()
{
return dist;
}
public static void beforeStart(ModuleLayer gameLayer) {
public static void beforeStart(ModuleLayer gameLayer)
{
ImmediateWindowHandler.acceptGameLayer(gameLayer);
ImmediateWindowHandler.updateProgress("Launching minecraft");
progressWindowTick.run();
}
public static LoadingModList getLoadingModList() {
public static LoadingModList getLoadingModList()
{
return loadingModList;
}
public static Path getGamePath() {
public static Path getGamePath()
{
return gamePath;
}
@@ -219,7 +232,7 @@ public class FMLLoader {
}
public static List<Map<String, String>> modLauncherModList() {
return Launcher.INSTANCE.environment().getProperty(IEnvironment.Keys.MODLIST.get()).orElse(List.of());
return Launcher.INSTANCE.environment().getProperty(IEnvironment.Keys.MODLIST.get()).orElseGet(Collections::emptyList);
}
public static String launcherHandlerName() {
@@ -7,6 +7,7 @@ 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;
@@ -14,6 +15,7 @@ import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
@@ -22,18 +24,36 @@ import java.util.Set;
import java.util.function.BiFunction;
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
public class FMLServiceProvider implements ITransformationService {
public class FMLServiceProvider implements ITransformationService
{
private static final Logger LOGGER = LogUtils.getLogger();
private final Map<String, Object> arguments = new HashMap<>();
private ArgumentAcceptingOptionSpec<String> modsOption;
private ArgumentAcceptingOptionSpec<String> modListsOption;
private ArgumentAcceptingOptionSpec<String> mavenRootsOption;
private ArgumentAcceptingOptionSpec<String> forgeOption;
private ArgumentAcceptingOptionSpec<String> mcOption;
private ArgumentAcceptingOptionSpec<String> forgeGroupOption;
private ArgumentAcceptingOptionSpec<String> mcpOption;
private ArgumentAcceptingOptionSpec<String> mappingsOption;
private List<String> modsArgumentList;
private List<String> modListsArgumentList;
private List<String> mavenRootsArgumentList;
private String targetForgeVersion;
private String targetMcVersion;
private String targetMcpVersion;
private String targetMcpMappings;
private String targetForgeGroup;
private Map<String, Object> arguments;
public FMLServiceProvider() {
var markers = System.getProperty("forge.logging.markers", "").split(",");
for (var marker : markers)
System.setProperty("forge.logging.marker." + marker.toLowerCase(Locale.ROOT), "ACCEPT");
public FMLServiceProvider()
{
final String markerselection = System.getProperty("forge.logging.markers", "");
Arrays.stream(markerselection.split(",")).forEach(marker -> System.setProperty("forge.logging.marker." + marker.toLowerCase(Locale.ROOT), "ACCEPT"));
}
@Override
public String name() {
public String name()
{
return "fml";
}
@@ -45,6 +65,15 @@ public class FMLServiceProvider implements ITransformationService {
FMLConfig.load();
LOGGER.debug(CORE, "Preparing ModFile");
environment.computePropertyIfAbsent(Environment.Keys.MODFILEFACTORY.get(), k->ModFile::new);
arguments = new HashMap<>();
arguments.put("modLists", modListsArgumentList);
arguments.put("mods", modsArgumentList);
arguments.put("mavenRoots", mavenRootsArgumentList);
arguments.put("forgeVersion", targetForgeVersion);
arguments.put("forgeGroup", targetForgeGroup);
arguments.put("mcVersion", targetMcVersion);
arguments.put("mcpVersion", targetMcpVersion);
arguments.put("mcpMappings", targetMcpMappings);
LOGGER.debug(CORE, "Preparing launch handler");
FMLLoader.setupLaunchHandler(environment, arguments);
FMLEnvironment.setupInteropEnvironment(environment);
@@ -63,14 +92,45 @@ public class FMLServiceProvider implements ITransformationService {
}
@Override
public void onLoad(IEnvironment environment, Set<String> otherServices) throws IncompatibleEnvironmentException {
public void onLoad(IEnvironment environment, Set<String> otherServices) throws IncompatibleEnvironmentException
{
// LOGGER.debug("Injecting tracing printstreams for STDOUT/STDERR.");
// System.setOut(new TracingPrintStream(LogManager.getLogger("STDOUT"), System.out));
// System.setErr(new TracingPrintStream(LogManager.getLogger("STDERR"), System.err));
FMLLoader.onInitialLoad(environment, otherServices);
}
@SuppressWarnings("rawtypes")
@Override
public @NotNull List<ITransformer> transformers() {
public void arguments(BiFunction<String, String, OptionSpecBuilder> argumentBuilder)
{
forgeOption = argumentBuilder.apply("forgeVersion", "Forge Version number").withRequiredArg().ofType(String.class).required();
forgeGroupOption = argumentBuilder.apply("forgeGroup", "Forge Group (for testing)").withRequiredArg().ofType(String.class).defaultsTo("net.minecraftforge");
mcOption = argumentBuilder.apply("mcVersion", "Minecraft Version number").withRequiredArg().ofType(String.class).required();
mcpOption = argumentBuilder.apply("mcpVersion", "MCP Version number").withRequiredArg().ofType(String.class).required();
mappingsOption = argumentBuilder.apply("mcpMappings", "MCP Mappings Channel and Version").withRequiredArg().ofType(String.class);
modsOption = argumentBuilder.apply("mods", "List of mods to add").withRequiredArg().ofType(String.class).withValuesSeparatedBy(",");
modListsOption = argumentBuilder.apply("modLists", "JSON modlists").withRequiredArg().ofType(String.class).withValuesSeparatedBy(",");
mavenRootsOption = argumentBuilder.apply("mavenRoots", "Maven root directories").withRequiredArg().ofType(String.class).withValuesSeparatedBy(",");
}
@Override
public void argumentValues(OptionResult option)
{
modsArgumentList = option.values(modsOption);
modListsArgumentList = option.values(modListsOption);
mavenRootsArgumentList = option.values(mavenRootsOption);
targetForgeVersion = option.value(forgeOption);
targetForgeGroup = option.value(forgeGroupOption);
targetMcVersion = option.value(mcOption);
targetMcpVersion = option.value(mcpOption);
targetMcpMappings = option.value(mappingsOption);
}
@Override
public @NotNull List<ITransformer> transformers()
{
LOGGER.debug(CORE, "Loading coremod transformers");
return new ArrayList<>(FMLLoader.getCoreModProvider().getCoreModTransformers());
}
}
@@ -0,0 +1,35 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import java.io.File;
import java.nio.file.Path;
public class FileUtils
{
public static String fileExtension(final Path path) {
String fileName = path.getFileName().toString();
int idx = fileName.lastIndexOf('.');
if (idx > -1) {
return fileName.substring(idx+1);
} else {
return "";
}
}
public static boolean matchFileName(String path, String... matches) {
// Extract file name from path
String name = path.substring(Math.min(path.lastIndexOf(File.separatorChar) + 1, path.length()));
// Check if it contains any of the desired keywords
for (String match : matches) {
if (name.contains(match)) {
return true;
}
}
return false;
}
}
@@ -23,7 +23,7 @@ public class ImmediateWindowHandler {
private static ProgressMeter earlyProgress;
public static void load(final String launchTarget, final String[] arguments) {
if (!launchTarget.contains("client")) {
if (!List.of("forgeclient", "forgeclientuserdev", "forgeclientdev").contains(launchTarget)) {
provider = new DummyProvider();
LOGGER.info("ImmediateWindowProvider not loading because launch target is {}", launchTarget);
} else if (!FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_CONTROL)) {
@@ -32,16 +32,15 @@ public class ImmediateWindowHandler {
} else {
final var providername = FMLConfig.getConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_PROVIDER);
LOGGER.info("Loading ImmediateWindowProvider {}", providername);
for (var service : ServiceLoader.load(ImmediateWindowProvider.class)) {
if (providername.equals(service.name())) {
provider = service;
break;
}
}
if (provider == null) {
final var maybeProvider = ServiceLoader.load(ImmediateWindowProvider.class)
.stream()
.map(ServiceLoader.Provider::get)
.filter(p -> Objects.equals(p.name(), providername))
.findFirst();
provider = maybeProvider.or(() -> {
LOGGER.info("Failed to find ImmediateWindowProvider {}, disabling", providername);
provider = new DummyProvider();
}
return Optional.of(new DummyProvider());
}).orElseThrow();
}
// Only update config if the provider isn't the dummy provider
if (!Objects.equals(provider.name(), "dummyprovider"))
@@ -140,7 +139,7 @@ public class ImmediateWindowHandler {
@Override
public void updateModuleReads(final ModuleLayer layer) {
var fm = layer.findModule("net.minecraftforge.forge");
var fm = layer.findModule("forge");
if (fm.isPresent()) {
getClass().getModule().addReads(fm.get());
var clz = fm.map(l -> Class.forName(l, "net.minecraftforge.client.loading.NoVizFallback")).orElseThrow();
@@ -11,72 +11,31 @@ import java.util.Optional;
* Finds Version data from a package, with possible default values
*/
public class JarVersionLookupHandler {
public record Version(Optional<String> title, Optional<String> vendor, Optional<String> version) {
@Override
public String toString() {
return "[" +
"title=" + title.orElse(null) +
", vendor=" + vendor.orElse(null) +
", version=" + version.orElse(null) +
"]";
}
}
public record Info(Version spec, Version impl) {}
@SuppressWarnings("deprecation")
public static Info getInfo(String pkgName) {
return getInfo(Package.getPackage(pkgName));
}
public static Info getInfo(Class<?> clazz) {
return getInfo(clazz.getPackage());
}
public static Info getInfo(Package pkg) {
return new Info(
new Version(
Optional.ofNullable(pkg.getSpecificationTitle()),
Optional.ofNullable(pkg.getSpecificationVendor()),
Optional.ofNullable(pkg.getSpecificationVersion())
),
new Version(
Optional.ofNullable(pkg.getImplementationTitle()),
Optional.ofNullable(pkg.getImplementationVendor()),
Optional.ofNullable(pkg.getImplementationVersion())
)
);
}
@Deprecated(forRemoval = true, since = "1.20.2")
public static Optional<String> getImplementationVersion(final String pkgName) {
// Note that with Java 9, you'll probably want the module's version data, hence pulling this out
final String pkgVersion = Package.getPackage(pkgName).getImplementationVersion();
return Optional.ofNullable(pkgVersion);
}
@Deprecated(forRemoval = true, since = "1.20.2")
public static Optional<String> getSpecificationVersion(final String pkgName) {
// Note that with Java 9, you'll probably want the module's version data, hence pulling this out
final String pkgVersion = Package.getPackage(pkgName).getSpecificationVersion();
return Optional.ofNullable(pkgVersion);
}
@Deprecated(forRemoval = true, since = "1.20.2")
public static Optional<String> getImplementationVersion(final Class<?> clazz) {
// With java 9 we'll use the module's version if it exists in preference.
final String pkgVersion = clazz.getPackage().getImplementationVersion();
return Optional.ofNullable(pkgVersion);
}
@Deprecated(forRemoval = true, since = "1.20.2")
public static Optional<String> getImplementationTitle(final Class<?> clazz) {
// With java 9 we'll use the module's version if it exists in preference.
final String pkgVersion = clazz.getPackage().getImplementationTitle();
return Optional.ofNullable(pkgVersion);
}
@Deprecated(forRemoval = true, since = "1.20.2")
public static Optional<String> getSpecificationVersion(final Class<?> clazz) {
// With java 9 we'll use the module's version if it exists in preference.
final String pkgVersion = clazz.getPackage().getSpecificationVersion();
@@ -7,19 +7,27 @@ package net.minecraftforge.fml.loading;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.Launcher;
import cpw.mods.modlauncher.api.IModuleLayerManager.Layer;
import cpw.mods.modlauncher.api.IModuleLayerManager;
import cpw.mods.modlauncher.util.ServiceLoaderUtils;
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
import net.minecraftforge.forgespi.language.IModLanguageProvider;
import net.minecraftforge.fml.loading.moddiscovery.ExplodedDirectoryLocator;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.artifact.versioning.VersionRange;
import org.slf4j.Logger;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ServiceConfigurationError;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -28,97 +36,115 @@ import java.util.stream.Stream;
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
import static net.minecraftforge.fml.loading.LogMarkers.LOADING;
public class LanguageLoadingProvider {
public void forEach(Consumer<IModLanguageProvider> consumer) {
providers.forEach(consumer);
}
public <T> Stream<T> applyForEach(Function<IModLanguageProvider, T> function) {
return providers.stream().map(function);
}
public void reload() {
loadLanguageProviders();
}
/** This doesn't actually do anything with the argument, so just call reload(). Not sure when cpw broke that. */
@Deprecated(forRemoval = true, since = "1.20.2")
public void addAdditionalLanguages(List<ModFile> modFiles) {
reload();
}
/*==========================================================================*
* INTERNAL SHIT *
*==========================================================================*/
public class LanguageLoadingProvider
{
private static final Logger LOGGER = LogUtils.getLogger();
private final List<IModLanguageProvider> languageProviders = new ArrayList<>();
private final ServiceLoader<IModLanguageProvider> serviceLoader;
private final List<IModLanguageProvider> providers = new ArrayList<>();
private final Map<String, Wrapper> providersByName = new HashMap<>();
private static final DefaultArtifactVersion IN_DEV = new DefaultArtifactVersion("0.0-dev");
private final Map<String, ModLanguageWrapper> languageProviderMap = new HashMap<>();
private List<Path> languagePaths = new ArrayList<>();
private record Wrapper(ArtifactVersion version, IModLanguageProvider service) {}
public void forEach(final Consumer<IModLanguageProvider> consumer)
{
languageProviders.forEach(consumer);
}
public <T> Stream<T> applyForEach(final Function<IModLanguageProvider, T> function) {
return languageProviders.stream().map(function);
}
private static class ModLanguageWrapper {
private final IModLanguageProvider modLanguageProvider;
private final ArtifactVersion version;
public ModLanguageWrapper(IModLanguageProvider modLanguageProvider, ArtifactVersion version)
{
this.modLanguageProvider = modLanguageProvider;
this.version = version;
}
public ArtifactVersion getVersion()
{
return version;
}
public IModLanguageProvider getModLanguageProvider()
{
return modLanguageProvider;
}
}
LanguageLoadingProvider() {
var sl = Launcher.INSTANCE.environment().findModuleLayerManager().flatMap(lm -> lm.getLayer(Layer.PLUGIN)).orElseThrow();
var sl = Launcher.INSTANCE.environment().findModuleLayerManager().flatMap(lm->lm.getLayer(IModuleLayerManager.Layer.PLUGIN)).orElseThrow();
serviceLoader = ServiceLoader.load(sl, IModLanguageProvider.class);
loadLanguageProviders();
}
private void loadLanguageProviders() {
providers.clear();
providersByName.clear();
serviceLoader.reload();
for (var itr = serviceLoader.iterator(); itr.hasNext(); ) {
try {
var srvc = itr.next();
providers.add(srvc);
} catch (ServiceConfigurationError sce) {
LOGGER.error(CORE, "Failed to load IModLauguageProvider Service: " + sce.getMessage());
}
}
LOGGER.debug(CORE, "Found {} language providers", providers.size());
LOGGER.debug(CORE, "Found {} language providers", ServiceLoaderUtils.streamServiceLoader(()->serviceLoader, sce->LOGGER.error("Problem with language loaders")).count());
serviceLoader.forEach(languageProviders::add);
ImmediateWindowHandler.updateProgress("Loading language providers");
for (var provider : providers) {
var info = JarVersionLookupHandler.getInfo(provider.getClass());
if (info.impl().version().isEmpty() && FMLEnvironment.production) {
LOGGER.error(CORE, "Found unversioned language provider {}: {}", provider.name(), provider.getClass().getName());
throw new RuntimeException("Failed to find implementation version for language provider " + provider.name() + " of type " + provider.getClass().getName());
languageProviders.forEach(lp -> {
final Path lpPath;
try {
lpPath = Paths.get(lp.getClass().getProtectionDomain().getCodeSource().getLocation().toURI());
} catch (URISyntaxException e) {
throw new RuntimeException("Huh?", e);
}
Optional<String> implementationVersion = JarVersionLookupHandler.getImplementationVersion(lp.getClass());
String impl = implementationVersion.orElse(Files.isDirectory(lpPath) ? FMLLoader.versionInfo().forgeVersion().split("\\.")[0] : null);
if (impl == null) {
LOGGER.error(CORE, "Found unversioned language provider {}", lp.name());
throw new RuntimeException("Failed to find implementation version for language provider "+ lp.name());
}
LOGGER.debug(CORE, "Found language provider {}, version {}", lp.name(), impl);
ImmediateWindowHandler.updateProgress("Loaded language provider "+lp.name()+ " " + impl);
languageProviderMap.put(lp.name(), new ModLanguageWrapper(lp, new DefaultArtifactVersion(impl)));
});
}
var version = info.impl().version().map(DefaultArtifactVersion::new).orElse(IN_DEV);
LOGGER.debug(CORE, "Found language provider {}, version {}", provider.name(), version);
ImmediateWindowHandler.updateProgress("Loaded language provider " + provider.name() + " " + version);
providersByName.put(provider.name(), new Wrapper(version, provider));
void addForgeLanguage(final Path forgePath) {
if (!languageProviderMap.containsKey("javafml")) {
LOGGER.debug(CORE,"Adding forge as a language from {}", forgePath.toString());
addLanguagePaths(Stream.of(forgePath));
serviceLoader.reload();
loadLanguageProviders();
} else {
LOGGER.debug(CORE, "Skipping adding forge jar - javafml is already present");
}
}
public IModLanguageProvider findLanguage(ModFile mod, String language, VersionRange versionRange) {
var consumer = mod.getFileName();
var wrapper = providersByName.get(language);
private void addLanguagePaths(final Stream<Path> langPaths) {
languageProviders.clear();
languageProviderMap.clear();
// langPaths.peek(languagePaths::add).map(Path::toFile).map(File::toURI).map(rethrowFunction(URI::toURL)).forEach(languageClassLoader::addURL);
}
if (wrapper == null) {
LOGGER.error(LOADING, "Missing language {} version {} wanted by {}", language, versionRange, consumer);
var data = new EarlyLoadingException.ExceptionData("fml.language.missingversion", language, versionRange, consumer, "null");
throw new EarlyLoadingException("Missing language " + language, null, List.of(data));
public void addAdditionalLanguages(List<ModFile> modFiles)
{
if (modFiles==null) return;
Stream<Path> langPaths = modFiles.stream().map(ModFile::getFilePath);
addLanguagePaths(langPaths);
serviceLoader.reload();
loadLanguageProviders();
}
Stream<Path> getLibraries() {
return languagePaths.stream();
}
public IModLanguageProvider findLanguage(ModFile mf, String modLoader, VersionRange modLoaderVersion) {
final String languageFileName = mf.getProvider() instanceof ExplodedDirectoryLocator ? "in-development" : mf.getFileName();
final ModLanguageWrapper mlw = languageProviderMap.get(modLoader);
if (mlw == null) {
LOGGER.error(LOADING,"Missing language {} version {} wanted by {}", modLoader, modLoaderVersion, languageFileName);
throw new EarlyLoadingException("Missing language "+modLoader, null, Collections.singletonList(new EarlyLoadingException.ExceptionData("fml.language.missingversion", modLoader, modLoaderVersion, languageFileName, "null")));
}
if (!VersionSupportMatrix.testVersionSupportMatrix(modLoaderVersion, modLoader, "languageloader", (llid, range) -> range.containsVersion(mlw.getVersion()))) {
LOGGER.error(LOADING,"Missing language {} version {} wanted by {}, found {}", modLoader, modLoaderVersion, languageFileName, mlw.getVersion());
throw new EarlyLoadingException("Missing language "+ modLoader + " matching range "+modLoaderVersion + " found "+mlw.getVersion(), null, Collections.singletonList(new EarlyLoadingException.ExceptionData("fml.language.missingversion", modLoader, modLoaderVersion, languageFileName, mlw.getVersion())));
}
var version = wrapper.version();
if (version == IN_DEV) {
LOGGER.debug(LOADING, "Language Provider {} version is unknown, but {} was requested by {}. Proceeding as this is development environement", language, versionRange, consumer);
return wrapper.service();
}
if (!versionRange.containsVersion(version)) {
LOGGER.error(LOADING, "Missing language {} version {} wanted by {}, found {}", language, versionRange, consumer, version);
var data = new EarlyLoadingException.ExceptionData("fml.language.missingversion", language, versionRange, consumer, version);
throw new EarlyLoadingException("Missing language " + language + " matching range " + versionRange + " found " + version, null, List.of(data));
}
return wrapper.service();
return mlw.getModLanguageProvider();
}
}
@@ -8,19 +8,21 @@ package net.minecraftforge.fml.loading;
import com.mojang.logging.LogUtils;
import org.slf4j.Logger;
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
public class LauncherVersion {
private static final Logger LOGGER = LogUtils.getLogger();
private static final String launcherVersion;
static {
var info = JarVersionLookupHandler.getInfo(LauncherVersion.class);
if (info.impl().version().isEmpty())
throw new IllegalStateException("Failed to find version for package " + LauncherVersion.class.getPackageName() + " This is an invalid environment");
launcherVersion = info.impl().version().get();
LOGGER.debug(LogMarkers.CORE, "Found FMLLauncher version {}", launcherVersion);
String vers = JarVersionLookupHandler.getImplementationVersion(LauncherVersion.class).orElse(System.getenv("LAUNCHER_VERSION"));
if (vers == null) throw new RuntimeException("Missing FMLLauncher version, cannot continue");
launcherVersion = vers;
LOGGER.debug(CORE, "Found FMLLauncher version {}", launcherVersion);
}
public static String getVersion() {
public static String getVersion()
{
return launcherVersion;
}
}
@@ -0,0 +1,53 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import com.mojang.logging.LogUtils;
import org.slf4j.Logger;
import java.nio.file.Files;
import java.nio.file.Path;
public class LibraryFinder {
private static final Logger LOGGER = LogUtils.getLogger();
private static Path libsPath;
static Path findLibsPath() {
if (libsPath == null) {
libsPath = Path.of(System.getProperty("libraryDirectory","crazysnowmannonsense/cheezwhizz"));
if (!Files.isDirectory(libsPath)) {
throw new IllegalStateException("Missing libraryDirectory system property, cannot continue");
}
}
return libsPath;
}
static Path getForgeLibraryPath(final String mcVersion, final String forgeVersion, final String forgeGroup) {
Path forgePath = findLibsPath().resolve(MavenCoordinateResolver.get(forgeGroup, "forge", "", "universal", mcVersion+"-"+forgeVersion));
LOGGER.debug(LogMarkers.CORE, "Found forge path {} is {}", forgePath, pathStatus(forgePath));
return forgePath;
}
static String pathStatus(final Path path) {
return Files.exists(path) ? "present" : "missing";
}
static Path[] getMCPaths(final String mcVersion, final String mcpVersion, final String forgeVersion, final String forgeGroup, final String type) {
Path srgMcPath = findLibsPath().resolve(MavenCoordinateResolver.get("net.minecraft", type, "", "srg", mcVersion+"-"+mcpVersion));
Path mcExtrasPath = findLibsPath().resolve(MavenCoordinateResolver.get("net.minecraft", type, "", "extra", mcVersion+"-"+mcpVersion));
Path patchedBinariesPath = findLibsPath().resolve(MavenCoordinateResolver.get(forgeGroup, "forge", "", type, mcVersion+"-"+forgeVersion));
LOGGER.debug(LogMarkers.CORE,"SRG MC at {} is {}", srgMcPath.toString(), pathStatus(srgMcPath));
LOGGER.debug(LogMarkers.CORE,"MC Extras at {} is {}", mcExtrasPath.toString(), pathStatus(mcExtrasPath));
LOGGER.debug(LogMarkers.CORE,"Forge patches at {} is {}", patchedBinariesPath.toString(), pathStatus(patchedBinariesPath));
return new Path[] { patchedBinariesPath, mcExtrasPath, srgMcPath };
}
public static Path findPathForMaven(final String group, final String artifact, final String extension, final String classifier, final String version) {
return findLibsPath().resolve(MavenCoordinateResolver.get(group, artifact, extension, classifier, version));
}
public static Path findPathForMaven(final String maven) {
return findLibsPath().resolve(MavenCoordinateResolver.get(maven));
}
}
@@ -5,14 +5,12 @@
package net.minecraftforge.fml.loading;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
import net.minecraftforge.fml.loading.moddiscovery.BackgroundScanHandler;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
import net.minecraftforge.forgespi.locating.IModFile;
import org.slf4j.Logger;
import java.net.URL;
import java.nio.file.Files;
@@ -32,7 +30,6 @@ import java.util.stream.Collectors;
*/
public class LoadingModList
{
private static final Logger LOGGER = LogUtils.getLogger();
private static LoadingModList INSTANCE;
private final List<ModFileInfo> modFiles;
private final List<ModInfo> sortedList;
@@ -81,22 +78,9 @@ public class LoadingModList
public void addAccessTransformers()
{
var errors = new ArrayList<EarlyLoadingException.ExceptionData>();
for (ModFileInfo modFile : modFiles) {
ModFile mod = modFile.getFile();
for (Path at : mod.getAccessTransformers()) {
if (!Files.exists(at)) {
var message = "Invalid mod file: " + modFile.getFile().getFileName() + ". Missing Access Transformer: " + at;
errors.add(new EarlyLoadingException.ExceptionData(message));
LOGGER.error(message);
} else {
FMLLoader.addAccessTransformer(at, mod);
}
}
}
if (!errors.isEmpty()) {
preLoadErrors.add(new EarlyLoadingException("Invalid Access Transformers", null, errors));
}
modFiles.stream()
.map(ModFileInfo::getFile)
.forEach(mod -> mod.getAccessTransformer().ifPresent(path -> FMLLoader.addAccessTransformer(path, mod)));
}
public void addForScanning(BackgroundScanHandler backgroundScanHandler)
@@ -36,7 +36,7 @@ public class MCPNamingService implements INameMappingService {
@Override
public Map.Entry<String, String> understanding() {
return Map.entry("srg", "mcp");
return Pair.of("srg", "mcp");
}
@Override
@@ -77,7 +77,7 @@ public class MCPNamingService implements INameMappingService {
}
private static void loadMappings(final String mappingFileName, BiConsumer<String, String> mapStore) {
URL path = MCPNamingService.class.getClassLoader().getResource(mappingFileName);
URL path = ClassLoader.getSystemResource(mappingFileName); //We EXPLICITLY go through the SystemClassLoader here because this is dev-time only. And will be on the root classpath.
if (path == null)
return;
@@ -34,7 +34,7 @@ public class MavenCoordinateResolver {
(!extension.isEmpty() ? "." + extension : ".jar");
String[] groups = groupId.split("\\.");
Path result = Path.of(groups[0]);
Path result = Paths.get(groups[0]);
for (int i = 1; i < groups.length; i++) {
result = result.resolve(groups[i]);
}
@@ -7,11 +7,10 @@ package net.minecraftforge.fml.loading;
import com.mojang.logging.LogUtils;
import cpw.mods.jarhandling.SecureJar;
import cpw.mods.modlauncher.Launcher;
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
import cpw.mods.modlauncher.api.NamedPath;
import cpw.mods.modlauncher.serviceapi.ITransformerDiscoveryService;
import org.jetbrains.annotations.ApiStatus;
import org.slf4j.Logger;
import java.io.IOException;
@@ -20,9 +19,10 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.ServiceLoader;
import java.util.zip.ZipFile;
import java.util.Set;
@ApiStatus.Internal
public class ModDirTransformerDiscoverer implements ITransformerDiscoveryService {
private static final Logger LOGGER = LogUtils.getLogger();
private static final Set<String> SERVICES = Set.of(
@@ -79,15 +79,4 @@ public class ModDirTransformerDiscoverer implements ITransformerDiscoveryService
.filter(SERVICES::contains)
.forEach(s -> found.add(new NamedPath(s, path)));
}
public static boolean isServiceProvider(Path path) {
var jar = SecureJar.from(path);
for (var providers : jar.moduleDataProvider().descriptor().provides()) {
if (SERVICES.contains(providers.service()))
return true;
}
return false;
}
}
@@ -0,0 +1,96 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
import org.slf4j.Logger;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
import java.util.Optional;
import java.util.jar.Manifest;
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
public class ModJarURLHandler extends URLStreamHandler
{
private static final Logger LOGGER = LogUtils.getLogger();
// modjar://modid/path/to/file
@Override
protected URLConnection openConnection(URL url) {
return new ModJarURLConnection(url);
}
@Override
protected int hashCode(final URL u) {
return Objects.hash(u.getHost(), u.getFile());
}
@Override
protected boolean equals(final URL u1, final URL u2) {
return Objects.equals(u1.getProtocol(), u2.getProtocol())
&& Objects.equals(u1.getHost(), u2.getHost())
&& Objects.equals(u1.getFile(), u2.getFile());
}
static class ModJarURLConnection extends URLConnection {
private Path resource;
private String modpath;
private String modid;
private Optional<Manifest> manifest;
public ModJarURLConnection(final URL url) {
super(url);
}
@Override
public void connect()
{
// if (resource == null) {
// modid = url.getHost();
// // trim first char
// modpath = url.getPath().substring(1);
// resource = FMLLoader.getLoadingModList().getModFileById(modid).getFile().findResource(modpath);
// manifest = FMLLoader.getLoadingModList().getModFileById(modid).getManifest();
// }
}
@Override
public InputStream getInputStream() throws IOException
{
connect();
LOGGER.trace(CORE, "Loading modjar URL {} got resource {} {}", url, resource, resource != null ? Files.exists(resource) : "missing");
return Files.newInputStream(resource);
}
@Override
public long getContentLengthLong() {
try {
connect();
return Files.size(resource);
} catch (IOException e) {
return -1L;
}
}
// Used to cache protectiondomains by "top level object" aka the modid
@Override
public URL getURL() {
return LamdbaExceptionUtils.uncheck(()->new URL("modjar://"+modid));
}
public Optional<Manifest> getManifest() {
return manifest;
}
}
}
@@ -8,6 +8,9 @@ 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;
@@ -19,14 +22,10 @@ import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.slf4j.Logger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.jar.Manifest;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -54,16 +53,13 @@ public class ModSorter
ms.buildUniqueList();
} catch (EarlyLoadingException e) {
// We cannot build any list with duped mods. We have to abort immediately and report it
// Note this will never actually throw an error because the duplicate checks are done in ModDiscovererer before we get to this phase
// So all this is really doing is wasting time.
// But i'm leaving it here until I rewrite all of cpw's mod loading code because its such a clusterfuck.
return LoadingModList.of(ms.systemMods, ms.systemMods.stream().map(mf->(ModInfo)mf.getModInfos().get(0)).toList(), e);
return LoadingModList.of(ms.systemMods, ms.systemMods.stream().map(mf->(ModInfo)mf.getModInfos().get(0)).collect(toList()), e);
}
// try and validate dependencies
final List<ExceptionData> failedList = Stream.concat(ms.verifyDependencyVersions().stream(), errors.stream()).toList();
// if we miss one or the other, we abort now
if (!failedList.isEmpty()) {
return LoadingModList.of(ms.systemMods, ms.systemMods.stream().map(mf->(ModInfo)mf.getModInfos().get(0)).toList(), new EarlyLoadingException("failure to validate mod list", null, failedList));
return LoadingModList.of(ms.systemMods, ms.systemMods.stream().map(mf->(ModInfo)mf.getModInfos().get(0)).collect(toList()), new EarlyLoadingException("failure to validate mod list", null, failedList));
} else {
// Otherwise, lets try and sort the modlist and proceed
EarlyLoadingException earlyLoadingException = null;
@@ -76,6 +72,7 @@ public class ModSorter
}
}
@SuppressWarnings("UnstableApiUsage")
private void sort()
{
// lambdas are identity based, so sorting them is impossible unless you hold reference to them
@@ -89,9 +86,9 @@ public class ModSorter
infos.keySet().forEach(graph::addNode);
modFiles.stream()
.map(ModFile::getModInfos)
.flatMap(List::stream)
.<IModInfo>mapMulti(Iterable::forEach)
.map(IModInfo::getDependencies)
.<IModInfo.ModVersion>flatMap(List::stream)
.<IModInfo.ModVersion>mapMulti(Iterable::forEach)
.forEach(dep -> addDependency(graph, dep));
final List<ModFileInfo> sorted;
@@ -107,7 +104,7 @@ public class ModSorter
LOGGER.error(LOADING, "Mod Sorting failed.\nDetected Cycles: {}\n", cycles);
}
var dataList = cycles.stream()
.flatMap(Set::stream)
.<ModFileInfo>mapMulti(Iterable::forEach)
.<IModInfo>mapMulti((mf,c)->mf.getMods().forEach(c))
.map(IModInfo::getModId)
.map(list -> new ExceptionData("fml.modloading.cycle", list))
@@ -116,7 +113,7 @@ public class ModSorter
}
this.sortedList = sorted.stream()
.map(ModFileInfo::getMods)
.flatMap(List::stream)
.<IModInfo>mapMulti(Iterable::forEach)
.map(ModInfo.class::cast)
.collect(toList());
this.modFiles = sorted.stream()
@@ -124,20 +121,18 @@ public class ModSorter
.collect(toList());
}
@SuppressWarnings("UnstableApiUsage")
private void addDependency(MutableGraph<ModFileInfo> topoGraph, IModInfo.ModVersion dep)
{
final ModFileInfo self = (ModFileInfo)dep.getOwner().getOwningFile();
final IModInfo targetModInfo = modIdNameLookup.get(dep.getModId());
// soft dep that doesn't exist. Just return. No edge required.
if (targetModInfo == null || !(targetModInfo.getOwningFile() instanceof final ModFileInfo target)) return;
final ModFileInfo self = (ModFileInfo)dep.getOwner().getOwningFile();
if (self == target)
return; // in case a jar has two mods that have dependencies between
switch (dep.getOrdering()) {
case BEFORE -> topoGraph.putEdge(self, target);
case AFTER -> topoGraph.putEdge(target, self);
default -> {}
}
}
@@ -157,17 +152,31 @@ public class ModSorter
));
}
private void detectSystemMods(final Map<String, List<ModFile>> modFilesByFirstId) {
private void detectSystemMods(final Map<String, List<ModFile>> modFilesByFirstId)
{
// Capture system mods (ex. MC, Forge) here, so we can keep them for later
var systemMods = List.of("minecraft", "forge");
final Set<String> systemMods = new HashSet<>();
// The minecraft mod is always a system mod
systemMods.add("minecraft");
// Find mod file from MinecraftLocator to define the system mods
modFiles.stream()
.filter(modFile -> modFile.getProvider().getClass() == MinecraftLocator.class)
.map(ModFile::getSecureJar)
.map(SecureJar::moduleDataProvider)
.map(SecureJar.ModuleDataProvider::getManifest)
.map(Manifest::getMainAttributes)
.map(mf -> mf.getValue("FML-System-Mods"))
.filter(Objects::nonNull)
.findFirst()
.ifPresent(value -> systemMods.addAll(Arrays.asList(value.split(","))));
LOGGER.debug("Configured system mods: {}", systemMods);
this.systemMods = new ArrayList<>();
for (var systemMod : systemMods) {
for (String systemMod : systemMods) {
var container = modFilesByFirstId.get(systemMod);
if (container != null && !container.isEmpty()) {
LOGGER.debug("Found system mod: {}", systemMod);
this.systemMods.add(container.getFirst());
this.systemMods.add((ModFile) container.get(0));
} else {
throw new IllegalStateException("Failed to find system mod: " + systemMod);
}
@@ -178,23 +187,23 @@ public class ModSorter
{
final var modVersions = modFiles.stream()
.map(ModFile::getModInfos)
.flatMap(List::stream)
.<IModInfo>mapMulti(Iterable::forEach)
.collect(toMap(IModInfo::getModId, IModInfo::getVersion));
final var modVersionDependencies = modFiles.stream()
.map(ModFile::getModInfos)
.flatMap(List::stream)
.<IModInfo>mapMulti(Iterable::forEach)
.collect(groupingBy(Function.identity(), flatMapping(e -> e.getDependencies().stream(), toList())));
final var modRequirements = modVersionDependencies.values().stream()
.<IModInfo.ModVersion>flatMap(List::stream)
.<IModInfo.ModVersion>mapMulti(Iterable::forEach)
.filter(mv -> mv.getSide().isCorrectSide())
.collect(toSet());
final long mandatoryRequired = modRequirements.stream().filter(IModInfo.ModVersion::isMandatory).count();
LOGGER.debug(LOADING, "Found {} mod requirements ({} mandatory, {} optional)", modRequirements.size(), mandatoryRequired, modRequirements.size() - mandatoryRequired);
final var missingVersions = modRequirements.stream()
.filter(mv -> (mv.isMandatory() || modVersions.containsKey(mv.getModId())) && !modVersionContained(mv, modVersions))
.filter(mv -> (mv.isMandatory() || modVersions.containsKey(mv.getModId())) && this.modVersionNotContained(mv, modVersions))
.collect(toSet());
final long mandatoryMissing = missingVersions.stream().filter(IModInfo.ModVersion::isMandatory).count();
LOGGER.debug(LOADING, "Found {} mod requirements missing ({} mandatory, {} optional)", missingVersions.size(), mandatoryMissing, missingVersions.size() - mandatoryMissing);
@@ -242,13 +251,9 @@ public class ModSorter
);
}
private static boolean modVersionContained(IModInfo.ModVersion mv, Map<String, ArtifactVersion> modVersions) {
var modId = mv.getModId();
var range = mv.getVersionRange();
if (modVersions.containsKey(modId)
&& (range.containsVersion(modVersions.get(modId)) || modVersions.get(modId).toString().equals("0.0NONE")))
return true;
return VersionSupportMatrix.testVersionSupportMatrix(mv.getVersionRange(), mv.getModId(), "mod");
private boolean modVersionNotContained(final IModInfo.ModVersion mv, final Map<String, ArtifactVersion> modVersions)
{
return !(VersionSupportMatrix.testVersionSupportMatrix(mv.getVersionRange(), mv.getModId(), "mod", (modId, range) -> modVersions.containsKey(modId) &&
(range.containsVersion(modVersions.get(modId)) || modVersions.get(modId).toString().equals("0.0NONE"))));
}
}
@@ -8,8 +8,14 @@ package net.minecraftforge.fml.loading;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import com.google.common.collect.Streams;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
import net.minecraftforge.api.distmarker.Dist;
@@ -22,177 +28,138 @@ import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.MethodNode;
import org.slf4j.Logger;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
public final class RuntimeDistCleaner implements ILaunchPluginService {
public class RuntimeDistCleaner implements ILaunchPluginService
{
private static final Logger LOGGER = LogUtils.getLogger();
private static final Marker DISTXFORM = MarkerFactory.getMarker("DISTXFORM");
private static String DIST;
private static final String ONLYIN = Type.getDescriptor(OnlyIn.class);
private static final String ONLYINS = Type.getDescriptor(OnlyIns.class);
private static final String MOD = "Lnet/minecraftforge/fml/common/Mod;";
@Override
public String name() {
public String name()
{
return "runtimedistcleaner";
}
@Override
public int processClassWithFlags(final Phase phase, final ClassNode classNode, final Type classType, final String reason) {
var changed = false;
var annotations = unpack(classNode.visibleAnnotations);
if (remove(annotations, DIST)) {
public int processClassWithFlags(final Phase phase, final ClassNode classNode, final Type classType, final String reason)
{
AtomicBoolean changes = new AtomicBoolean();
if (remove(classNode.visibleAnnotations, DIST))
{
LOGGER.error(DISTXFORM, "Attempted to load class {} for invalid dist {}", classNode.name, DIST);
throw new RuntimeException("Attempted to load class "+ classNode.name + " for invalid dist "+ DIST);
}
if (!FMLEnvironment.production && !annotations.isEmpty() && hasModAnnotation(classNode.visibleAnnotations)) {
LOGGER.error(DISTXFORM, "Attempted to load class {} with @Mod and @OnlyIn/@OnlyIns annotations", classNode.name);
throw new RuntimeException("Found @OnlyIn on @Mod class "+ classNode.name + " - this is not allowed as it causes crashes. Remove the OnlyIn and consider setting clientSideOnly=true in the root of your mods.toml instead");
}
if (classNode.interfaces != null && !classNode.interfaces.isEmpty()) {
for (var ann : annotations) {
if (ann.intf == null || DIST.equals(ann.side))
continue;
if (classNode.interfaces.remove(ann.intf)) {
LOGGER.debug(DISTXFORM,"Removing Interface: {} implements {}", classNode.name, ann.intf);
changed = true;
}
}
if (classNode.interfaces != null )
{
unpack(classNode.visibleAnnotations).stream()
.filter(ann->Objects.equals(ann.desc, ONLYIN))
.filter(ann->ann.values.indexOf("_interface") != -1)
.filter(ann->!Objects.equals(((String[])ann.values.get(ann.values.indexOf("value") + 1))[1], DIST))
.map(ann -> ((Type)ann.values.get(ann.values.indexOf("_interface") + 1)).getInternalName())
.forEach(intf -> {
if (classNode.interfaces.remove(intf)) {
LOGGER.debug(DISTXFORM,"Removing Interface: {} implements {}", classNode.name, intf);
changes.compareAndSet(false, true);
}
});
//Remove Class level @OnlyIn/@OnlyIns annotations, this is important if anyone gets ambitious and tries to reflect an annotation with _interface set.
if (classNode.visibleAnnotations != null) {
for (var itr = classNode.visibleAnnotations.iterator(); itr.hasNext(); ) {
var ann = itr.next();
if (ONLYIN.equals(ann.desc) || ONLYINS.equals(ann.desc)) {
LOGGER.debug(DISTXFORM, "Removing Class Annotation: {} @{}", classNode.name, ann.desc);
Iterator<AnnotationNode> itr = classNode.visibleAnnotations.iterator();
while (itr.hasNext()) {
AnnotationNode ann = itr.next();
if (Objects.equals(ann.desc, ONLYIN) || Objects.equals(ann.desc, ONLYINS)) {
LOGGER.debug(DISTXFORM,"Removing Class Annotation: {} @{}", classNode.name, ann.desc);
itr.remove();
changed = true;
changes.compareAndSet(false, true);
}
}
}
}
for (var itr = classNode.fields.iterator(); itr.hasNext(); ) {
var field = itr.next();
if (remove(unpack(field.visibleAnnotations), DIST)) {
LOGGER.debug(DISTXFORM, "Removing field: {}.{}", classNode.name, field.name);
itr.remove();
changed = true;
Iterator<FieldNode> fields = classNode.fields.iterator();
while(fields.hasNext())
{
FieldNode field = fields.next();
if (remove(field.visibleAnnotations, DIST))
{
LOGGER.debug(DISTXFORM,"Removing field: {}.{}", classNode.name, field.name);
fields.remove();
changes.compareAndSet(false, true);
}
}
var lambdaGatherer = new LambdaGatherer();
for (var itr = classNode.methods.iterator(); itr.hasNext(); ) {
var method = itr.next();
if (remove(unpack(method.visibleAnnotations), DIST)) {
LOGGER.debug(DISTXFORM, "Removing method: {}.{}{}", classNode.name, method.name, method.desc);
itr.remove();
LambdaGatherer lambdaGatherer = new LambdaGatherer();
Iterator<MethodNode> methods = classNode.methods.iterator();
while(methods.hasNext())
{
MethodNode method = methods.next();
if (remove(method.visibleAnnotations, DIST))
{
LOGGER.debug(DISTXFORM,"Removing method: {}.{}{}", classNode.name, method.name, method.desc);
methods.remove();
lambdaGatherer.accept(method);
changed = true;
changes.compareAndSet(false, true);
}
}
// remove dynamic synthetic lambda methods that are inside of removed methods
for (List<Handle> handles = lambdaGatherer.getDynamicLambdaHandles();
!handles.isEmpty(); handles = lambdaGatherer.getDynamicLambdaHandles()) {
for (List<Handle> dynamicLambdaHandles = lambdaGatherer.getDynamicLambdaHandles();
!dynamicLambdaHandles.isEmpty(); dynamicLambdaHandles = lambdaGatherer.getDynamicLambdaHandles())
{
lambdaGatherer = new LambdaGatherer();
for (var itr = classNode.methods.iterator(); itr.hasNext(); ) {
MethodNode method = itr.next();
if ((method.access & Opcodes.ACC_SYNTHETIC) == 0)
continue;
for (var handle : handles) {
if (method.name.equals(handle.getName()) && method.desc.equals(handle.getDesc())) {
LOGGER.debug(DISTXFORM, "Removing lambda method: {}.{}{}", classNode.name, method.name, method.desc);
itr.remove();
methods = classNode.methods.iterator();
while (methods.hasNext())
{
MethodNode method = methods.next();
if ((method.access & Opcodes.ACC_SYNTHETIC) == 0) continue;
for (Handle dynamicLambdaHandle : dynamicLambdaHandles)
{
if (method.name.equals(dynamicLambdaHandle.getName()) && method.desc.equals(dynamicLambdaHandle.getDesc()))
{
LOGGER.debug(DISTXFORM,"Removing lambda method: {}.{}{}", classNode.name, method.name, method.desc);
methods.remove();
lambdaGatherer.accept(method);
changed = true;
changes.compareAndSet(false, true);
}
}
}
}
return changed ? ComputeFlags.SIMPLE_REWRITE : ComputeFlags.NO_REWRITE;
return changes.get() ? ComputeFlags.SIMPLE_REWRITE : ComputeFlags.NO_REWRITE;
}
private static record Target(String side, String intf) {
static Target from(final AnnotationNode node) {
var idx = node.values.indexOf("value");
var value = (String[])node.values.get(idx + 1); // Enums are stored as [Type, Value]
idx = node.values.indexOf("_interface");
if (idx == -1)
return new Target(value[1], null);
var intf = (Type)node.values.get(idx + 1);
return new Target(value[1], intf.getInternalName());
}
}
/**
* Unpack any OnlyIn or OnlyIns annotations to Target objects.
*/
@SuppressWarnings("unchecked")
private static List<Target> unpack(final List<AnnotationNode> anns) {
if (anns == null || anns.isEmpty())
return Collections.emptyList();
var ret = new ArrayList<Target>();
for (var node : anns) {
if (ONLYIN.equals(node.desc))
ret.add(Target.from(node));
else if (ONLYINS.equals(node.desc)) {
if (node.values == null || node.values.isEmpty())
continue;
int idx = node.values.indexOf("value");
if (idx == -1)
continue;
var subNodes = (List<AnnotationNode>)node.values.get(idx + 1);
if (subNodes != null) {
for (var sub : subNodes)
ret.add(Target.from(sub));
}
}
}
private static List<AnnotationNode> unpack(final List<AnnotationNode> anns) {
if (anns == null) return Collections.emptyList();
List<AnnotationNode> ret = anns.stream().filter(ann->Objects.equals(ann.desc, ONLYIN)).collect(Collectors.toList());
anns.stream().filter(ann->Objects.equals(ann.desc, ONLYINS) && ann.values != null)
.map( ann -> (List<AnnotationNode>)ann.values.get(ann.values.indexOf("value") + 1))
.filter(v -> v != null)
.forEach(v -> v.forEach(ret::add));
return ret;
}
private static boolean remove(final List<Target> targets, final String side) {
for (var target : targets) {
if (target.intf == null && !side.equals(target.side))
return true;
}
return false;
}
private static boolean hasModAnnotation(final List<AnnotationNode> anns) {
if (anns == null || anns.isEmpty())
return false;
for (var ann : anns) {
if (ann.desc.equals(MOD))
return true;
}
return false;
private boolean remove(final List<AnnotationNode> anns, final String side)
{
return unpack(anns).stream().
filter(ann->Objects.equals(ann.desc, ONLYIN)).
filter(ann->ann.values.indexOf("_interface") == -1).
anyMatch(ann -> !Objects.equals(((String[])ann.values.get(ann.values.indexOf("value")+1))[1], side));
}
@SuppressWarnings("unchecked")
@Override
public Consumer<Dist> getExtension() {
return s -> {
public Consumer<Dist> getExtension()
{
return (s)-> {
DIST = s.name();
LOGGER.debug(DISTXFORM, "Configuring for Dist {}", DIST);
};
@@ -202,19 +169,9 @@ public final class RuntimeDistCleaner implements ILaunchPluginService {
private static final EnumSet<Phase> NAY = EnumSet.noneOf(Phase.class);
@Override
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty) {
if (isEmpty)
return NAY;
String internalName = classType.getInternalName();
if (internalName.startsWith("net/minecraftforge/"))
return NAY;
// No Vanilla classes use @OnlyIn(Dist.SERVER), so we can skip them entirely when we're running on a client
if (FMLEnvironment.dist.isClient() && (internalName.startsWith("net/minecraft/") || internalName.startsWith("com/mojang/")))
return NAY;
return YAY;
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty)
{
return isEmpty ? NAY : YAY;
}
private static class LambdaGatherer extends MethodVisitor {
@@ -229,21 +186,23 @@ public final class RuntimeDistCleaner implements ILaunchPluginService {
}
public void accept(MethodNode method) {
for (var insn : method.instructions) {
if (insn.getType() == AbstractInsnNode.INVOKE_DYNAMIC_INSN)
insn.accept(this);
}
Streams.stream(method.instructions.iterator()).
filter(insnNode->insnNode.getType() == AbstractInsnNode.INVOKE_DYNAMIC_INSN).
forEach(insnNode->insnNode.accept(this));
}
@Override
public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
if (META_FACTORY.equals(bsm)) {
var handle = (Handle)bsmArgs[1];
dynamicLambdaHandles.add(handle);
public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs)
{
if (META_FACTORY.equals(bsm))
{
Handle dynamicLambdaHandle = (Handle) bsmArgs[1];
dynamicLambdaHandles.add(dynamicLambdaHandle);
}
}
public List<Handle> getDynamicLambdaHandles() {
public List<Handle> getDynamicLambdaHandles()
{
return dynamicLambdaHandles;
}
}
@@ -5,18 +5,19 @@
package net.minecraftforge.fml.loading;
import com.google.common.collect.ImmutableMap;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import java.util.Map;
import org.apache.commons.lang3.text.StrLookup;
import org.apache.commons.lang3.text.StrSubstitutor;
import java.util.Map;
@SuppressWarnings("deprecation")
public class StringSubstitutor {
private static final Map<String, String> GLOBALS = Map.of(
"mcVersion", FMLLoader.versionInfo().mcVersion(),
"forgeVersion", FMLLoader.versionInfo().forgeVersion()
public class StringSubstitutor
{
private static final Map<String,String> globals = ImmutableMap.of(
"mcVersion", FMLLoader.versionInfo().mcVersion(),
"forgeVersion", FMLLoader.versionInfo().forgeVersion()
);
public static String replace(final String in, final ModFile file) {
@@ -24,19 +25,22 @@ public class StringSubstitutor {
}
private static StrLookup<String> getStringLookup(final ModFile file) {
return new StrLookup<>() {
return new StrLookup<String>()
{
@Override
public String lookup(String key) {
var parts = key.split("\\.");
if (parts.length == 1)
return key;
var pfx = parts[0];
public String lookup(String key)
{
final String[] parts = key.split("\\.");
if (parts.length == 1) return key;
final String pfx = parts[0];
if ("global".equals(pfx))
return GLOBALS.get(parts[1]);
{
return globals.get(parts[1]);
}
else if ("file".equals(pfx) && file != null)
{
return String.valueOf(file.getSubstitutionMap().get().get(parts[1]));
}
return key;
}
};
@@ -47,12 +47,12 @@ public class UniqueModListBuilder
// Select the newest by artifact version sorting of non-unique files thus identified
uniqueModList = modFilesByFirstId.entrySet().stream()
.map(UniqueModListBuilder::selectNewestModInfo)
.map(this::selectNewestModInfo)
.toList();
// Select the newest by artifact version sorting of non-unique files thus identified
uniqueLibListWithVersion = libFilesWithVersionByModuleName.entrySet().stream()
.map(UniqueModListBuilder::selectNewestModInfo)
.map(this::selectNewestModInfo)
.toList();
// Transform to the full mod id list
@@ -75,7 +75,7 @@ public class UniqueModListBuilder
final List<String> dupedModErrors = modIds.values().stream()
.filter(modInfos -> modInfos.size() > 1)
.map(mods -> String.format("\tMod ID: '%s' from mod files: %s",
mods.getFirst().getModId(),
mods.get(0).getModId(),
mods.stream()
.map(modInfo -> modInfo.getOwningFile().getFile().getFileName()).collect(joining(", "))
)).toList();
@@ -91,8 +91,9 @@ public class UniqueModListBuilder
final List<String> dupedLibErrors = versionedLibIds.values().stream()
.filter(modFiles -> modFiles.size() > 1)
.map(mods -> String.format("\tLibrary: '%s' from files: %s",
getModId(mods.getFirst()),
mods.stream().map(ModFile::getFileName).collect(joining(", "))
getModId(mods.get(0)),
mods.stream()
.map(modFile -> modFile.getFileName()).collect(joining(", "))
)).toList();
if (!dupedLibErrors.isEmpty()) {
@@ -106,23 +107,25 @@ public class UniqueModListBuilder
final Map<String, List<ModFile>> uniqueModFilesByFirstId = uniqueModList.stream()
.collect(groupingBy(UniqueModListBuilder::getModId));
final List<ModFile> loadedList = new ArrayList<>(uniqueModList);
final List<ModFile> loadedList = new ArrayList<>();
loadedList.addAll(uniqueModList);
loadedList.addAll(uniqueLibListWithVersion);
return new UniqueModListData(loadedList, uniqueModFilesByFirstId);
}
private static ModFile selectNewestModInfo(Map.Entry<String, List<ModFile>> fullList) {
private ModFile selectNewestModInfo(Map.Entry<String, List<ModFile>> fullList) {
List<ModFile> modInfoList = fullList.getValue();
if (modInfoList.size() > 1) {
LOGGER.debug("Found {} mods for first modid {}, selecting most recent based on version data", modInfoList.size(), fullList.getKey());
modInfoList.sort(Comparator.comparing(UniqueModListBuilder::getVersion).reversed());
LOGGER.debug("Selected file {} for modid {} with version {}", modInfoList.getFirst().getFileName(), fullList.getKey(), getVersion(modInfoList.getFirst()));
modInfoList.sort(Comparator.comparing(this::getVersion).reversed());
LOGGER.debug("Selected file {} for modid {} with version {}", modInfoList.get(0).getFileName(), fullList.getKey(), this.getVersion(modInfoList.get(0)));
}
return modInfoList.get(0);
}
private static ArtifactVersion getVersion(final ModFile mf) {
private ArtifactVersion getVersion(final ModFile mf)
{
if (mf.getModFileInfo() == null || mf.getModInfos() == null || mf.getModInfos().isEmpty()) {
return mf.getJarVersion();
}
@@ -135,7 +138,7 @@ public class UniqueModListBuilder
return modFile.getSecureJar().name();
}
return modFile.getModFileInfo().getMods().get(0).getModId();
return modFile.getModFileInfo().moduleName();
}
public record UniqueModListData(List<ModFile> modFiles, Map<String, List<ModFile>> modFilesByFirstId) {}
@@ -5,45 +5,18 @@
package net.minecraftforge.fml.loading;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Map;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
/** TODO: [FML][Loader] Remove VersionInfo - we shouldn't need to base any decisions on forge or mc version info at the loader level. So once I get a proper replacement i'm gunna kill this. */
public record VersionInfo(String forgeVersion, String mcVersion, String mcpVersion, String forgeGroup) {
VersionInfo(Map<String, ?> arguments) {
this((String) arguments.get("forgeVersion"), (String) arguments.get("mcVersion"), (String) arguments.get("mcpVersion"), (String) arguments.get("forgeGroup"));
}
public String mcAndForgeVersion() {
return mcVersion + "-" + forgeVersion;
return mcVersion+"-"+forgeVersion;
}
public String mcAndMCPVersion() {
return mcVersion + "-" + mcpVersion;
}
/*==========================================================================*
* INTERNAL SHIT *
*==========================================================================*/
private static final Gson GSON = new GsonBuilder().create();
private static <T> T readJson(String path, Class<T> type) {
try (var is = VersionInfo.class.getResourceAsStream(path)) {
if (is == null)
return null;
try (var reader = new InputStreamReader(is)) {
return GSON.fromJson(reader, type);
}
} catch (IOException | JsonSyntaxException e) {
throw new IllegalStateException("Failed to parse version info from " + path , e);
}
}
static VersionInfo detect() {
var forge = readJson("/forge_version.json", JSON.class);
if (forge == null)
throw new IllegalStateException("Failed to find /forge_version.json This is a critical issue");
return new VersionInfo(forge.forge(), forge.mc(), forge.mcp(), "net.minecraftforge");
}
private record JSON(String forge, String mcp, String mc) {}
}
@@ -5,53 +5,33 @@
package net.minecraftforge.fml.loading;
import net.minecraftforge.forgespi.language.MavenVersionAdapter;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.artifact.versioning.VersionRange;
import org.jetbrains.annotations.ApiStatus;
import java.util.Collections;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiPredicate;
@ApiStatus.Internal // since 1.21.1, will be made non-public in a later MC version
public class VersionSupportMatrix {
private static final Map<String, List<ArtifactVersion>> OVERRIDE_VERSIONS;
private static final HashMap<String, List<ArtifactVersion>> overrideVersions = new HashMap<>();
static {
if ("1.21.1".equals(FMLLoader.versionInfo().mcVersion())) {
OVERRIDE_VERSIONS = Map.ofEntries(
// 1.21.1 is compatible with 1.21
entry("languageloader.javafml", "51"),
entry("mod.minecraft", "1.21"),
entry("mod.forge", "51.0.33")
);
} else {
OVERRIDE_VERSIONS = Collections.emptyMap();
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");
}
}
/**
* @deprecated Use {@link #testVersionSupportMatrix(VersionRange, String, String)} instead, unwrapping your BiPredicate.
*/
@Deprecated(forRemoval = true, since = "1.21.1")
public static boolean testVersionSupportMatrix(VersionRange declaredRange, String lookupId, String type, BiPredicate<String, VersionRange> standardLookup) {
private static void add(String key, String value) {
overrideVersions.computeIfAbsent(key, k -> new ArrayList<>()).add(new DefaultArtifactVersion(value));
}
public static <T> boolean testVersionSupportMatrix(VersionRange declaredRange, String lookupId, String type, BiPredicate<String, VersionRange> standardLookup) {
if (standardLookup.test(lookupId, declaredRange)) return true;
return testVersionSupportMatrix(declaredRange, lookupId, type);
}
public static boolean testVersionSupportMatrix(VersionRange declaredRange, String lookupId, String type) {
if (OVERRIDE_VERSIONS.isEmpty()) return false;
List<ArtifactVersion> custom = OVERRIDE_VERSIONS.get(type + "." + lookupId);
return custom != null && custom.stream().anyMatch(declaredRange::containsVersion);
}
private static Map.Entry<String, List<ArtifactVersion>> entry(String typeAndLookupId, String declaredRange) {
return Map.entry(typeAndLookupId, List.of(new DefaultArtifactVersion(declaredRange)));
}
private static Map.Entry<String, List<ArtifactVersion>> entry(String typeAndLookupId, List<String> declaredRanges) {
return Map.entry(typeAndLookupId, declaredRanges.stream().map(DefaultArtifactVersion::new).map(it -> (ArtifactVersion) it).toList());
List<ArtifactVersion> custom = overrideVersions.get(type +"." +lookupId);
return custom == null ? false : custom.stream().anyMatch(declaredRange::containsVersion);
}
}
@@ -49,7 +49,10 @@ public class ForgeHighlight {
LOGGER.warn("Failed to invoke initializeTerminal on TCA", e);
}
if (!TerminalConsoleAppender.isAnsiSupported() && Arrays.stream(options).noneMatch(s -> s.equals("disableAnsi=true"))) {
options = new String[] { options[0], "disableAnsi=true" };
List<String> optionList = new ArrayList<>();
optionList.add(options[0]);
optionList.add("disableAnsi=true");
options = optionList.toArray(new String[0]);
}
return HighlightConverter.newInstance(config, options);
}
@@ -0,0 +1,52 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.moddiscovery;
import com.mojang.logging.LogUtils;
import net.minecraftforge.forgespi.locating.IDependencyLocator;
import net.minecraftforge.forgespi.locating.IModFile;
import net.minecraftforge.forgespi.locating.ModFileLoadingException;
import org.slf4j.Logger;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
public abstract class AbstractJarFileDependencyLocator extends AbstractJarFileModProvider implements IDependencyLocator
{
private static final Logger LOGGER = LogUtils.getLogger();
protected Optional<InputStream> loadResourceFromModFile(final IModFile modFile, final Path path) {
try {
return Optional.of(Files.newInputStream(modFile.findResource(path.toString())));
}
catch (final FileNotFoundException e) {
LOGGER.debug("Failed to load resource {} from {}, it does not contain dependency information.", path, modFile.getFileName());
return Optional.empty();
}
catch (final Exception e) {
LOGGER.error("Failed to load resource {} from mod {}, cause {}", path, modFile.getFileName(), e);
return Optional.empty();
}
}
protected Optional<IModFile> loadModFileFrom(final IModFile file, final Path path) {
try {
final Path pathInModFile = file.findResource(path.toString());
return Optional.of(createMod(pathInModFile).file());
}
catch (Exception e) {
LOGGER.error("Failed to load mod file {} from {}", path, file.getFileName());
throw new ModFileLoadingException("Failed to load mod file "+file.getFileName());
}
}
protected String identifyMod(final IModFile modFile) {
return modFile.getFileName();
}
}
@@ -0,0 +1,23 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.moddiscovery;
import net.minecraftforge.forgespi.locating.IModLocator;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Stream;
public abstract class AbstractJarFileModLocator extends AbstractJarFileModProvider implements IModLocator
{
@Override
public List<IModLocator.ModFileOrException> scanMods()
{
return scanCandidates().map(this::createMod).toList();
}
public abstract Stream<Path> scanCandidates();
}
@@ -0,0 +1,36 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.moddiscovery;
import com.mojang.logging.LogUtils;
import cpw.mods.jarhandling.SecureJar;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.forgespi.locating.IModFile;
import org.slf4j.Logger;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream;
public abstract class AbstractJarFileModProvider extends AbstractModProvider
{
private static final Logger LOGGER = LogUtils.getLogger();
@Override
public void scanFile(final IModFile file, final Consumer<Path> pathConsumer) {
LOGGER.debug(LogMarkers.SCAN,"Scan started: {}", file);
final Function<Path, SecureJar.Status> status = p->file.getSecureJar().verifyPath(p);
try (Stream<Path> files = Files.find(file.getSecureJar().getRootPath(), Integer.MAX_VALUE, (p, a) -> p.getNameCount() > 0 && p.getFileName().toString().endsWith(".class"))) {
file.setSecurityStatus(files.peek(pathConsumer).map(status).reduce((s1, s2)-> SecureJar.Status.values()[Math.min(s1.ordinal(), s2.ordinal())]).orElse(SecureJar.Status.INVALID));
} catch (IOException e) {
e.printStackTrace();
}
LOGGER.debug(LogMarkers.SCAN,"Scan finished: {}", file);
}
}
@@ -6,7 +6,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;
@@ -17,130 +16,55 @@ 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.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import java.io.IOException;
import java.lang.module.InvalidModuleDescriptorException;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleDescriptor.Version;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
import java.util.function.Function;
import java.util.jar.Manifest;
@ApiStatus.Internal
public abstract class AbstractModProvider implements IModProvider {
private static final Logger LOGGER = LogUtils.getLogger();
protected static final String MODS_TOML = "META-INF/mods.toml";
protected static final String MODULE_INFO = "module-info.class";
public abstract class AbstractModProvider implements IModProvider
{
private static final Logger LOGGER = LogUtils.getLogger();
protected static final String MODS_TOML = "META-INF/mods.toml";
protected static final String MANIFEST = "META-INF/MANIFEST.MF";
protected IModLocator.ModFileOrException createMod(Path path) {
return createMod(path, false);
}
@Nullable
protected IModLocator.ModFileOrException createMod(Path path, boolean ignoreUnknown) {
return createMod(path, ignoreUnknown, getDefaultJarModType());
}
@Nullable
protected IModLocator.ModFileOrException createMod(Path path, boolean ignoreUnknown, String defaultType) {
protected IModLocator.ModFileOrException createMod(Path... path) {
var mjm = new ModJarMetadata();
SecureJar sj = null;
try {
sj = SecureJar.from(jar -> loadMetaFromJar(jar, mjm), path);
} catch (Throwable t) {
return new IModLocator.ModFileOrException(null, new ModFileLoadingException("Failed to create secure jar for \"" + path + "\" - " + t.getMessage()));
}
var sj = SecureJar.from(
Manifest::new,
jar -> jar.moduleDataProvider().findFile(MODS_TOML).isPresent() ? mjm : JarMetadata.from(jar, path),
(root, p) -> true,
path
);
IModFile mod;
var type = sj.moduleDataProvider().getManifest().getMainAttributes().getValue(ModFile.TYPE);
if (type == null)
type = defaultType;
if (type == null) {
type = getDefaultJarModType();
}
if (sj.moduleDataProvider().findFile(MODS_TOML).isPresent()) {
LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", MODS_TOML, type, path);
mod = new ModFile(sj, this, ModFileParser::modsTomlParser);
// ModJarMetadata is only used when loading mods.toml mods as ModJarMetadata
mjm.setModFile(mod);
if (mod.getModFileInfo().getFileProperties().containsKey(ModFileInfo.NOT_A_FORGE_MOD_PROP)) {
LOGGER.error(LogMarkers.SCAN, "Unable to load file \"{}\" because its mods.toml is requesting an invalid javafml loaderVersion (use \"*\" if you want to allow all versions) and is missing a forge modId dependency declaration (see the sample mods.toml in the MDK).", path);
return new IModLocator.ModFileOrException(null, new ModFileLoadingException("File \"%s\" is not a Forge mod and cannot be loaded. Look for a Forge version of this mod or consider alternative mods.".formatted(mod.getFileName())));
}
} else if (type != null) {
LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", JarFile.MANIFEST_NAME, type, path);
LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", MANIFEST, type, path);
mod = new ModFile(sj, this, this::manifestParser, type);
} else if (ignoreUnknown) {
return null;
} else
return new IModLocator.ModFileOrException(null, new ModFileLoadingException("Invalid mod file found " + path));
} else {
return new IModLocator.ModFileOrException(null, new ModFileLoadingException("Invalid mod file found "+ Arrays.toString(path)));
}
mjm.setModFile(mod);
return new IModLocator.ModFileOrException(mod, null);
}
private static JarMetadata loadMetaFromJar(SecureJar jar, ModJarMetadata mjm) {
var info = jar.moduleDataProvider().open(MODULE_INFO).orElse(null);
if (info != null) {
try {
var desc = ModuleDescriptor.read(info, jar::getPackages);
var all = new HashSet<>(jar.getPackages());
all.removeAll(desc.packages());
if (!all.isEmpty()) {
var missing = all.stream().sorted().collect(Collectors.joining(", "));
LOGGER.error("Invalid module-info, missing packages " + missing);
throw new ModFileLoadingException("Invalid module-info, missing packages " + missing);
} else {
return new JarMetadata() {
@Override
public String name() {
return desc.name();
}
@Override
public String version() {
return desc.version().map(Version::toString).or(desc::rawVersion).orElse(null);
}
@Override
public ModuleDescriptor descriptor() {
return desc;
}
};
}
} catch (InvalidModuleDescriptorException | IOException e) {
LOGGER.error("Failed to parse " + jar.getPrimaryPath() + " module-info", e);
throw new ModFileLoadingException("Invalid module-info: " + e.getMessage());
} finally {
try {
info.close();
} catch (IOException e) {}
}
}
if (jar.moduleDataProvider().findFile(MODS_TOML).isEmpty())
return JarMetadata.from(jar, jar.getPrimaryPath());
return mjm;
}
protected IModFileInfo manifestParser(final IModFile mod) {
var mf = mod.getSecureJar().moduleDataProvider().getManifest().getMainAttributes();
var license = mf.getValue("LICENSE");
Function<String, Optional<String>> cfg = name -> Optional.ofNullable(mod.getSecureJar().moduleDataProvider().getManifest().getMainAttributes().getValue(name));
var license = cfg.apply("LICENSE").orElse("");
var dummy = new IConfigurable() {
@Override
public <T> Optional<T> getConfigElement(String key) {
return Optional.empty();
}
@Override
public <T> Optional<T> getConfigElement(String... key) {
return Optional.empty();
@@ -151,7 +75,7 @@ public abstract class AbstractModProvider implements IModProvider {
}
};
return new DefaultModFileInfo(mod, license == null ? "" : license, dummy);
return new DefaultModFileInfo(mod, license, dummy);
}
@Override
@@ -159,66 +83,41 @@ public abstract class AbstractModProvider implements IModProvider {
return true;
}
@Override
public void initArguments(final Map<String, ?> arguments) {
}
protected String getDefaultJarModType() {
return null;
}
@Override
public void scanFile(IModFile file, Consumer<Path> pathConsumer) {
LOGGER.debug(LogMarkers.SCAN, "Scan started: {}", file);
var jar = file.getSecureJar();
var root = jar.getRootPath();
Consumer<Path> consumer = pathConsumer;
//TODO: [SecureJar] Actually redesign this to have a proper validation.
var holder = new Holder<SecureJar.Status>();
holder.value = SecureJar.Status.NONE;
if (jar.hasSecurityData()) {
consumer = path -> {
pathConsumer.accept(path);
var status = jar.verifyPath(path);
if (status.ordinal() < holder.value.ordinal())
holder.value = status;
};
}
try (var files = Files.walk(root)) {
files
.filter(p -> p.toString().endsWith(".class"))
.forEach(consumer);
file.setSecurityStatus(holder.value);
} catch (IOException e) {
e.printStackTrace();
}
LOGGER.debug(LogMarkers.SCAN, "Scan finished: {}", file);
}
private static final class Holder<T> {
private T value;
}
private record DefaultModFileInfo(IModFile mod, String license, IConfigurable configurable) implements IModFileInfo, IConfigurable {
@Override public <T> Optional<T> getConfigElement(final String string) { return Optional.empty(); }
@Override public <T> Optional<T> getConfigElement(final String... strings) { return Optional.empty(); }
@Override public List<? extends IConfigurable> getConfigList(final String... strings) { return null; }
@Override
public <T> Optional<T> getConfigElement(final String... strings)
{
return Optional.empty();
}
@Override
public List<? extends IConfigurable> getConfigList(final String... strings)
{
return null;
}
@Override public List<IModInfo> getMods() { return Collections.emptyList(); }
@Override public List<LanguageSpec> requiredLanguageLoaders() { return Collections.emptyList(); }
@Override public boolean showAsResourcePack() { return false; }
@Override public boolean showAsDataPack() { return false; }
@Override public Map<String, Object> getFileProperties() { return Collections.emptyMap(); }
@Override public String getLicense() { return license; }
@Override
public String getLicense() { return license; }
@Override public IModFile getFile() { return mod; }
@Override public IConfigurable getConfig() { return configurable; }
// These Should never be called as it's only called from ModJarMetadata.version and we bypass that
@Override public String moduleName() { return mod.getSecureJar().name(); }
@Override public String versionString() { return null; }
@Override public List<String> usesServices() { return null; }
@Override public String toString() { return "IModFileInfo(" + mod.getFilePath() + ")"; }
@Override
public String toString() {
return "IModFileInfo(" + mod.getFilePath() + ")";
}
}
}
@@ -9,11 +9,13 @@ import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.loading.ImmediateWindowHandler;
import net.minecraftforge.fml.loading.LoadingModList;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
import net.minecraftforge.forgespi.language.ModFileScanData;
import org.slf4j.Logger;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
@@ -29,8 +31,10 @@ public class BackgroundScanHandler
}
private static final Logger LOGGER = LogUtils.getLogger();
private static final boolean DEBUG = LOGGER.isErrorEnabled(LogMarkers.SCAN);
private final ExecutorService modContentScanner;
private final List<ModFile> pendingFiles;
private final List<ModFile> scannedFiles;
private final List<ModFile> allFiles;
private final List<ModFile> modFiles;
private ScanStatus status;
private LoadingModList loadingModList;
@@ -42,6 +46,9 @@ public class BackgroundScanHandler
thread.setDaemon(true);
return thread;
});
scannedFiles = new ArrayList<>();
pendingFiles = new ArrayList<>();
allFiles = new ArrayList<>();
status = ScanStatus.NOT_STARTED;
}
@@ -56,17 +63,21 @@ public class BackgroundScanHandler
}
status = ScanStatus.RUNNING;
ImmediateWindowHandler.updateProgress("Scanning mod candidates");
CompletableFuture<ModFileScanData> future = CompletableFuture.supplyAsync(file::compileContent, modContentScanner)
.whenComplete(file::setScanResult);
if (DEBUG) future = future.whenComplete((r, t) -> addCompletedFile(file, t));
allFiles.add(file);
pendingFiles.add(file);
final CompletableFuture<ModFileScanData> future = CompletableFuture.supplyAsync(file::compileContent, modContentScanner)
.whenComplete(file::setScanResult)
.whenComplete((r,t)-> this.addCompletedFile(file,r,t));
file.setFutureScanResult(future);
}
private void addCompletedFile(final ModFile file, final Throwable throwable) {
private void addCompletedFile(final ModFile file, final ModFileScanData modFileScanData, final Throwable throwable) {
if (throwable != null) {
status = ScanStatus.ERRORED;
LOGGER.error(LogMarkers.SCAN, "An error occurred scanning file {}", file, throwable);
LOGGER.error(LogMarkers.SCAN,"An error occurred scanning file {}", file, throwable);
}
pendingFiles.remove(file);
scannedFiles.add(file);
}
public void setLoadingModList(LoadingModList loadingModList)
@@ -5,112 +5,90 @@
package net.minecraftforge.fml.loading.moddiscovery;
import net.minecraftforge.fml.loading.ClasspathLocatorUtils;
import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.fml.loading.ModDirTransformerDiscoverer;
import net.minecraftforge.forgespi.locating.IModLocator;
import org.jetbrains.annotations.ApiStatus;
import org.slf4j.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.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.Map;
import java.util.stream.Stream;
@ApiStatus.Internal
public final class ClasspathLocator extends AbstractModProvider implements IModLocator {
public class ClasspathLocator extends AbstractJarFileModLocator
{
private static final Logger LOGGER = LogUtils.getLogger();
private static final Attributes.Name MOD_TYPE = new Attributes.Name("FMLModType");
private final List<Path> legacyClasspath = Arrays.stream(System.getProperty("legacyClassPath", "").split(File.pathSeparator)).map(Path::of).toList();
private boolean enabled = false;
@Override
public String name() {
return "classpath";
return "userdev classpath";
}
@Override
public List<ModFileOrException> scanMods() {
//var cl = ClassLoader.getSystemClassLoader();
var cl = getClass().getClassLoader();
public Stream<Path> scanCandidates() {
if (!enabled)
return Stream.of();
// Find 'minecraft' during dev time this is also Forge. So skip it.
// Can return null for dedicated server as Minecraft isn't on the classpath
// On the client it doesn't matter as it's the vanilla jar so wouldnt have mods.toml in it.
var minecraft = getPathFromResource(cl, "net/minecraft/obfuscate/DontObfuscate.class");
try {
var claimed = new ArrayList<>(legacyClasspath);
var paths = Stream.<Path>builder();
var claimed = new HashSet<Path>();
var tomls = getUrls(cl, MODS_TOML);
for (var url : tomls) {
var path = getPath(url, MODS_TOML);
if (!path.equals(minecraft))
claimed.add(path);
findPaths(claimed, MODS_TOML).forEach(paths::add);
findPaths(claimed, MANIFEST).forEach(paths::add);
return paths.build();
} catch (IOException e) {
LOGGER.error(LogMarkers.SCAN, "Error trying to find resources", e);
throw new RuntimeException(e);
}
}
var manifests = getUrls(cl, JarFile.MANIFEST_NAME);
for (var url : manifests) {
var path = getPath(url, JarFile.MANIFEST_NAME);
if (claimed.contains(path))
private List<Path> findPaths(List<Path> claimed, String resource) throws IOException {
var ret = new ArrayList<Path>();
final Enumeration<URL> resources = ClassLoader.getSystemClassLoader().getResources(resource);
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
Path path = ClasspathLocatorUtils.findJarPathFor(resource, resource, url);
if (claimed.stream().anyMatch(path::equals) || !Files.exists(path) || Files.isDirectory(path))
continue;
try (var is = url.openStream()) {
var mf = new Manifest(is);
var type = mf.getMainAttributes().getValue(MOD_TYPE);
if (type != null)
claimed.add(path);
} catch (IOException e) {
LOGGER.warn(LogMarkers.SCAN, "Error reading manifest from: " + url, e);
}
}
var ret = new ArrayList<ModFileOrException>();
for (var path : claimed) {
// Filter out anything found by the ServiceLoader
if (!ModDirTransformerDiscoverer.isServiceProvider(path))
ret.add(createMod(path));
ret.add(path);
}
return ret;
}
private static List<URL> getUrls(ClassLoader cl, String resource) {
@Override
public void initArguments(Map<String, ?> arguments) {
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 {
var lst = Collections.list(cl.getResources(resource));
if (LOGGER.isDebugEnabled(LogMarkers.SCAN)) {
lst.sort(Comparator.comparing(URL::toString));
LOGGER.debug(LogMarkers.SCAN, "Scanning Classloader: {} for {}", cl, resource);
for (var url : lst)
LOGGER.debug(LogMarkers.SCAN, "\t{}", url);
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())));
}
return lst;
} catch (IOException e) {
LOGGER.warn(LogMarkers.SCAN, "Error finding resources for: " + resource, e);
return List.of();
//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);
}
}
private static Path getPath(URL url, String resource) {
var str = url.toString();
int len = resource.length();
if ("jar".equalsIgnoreCase(url.getProtocol())) {
str = url.getFile();
len += 2;
}
str = str.substring(0, str.length() - len);
return Path.of(URI.create(str));
}
private static Path getPathFromResource(ClassLoader cl, String resource) {
var url = cl.getResource(resource);
if (url == null)
return null;
return getPath(url, resource);
}
}
@@ -12,10 +12,7 @@ import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Path;
import org.jetbrains.annotations.ApiStatus;
@ApiStatus.Internal
public final class CoreModFile implements ICoreModFile {
public class CoreModFile implements ICoreModFile {
private final Path internalPath;
private final ModFile file;
private final String name;
@@ -0,0 +1,78 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.moddiscovery;
import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.forgespi.locating.IModFile;
import net.minecraftforge.forgespi.locating.IModLocator;
import org.slf4j.Logger;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Stream;
public class ExplodedDirectoryLocator implements IModLocator {
private static final Logger LOGGER = LogUtils.getLogger();
public record ExplodedMod(String modid, List<Path> paths) {}
private final List<ExplodedMod> explodedMods = new ArrayList<>();
private final Map<ExplodedMod, IModFile> mods = new HashMap<>();
@Override
public List<IModLocator.ModFileOrException> scanMods() {
explodedMods.forEach(explodedMod ->
ModJarMetadata.buildFile(this,
jar->jar.moduleDataProvider().findFile("/META-INF/mods.toml").isPresent(),
(a,b) -> true,
explodedMod.paths().toArray(Path[]::new))
.ifPresentOrElse(f->mods.put(explodedMod, f), () -> LOGGER.warn(LogMarkers.LOADING, "Failed to find exploded resource mods.toml in directory {}", explodedMod.paths().get(0).toString())));
return mods.values().stream().map(mf->new IModLocator.ModFileOrException(mf, null)).toList();
}
@Override
public String name() {
return "exploded directory";
}
@Override
public void scanFile(final IModFile file, final Consumer<Path> pathConsumer) {
LOGGER.debug(LogMarkers.SCAN,"Scanning exploded directory {}", file.getFilePath().toString());
try (Stream<Path> files = Files.find(file.getSecureJar().getRootPath(), Integer.MAX_VALUE, (p, a) -> p.getNameCount() > 0 && p.getFileName().toString().endsWith(".class"))) {
files.forEach(pathConsumer);
} catch (IOException e) {
e.printStackTrace();
}
LOGGER.debug(LogMarkers.SCAN,"Exploded directory scan complete {}", file.getFilePath().toString());
}
@Override
public String toString()
{
return "{ExplodedDir locator}";
}
@SuppressWarnings("unchecked")
@Override
public void initArguments(final Map<String, ?> arguments) {
final var explodedTargets = ((Map<String, List<ExplodedMod>>) arguments).get("explodedTargets");
if (explodedTargets != null && !explodedTargets.isEmpty()) {
explodedMods.addAll(explodedTargets);
}
}
@Override
public boolean isValid(final IModFile modFile) {
return true;
}
}
@@ -11,11 +11,12 @@ import net.minecraftforge.forgespi.locating.ModFileLoadingException;
import java.util.Locale;
import java.util.Optional;
public class InvalidModFileException extends ModFileLoadingException {
private static final long serialVersionUID = 1230464325917450374L;
public class InvalidModFileException extends ModFileLoadingException
{
private final IModFileInfo modFileInfo;
public InvalidModFileException(String message, 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")));
this.modFileInfo = modFileInfo;
}
Loaded 100 of 3102 files, more files were not shown because too many files have changed in this diff. Show more