mirror of
https://github.com/MinecraftForge/MinecraftForge.git
synced 2026-11-14 17:14:44 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4caf46cb9 | ||
|
|
7b866e7e43 | ||
|
|
c2b79dd6ad | ||
|
|
b290cc6477 | ||
|
|
16127a61c7 | ||
|
|
0965ebae09 | ||
|
|
b06fb810ca | ||
|
|
261313a682 | ||
|
|
da96dd3514 | ||
|
|
201be84b9c | ||
|
|
03d7eaae23 | ||
|
|
1be1bb59c5 | ||
|
|
fb44ba2e6d | ||
|
|
b16994717f | ||
|
|
b2964a1ae7 | ||
|
|
6f361dafa8 | ||
|
|
376f172990 | ||
|
|
9fe6385e1f | ||
|
|
b0ae0be80e | ||
|
|
b109baf7d6 | ||
|
|
7065cb36bf | ||
|
|
a6b9fdf8c4 | ||
|
|
6e8dd77bbe | ||
|
|
a4983be979 | ||
|
|
62cc9f2e63 | ||
|
|
49ec3d1b93 | ||
|
|
cf26f4acbe | ||
|
|
882e0e3bf2 | ||
|
|
2cb99f0e74 | ||
|
|
be73ec3d5e | ||
|
|
0348760a77 | ||
|
|
2f802b6d71 | ||
|
|
6b52023a5c | ||
|
|
5bea8218d2 | ||
|
|
b24eb9af3c | ||
|
|
d48985fafa | ||
|
|
c416f38461 | ||
|
|
0a5ef64d6e |
No files matched your search
@@ -3,7 +3,6 @@
|
||||
*.patch text eol=lf
|
||||
*.java text eol=lf
|
||||
*.gradle text eol=crlf
|
||||
**/MANIFEST.MF text eol=crlf
|
||||
*.png binary
|
||||
*.gif binary
|
||||
*.exe binary
|
||||
@@ -14,14 +13,3 @@
|
||||
*.pyd binary
|
||||
*.cfg text eol=lf
|
||||
*.py text eol=lf
|
||||
*.jks binary
|
||||
|
||||
# Disable autocrlf on generated files, they always generate with LF
|
||||
src/generated/**/.cache/cache text eol=lf
|
||||
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
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Want to request a feature?
|
||||
title: ''
|
||||
labels: Feature
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- Please note, feature requests that are submitted as GitHub issues here are rarely implemented. -->
|
||||
<!-- If you'd like to see something in Forge, you should be ready to implement it as a PR. -->
|
||||
<!-- If you're not sure, open a Draft PR or discuss it in the Discord server. -->
|
||||
<!-- Discord: https://discord.gg/UvedJ9m -->
|
||||
@@ -1,26 +0,0 @@
|
||||
---
|
||||
name: Issue report
|
||||
about: Visit the forum first for issues with crashes.
|
||||
title: ''
|
||||
labels: Triage
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- Thank you for reporting! -->
|
||||
|
||||
<!-- For support with crashes and issues, please create a post on the forum instead: -->
|
||||
<!-- https://www.minecraftforge.net/forum/forum/18-support-bug-reports/ -->
|
||||
|
||||
**Minecraft Version:** {Minecraft version}
|
||||
|
||||
**Forge Version:** {Forge version. *Version number, not latest/rb*}
|
||||
|
||||
**Logs:** {Link(s) to GitHub Gist with full latest.log and/or crash report}
|
||||
|
||||
**Steps to Reproduce:**
|
||||
1. Do this
|
||||
2. Then do that
|
||||
3. ...
|
||||
|
||||
**Description of issue:**
|
||||
@@ -1,43 +0,0 @@
|
||||
# Configuration for probot-stale - https://github.com/probot/stale
|
||||
|
||||
# Label to use when marking as stale
|
||||
staleLabel: Stale
|
||||
# Comment to post when removing the stale label. Set to `false` to disable
|
||||
unmarkComment: false
|
||||
# Limit the number of actions per hour, from 1-30. Default is 30
|
||||
limitPerRun: 1
|
||||
# Issues with these labels will never be considered stale. Set to `[]` to disable
|
||||
exemptLabels:
|
||||
- Assigned
|
||||
|
||||
pulls:
|
||||
# Number of days of inactivity before a Pull Request becomes stale
|
||||
daysUntilStale: 60
|
||||
# Number of days of inactivity before a stale Pull Request is closed
|
||||
daysUntilClose: 14
|
||||
# Comment to post when marking as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This pull request has been automatically marked as stale because it has not had recent
|
||||
activity, and will be closed if no further activity occurs. If this pull request was
|
||||
overlooked, forgotten, or should remain open for any other reason, please reply
|
||||
here to call attention to it and remove the stale status. Thank you for your contributions.
|
||||
# Comment to post when closing a stale Pull Request. Set to `false` to disable
|
||||
closeComment: >
|
||||
This pull request has been automatically closed because it has not had
|
||||
recent activity. Please feel free to update or reopen it.
|
||||
issues:
|
||||
# Number of days of inactivity before an Issue becomes stale
|
||||
daysUntilStale: 180
|
||||
# Number of days of inactivity before a stale Issue is closed
|
||||
daysUntilClose: 14
|
||||
# Comment to post when marking as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had activity
|
||||
in a long time. If this issue is still relevant and should remain open, please reply
|
||||
with a short explanation (e.g. "I have checked the code and this issue is still relevant because ___."
|
||||
or "Here's a screenshot of this issue on the latest version").
|
||||
Thank you for your contributions!
|
||||
# Comment to post when closing a stale Issue. Set to `false` to disable
|
||||
closeComment: >
|
||||
This issue has been automatically closed because it has not had
|
||||
activity in a long time. Please feel free to reopen it or create a new issue.
|
||||
@@ -1,16 +0,0 @@
|
||||
name: 'Comment actions'
|
||||
|
||||
on: issue_comment
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
discussions: write
|
||||
|
||||
jobs:
|
||||
labels:
|
||||
uses: MinecraftForge/SharedActions/.github/workflows/comment_actions.yml@main
|
||||
secrets:
|
||||
GH_APP_KEY: ${{ secrets.GH_APP_KEY }}
|
||||
GH_APP_NAME: ${{ secrets.GH_APP_NAME }}
|
||||
@@ -1,16 +0,0 @@
|
||||
name: 'Issue actions'
|
||||
|
||||
on: issues
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
discussions: write
|
||||
|
||||
jobs:
|
||||
labels:
|
||||
uses: MinecraftForge/SharedActions/.github/workflows/issue_actions.yml@main
|
||||
secrets:
|
||||
GH_APP_KEY: ${{ secrets.GH_APP_KEY }}
|
||||
GH_APP_NAME: ${{ secrets.GH_APP_NAME }}
|
||||
@@ -1,13 +0,0 @@
|
||||
name: 'PR Review'
|
||||
|
||||
on: pull_request_review
|
||||
|
||||
jobs:
|
||||
upload_payload:
|
||||
name: Upload payload
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/upload-artifact@v3 # Payload artifact is consumed in Actionable
|
||||
with:
|
||||
name: payload
|
||||
path: ${{ github.event_path }}
|
||||
@@ -1,13 +0,0 @@
|
||||
name: 'PR Review Comment'
|
||||
|
||||
on: pull_request_review_comment
|
||||
|
||||
jobs:
|
||||
upload_payload:
|
||||
name: Upload payload
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/upload-artifact@v3 # Payload artifact is consumed in Actionable
|
||||
with:
|
||||
name: payload
|
||||
path: ${{ github.event_path }}
|
||||
@@ -1,20 +0,0 @@
|
||||
name: 'Pull Request Review Comment actions'
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: [PR Review Comment]
|
||||
types:
|
||||
- completed
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
discussions: write
|
||||
|
||||
jobs:
|
||||
labels:
|
||||
uses: MinecraftForge/SharedActions/.github/workflows/pr_review_comment_actions.yml@main
|
||||
secrets:
|
||||
GH_APP_KEY: ${{ secrets.GH_APP_KEY }}
|
||||
GH_APP_NAME: ${{ secrets.GH_APP_NAME }}
|
||||
@@ -1,20 +0,0 @@
|
||||
name: 'Pull Request Review actions'
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: [PR Review]
|
||||
types:
|
||||
- completed
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
discussions: write
|
||||
|
||||
jobs:
|
||||
labels:
|
||||
uses: MinecraftForge/SharedActions/.github/workflows/pr_review_actions.yml@main
|
||||
secrets:
|
||||
GH_APP_KEY: ${{ secrets.GH_APP_KEY }}
|
||||
GH_APP_NAME: ${{ secrets.GH_APP_NAME }}
|
||||
@@ -1,18 +0,0 @@
|
||||
name: 'Pull Request actions'
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [assigned, unassigned, labeled, unlabeled, opened, edited, closed, reopened, synchronize, converted_to_draft, ready_for_review, locked, unlocked, review_requested, review_request_removed, auto_merge_enabled, auto_merge_disabled]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
discussions: write
|
||||
|
||||
jobs:
|
||||
labels:
|
||||
uses: MinecraftForge/SharedActions/.github/workflows/pr_actions.yml@main
|
||||
secrets:
|
||||
GH_APP_KEY: ${{ secrets.GH_APP_KEY }}
|
||||
GH_APP_NAME: ${{ secrets.GH_APP_NAME }}
|
||||
@@ -1,16 +0,0 @@
|
||||
name: 'Push actions'
|
||||
|
||||
on: push
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
discussions: write
|
||||
|
||||
jobs:
|
||||
labels:
|
||||
uses: MinecraftForge/SharedActions/.github/workflows/push_actions.yml@main
|
||||
secrets:
|
||||
GH_APP_KEY: ${{ secrets.GH_APP_KEY }}
|
||||
GH_APP_NAME: ${{ secrets.GH_APP_NAME }}
|
||||
+14
-30
@@ -1,48 +1,32 @@
|
||||
#eclipse
|
||||
**/bin
|
||||
**/.settings
|
||||
**/.classpath
|
||||
*.project
|
||||
/bin
|
||||
/.settings
|
||||
/.classpath
|
||||
|
||||
#In case people make their workspace in the repo
|
||||
/eclipse/
|
||||
|
||||
#idea
|
||||
/.idea
|
||||
/*/out
|
||||
/classes
|
||||
*.iml
|
||||
|
||||
# We however WANT the ide folder as it has tempaltes/configs for developing Forge.
|
||||
!ide/**
|
||||
|
||||
#gradle
|
||||
/build
|
||||
/.gradle
|
||||
/*/build
|
||||
/*/.gradle
|
||||
|
||||
# Projects repo, either ignore it, or ignore patches.
|
||||
/projects/mcp/
|
||||
/projects/clean/
|
||||
/projects/forge/
|
||||
|
||||
#occupational hazards
|
||||
/projects/**/build/
|
||||
/projects/**/out/
|
||||
/projects/**/run/
|
||||
/projects/**/*.launch
|
||||
/projects/
|
||||
/repo/
|
||||
src/*/generated/**/.cache/
|
||||
/buildSrc
|
||||
|
||||
# Generated by gradle every import
|
||||
**/src/main/resources/META-INF/MANIFEST.MF
|
||||
/fmlloader/src/main/resources/forge_version.json
|
||||
#Patch rejects
|
||||
/patches-/
|
||||
*.patch.rej
|
||||
|
||||
/ForgeRoot.ipr
|
||||
/ForgeRoot.iws
|
||||
|
||||
# APT stuff
|
||||
/*/.gitignore
|
||||
/*/.factorypath
|
||||
/*/.apt_generated/
|
||||
# FML changelog
|
||||
changelog.txt
|
||||
/patches_old/
|
||||
/patches_old_bak/
|
||||
*.py
|
||||
*.7z
|
||||
Vendored
-104
@@ -1,104 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<project>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<name>MinecraftForge_Forge Config DSL Script</name>
|
||||
<groupId>MinecraftForge_Forge</groupId>
|
||||
<artifactId>MinecraftForge_Forge_dsl</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.jetbrains.teamcity</groupId>
|
||||
<artifactId>configs-dsl-kotlin-parent</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>jetbrains-all</id>
|
||||
<url>https://download.jetbrains.com/teamcity-repository</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>teamcity-server</id>
|
||||
<url>https://teamcity.minecraftforge.net/app/dsl-plugins-repository</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>JetBrains</id>
|
||||
<url>https://download.jetbrains.com/teamcity-repository</url>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
|
||||
<build>
|
||||
<sourceDirectory>${basedir}</sourceDirectory>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>kotlin-maven-plugin</artifactId>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<version>${kotlin.version}</version>
|
||||
|
||||
<configuration/>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>compile</id>
|
||||
<phase>process-sources</phase>
|
||||
<goals>
|
||||
<goal>compile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>test-compile</id>
|
||||
<phase>process-test-sources</phase>
|
||||
<goals>
|
||||
<goal>test-compile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.jetbrains.teamcity</groupId>
|
||||
<artifactId>teamcity-configs-maven-plugin</artifactId>
|
||||
<version>${teamcity.dsl.version}</version>
|
||||
<configuration>
|
||||
<format>kotlin</format>
|
||||
<dstDir>target/generated-configs</dstDir>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.teamcity</groupId>
|
||||
<artifactId>configs-dsl-kotlin</artifactId>
|
||||
<version>${teamcity.dsl.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.teamcity</groupId>
|
||||
<artifactId>configs-dsl-kotlin-plugins</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<type>pom</type>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-stdlib-jdk8</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-script-runtime</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
Vendored
-193
@@ -1,193 +0,0 @@
|
||||
import jetbrains.buildServer.configs.kotlin.v2019_2.*
|
||||
import jetbrains.buildServer.configs.kotlin.v2019_2.projectFeatures.githubIssues
|
||||
import jetbrains.buildServer.configs.kotlin.*
|
||||
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.gradle
|
||||
|
||||
/*
|
||||
The settings script is an entry point for defining a TeamCity
|
||||
project hierarchy. The script should contain a single call to the
|
||||
project() function with a Project instance or an init function as
|
||||
an argument.
|
||||
|
||||
VcsRoots, BuildTypes, Templates, and subprojects can be
|
||||
registered inside the project using the vcsRoot(), buildType(),
|
||||
template(), and subProject() methods respectively.
|
||||
|
||||
To debug settings scripts in command-line, run the
|
||||
|
||||
mvnDebug org.jetbrains.teamcity:teamcity-configs-maven-plugin:generate
|
||||
|
||||
command and attach your debugger to the port 8000.
|
||||
|
||||
To debug in IntelliJ Idea, open the 'Maven Projects' tool window (View
|
||||
-> Tool Windows -> Maven Projects), find the generate task node
|
||||
(Plugins -> teamcity-configs -> teamcity-configs:generate), the
|
||||
'Debug' option is available in the context menu for the task.
|
||||
*/
|
||||
|
||||
version = "2021.2"
|
||||
|
||||
project {
|
||||
|
||||
buildType(Build)
|
||||
buildType(BuildSecondaryBranches)
|
||||
buildType(PullRequests)
|
||||
buildType(PullRequestChecks)
|
||||
buildType(PullRequestCompatibility)
|
||||
|
||||
params {
|
||||
text("docker_jdk_version", "21", label = "Gradle version", description = "The version of the JDK to use during execution of tasks in a JDK.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
|
||||
text("docker_gradle_version", "8.7", 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.21.x", label = "Git Main Branch", description = "The git main or default branch to use in VCS operations.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
|
||||
text("git_branch_spec", """
|
||||
+:refs/heads/(main*)
|
||||
+:refs/heads/(master*)
|
||||
+:refs/heads/(develop|release|staging|main|master)
|
||||
+:refs/heads/(1.*)
|
||||
""".trimIndent(), label = "The branch specification of the repository", description = "By default all main branches are build by the configuration. Modify this value to adapt the branches build.", display = ParameterDisplay.HIDDEN, allowEmpty = true)
|
||||
text("github_repository_name", "MinecraftForge", label = "The github repository name. Used to connect to it in VCS Roots.", description = "This is the repository slug on github. So for example `MinecraftForge` or `MinecraftForge`. It is interpolated into the global VCS Roots.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
|
||||
text("env.PUBLISHED_JAVA_ARTIFACT_ID", "forge", label = "Published artifact id", description = "The maven coordinate artifact id that has been published by this build. Can not be empty.", allowEmpty = false)
|
||||
text("env.PUBLISHED_JAVA_GROUP", "net.minecraftforge", label = "Published group", description = "The maven coordinate group that has been published by this build. Can not be empty.", allowEmpty = false)
|
||||
//These are references and not actually keys
|
||||
password("env.CROWDIN_KEY", "credentialsJSON:a3102dbe-805d-4177-9f54-3d2c2eb08fd5", display = ParameterDisplay.HIDDEN)
|
||||
password("env.KEYSTORE_URL", "credentialsJSON:a7ae1c82-8058-4061-8d12-7f6bc2618d2e", display = ParameterDisplay.HIDDEN)
|
||||
password("env.KEYSTORE_PASSWORD", "credentialsJSON:d7b964e3-a1fd-47a8-b892-6f601fe47479", display = ParameterDisplay.HIDDEN)
|
||||
text("additional_publishing_gradle_parameters", "-PcrowdinKey=%env.CROWDIN_KEY% -PkeystoreKeyPass=%env.KEYSTORE_PASSWORD% -PkeystoreStorePass=%env.KEYSTORE_PASSWORD% -Pkeystore=%env.KEYSTORE_URL%", label = "Additional gradle parameters for publish", description = "Contains the additional gradle parameters used during publishing.", display = ParameterDisplay.HIDDEN, allowEmpty = true)
|
||||
|
||||
checkbox("should_execute_build", "false", label = "Should build", description = "Indicates if the build task should be executed.", display = ParameterDisplay.HIDDEN,
|
||||
checked = "true", unchecked = "false")
|
||||
checkbox("should_execute_test", "false", label = "Should build", description = "Indicates if the build task should be executed.", display = ParameterDisplay.HIDDEN,
|
||||
checked = "true", unchecked = "false")
|
||||
}
|
||||
|
||||
features {
|
||||
githubIssues {
|
||||
id = "MinecraftForge_MinecraftForge__IssueTracker"
|
||||
displayName = "MinecraftForge/MinecraftForge"
|
||||
repositoryURL = "https://github.com/MinecraftForge/MinecraftForge"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object Build : BuildType({
|
||||
templates(AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_BuildMainBranches"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"), AbsoluteId("MinecraftForge_BuildUsingGradle"), AbsoluteId("MinecraftForge_PublishProjectUsingGradle"), AbsoluteId("MinecraftForge_TriggersStaticFilesWebpageGenerator"))
|
||||
id("MinecraftForge_MinecraftForge__Build")
|
||||
name = "Build"
|
||||
description = "Builds and Publishes the main branches of the project."
|
||||
})
|
||||
|
||||
object BuildSecondaryBranches : BuildType({
|
||||
templates(AbsoluteId("MinecraftForge_ExcludesBuildingDefaultBranch"), AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_BuildMainBranches"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"), AbsoluteId("MinecraftForge_BuildUsingGradle"))
|
||||
id("MinecraftForge_MinecraftForge__BuildSecondaryBranches")
|
||||
name = "Build - Secondary Branches"
|
||||
description = "Builds and Publishes the secondary branches of the project."
|
||||
params {
|
||||
text("git_branch_spec", """
|
||||
+:refs/heads/(*)
|
||||
-:refs/heads/noci*
|
||||
""".trimIndent(), label = "The branch specification of the repository", description = "By default all main branches are build by the configuration. Modify this value to adapt the branches build.", display = ParameterDisplay.HIDDEN, allowEmpty = true)
|
||||
}
|
||||
vcs {
|
||||
branchFilter = """
|
||||
+:*
|
||||
-:1.*
|
||||
-:<default>
|
||||
""".trimIndent()
|
||||
}
|
||||
})
|
||||
|
||||
object PullRequests : BuildType({
|
||||
templates(AbsoluteId("MinecraftForge_BuildPullRequests"), AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"), AbsoluteId("MinecraftForge_BuildUsingGradle"))
|
||||
id("MinecraftForge_MinecraftForge__PullRequests")
|
||||
name = "Pull Requests"
|
||||
description = "Builds pull requests for the project"
|
||||
|
||||
params {
|
||||
checkbox("should_execute_build", "true", label = "Should build", description = "Indicates if the build task should be executed.", display = ParameterDisplay.HIDDEN,
|
||||
checked = "true", unchecked = "false")
|
||||
text(
|
||||
"gradle_build_task",
|
||||
"assemble",
|
||||
label = "Gradle build task to execute during build",
|
||||
description = "Determines the build task that is executed to build the project.",
|
||||
display = ParameterDisplay.HIDDEN,
|
||||
allowEmpty = false
|
||||
)
|
||||
}
|
||||
|
||||
vcs {
|
||||
branchFilter = """
|
||||
+:*
|
||||
-:1.*
|
||||
-:<default>
|
||||
""".trimIndent()
|
||||
}
|
||||
})
|
||||
|
||||
object PullRequestChecks : BuildType({
|
||||
templates(AbsoluteId("MinecraftForge_BuildPullRequests"), AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"))
|
||||
id("MinecraftForge_MinecraftForge__PullRequestChecks")
|
||||
name = "Pull Requests (Checks)"
|
||||
description = "Checks pull requests for the project"
|
||||
|
||||
steps {
|
||||
gradle {
|
||||
name = "Check"
|
||||
id = "RUNNER_10_Check"
|
||||
|
||||
tasks = "checkAll"
|
||||
gradleParams = "--continue %gradle_custom_args%"
|
||||
enableStacktrace = true
|
||||
dockerImage = "%docker_gradle_image%"
|
||||
dockerRunParameters = """
|
||||
-v "/opt/cache/agent/gradle:/home/gradle/.gradle"
|
||||
-v "/opt/cache/shared/gradle:/home/gradle/rocache:ro"
|
||||
--network=host
|
||||
-u 1000:1000
|
||||
%docker_additional_args%
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
|
||||
vcs {
|
||||
branchFilter = """
|
||||
+:*
|
||||
-:1.*
|
||||
-:<default>
|
||||
""".trimIndent()
|
||||
}
|
||||
})
|
||||
|
||||
object PullRequestCompatibility : BuildType({
|
||||
templates(AbsoluteId("MinecraftForge_BuildPullRequests"), AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"))
|
||||
id("MinecraftForge_MinecraftForge__PullRequestCompatibility")
|
||||
name = "Pull Requests (Compatibility)"
|
||||
description = "Validates binary compatibility for pull requests made to the project"
|
||||
|
||||
steps {
|
||||
gradle {
|
||||
name = "Validate"
|
||||
id = "RUNNER_10_Compatibility"
|
||||
|
||||
tasks = "checkJarCompatibility"
|
||||
gradleParams = "--continue %gradle_custom_args%"
|
||||
enableStacktrace = true
|
||||
dockerImage = "%docker_gradle_image%"
|
||||
dockerRunParameters = """
|
||||
-v "/opt/cache/agent/gradle:/home/gradle/.gradle"
|
||||
-v "/opt/cache/shared/gradle:/home/gradle/rocache:ro"
|
||||
--network=host
|
||||
-u 1000:1000
|
||||
%docker_additional_args%
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
|
||||
vcs {
|
||||
branchFilter = """
|
||||
+:*
|
||||
-:1.*
|
||||
-:<default>
|
||||
""".trimIndent()
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,22 @@
|
||||
Contributing to Forge
|
||||
=====================
|
||||
|
||||
1) Keep patches to Minecraft classes minimal. If you need a lot of things done, you may either add to relevant forge classes or make a new class.
|
||||
|
||||
2) An example mod must be provided for all PRs adding events/hooks to Forge. Whether it's a full mod using your proposed hooks, or just a pastebin containing a simple example, an example mod is required. Please also explain in detail what your new hook does.
|
||||
|
||||
3) Follow Forge coding guidelines (braces on newlines, spaces instead of tabs, etc)
|
||||
|
||||
For more information, refer to [the wiki](https://github.com/MinecraftForge/MinecraftForge/wiki/If-you-want-to-contribute-to-Forge)
|
||||
|
||||
|
||||
Contributor License Agreement
|
||||
=============================
|
||||
- You grant Forge a license to use your code contributed to the primary codebase (everything **not** under patches) in Forge, under the LGPLv2.1 license.
|
||||
- You assign copyright ownership of your contributions to the patches codebase (everything under patches) to Forge, where it will be licensed under the LGPLv2.1 license.
|
||||
|
||||
Additionally, while 1.9.4 is being maintained, you will be asked to acknowledge these two additional clauses.
|
||||
- You grant Forge a license to use your code contributed to the primary codebase (everything **not** under patches) in Forge, under the MinecraftForge license.
|
||||
- You grant assign copyright ownership of your contributions to the patches codebase (everything under patches) to Forge, where it will be licensed under the MinecraftForge license.
|
||||
|
||||
"Forge" is Forge Development LLC, a legally incorporated entity in Oregon, USA.
|
||||
@@ -0,0 +1,28 @@
|
||||
This is Forge Mod Loader.
|
||||
|
||||
You can find the source code at all times at https://github.com/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.
|
||||
|
||||
-65
@@ -1,65 +0,0 @@
|
||||
Minecraft Forge: Credits/Thank You
|
||||
|
||||
Forge is a set of tools and modifications to the Minecraft base game code to assist
|
||||
mod developers in creating new and exciting content. It has been in development for
|
||||
several years now, but I would like to take this time thank a few people who have
|
||||
helped it along its way.
|
||||
|
||||
First, the people who originally created the Forge projects way back in Minecraft
|
||||
alpha. Eloraam of RedPower, and SpaceToad of Buildcraft, without their acceptiance
|
||||
of me taking over the project, who knows what Minecraft modding would be today.
|
||||
|
||||
Secondly, someone who has worked with me, and developed some of the core features
|
||||
that allow modding to be as functional, and as simple as it is, cpw. For developing
|
||||
FML, which stabilized the client and server modding ecosystem. As well as the base
|
||||
loading system that allows us to modify Minecraft's code as elegently as possible.
|
||||
|
||||
Mezz, who has stepped up as the issue and pull request manager. Helping to keep me
|
||||
sane as well as guiding the community into creating better additions to Forge.
|
||||
|
||||
Searge, Bspks, Fesh0r, ProfMobious, and all the rest over on the MCP team {of which
|
||||
I am a part}. For creating some of the core tools needed to make Minecraft modding
|
||||
both possible, and as stable as can be.
|
||||
On that note, here is some specific information of the MCP data we use:
|
||||
* Minecraft Coder Pack (MCP) *
|
||||
Forge Mod Loader and Minecraft Forge have permission to distribute and automatically
|
||||
download components of MCP and distribute MCP data files. This permission is not
|
||||
transitive and others wishing to redistribute the Minecraft Forge source independently
|
||||
should seek permission of MCP or remove the MCP data files and request their users
|
||||
to download MCP separately.
|
||||
|
||||
And lastly, the countless community members who have spent time submitting bug reports,
|
||||
pull requests, and just helping out the community in general. Thank you.
|
||||
|
||||
--LexManos
|
||||
|
||||
=========================================================================
|
||||
|
||||
This is Forge Mod Loader.
|
||||
|
||||
You can find the source code at all times at https://github.com/MinecraftForge/MinecraftForge/tree/1.12.x/src/main/java/net/minecraftforge/fml
|
||||
|
||||
This minecraft mod is a clean open source implementation of a mod loader for minecraft servers
|
||||
and minecraft clients.
|
||||
|
||||
The code is authored by cpw.
|
||||
|
||||
It began by partially implementing an API defined by the client side ModLoader, authored by Risugami.
|
||||
https://www.minecraftforum.net/topic/75440-
|
||||
This support has been dropped as of Minecraft release 1.7, as Risugami no longer maintains ModLoader.
|
||||
|
||||
It also contains suggestions and hints and generous helpings of code from LexManos, author of MinecraftForge.
|
||||
https://minecraftforge.net/
|
||||
|
||||
Additionally, it contains an implementation of topological sort based on that
|
||||
published at http://keithschwarz.com/interesting/code/?dir=topological-sort
|
||||
|
||||
It also contains code from the Maven project for performing versioned dependency
|
||||
resolution. http://maven.apache.org/
|
||||
|
||||
It also contains a partial repackaging of the javaxdelta library from http://sourceforge.net/projects/javaxdelta/
|
||||
with credit to it's authors.
|
||||
|
||||
Forge Mod Loader downloads components from the Minecraft Coder Pack
|
||||
(http://mcp.ocean-labs.de/index.php/Main_Page) with kind permission from the MCP team.
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# On the Contribution of an RF-lite Energy System: "Energy"
|
||||
Forge has acquired an Energy system, after many years of debate on the topic. The prevalence of the RF API has warranted the inclusion of a similar (but simpler) implementation to forge for 1.10.2 and onwards.
|
||||
Those who wish to use an RF like API, feel free to use it. If you don't want to think about Energy, but know you need it, you can use it *if you wish*. If you want to interoperate with other energy systems, feel free to use this to broker with them.
|
||||
|
||||
# On Diversity in Energy Systems
|
||||
Forge has always supported diversity in energy-like systems. If you have a burning desire to simulate electricity flow in all it's glory, we fully support you and look forward to playing your mod. If you wish to have pneumatic power, we have no objections, and would love to see your wonderful creations. If you have a power system of your own devising that is simply beyond our imagination, please, blow us away.
|
||||
|
||||
# On Modder Obligations
|
||||
As a modder, you are under *no* obligation to use or support the forge Energy system. If anyone, user, mod-pack maker or even us, tells you otherwise, refer them to this statement. The Forge energy system is not intended to supplant diversity in energy systems. It is not intended to be one-size-fits-all. The choice to support it, is the choice of the modder.
|
||||
+491
@@ -0,0 +1,491 @@
|
||||
This minecraft mod, Forge Mod Loader, including all parts herein except as noted below,
|
||||
is licensed under the GNU LGPL v2.1 or later.
|
||||
|
||||
Homepage: https://github.com/MinecraftForge/FML
|
||||
|
||||
This software includes portions from the Apache Maven project at
|
||||
http://maven.apache.org/ specifically the ComparableVersion.java code. It is
|
||||
included based on guidelines at
|
||||
http://www.softwarefreedom.org/resources/2007/gpl-non-gpl-collaboration.html
|
||||
with notices intact. The only change is a non-functional change of package name.
|
||||
|
||||
This software contains a partial repackaging of javaxdelta, a BSD licensed program for generating
|
||||
binary differences and applying them, sourced from the subversion at http://sourceforge.net/projects/javaxdelta/
|
||||
authored by genman, heikok, pivot.
|
||||
The only changes are to replace some Trove collection types with standard Java collections, and repackaged.
|
||||
|
||||
|
||||
=== MCP Data ===
|
||||
This software includes data from the Minecraft Coder Pack (MCP), with kind permission
|
||||
from them. The license to MCP data is not transitive - distribution of this data by
|
||||
third parties requires independent licensing from the MCP team. This data is not
|
||||
redistributable without permission from the MCP team.
|
||||
|
||||
=== Sharing ===
|
||||
I grant permission for some parts of FML to be redistributed outside the terms of the LGPL, for the benefit of
|
||||
the minecraft modding community. All contributions to these parts should be licensed under the same additional grant.
|
||||
|
||||
-- Runtime patcher --
|
||||
License is granted to redistribute the runtime patcher code (common/cpw/mods/fml/patcher and subdirectories) under
|
||||
any alternative open source license as classified by the OSI (http://opensource.org/licenses)
|
||||
|
||||
-- ASM transformers --
|
||||
License is granted to redistribute the ASM transformer code (common/cpw/mods/fml/common/asm/ and subdirectories)
|
||||
under any alternative open source license as classified by the OSI (http://opensource.org/licenses)
|
||||
|
||||
========
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
not price. Our General Public Licenses are designed to make sure that
|
||||
you have the freedom to distribute copies of free software (and charge
|
||||
for this service if you wish); that you receive source code or can get
|
||||
it if you want it; that you can change the software and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
rights. These restrictions translate to certain responsibilities for
|
||||
you if you distribute copies of the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link other code with the library, you must provide
|
||||
complete object files to the recipients, so that they can relink them
|
||||
with the library after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it becomes
|
||||
a de-facto standard. To achieve this, non-free programs must be
|
||||
allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser General Public License (also called "this License").
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the materials to be distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Lesser General Public License from time to time.
|
||||
Such new versions will be similar in spirit to the present version,
|
||||
but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
@@ -1,2 +0,0 @@
|
||||
Copyright (c) Forge Development LLC and contributors
|
||||
SPDX-License-Identifier: LGPL-2.1-only
|
||||
+483
@@ -0,0 +1,483 @@
|
||||
Minecraft Forge is licensed under the terms of the LGPL 2.1 found
|
||||
here http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt and copied
|
||||
below.
|
||||
|
||||
A note on authorship:
|
||||
All source artifacts are property of their original author, with
|
||||
the exclusion of the contents of the patches directory and others
|
||||
copied from it from time to time. Authorship of the contents of
|
||||
the patches directory is retained by the Minecraft Forge project.
|
||||
This is because the patches are partially machine generated
|
||||
artifacts, and are changed heavily due to the way forge works.
|
||||
Individual attribution within them is impossible.
|
||||
|
||||
Consent:
|
||||
All contributions to Forge must consent to the release of any
|
||||
patch content to the Forge project.
|
||||
|
||||
A note on infectivity:
|
||||
The LGPL is chosen specifically so that projects may depend on Forge
|
||||
features without being infected with its license. That is the
|
||||
purpose of the LGPL. Mods and others using this code via ordinary
|
||||
Java mechanics for referencing libraries are specifically not bound
|
||||
by Forge's license for the Mod code.
|
||||
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
not price. Our General Public Licenses are designed to make sure that
|
||||
you have the freedom to distribute copies of free software (and charge
|
||||
for this service if you wish); that you receive source code or can get
|
||||
it if you want it; that you can change the software and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
rights. These restrictions translate to certain responsibilities for
|
||||
you if you distribute copies of the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link other code with the library, you must provide
|
||||
complete object files to the recipients, so that they can relink them
|
||||
with the library after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it becomes
|
||||
a de-facto standard. To achieve this, non-free programs must be
|
||||
allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser General Public License (also called "this License").
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the materials to be distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Lesser General Public License from time to time.
|
||||
Such new versions will be similar in spirit to the present version,
|
||||
but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
-523
@@ -1,523 +0,0 @@
|
||||
Unless noted below, Minecraft Forge, Forge Mod Loader, and all
|
||||
parts herein are licensed under the terms of the LGPL 2.1 found
|
||||
here http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt and
|
||||
copied below.
|
||||
|
||||
Homepage: http://minecraftforge.net/
|
||||
https://github.com/MinecraftForge/MinecraftForge
|
||||
|
||||
|
||||
A note on authorship:
|
||||
All source artifacts are property of their original author, with
|
||||
the exclusion of the contents of the patches directory and others
|
||||
copied from it from time to time. Authorship of the contents of
|
||||
the patches directory is retained by the Minecraft Forge project.
|
||||
This is because the patches are partially machine generated
|
||||
artifacts, and are changed heavily due to the way forge works.
|
||||
Individual attribution within them is impossible.
|
||||
|
||||
Consent:
|
||||
All contributions to Forge must consent to the release of any
|
||||
patch content to the Forge project.
|
||||
|
||||
A note on infectivity:
|
||||
The LGPL is chosen specifically so that projects may depend on Forge
|
||||
features without being infected with its license. That is the
|
||||
purpose of the LGPL. Mods and others using this code via ordinary
|
||||
Java mechanics for referencing libraries are specifically not bound
|
||||
by Forge's license for the Mod code.
|
||||
|
||||
|
||||
=== MCP Data ===
|
||||
This software includes data from the Minecraft Coder Pack (MCP), with kind permission
|
||||
from them. The license to MCP data is not transitive - distribution of this data by
|
||||
third parties requires independent licensing from the MCP team. This data is not
|
||||
redistributable without permission from the MCP team.
|
||||
|
||||
=== Sharing ===
|
||||
I grant permission for some parts of FML to be redistributed outside the terms of the LGPL, for the benefit of
|
||||
the minecraft modding community. All contributions to these parts should be licensed under the same additional grant.
|
||||
|
||||
-- Runtime patcher --
|
||||
License is granted to redistribute the runtime patcher code (src/main/java/net/minecraftforge/fml/common/patcher
|
||||
and subdirectories) under any alternative open source license as classified by the OSI (http://opensource.org/licenses)
|
||||
|
||||
-- ASM transformers --
|
||||
License is granted to redistribute the ASM transformer code (src/main/java/net/minecraftforge/common/asm/ and subdirectories)
|
||||
under any alternative open source license as classified by the OSI (http://opensource.org/licenses)
|
||||
|
||||
=========================================================================
|
||||
This software includes portions from the Apache Maven project at
|
||||
http://maven.apache.org/ specifically the ComparableVersion.java code. It is
|
||||
included based on guidelines at
|
||||
http://www.softwarefreedom.org/resources/2007/gpl-non-gpl-collaboration.html
|
||||
with notices intact. The only change is a non-functional change of package name.
|
||||
|
||||
This software contains a partial repackaging of javaxdelta, a BSD licensed program for generating
|
||||
binary differences and applying them, sourced from the subversion at http://sourceforge.net/projects/javaxdelta/
|
||||
authored by genman, heikok, pivot.
|
||||
The only changes are to replace some Trove collection types with standard Java collections, and repackaged.
|
||||
|
||||
This software includes the Monocraft font from https://github.com/IdreesInc/Monocraft/ for use in the early loading
|
||||
display.
|
||||
=========================================================================
|
||||
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
not price. Our General Public Licenses are designed to make sure that
|
||||
you have the freedom to distribute copies of free software (and charge
|
||||
for this service if you wish); that you receive source code or can get
|
||||
it if you want it; that you can change the software and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
rights. These restrictions translate to certain responsibilities for
|
||||
you if you distribute copies of the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link other code with the library, you must provide
|
||||
complete object files to the recipients, so that they can relink them
|
||||
with the library after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it becomes
|
||||
a de-facto standard. To achieve this, non-free programs must be
|
||||
allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser General Public License (also called "this License").
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the materials to be distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Lesser General Public License from time to time.
|
||||
Such new versions will be similar in spirit to the present version,
|
||||
but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
@@ -0,0 +1,26 @@
|
||||
* Eloraam *
|
||||
|
||||
* FlowerChild *
|
||||
|
||||
* Hawkye *
|
||||
|
||||
* MALfunction84 *
|
||||
|
||||
Submitted the sleep handler code for his mod (Somnia) and others to use.
|
||||
|
||||
* Scokeev9 *
|
||||
|
||||
Gave permission for ScotTools API to be integrated into MCF, and also supported the Forge by converting his mods to use it.
|
||||
|
||||
ScotTools Background: ScotTools was an API that enabled modders to add blocks to harvesting levels (and many other ease-of-use features to create new tools), and the first tool API that used block material for block breaking efficiency which allowed blocks from mods that didn't use ScotTools API to break with the correct speed.
|
||||
|
||||
* SpaceToad *
|
||||
|
||||
* LexManos *
|
||||
|
||||
* cpw *
|
||||
|
||||
* 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.
|
||||
@@ -0,0 +1,70 @@
|
||||
Minecraft Forge Public Licence
|
||||
==============================
|
||||
|
||||
Version 1.0
|
||||
|
||||
0. Definitions
|
||||
--------------
|
||||
|
||||
Minecraft: Denotes a copy of the Minecraft game licensed by Mojang AB
|
||||
|
||||
User: Anybody that interract with the software in one of the following ways:
|
||||
- play
|
||||
- decompile
|
||||
- recompile or compile
|
||||
- modify
|
||||
|
||||
Minecraft Forge: The Minecraft Forge code, in source form, class file form, as
|
||||
obtained in a standalone fashion or as part of a wider distribution.
|
||||
|
||||
Dependency: Code required to have Minecraft Forge working properly. That can
|
||||
include dependencies required to compile the code as well as modifications in
|
||||
the Minecraft sources that are required to have Minecraft Forge working.
|
||||
|
||||
1. Scope
|
||||
--------
|
||||
|
||||
The present license is granted to any user of Minecraft Forge, for all files included
|
||||
unless stated otherwise in the file itself. As a prerequisite, a user of Minecraft
|
||||
Forge must own a legally aquired copy of Minecraft.
|
||||
|
||||
2. Play rights
|
||||
--------------
|
||||
|
||||
The user of Minecraft Forge is allowed to install the software on a client or
|
||||
a server and to play it without restriction.
|
||||
|
||||
3. Modification rights
|
||||
----------------------
|
||||
|
||||
The user has the right to decompile the source code, look at either the
|
||||
decompiled version or the original source code, and to modify it.
|
||||
|
||||
4. Derivation rights
|
||||
--------------------
|
||||
|
||||
The user has the rights to derive code from Minecraft Forge, that is to say to
|
||||
write code that either extends Minecraft Forge class and interfaces,
|
||||
instantiate the objects declared or calls the functions. This code is known as
|
||||
"derived" code, and can be licensed with conditions different from Minecraft
|
||||
Forge.
|
||||
|
||||
|
||||
5. Distribution rights
|
||||
----------------------
|
||||
|
||||
The user of Minecraft Forge is allowed to redistribute Minecraft Forge in
|
||||
partially, in totallity, or included in a distribution. When distributing
|
||||
binaries or class files, the user must provide means to obtain the sources of
|
||||
the distributed version of Minecraft Forge at no costs. This includes the
|
||||
files as well as any dependency that the code may rely on, including patches to
|
||||
minecraft original sources.
|
||||
|
||||
Modification of Minecraft Forge as well as dependencies, including patches to
|
||||
minecraft original sources, has to remain under the terms of the present
|
||||
license.
|
||||
|
||||
The right to distribute Minecraft Forge does not extend to the right to distribute
|
||||
MCP data files included within Minecraft Forge. These are the property of the MCP
|
||||
project and should be removed from any customized distribution of Minecraft Forge
|
||||
or permission sought separately from the MCP team.
|
||||
@@ -0,0 +1,10 @@
|
||||
IBXM is copyright (c) 2007, Martin Cameron, and is licensed under the BSD License.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
Neither the name of mumart nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
SoundSystem CodecIBXM Class License:
|
||||
|
||||
You are free to use this class for any purpose, commercial or otherwise.
|
||||
You may modify this class or source code, and distribute it any way you
|
||||
like, provided the following conditions are met:
|
||||
|
||||
1) You may not falsely claim to be the author of this class or any
|
||||
unmodified portion of it.
|
||||
2) You may not copyright this class or a modified version of it and then
|
||||
sue me for copyright infringement.
|
||||
3) If you modify the source code, you must clearly document the changes
|
||||
made before redistributing the modified source code, so other users know
|
||||
it is not the original code.
|
||||
4) You are not required to give me credit for this class in any derived
|
||||
work, but if you do, you must also mention my website:
|
||||
http://www.paulscode.com
|
||||
5) I the author will not be responsible for any damages (physical,
|
||||
financial, or otherwise) caused by the use if this class or any
|
||||
portion of it.
|
||||
6) I the author do not guarantee, warrant, or make any representations,
|
||||
either expressed or implied, regarding the use of this class or any
|
||||
portion of it.
|
||||
|
||||
Author: Paul Lamb
|
||||
http://www.paulscode.com
|
||||
|
||||
|
||||
This software is based on or using the IBXM library available from
|
||||
http://www.geocities.com/sunet2000/
|
||||
|
||||
|
||||
IBXM is copyright (c) 2007, Martin Cameron, and is licensed under the BSD License.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
Neither the name of mumart nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -0,0 +1,63 @@
|
||||
# How to install Forge: For Players
|
||||
|
||||
Go to [http://files.minecraftforge.net](http://files.minecraftforge.net)
|
||||
and select the minecraft version you wish to get forge for from the list.
|
||||
|
||||
You can download the installer for the *Recommended Build* or the
|
||||
*Lastest 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!
|
||||
|
||||
Here is a short video from Rorax showing how to install and setup Forge.
|
||||
|
||||
[](https://www.youtube.com/watch?v=lB3ArN_-3Oc)
|
||||
|
||||
For support and user questions, visit [http://www.minecraftforge.net](http://www.minecraftforge.net).
|
||||
|
||||
# How to install Forge: For Modders
|
||||
|
||||
If you wish to setup a new mod for Forge, visit
|
||||
[http://files.minecraftforge.net](http://files.minecraftforge.net) and
|
||||
select the **MDK** download.
|
||||
|
||||
This is the `Modder Developer Kit` - basically, an example mod with all
|
||||
the tooling needed to create a Forge mod workspace ready for use in your
|
||||
IDE of choice.
|
||||
|
||||
Here is a short video from @cpw showing how to install and setup the MDK
|
||||
in Intellij.
|
||||
|
||||
[](https://www.youtube.com/watch?v=PfmlNiHonV0)
|
||||
|
||||
# How to install Forge: For those wishing to work on Forge itself
|
||||
|
||||
If you wish to actually inspect Forge, submit PRs or otherwise work
|
||||
with Forge itself, you're in the right place! Clone this (either
|
||||
directly, or make a Fork first if you want to make a PR) and run
|
||||
```gradlew.bat setupForge``` or ```gradlew setupForge```. This will download and
|
||||
setup all the bits you need to have a functional Forge workspace.
|
||||
|
||||
After that, open a workspace in your favorite IDE and import existing projects
|
||||
the projects folder.
|
||||
|
||||
Here is a short video from @cpw showing how to install and setup Forge
|
||||
in Intellij.
|
||||
|
||||
[](https://www.youtube.com/watch?v=yanCpy8p2ZE)
|
||||
|
||||
## Pull requests
|
||||
|
||||
Pull requests should target the current default branch. Currently, that is
|
||||
the 1.10.x branch for Minecraft 1.10.2.
|
||||
|
||||
If you intend to modify a minecraft patch, or add a new one, always run
|
||||
```gradlew genPatches``` to generate your patch and validate that you
|
||||
are not doing anything unexpected in your patch.
|
||||
|
||||
### Contributor License Agreement
|
||||
As stated in the (https://github.com/MinecraftForge/MinecraftForge/blob/1.10.x/LICENSE-new.txt)
|
||||
file, we require all contributors to acknowledge the Forge Contributor
|
||||
License Agreement. Please ensure you have a valid email address
|
||||
associated with your github account to do this. If you have previously
|
||||
signed it, you should be OK.
|
||||
+391
-49
@@ -1,55 +1,397 @@
|
||||
import net.minecraftforge.forge.tasks.*
|
||||
import net.minecraftforge.gradleutils.PomUtils
|
||||
|
||||
plugins {
|
||||
id 'net.minecraftforge.licenser' version '1.0.1'
|
||||
id 'com.github.ben-manes.versions' version '0.46.0'
|
||||
id 'net.minecraftforge.gradleutils' version '[2.3.0,2.4.0)'
|
||||
id 'eclipse'
|
||||
id 'de.undercouch.download' version '5.4.0'
|
||||
id 'net.minecraftforge.gradle.patcher' version '[6.0.22,6.2)' apply false
|
||||
id 'net.minecraftforge.gradle.mcp' version '[6.0.22,6.2)' apply false
|
||||
id 'net.minecraftforge.gradlejarsigner' version '1.0.4'
|
||||
id 'org.barfuin.gradle.taskinfo' version '2.1.0'
|
||||
}
|
||||
|
||||
Util.init() //Init all our extension methods!
|
||||
|
||||
ext {
|
||||
GIT_INFO = gradleutils.gitInfo
|
||||
VERSION = gradleutils.getFilteredMCTagOffsetBranchVersion(true, '[0-9]', MC_VERSION)
|
||||
FORGE_VERSION = VERSION.substring(MC_VERSION.length() + 1)
|
||||
}
|
||||
|
||||
changelog {
|
||||
from '47.999'
|
||||
}
|
||||
|
||||
tasks.register('setup') {
|
||||
dependsOn ':forge:extractMapped'
|
||||
if (findProject(':clean'))
|
||||
dependsOn ':clean:extractMapped'
|
||||
}
|
||||
|
||||
tasks.register('doChecks') {
|
||||
dependsOn ':forge:checkJarCompatibility'
|
||||
dependsOn ':forge:publish'
|
||||
}
|
||||
|
||||
project(':mcp') {
|
||||
apply plugin: 'net.minecraftforge.gradle.mcp'
|
||||
mcp {
|
||||
config MC_VERSION + '-' + MCP_VERSION
|
||||
pipeline = 'joined'
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
name = "forge"
|
||||
url = "http://files.minecraftforge.net/maven"
|
||||
}
|
||||
maven {
|
||||
name = "sonatype"
|
||||
url = "https://oss.sonatype.org/content/repositories/snapshots/"
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
classpath 'net.minecraftforge.gradle:ForgeGradle:2.2-SNAPSHOT'
|
||||
}
|
||||
}
|
||||
|
||||
if (System.env.TEAMCITY_VERSION) {
|
||||
//Only setup the CI environment if and only if the environment variables are set.
|
||||
tasks.named('configureTeamCity').configure {
|
||||
doLast {
|
||||
println "##teamcity[buildNumber '${project(':forge').version}']"
|
||||
println "##teamcity[setParameter name='env.PUBLISHED_JAVA_ARTIFACT_VERSION' value='${project(':forge').version}']"
|
||||
repositories {
|
||||
maven {
|
||||
name = "local"
|
||||
url = "file:///home/cpw/projects/repo"
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: "maven"
|
||||
apply plugin: "net.minecraftforge.gradle.patcher"
|
||||
apply plugin: "net.minecraftforge.gradle.launch4j"
|
||||
|
||||
minecraft.version = "1.10.2"
|
||||
|
||||
minecraft {
|
||||
mappings = 'snapshot_nodoc_20161111'
|
||||
workspaceDir = "projects"
|
||||
versionJson = "jsons/${minecraft.version}-dev.json"
|
||||
buildUserdev = true
|
||||
buildInstaller = true
|
||||
installerVersion = "1.5"
|
||||
|
||||
def common = {
|
||||
patchPrefixOriginal "../src-base/minecraft"
|
||||
patchPrefixChanged "../src-work/minecraft"
|
||||
mainClassClient "net.minecraft.launchwrapper.Launch"
|
||||
tweakClassClient "net.minecraftforge.fml.common.launcher.FMLTweaker"
|
||||
mainClassServer "net.minecraft.launchwrapper.Launch"
|
||||
tweakClassServer "net.minecraftforge.fml.common.launcher.FMLServerTweaker"
|
||||
}
|
||||
|
||||
projects {
|
||||
forge {
|
||||
rootDir "."
|
||||
patchDir "patches/minecraft"
|
||||
patchAfter "clean"
|
||||
genPatchesFrom "clean"
|
||||
genMcpPatches = false
|
||||
applyMcpPatches = false
|
||||
s2sKeepImports = true
|
||||
with common
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
group = 'net.minecraftforge'
|
||||
version = getVersionFromJava(file("src/main/java/net/minecraftforge/common/ForgeVersion.java"))
|
||||
|
||||
extractForgeSources { exclude "**/SideOnly.java", "**/Side.java" }
|
||||
extractForgeResources { exclude "**/log4j2.xml" }
|
||||
|
||||
genGradleProjects { addTestCompileDep "junit:junit:4.12" }
|
||||
|
||||
processJson {
|
||||
releaseJson = "jsons/${minecraft.version}-rel.json"
|
||||
addReplacements([
|
||||
"@minecraft_version@": project.minecraft.version,
|
||||
"@version@": project.version,
|
||||
"@project@": "forge",
|
||||
"@artifact@": "net.minecraftforge:forge:${project.version}",
|
||||
"@universal_jar@": { outputJar.archiveName },
|
||||
"@timestamp@": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"),
|
||||
])
|
||||
}
|
||||
|
||||
task changelog(type: JenkinsChangelog) {
|
||||
|
||||
// skip if there is no forge jenkins pass
|
||||
onlyIf {
|
||||
project.hasProperty('forgeJenkinsPass')
|
||||
}
|
||||
|
||||
outputs.upToDateWhen { false } // never up to date
|
||||
serverRoot = "http://ci.jenkins.minecraftforge.net/"
|
||||
jobName = "minecraftforge"
|
||||
targetBuild = System.env['BUILD_NUMBER'] ?: project.ext.properties.buildNumber ?:0;
|
||||
authName = "console_script"
|
||||
authPassword = project.hasProperty('forgeJenkinsPass') ? project.getProperty('forgeJenkinsPass') : "";
|
||||
output = "build/distributions/${project.name}-${project.version}-changelog.txt"
|
||||
}
|
||||
|
||||
task crowdin(type: CrowdinDownload) {
|
||||
output = "build/crowdin.zip"
|
||||
projectId = 'minecraft-forge'
|
||||
extract = false // we wanna keep it as a zip. not extract it to a folder named "crowdin.zip"
|
||||
|
||||
// task auomatically skips if this is null
|
||||
if (project.hasProperty('crowdinKey'))
|
||||
apiKey = project.crowdinKey
|
||||
}
|
||||
|
||||
def extraTxts = [
|
||||
"CREDITS-fml.txt",
|
||||
"LICENSE-new.txt",
|
||||
"MinecraftForge-Credits.txt",
|
||||
"Paulscode SoundSystem CodecIBXM License.txt",
|
||||
"Paulscode IBXM Library License.txt"
|
||||
]
|
||||
if (project.hasProperty('forgeJenkinsPass'))
|
||||
extraTxts += changelog
|
||||
|
||||
outputJar {
|
||||
classifier = 'universal'
|
||||
from extraTxts
|
||||
|
||||
// add crowdin locales
|
||||
from { crowdin.getDidWork() ? zipTree(crowdin.output) : null}
|
||||
dependsOn 'crowdin'
|
||||
|
||||
manifest.attributes([
|
||||
"Main-Class": "net.minecraftforge.fml.relauncher.ServerLaunchWrapper",
|
||||
"TweakClass": "net.minecraftforge.fml.common.launcher.FMLTweaker",
|
||||
"Class-Path": getServerClasspath(file("jsons/${minecraft.version}-rel.json"))
|
||||
])
|
||||
}
|
||||
|
||||
installer {
|
||||
classifier = 'installer'
|
||||
from extraTxts
|
||||
from "src/main/resources/forge_logo.png"
|
||||
from "src/main/resources/url.png"
|
||||
rename "forge_logo\\.png", "big_logo.png"
|
||||
}
|
||||
|
||||
task signUniversal(type: SignJar, dependsOn: 'outputJar') {
|
||||
onlyIf {
|
||||
project.hasProperty('jarsigner')
|
||||
}
|
||||
|
||||
def jarsigner = [:];
|
||||
|
||||
if (project.hasProperty('jarsigner'))
|
||||
jarsigner = project.jarsigner;
|
||||
|
||||
alias = 'forge'
|
||||
exclude "paulscode/**"
|
||||
storePass = jarsigner.storepass
|
||||
keyPass = jarsigner.keypass
|
||||
keyStore = jarsigner.keystore
|
||||
inputFile = outputJar.archivePath
|
||||
outputFile = outputJar.archivePath
|
||||
}
|
||||
uploadArchives.dependsOn signUniversal
|
||||
build.dependsOn signUniversal
|
||||
installer.dependsOn signUniversal
|
||||
|
||||
// MDK package
|
||||
|
||||
import org.apache.tools.ant.filters.ReplaceTokens
|
||||
task makeMdk(type: Zip) {
|
||||
baseName = project.name
|
||||
classifier = "mdk"
|
||||
version = project.version
|
||||
destinationDir = file('build/distributions')
|
||||
|
||||
from 'gradlew'
|
||||
from 'gradlew.bat'
|
||||
into ('gradle') {
|
||||
from 'gradle'
|
||||
}
|
||||
into ('eclipse') {
|
||||
from 'mdk/eclipse'
|
||||
}
|
||||
if (project.hasProperty('forgeJenkinsPass'))
|
||||
from changelog.output
|
||||
from ('mdk') {
|
||||
filter(ReplaceTokens, tokens: [
|
||||
VERSION: project.version,
|
||||
MAPPINGS: minecraft.mappings.replace('nodoc_', '')
|
||||
])
|
||||
exclude 'eclipse'
|
||||
rename 'gitignore\\.txt', '.gitignore'
|
||||
from 'MinecraftForge-Credits.txt'
|
||||
from 'LICENSE-new.txt'
|
||||
from 'Paulscode IBXM Library License.txt'
|
||||
from 'Paulscode SoundSystem CodecIBXM License.txt'
|
||||
from 'CREDITS-fml.txt'
|
||||
}
|
||||
}
|
||||
tasks.build.dependsOn makeMdk
|
||||
|
||||
// launch4j
|
||||
|
||||
launch4j {
|
||||
jar = installer.archivePath.canonicalPath
|
||||
outfile = file("build/distributions/${project.name}-${project.version}-installer-win.exe").canonicalPath
|
||||
icon = file('icon.ico').canonicalPath
|
||||
manifest = file('l4jManifest.xml').canonicalPath
|
||||
jreMinVersion = '1.6.0'
|
||||
initialHeapPercent = 5;
|
||||
maxHeapPercent = 100;
|
||||
}
|
||||
tasks.generateXmlConfig.dependsOn installer
|
||||
tasks.build.dependsOn 'launch4j'
|
||||
|
||||
// MAVEN
|
||||
|
||||
artifacts {
|
||||
if (project.hasProperty('forgeJenkinsPass'))
|
||||
archives changelog.output
|
||||
archives file("build/distributions/${project.name}-${project.version}-installer-win.exe")
|
||||
archives makeMdk
|
||||
}
|
||||
|
||||
task ciWriteBuildNumber << {
|
||||
def file = file("src/main/java/net/minecraftforge/common/ForgeVersion.java");
|
||||
def bn = System.getenv("BUILD_NUMBER")?:project.ext.properties.buildNumber?:0;
|
||||
def outfile = "";
|
||||
def ln = "\n"; //Linux line endings because we're on git!
|
||||
|
||||
file.eachLine{ String s ->
|
||||
if (s.matches("^ public static final int buildVersion = [\\d]+;\$"))
|
||||
s = " public static final int buildVersion = ${bn};";
|
||||
if (s.matches('^ public static final String mcVersion = "[^\\"]+";'))
|
||||
s = " public static final String mcVersion = \"${minecraft.version}\";";
|
||||
outfile += (s+ln);
|
||||
}
|
||||
file.write(outfile);
|
||||
}
|
||||
|
||||
uploadArchives {
|
||||
repositories.mavenDeployer {
|
||||
|
||||
dependsOn 'build'
|
||||
|
||||
if (project.hasProperty('forgeMavenPass'))
|
||||
{
|
||||
repository(url: "http://files.minecraftforge.net/maven/manage/upload") {
|
||||
authentication(userName: "forge", password: project.getProperty('forgeMavenPass')) // the elvis operator. look it up.
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// local repo folder. Might wanna juset use gradle install if you wanans end it to maven-local
|
||||
repository(url: 'file://localhost/' + project.file('repo').getAbsolutePath())
|
||||
}
|
||||
|
||||
pom {
|
||||
groupId = project.group
|
||||
version = project.version
|
||||
artifactId = project.archivesBaseName
|
||||
project {
|
||||
name project.archivesBaseName
|
||||
packaging 'jar'
|
||||
description 'Minecraft Forge API'
|
||||
url 'https://github.com/MinecraftForge/MinecraftForge'
|
||||
|
||||
scm {
|
||||
url 'https://github.com/MinecraftForge/MinecraftForge'
|
||||
connection 'scm:git:git://github.com/MinecraftForge/MinecraftForge.git'
|
||||
developerConnection 'scm:git:[email protected]:MinecraftForge/MinecraftForge.git'
|
||||
}
|
||||
|
||||
issueManagement {
|
||||
system 'github'
|
||||
url 'https://github.com/MinecraftForge/MinecraftForge/issues'
|
||||
}
|
||||
|
||||
licenses {
|
||||
license {
|
||||
name 'Forge Public License'
|
||||
url 'https://raw.github.com/MinecraftForge/MinecraftForge/master/MinecraftForge-License.txt'
|
||||
distribution 'repo'
|
||||
}
|
||||
}
|
||||
|
||||
developers {
|
||||
developer {
|
||||
id 'cpw'
|
||||
name 'cpw'
|
||||
roles { role 'developer' }
|
||||
}
|
||||
developer {
|
||||
id 'LexManos'
|
||||
name 'Lex Manos'
|
||||
roles { role 'developer' }
|
||||
}
|
||||
developer {
|
||||
id 'AbrarSyed'
|
||||
name 'Abrar Syed'
|
||||
roles { role 'contributor' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HELPER METHODS
|
||||
|
||||
import groovy.json.JsonSlurper;
|
||||
|
||||
String getServerClasspath(File file)
|
||||
{
|
||||
def node = new JsonSlurper().parse(file);
|
||||
def out = new StringBuilder()
|
||||
node.versionInfo.libraries.each { lib ->
|
||||
if (lib.serverreq)
|
||||
{
|
||||
// group : artifact : version
|
||||
def split = lib.name.split(':')
|
||||
def group = split[0].replace('.', '/')
|
||||
def artifact = split[1]
|
||||
def version = split[2]
|
||||
out += "libraries/$group/$artifact/$version/$artifact-${version}.jar "
|
||||
}
|
||||
}
|
||||
out += "minecraft_server.${minecraft.version}.jar"
|
||||
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
String getVersionFromJava(File file)
|
||||
{
|
||||
String major = "0";
|
||||
String minor = "0";
|
||||
String revision = "0";
|
||||
String build = "0";
|
||||
|
||||
String prefix = "public static final int";
|
||||
file.eachLine{ String s ->
|
||||
s = s.trim();
|
||||
if (s.startsWith(prefix))
|
||||
{
|
||||
s = s.substring(prefix.length(), s.length() - 1);
|
||||
s = s.replace('=', ' ').replace("Version", "").replaceAll(" +", " ").trim();
|
||||
String[] pts = s.split(" ");
|
||||
|
||||
if (pts[0].equals("major")) major = pts[pts.length - 1];
|
||||
else if (pts[0] == "minor") minor = pts[pts.length - 1];
|
||||
else if (pts[0] == "revision") revision = pts[pts.length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
build = System.getenv("BUILD_NUMBER") ?: project.ext.properties.buildNumber ?: 0
|
||||
|
||||
|
||||
String branch = null;
|
||||
if (!System.getenv().containsKey("GIT_BRANCH"))
|
||||
{
|
||||
// TODO: use grgit - Tried to switch 07/07/16 - jgit broken on windows?
|
||||
branch = "git rev-parse --abbrev-ref HEAD".execute().text.trim()
|
||||
}
|
||||
else
|
||||
{
|
||||
branch = System.getenv("GIT_BRANCH");
|
||||
branch = branch.substring(branch.lastIndexOf('/') + 1);
|
||||
}
|
||||
|
||||
def out = "${minecraft.version.replace('-', '_')}-$major.$minor.$revision.$build"
|
||||
|
||||
if (branch && branch != 'master' && branch != 'HEAD' && branch != minecraft.version && branch != minecraft.version + '.0')
|
||||
{
|
||||
if (!(branch.endsWith('.x') && minecraft.version.startsWith(branch.substring(0, branch.length() -1))))
|
||||
out += "-$branch"
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
task resetBuildNumber << {
|
||||
project.ext.properties.buildNumber = 0;
|
||||
ciWriteBuildNumber.execute()
|
||||
}
|
||||
// re-add old tasks for jenkins compat
|
||||
// should be remvoed, and the jenkisn fixed when no longer building with FG 1.2
|
||||
task setupForge { dependsOn 'setup', 'ciWriteBuildNumber' }
|
||||
task buildPackages { dependsOn 'build' }
|
||||
|
||||
//Temporary hack to fix compile errors caused by mappings shading in Bootstrap
|
||||
/*
|
||||
task fixParams << {
|
||||
logger.lifecycle('Fixing param names!')
|
||||
def params = new File(extractMcpMappings.destinationDir, 'params.csv')
|
||||
def text = params.text
|
||||
text = text.replaceAll('p_180276_1_,biome,', 'p_180276_1_,biomeIn,')
|
||||
params.write(text)
|
||||
}
|
||||
fixParams.dependsOn('extractMcpMappings')
|
||||
extractMcpMappings.finalizedBy('fixParams')
|
||||
*/
|
||||
@@ -1,3 +0,0 @@
|
||||
/.gradle/
|
||||
/build/
|
||||
/out/
|
||||
@@ -1,13 +0,0 @@
|
||||
repositories {
|
||||
maven { url = 'https://maven.minecraftforge.net/' }
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.ow2.asm:asm:9.7'
|
||||
implementation 'org.ow2.asm:asm-tree:9.7'
|
||||
implementation 'net.minecraftforge:srgutils:0.5.10'
|
||||
implementation 'commons-io:commons-io:2.13.0'
|
||||
implementation 'com.google.code.gson:gson:2.10.1'
|
||||
implementation 'org.eclipse.jgit:org.eclipse.jgit:6.7.0.202309050840-r'
|
||||
}
|
||||
@@ -1,70 +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)
|
||||
}
|
||||
}
|
||||
|
||||
@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,52 +0,0 @@
|
||||
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
|
||||
import org.gradle.api.tasks.Internal
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
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.
|
||||
// This also shuts Gradle 8 up about implicit task dependencies.
|
||||
@Internal abstract RegularFileProperty getOutput()
|
||||
|
||||
BytecodeFinder() {
|
||||
output.convention(project.layout.buildDirectory.dir(name).map { it.file("output.json") })
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
protected void exec() {
|
||||
Util.init()
|
||||
|
||||
var outputFile = output.get().asFile
|
||||
if (outputFile.exists())
|
||||
outputFile.delete()
|
||||
|
||||
pre()
|
||||
|
||||
Util.processClassNodes(jar.get().asFile, this.&process)
|
||||
|
||||
post()
|
||||
outputFile.text = new JsonBuilder(getData()).toPrettyString()
|
||||
}
|
||||
|
||||
|
||||
protected process(ClassNode node) {
|
||||
if (node.fields !== null) node.fields.each { process(node, it) }
|
||||
if (node.methods !== null) node.methods.each { process(node, it) }
|
||||
}
|
||||
|
||||
protected pre() {}
|
||||
protected process(ClassNode parent, FieldNode node) {}
|
||||
protected process(ClassNode parent, MethodNode node) {}
|
||||
protected post() {}
|
||||
protected abstract Object getData()
|
||||
}
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.Internal
|
||||
import org.objectweb.asm.Opcodes
|
||||
import org.objectweb.asm.tree.ClassNode
|
||||
import org.objectweb.asm.tree.MethodNode
|
||||
|
||||
@CompileStatic
|
||||
abstract class BytecodePredicateFinder extends BytecodeFinder {
|
||||
|
||||
@Internal
|
||||
abstract Property<Closure<Boolean>> getPredicate()
|
||||
|
||||
private final Map<String, List<String>> matches = new TreeMap<>(Comparator.naturalOrder())
|
||||
|
||||
@Override
|
||||
protected process(ClassNode parent, MethodNode node) {
|
||||
for (final current : node.instructions) {
|
||||
if (predicate.get().call(parent, node, current)) {
|
||||
var methods = matches.compute(parent.name, { k, v -> v ?: new ArrayList<>() })
|
||||
|
||||
// only add non-synthetic methods
|
||||
if ((node.access & Opcodes.ACC_SYNTHETIC) == 0) {
|
||||
methods.add(node.name + node.desc)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Internal
|
||||
@Override
|
||||
protected Object getData() {
|
||||
var array = new ArrayList<Object>()
|
||||
matches.forEach { c, m ->
|
||||
{
|
||||
array.add(
|
||||
'class': c,
|
||||
'methods': m
|
||||
)
|
||||
}
|
||||
}
|
||||
return array ?: { throw new RuntimeException('Failed to find any targets, please ensure that method names and descriptors are correct.') }()
|
||||
}
|
||||
}
|
||||
@@ -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,33 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import groovy.transform.stc.FirstParam
|
||||
|
||||
import java.util.function.BiConsumer
|
||||
|
||||
public class ClosureHelper {
|
||||
BiConsumer<String, Closure> callback
|
||||
|
||||
public ClosureHelper(Closure cl, BiConsumer<String, Closure> callback) {
|
||||
this.callback = callback
|
||||
apply(this, cl)
|
||||
}
|
||||
|
||||
|
||||
def methodMissing(String name, Object args) {
|
||||
if (!args.class.isArray()) return
|
||||
Object[] aargs = (Object[])args
|
||||
|
||||
if (aargs.length == 1 && aargs[0] instanceof Closure) {
|
||||
this.callback.accept(name, (Closure)aargs[0])
|
||||
} else {
|
||||
throw new IllegalArgumentException('Unknown method: "' + name + '" with arguments ' + args + ' for ' + this)
|
||||
}
|
||||
}
|
||||
|
||||
static <T> T apply(T obj, @DelegatesTo(value = FirstParam, strategy = Closure.DELEGATE_FIRST) Closure cl) {
|
||||
cl.delegate = obj
|
||||
cl.resolveStrategy = Closure.DELEGATE_FIRST
|
||||
cl()
|
||||
return obj
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.file.DirectoryProperty
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.tasks.InputFile
|
||||
import org.gradle.api.tasks.Internal
|
||||
import org.gradle.api.tasks.OutputFile
|
||||
import org.gradle.api.tasks.OutputFiles
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.gradle.api.tasks.OutputDirectory
|
||||
|
||||
import java.nio.file.Files
|
||||
|
||||
abstract class DownloadLibraries extends DefaultTask {
|
||||
@InputFile abstract RegularFileProperty getInput()
|
||||
@OutputDirectory abstract DirectoryProperty getOutput()
|
||||
@OutputFile abstract RegularFileProperty getLibrariesOutput()
|
||||
|
||||
DownloadLibraries() {
|
||||
output.convention(project.layout.buildDirectory.dir(name))
|
||||
librariesOutput.convention(project.layout.buildDirectory.dir(name).map(d -> d.file('libraries.txt')))
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
def run() {
|
||||
Util.init()
|
||||
File outputDir = output.get().asFile
|
||||
var libraries = new ArrayList<String>()
|
||||
|
||||
def json = input.get().asFile.json().libraries.each { lib ->
|
||||
//TODO: Thread?
|
||||
def artifacts = [lib.downloads.artifact] + lib.downloads.get('classifiers', [:]).values()
|
||||
artifacts.each{ art ->
|
||||
def target = new File(outputDir, art.path)
|
||||
libraries.add(target.absolutePath)
|
||||
if (!target.exists() || art.sha1 != target.sha1()) {
|
||||
project.logger.lifecycle("Downloading ${art.url}")
|
||||
if (!target.parentFile.exists()) {
|
||||
target.parentFile.mkdirs()
|
||||
}
|
||||
new URL(art.url).withInputStream { i ->
|
||||
target.withOutputStream { it << i }
|
||||
}
|
||||
if (art.sha1 != target.sha1()) {
|
||||
throw new IllegalStateException("Failed to download ${art.url} to ${target.canonicalPath} SHA Mismatch")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Files.write(librariesOutput.get().asFile.toPath(), libraries)
|
||||
}
|
||||
}
|
||||
@@ -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,101 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.EqualsAndHashCode
|
||||
|
||||
import org.gradle.api.tasks.*
|
||||
import org.objectweb.asm.tree.AbstractInsnNode
|
||||
import org.objectweb.asm.tree.ClassNode
|
||||
import org.objectweb.asm.tree.MethodNode
|
||||
|
||||
import static org.objectweb.asm.Opcodes.*
|
||||
|
||||
abstract class FieldCompareFinder extends BytecodeFinder {
|
||||
@Nested
|
||||
Map<String, Search> fields = [:] as HashMap
|
||||
@Internal
|
||||
Map<Search, String> fieldsReverse = [:] as HashMap
|
||||
@Internal
|
||||
Map<String, Set<ObjectTarget>> targets = [:] as TreeMap
|
||||
|
||||
@Override
|
||||
protected pre() {
|
||||
//fields.each{ k,v -> logger.lifecycle("Fields: " + k + ' ' + v) }
|
||||
}
|
||||
|
||||
@Override
|
||||
protected process(ClassNode parent, MethodNode node) {
|
||||
AbstractInsnNode 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)) {
|
||||
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)))) {
|
||||
targets.computeIfAbsent(wanted, { k -> new TreeSet() }).add(instance)
|
||||
}
|
||||
}
|
||||
}
|
||||
last = current
|
||||
}
|
||||
}
|
||||
|
||||
@Internal
|
||||
@Override
|
||||
protected Object getData() {
|
||||
def ret = [:] as HashMap
|
||||
targets.forEach{ k, v ->
|
||||
def e = fields.get(k)
|
||||
ret[k] = [
|
||||
cls: e.cls,
|
||||
name: e.name,
|
||||
replacement: e.replacement,
|
||||
targets: v
|
||||
]
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
@CompileStatic
|
||||
@EqualsAndHashCode(excludes = ['replacement', 'blacklist'])
|
||||
static class Search {
|
||||
@Input
|
||||
String cls
|
||||
|
||||
@Input
|
||||
String name
|
||||
|
||||
@Input
|
||||
String replacement
|
||||
|
||||
@Nested
|
||||
@Optional
|
||||
Set<ObjectTarget> blacklist
|
||||
|
||||
@Override
|
||||
String toString() {
|
||||
return cls + '.' + name
|
||||
}
|
||||
|
||||
def blacklist(String owner, String name, String desc) {
|
||||
if (blacklist === null)
|
||||
blacklist = new HashSet<>()
|
||||
blacklist.add(new ObjectTarget(owner: owner, name: name, desc: desc))
|
||||
}
|
||||
def blacklist(String owner) {
|
||||
blacklist(owner, '', '')
|
||||
}
|
||||
}
|
||||
|
||||
void fields(Closure cl) {
|
||||
new ClosureHelper(cl, {name, ccl ->
|
||||
def search = ClosureHelper.apply(new Search(), ccl)
|
||||
this.fields.put(name, search)
|
||||
this.fieldsReverse.put(search, name)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.TupleConstructor
|
||||
|
||||
@CompileStatic
|
||||
@TupleConstructor
|
||||
final class InheritanceData implements Annotatable {
|
||||
String name
|
||||
int access
|
||||
String superName
|
||||
List<String> interfaces = []
|
||||
Map<String, Method> methods = [:]
|
||||
Map<String, Field> fields = [:]
|
||||
List<Annotation> annotations = []
|
||||
|
||||
@TupleConstructor
|
||||
static final class Method implements Annotatable {
|
||||
int access
|
||||
String override
|
||||
List<Annotation> annotations = []
|
||||
}
|
||||
|
||||
@TupleConstructor
|
||||
static final class Field implements Annotatable {
|
||||
int access
|
||||
String desc
|
||||
List<Annotation> annotations = []
|
||||
}
|
||||
|
||||
@TupleConstructor
|
||||
static final class Annotation {
|
||||
String desc
|
||||
}
|
||||
|
||||
private static final Gson GSON = new Gson()
|
||||
static Map<String, InheritanceData> parse(File file) {
|
||||
try (final reader = file.newReader()) {
|
||||
return GSON.fromJson(reader, new TypeToken<Map<String, InheritanceData>>() {})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@CompileStatic
|
||||
interface Annotatable {
|
||||
List<InheritanceData.Annotation> getAnnotations()
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
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
|
||||
|
||||
abstract class InstallerJar extends Zip {
|
||||
@Input @Optional abstract Property<Boolean> getFat()
|
||||
@Input @Optional abstract Property<Boolean> getOffline()
|
||||
|
||||
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)
|
||||
from(installerJson, launcherJson)
|
||||
|
||||
from(project.rootProject.file('/src/main/resources/url.png'))
|
||||
project.afterEvaluate {
|
||||
from(project.zipTree(downloadInstaller.output)) {
|
||||
duplicatesStrategy = 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)
|
||||
}
|
||||
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" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import groovy.json.JsonBuilder
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
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 final Map<String, Object> libraries = new LinkedHashMap<>()
|
||||
@Input Map<String, Object> json = new LinkedHashMap<>()
|
||||
@InputFile abstract RegularFileProperty getIcon()
|
||||
@Input abstract Property<String> getLauncherJsonName()
|
||||
@Input abstract Property<String> getLogo()
|
||||
@Input abstract Property<String> getMirrors()
|
||||
@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'))
|
||||
|
||||
project.afterEvaluate {
|
||||
[
|
||||
project.tasks.universalJar,
|
||||
project.tasks.serverShimJar
|
||||
].forEach { packed ->
|
||||
dependsOn(packed)
|
||||
input.from packed.archiveFile
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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,
|
||||
downloads: [
|
||||
artifact: [
|
||||
path: info.path,
|
||||
url: "https://maven.minecraftforge.net/$info.path",
|
||||
sha1: packed.archiveFile.get().asFile.sha1(),
|
||||
size: packed.archiveFile.get().asFile.length()
|
||||
]
|
||||
]
|
||||
])
|
||||
}
|
||||
json.libraries = libs.values().sort{a,b -> a.name.compareTo(b.name)}
|
||||
json.icon = "data:image/png;base64," + new String(Base64.getEncoder().encode(Files.readAllBytes(icon.get().asFile.toPath())))
|
||||
json.json = launcherJsonName.get()
|
||||
json.logo = logo.get()
|
||||
if (!mirrors.get().isEmpty())
|
||||
json.mirrorList = mirrors.get()
|
||||
json.welcome = welcome.get()
|
||||
|
||||
Files.writeString(output.get().getAsFile().toPath(), new JsonBuilder(json).toPrettyString())
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import groovy.json.JsonBuilder
|
||||
import org.gradle.api.DefaultTask
|
||||
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
|
||||
|
||||
import static net.minecraftforge.forge.tasks.Util.getArtifacts
|
||||
import static net.minecraftforge.forge.tasks.Util.iso8601Now
|
||||
|
||||
abstract class LauncherJson extends DefaultTask {
|
||||
@OutputFile abstract RegularFileProperty getOutput()
|
||||
@InputFiles abstract ConfigurableFileCollection getInput()
|
||||
@Input Map<String, Object> json = new LinkedHashMap<>()
|
||||
|
||||
LauncherJson() {
|
||||
output.convention(project.layout.buildDirectory.file('libs/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
|
||||
}
|
||||
|
||||
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()
|
||||
]
|
||||
]
|
||||
])
|
||||
|
||||
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())
|
||||
Files.writeString(output.get().asFile.toPath(), new JsonBuilder(json).toPrettyString())
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
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
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.tasks.InputFiles
|
||||
import org.gradle.api.tasks.OutputFile
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
@CompileStatic
|
||||
abstract class MergeJars extends DefaultTask {
|
||||
MergeJars() {
|
||||
output.convention(project.layout.buildDirectory.dir(name).map { it.file('output.jar') })
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
void run() {
|
||||
def jars = inputJars.files
|
||||
|
||||
try (ZipOutputStream zout = new ZipOutputStream(new FileOutputStream(output.get().asFile))) {
|
||||
for (def jar : jars) {
|
||||
try (ZipInputStream zin = new ZipInputStream(new FileInputStream(jar))) {
|
||||
ZipEntry entry
|
||||
while ((entry = zin.getNextEntry()) !== null) {
|
||||
ZipEntry _new = new ZipEntry(entry.getName())
|
||||
_new.setTime(0) //SHOULD be the same time as the main entry, but NOOOO _new.setTime(entry.getTime()) throws DateTimeException, so you get 0, screw you!
|
||||
zout.putNextEntry(_new)
|
||||
IOUtils.copy(zin, zout)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@InputFiles
|
||||
abstract ConfigurableFileCollection getInputJars()
|
||||
|
||||
@OutputFile
|
||||
abstract RegularFileProperty getOutput()
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import groovy.transform.EqualsAndHashCode
|
||||
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.Nested
|
||||
import org.gradle.api.tasks.Optional
|
||||
|
||||
@EqualsAndHashCode
|
||||
public class ObjectTarget implements Comparable<ObjectTarget> {
|
||||
@Input
|
||||
String owner
|
||||
|
||||
@Input
|
||||
String name
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
String desc
|
||||
|
||||
@Override
|
||||
String toString() {
|
||||
if (desc == null)
|
||||
return owner + '.' + name
|
||||
return owner + '.' + name + desc
|
||||
}
|
||||
|
||||
@Override
|
||||
int compareTo(ObjectTarget o) {
|
||||
return toString() <=> o.toString()
|
||||
}
|
||||
}
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.artifacts.Dependency
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.Optional
|
||||
import org.gradle.api.tasks.OutputFile
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.StandardCopyOption
|
||||
|
||||
abstract class SetupCheckJarCompatibility extends DefaultTask {
|
||||
SetupCheckJarCompatibility() {
|
||||
group = 'jar compatibility'
|
||||
onlyIf {
|
||||
inputVersion.getOrNull() !== null
|
||||
}
|
||||
outputs.upToDateWhen { false } // Never up to date, because this setup task should always run
|
||||
|
||||
baseBinPatchesOutput.convention(project.layout.buildDirectory.dir(name).map { it.file('joined.lzma') })
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
void run() {
|
||||
def inputVersion = inputVersion.get()
|
||||
|
||||
def baseForgeUserdev = project.layout.buildDirectory.dir(name).map { it.file("forge-${inputVersion}-userdev.jar") }.get().asFile
|
||||
project.rootProject.extensions.download.run {
|
||||
src "https://maven.minecraftforge.net/net/minecraftforge/forge/${inputVersion}/forge-${inputVersion}-userdev.jar"
|
||||
dest baseForgeUserdev
|
||||
}
|
||||
|
||||
def joinedLzma = project.zipTree(baseForgeUserdev).matching { it.include('joined.lzma') }.singleFile
|
||||
|
||||
Files.copy(joinedLzma.toPath(), baseBinPatchesOutput.get().asFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
||||
}
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
abstract Property<String> getInputVersion()
|
||||
|
||||
@OutputFile
|
||||
abstract RegularFileProperty getBaseBinPatchesOutput()
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import groovy.transform.CompileStatic
|
||||
import org.eclipse.jgit.api.Git
|
||||
|
||||
import javax.annotation.Nullable
|
||||
import java.util.stream.Collectors
|
||||
|
||||
@CompileStatic
|
||||
class TeamcityRequests {
|
||||
public static final Gson GSON = new Gson()
|
||||
|
||||
@Nullable
|
||||
static <T> T jsonRequest(TypeToken<T> clazz, String url) throws Exception {
|
||||
final HttpURLConnection conn = (HttpURLConnection) URI.create(url).toURL().openConnection()
|
||||
conn.setRequestProperty('Accept', 'application/json')
|
||||
conn.setReadTimeout(5 * 1000)
|
||||
conn.setConnectTimeout(5 * 1000)
|
||||
conn.connect()
|
||||
|
||||
if (conn.responseCode !== 200) {
|
||||
return null
|
||||
}
|
||||
|
||||
try (final InputStream is = conn.getInputStream()) {
|
||||
GSON.fromJson(new InputStreamReader(is), clazz.getType())
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static Map<String, Build> buildsByCommit() throws IOException {
|
||||
final Map<String, Build> builds = [:]
|
||||
jsonRequest(new TypeToken<Builds>() {}, 'https://teamcity.minecraftforge.net/guestAuth/app/rest/builds?fields=build:(revisions,number)&locator=buildType:(id:MinecraftForge_MinecraftForge_MinecraftForge_MinecraftForge__Build),defaultFilter:false,count:300,status:SUCCESS')
|
||||
?.build?.forEach {
|
||||
it.revisions.revision.each { rev -> builds[rev.version] = it }
|
||||
}
|
||||
return builds
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static String attemptFindBase(File gitPath) {
|
||||
try (final git = Git.open(gitPath)) {
|
||||
final Map<String, Build> buildByCommit = buildsByCommit()
|
||||
for (final commit in git.log().setMaxCount(100).call()) {
|
||||
// Find the first commit which was built on CI
|
||||
final build = buildByCommit[commit.id.name]
|
||||
if (build) {
|
||||
return build.number
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
return null
|
||||
}
|
||||
|
||||
static final class Builds {
|
||||
public List<Build> build
|
||||
}
|
||||
|
||||
static final class Build {
|
||||
public String number
|
||||
public Revisions revisions
|
||||
}
|
||||
|
||||
static final class Revisions {
|
||||
public int count
|
||||
public List<Revision> revision
|
||||
}
|
||||
|
||||
static final class Revision {
|
||||
public String version
|
||||
}
|
||||
}
|
||||
@@ -1,230 +0,0 @@
|
||||
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.security.MessageDigest
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.Semaphore
|
||||
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()
|
||||
|
||||
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('')
|
||||
}
|
||||
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.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]
|
||||
}
|
||||
|
||||
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)) {
|
||||
lst.add(0, tmp.substring(idx + del.length(), tmp.length()))
|
||||
tmp = tmp.substring(0, idx)
|
||||
}
|
||||
lst.add(0, tmp)
|
||||
return lst
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@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'
|
||||
|
||||
var sha1 = sha1(dep.file)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@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
|
||||
])
|
||||
}
|
||||
|
||||
@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) {
|
||||
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) {
|
||||
file.withInputStream { i ->
|
||||
new ZipInputStream(i).withCloseable { zin ->
|
||||
ZipEntry zein
|
||||
while ((zein = zin.nextEntry) !== null) {
|
||||
if (zein.name.endsWith('.class')) {
|
||||
var node = new ClassNode(ASM_LEVEL)
|
||||
new ClassReader(zin).accept(node, 0)
|
||||
process(node)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
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
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.InputFile
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.objectweb.asm.tree.AnnotationNode
|
||||
import org.objectweb.asm.tree.ClassNode
|
||||
|
||||
abstract class ValidateDeprecations extends DefaultTask {
|
||||
@InputFile
|
||||
abstract RegularFileProperty getInput()
|
||||
|
||||
@Input
|
||||
abstract Property<String> getMcVersion()
|
||||
|
||||
ValidateDeprecations() {
|
||||
this.onlyIf { !System.env.TEAMCITY_VERSION }
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
protected void exec() {
|
||||
var mcVer = MinecraftVersion.from(mcVersion.get())
|
||||
List<String> errors = []
|
||||
|
||||
Util.processClassNodes(input.get().asFile) {
|
||||
processNode(mcVer, errors, it)
|
||||
}
|
||||
|
||||
if (!errors.isEmpty()) {
|
||||
errors.forEach {
|
||||
this.logger.error("Deprecated ${it[0]} is marked for removal in ${it[1]} but is not yet removed")
|
||||
}
|
||||
throw new IllegalStateException("Found deprecated members marked for removal but not yet removed in ${mcVer}; see log for details")
|
||||
}
|
||||
}
|
||||
|
||||
protected processNode(MinecraftVersion mcVer, List<String> errors, ClassNode node) {
|
||||
node.visibleAnnotations?.each { annotation ->
|
||||
ValidateDeprecations.processAnnotations(annotation, mcVer, errors) {
|
||||
"class ${node.name}"
|
||||
}
|
||||
}
|
||||
node.fields?.each { field ->
|
||||
field.visibleAnnotations?.each { annotation ->
|
||||
ValidateDeprecations.processAnnotations(annotation, mcVer, errors) {
|
||||
"field ${node.name}#${field.name}"
|
||||
}
|
||||
}
|
||||
}
|
||||
node.methods?.each { method ->
|
||||
method.visibleAnnotations?.each { annotation ->
|
||||
ValidateDeprecations.processAnnotations(annotation, mcVer, errors) {
|
||||
"method ${node.name}#${method.name}${method.desc}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void processAnnotations(AnnotationNode annotation, MinecraftVersion mcVer, List<String> errors, Closure<String> context) {
|
||||
def values = annotation.values
|
||||
if (values === null)
|
||||
return
|
||||
int forRemoval = values.indexOf('forRemoval')
|
||||
int since = values.indexOf('since')
|
||||
if (annotation.desc == 'Ljava/lang/Deprecated;' && forRemoval !== -1 && since !== -1 && values.size() >= 4 && values[forRemoval + 1] === true) {
|
||||
def oldVersion = MinecraftVersion.from(values[since + 1])
|
||||
int[] split = ValidateDeprecations.splitDots(oldVersion.toString())
|
||||
if (split.length < 2)
|
||||
return
|
||||
def removeVersion = MinecraftVersion.from("${split[0]}.${split[1] + 1}")
|
||||
if (removeVersion <= mcVer)
|
||||
errors.add([context(), removeVersion])
|
||||
}
|
||||
}
|
||||
|
||||
@CompileStatic
|
||||
private static int[] splitDots(String version) {
|
||||
String[] pts = version.split('\\.')
|
||||
int[] values = new int[pts.length]
|
||||
for (int x = 0; x < pts.length; x++)
|
||||
values[x] = Integer.parseInt(pts[x])
|
||||
return values
|
||||
}
|
||||
}
|
||||
@@ -1,310 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks.checks
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.TupleConstructor
|
||||
import net.minecraftforge.forge.tasks.InheritanceData
|
||||
import net.minecraftforge.srgutils.IMappingFile
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.file.RegularFile
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.tasks.InputFile
|
||||
import org.gradle.api.tasks.InputFiles
|
||||
import org.gradle.api.tasks.Optional
|
||||
import org.objectweb.asm.Opcodes
|
||||
|
||||
@CompileStatic
|
||||
abstract class CheckATs extends CheckTask {
|
||||
|
||||
@InputFile abstract RegularFileProperty getInheritance()
|
||||
@InputFiles abstract ConfigurableFileCollection getAts()
|
||||
@InputFile @Optional abstract RegularFileProperty getMappings()
|
||||
|
||||
@Override
|
||||
void check(Reporter reporter, boolean fix) {
|
||||
final IMappingFile mappings = mappings.map { RegularFile it -> IMappingFile.load(it.asFile) }.getOrNull()
|
||||
final inheritance = InheritanceData.parse(this.inheritance.get().asFile)
|
||||
|
||||
ats.each {
|
||||
final lines = process(it, reporter, inheritance)
|
||||
if (fix) {
|
||||
it.text = joinBack(lines, inheritance, mappings).join('\n')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static TreeMap<String, ATParser.Entry> process(File file, Reporter reporter, Map<String, InheritanceData> inheritance) {
|
||||
final TreeMap<String, ATParser.Entry> lines = ATParser.parse(file.readLines(), reporter)
|
||||
final Map<String, ATParser.Entry> constructorGroups = [:]
|
||||
|
||||
final itr = lines.entrySet().iterator()
|
||||
final toRemove = []
|
||||
while (itr.hasNext()) {
|
||||
final next = itr.next()
|
||||
String key = next.key
|
||||
final entry = next.value
|
||||
if (entry === null) continue
|
||||
|
||||
final binaryName = entry.cls.replaceAll('\\.', '/')
|
||||
|
||||
// Process Groups, this will remove any entries outside the group that is covered by the group
|
||||
if (entry.group) {
|
||||
final jcls = inheritance[binaryName]
|
||||
if (jcls === null) {
|
||||
itr.remove()
|
||||
reporter.report("Invalid group: $key")
|
||||
} else if ('*' == entry.desc) {
|
||||
if (!jcls.fields) {
|
||||
itr.remove()
|
||||
reporter.report("Invalid group, class has no fields: $key")
|
||||
} else {
|
||||
jcls.fields.each { field, value ->
|
||||
final fkey = entry.cls + ' ' + field
|
||||
if (accessLevel(value.access) < accessStr(entry.modifier)) {
|
||||
if (lines.containsKey(fkey)) {
|
||||
toRemove.add(fkey)
|
||||
} else if (!entry.existing.contains(fkey)) {
|
||||
reporter.report("Missing group entry: $fkey")
|
||||
}
|
||||
entry.children.add(fkey)
|
||||
} else if (lines.containsKey(fkey)) {
|
||||
toRemove.add(fkey)
|
||||
reporter.report("Found invalid group entry: $fkey")
|
||||
}
|
||||
}
|
||||
entry.existing.findAll { it !in entry.children }.each { println('Removed: ' + it) }
|
||||
}
|
||||
} else if ('*()' == entry.desc) {
|
||||
if (!jcls.methods) {
|
||||
itr.remove()
|
||||
reporter.report("Invalid group, class has no methods: $key")
|
||||
} else {
|
||||
jcls.methods.each { mtd, value ->
|
||||
if (mtd.startsWith('<clinit>') || mtd.startsWith('lambda$')) return
|
||||
key = entry.cls + ' ' + mtd.replace(' ', '')
|
||||
if (accessLevel(value.access) < accessStr(entry.modifier)) {
|
||||
if (lines.containsKey(key)) {
|
||||
toRemove.add(key)
|
||||
} else if (!entry.existing.contains(key)) {
|
||||
reporter.report("Missing group entry: $key")
|
||||
}
|
||||
entry.children.add(key)
|
||||
} else if (lines.containsKey(key)) {
|
||||
toRemove.add(key)
|
||||
reporter.report("Found invalid group entry: $key")
|
||||
}
|
||||
}
|
||||
entry.existing.findAll { it !in entry.children }.each { println('Removed: ' + it) }
|
||||
}
|
||||
} else if ('<init>' == entry.desc) { //Make all public non-abstract subclasses
|
||||
constructorGroups.put(binaryName, entry)
|
||||
}
|
||||
}
|
||||
|
||||
// Process normal lines, remove invalid and remove narrowing
|
||||
else {
|
||||
def jcls = inheritance.get(binaryName)
|
||||
if (jcls === null) {
|
||||
itr.remove()
|
||||
reporter.report("Invalid: $key")
|
||||
} else if (entry.desc == '') {
|
||||
if (accessLevel(jcls.access) > accessStr(entry.modifier) && (entry.comment === null || !entry.comment.startsWith('#force '))) {
|
||||
itr.remove()
|
||||
reporter.report("Invalid Narrowing: $key")
|
||||
}
|
||||
} else if (!entry.desc.contains('(')) {
|
||||
if (!jcls.fields || !jcls.fields.containsKey(entry.desc)) {
|
||||
itr.remove()
|
||||
reporter.report("Invalid: $key")
|
||||
} else {
|
||||
final value = jcls.fields[entry.desc]
|
||||
if (accessLevel(value.access) > accessStr(entry.modifier) && (entry.comment === null || !entry.comment.startsWith('#force '))) {
|
||||
itr.remove()
|
||||
reporter.report("Invalid Narrowing: $key - ${entry.comment}")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
final jdesc = entry.desc.replace('(', ' (')
|
||||
if (!jcls.methods || !jcls.methods.containsKey(jdesc)) {
|
||||
itr.remove()
|
||||
reporter.report("Invalid: $key")
|
||||
} else {
|
||||
final value = jcls.methods[jdesc]
|
||||
if (accessLevel(value.access) > accessStr(entry.modifier) && (entry.comment === null || !entry.comment.startsWith('#force '))) {
|
||||
itr.remove()
|
||||
reporter.report("Invalid Narrowing: $key")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inheritance.each { tcls, value ->
|
||||
if (!value.methods || ((value.access & Opcodes.ACC_ABSTRACT) !== 0)) return
|
||||
String parent = tcls
|
||||
while (parent !== null) {
|
||||
constructorGroups[parent]?.tap { entry ->
|
||||
value.methods.each { mtd, v ->
|
||||
if (mtd.startsWith('<init>')) {
|
||||
final child = tcls.replaceAll('/', '\\.') + ' ' + mtd.replace(' ', '')
|
||||
if (accessLevel(v.access) < 3) {
|
||||
if (lines.containsKey(child)) {
|
||||
toRemove.add(child)
|
||||
} else if (child !in entry.existing) {
|
||||
reporter.report("Missing group entry: $child")
|
||||
}
|
||||
entry.children.add(child)
|
||||
} else if (lines.containsKey(child)) {
|
||||
toRemove.add(child)
|
||||
reporter.report("Found invalid group entry: $child")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
parent = inheritance[parent]?.superName
|
||||
}
|
||||
}
|
||||
constructorGroups.values().each { entry -> entry.existing.findAll { it !in entry.children }.each{ reporter.report("Found invalid group entry: $it") } }
|
||||
|
||||
toRemove.each(lines.&remove)
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
private static List<String> joinBack(TreeMap<String, ATParser.Entry> lines, Map<String, InheritanceData> inheritance, IMappingFile mappings) {
|
||||
final data = [] as List<String>
|
||||
final remapComment = { ATParser.Entry entry ->
|
||||
if (!mappings || !entry || !entry.desc) return null
|
||||
final comment = entry.comment?.substring(1)?.trim()
|
||||
final jsonCls = inheritance.get(entry.cls.replaceAll('\\.', '/'))
|
||||
final mappingsClass = mappings?.getClass(jsonCls.name)
|
||||
if (mappingsClass === null) return entry.comment
|
||||
final idx = entry.desc.indexOf('(')
|
||||
|
||||
String mappedName = idx == -1
|
||||
? mappingsClass.remapField(entry.desc)
|
||||
: mappingsClass.remapMethod(entry.desc.substring(0, idx), entry.desc.substring(idx))
|
||||
if (!mappedName) return entry.comment
|
||||
if (mappedName == '<init>')
|
||||
mappedName = 'constructor'
|
||||
|
||||
if (comment?.startsWith(mappedName))
|
||||
return '# ' + comment
|
||||
if (comment && comment.indexOf(' ') !== -1) {
|
||||
def split = comment.split(' - ').toList()
|
||||
if (split[0].indexOf(' ') !== -1)
|
||||
// The first string is more than one word, so append before it
|
||||
return "# ${mappedName} - ${comment}"
|
||||
split.remove(0)
|
||||
return "# ${mappedName} - ${String.join(' - ', split)}"
|
||||
}
|
||||
return '# ' + mappedName
|
||||
}
|
||||
lines.each { key, value ->
|
||||
if (!value.group) {
|
||||
def comment = remapComment.call(value)
|
||||
data.add(value.modifier + ' ' + key + (comment ? ' ' + comment : ''))
|
||||
} else {
|
||||
data.add(('#group ' + value.modifier + ' ' + key + ' ' + (value.comment ?: '')).trim())
|
||||
value.children.each {
|
||||
final line = value.modifier + ' ' + it
|
||||
final entry = ATParser.parseEntry(line)
|
||||
final comment = remapComment(entry)
|
||||
data.add(line + (comment ? ' ' + comment : ''))
|
||||
}
|
||||
data.add('#endgroup')
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
static int accessStr(String access) {
|
||||
if (access.endsWith('-f') || access.endsWith('+f')) return 4
|
||||
switch (access.toLowerCase()) {
|
||||
case 'public': return 3
|
||||
case 'protected': return 2
|
||||
case 'default': return 1
|
||||
case 'private': return 0
|
||||
default: return -1
|
||||
}
|
||||
}
|
||||
|
||||
static int accessLevel(int access) {
|
||||
if ((access & Opcodes.ACC_PUBLIC) !== 0) return 3
|
||||
if ((access & Opcodes.ACC_PROTECTED) !== 0) return 2
|
||||
if ((access & Opcodes.ACC_PRIVATE) !== 0) return 0
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
@CompileStatic
|
||||
class ATParser {
|
||||
static TreeMap<String, Entry> parse(List<String> lines, CheckTask.Reporter reporter) {
|
||||
TreeMap<String, Entry> outLines = new TreeMap<>()
|
||||
Entry group = null
|
||||
for (final line : lines) {
|
||||
if (line.isEmpty()) continue
|
||||
if (line.startsWith('#group ')) {
|
||||
final entry = parseEntry(line.substring(7))
|
||||
|
||||
if (entry.desc != '*' && entry.desc != '*()' && entry.desc != '<init>') {
|
||||
reporter.report("Invalid group: $line", false)
|
||||
}
|
||||
|
||||
entry.group = true
|
||||
entry.children = []
|
||||
entry.existing = []
|
||||
|
||||
group = entry
|
||||
|
||||
if (outLines.containsKey(entry.key)) {
|
||||
reporter.report("Duplicate group: $line", false)
|
||||
}
|
||||
|
||||
outLines[entry.key] = group
|
||||
} else if (group !== null) {
|
||||
if (line.startsWith('#endgroup')) {
|
||||
group = null
|
||||
} else {
|
||||
final key = parseEntry(line).key
|
||||
group.existing.add(key)
|
||||
}
|
||||
} else if (line.startsWith('#endgroup')) {
|
||||
reporter.report("Invalid group ending: $line", false)
|
||||
} else if (line.startsWith('#')) {
|
||||
//Nom
|
||||
} else {
|
||||
final entry = parseEntry(line)
|
||||
if (outLines.containsKey(entry.key)) {
|
||||
reporter.report("Found duplicate: $line")
|
||||
continue
|
||||
}
|
||||
outLines[entry.key] = entry
|
||||
}
|
||||
}
|
||||
return outLines
|
||||
}
|
||||
|
||||
static Entry parseEntry(String line) {
|
||||
final idx = line.indexOf('#')
|
||||
final String comment = idx === -1 ? null : line.substring(idx)
|
||||
if (idx !== -1) line = line.substring(0, idx - 1)
|
||||
final data = (line.trim() + ' ').split(' ', -1)
|
||||
new Entry(data[0], data[1], data[2], comment)
|
||||
}
|
||||
|
||||
@TupleConstructor
|
||||
static final class Entry {
|
||||
String modifier, cls, desc, comment
|
||||
|
||||
Set<String> existing
|
||||
TreeSet<String> children
|
||||
boolean group = false
|
||||
|
||||
@Lazy
|
||||
String key = {cls + (desc.isEmpty() ? '' : ' ' + desc)}()
|
||||
|
||||
Object getAt(String key) {
|
||||
return getProperty(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks.checks
|
||||
|
||||
import net.minecraftforge.forge.tasks.Util
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.tasks.InputFile
|
||||
import org.gradle.api.tasks.InputFiles
|
||||
import org.objectweb.asm.ClassReader
|
||||
import org.objectweb.asm.ClassVisitor
|
||||
import org.objectweb.asm.MethodVisitor
|
||||
import org.objectweb.asm.Type
|
||||
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
abstract class CheckExcs extends CheckTask {
|
||||
|
||||
@InputFile abstract RegularFileProperty getBinary()
|
||||
@InputFiles abstract ConfigurableFileCollection getExcs()
|
||||
|
||||
@Override
|
||||
void check(Reporter reporter, boolean fix) {
|
||||
final Set<String> known = []
|
||||
collectKnown(known)
|
||||
|
||||
excs.each { f ->
|
||||
final lines = []
|
||||
f.eachLine { line ->
|
||||
int idx = line.indexOf('#')
|
||||
if (idx === 0 || line.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (idx !== -1) line = line.substring(0, idx - 1)
|
||||
|
||||
if (!line.contains('=')) {
|
||||
reporter.report("Invalid: $line")
|
||||
return
|
||||
}
|
||||
|
||||
def (String key, String value) = line.split('=', 2)
|
||||
if (!known.contains(key)) {
|
||||
reporter.report("Unknown: $line")
|
||||
return
|
||||
}
|
||||
|
||||
String desc = key.split('\\.', 2)[1]
|
||||
if (!desc.contains('(')) {
|
||||
reporter.report("Invalid: $line")
|
||||
return
|
||||
}
|
||||
desc = '(' + desc.split('\\(', 2)[1]
|
||||
|
||||
def (exceptions, String args) = value.contains('|') ? value.split('|', 2) : [value, '']
|
||||
|
||||
if (args.split(',').length !== Type.getArgumentTypes(desc).length) {
|
||||
reporter.report("Invalid: $line")
|
||||
return
|
||||
}
|
||||
lines.add(line)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (fix) f.text = lines.sort().join('\n')
|
||||
}
|
||||
}
|
||||
|
||||
private void collectKnown(Collection<String> known) {
|
||||
binary.get().asFile.withInputStream { i ->
|
||||
new ZipInputStream(i).withCloseable { zin ->
|
||||
final visitor = new ClassVisitor(Util.ASM_LEVEL) {
|
||||
private String cls
|
||||
@Override
|
||||
void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
|
||||
this.cls = name
|
||||
}
|
||||
|
||||
@Override
|
||||
MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
|
||||
known.add(this.cls + '.' + name + descriptor)
|
||||
super.visitMethod(access, name, descriptor, signature, exceptions)
|
||||
}
|
||||
}
|
||||
ZipEntry zein
|
||||
while ((zein = zin.nextEntry) !== null) {
|
||||
if (zein.name.endsWith('.class')) {
|
||||
ClassReader reader = new ClassReader(zin)
|
||||
reader.accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks.checks
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
|
||||
@CompileStatic
|
||||
enum CheckMode {
|
||||
CHECK,
|
||||
FIX
|
||||
|
||||
CheckMode() {}
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks.checks
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import org.gradle.api.file.DirectoryProperty
|
||||
import org.gradle.api.provider.ListProperty
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.InputDirectory
|
||||
import org.gradle.api.tasks.Optional
|
||||
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.util.regex.Pattern
|
||||
|
||||
@CompileStatic
|
||||
abstract class CheckPatches extends CheckTask {
|
||||
|
||||
private static final Pattern HUNK_START_PATTERN = Pattern.compile('^@@ -[0-9,]* \\+[0-9,_]* @@$')
|
||||
private static final Pattern WHITESPACE_PATTERN = Pattern.compile('^[+\\-]\\s*$')
|
||||
private static final Pattern IMPORT_PATTERN = Pattern.compile('^[+\\-]\\s*import.*')
|
||||
private static final Pattern FIELD_PATTERN = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final)?([^=;]*)(=.*)?;\\s*$')
|
||||
private static final Pattern METHOD_PATTERN = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final)?([^(]*)[(]([^)]*)?[)]\\s*[{]\\s*$')
|
||||
private static final Pattern CLASS_PATTERN = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final[\\s]*)?(class|interface)([^{]*)[{]\\s*$')
|
||||
private static final Map<String, Integer> ACCESS_MAP = [private: 0, protected: 2, public: 3].tap { it.put(null, 1) }
|
||||
|
||||
@InputDirectory abstract DirectoryProperty getPatchDir()
|
||||
@Input @Optional abstract ListProperty<String> getPatchesWithS2SArtifact()
|
||||
|
||||
@Override
|
||||
void check(Reporter reporter, boolean fix) {
|
||||
final patchDir = getPatchDir().get().asFile.toPath()
|
||||
Files.walk(patchDir).withCloseable {
|
||||
it.filter(Files.&isRegularFile).forEach { path ->
|
||||
final String relativeName = patchDir.relativize(path).toString()
|
||||
verifyPatch(path, reporter, fix, relativeName, patchesWithS2SArtifact.get().contains(relativeName.replace('\\', '/')))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static boolean accessChange(String previous, String 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 lines = Files.readAllLines(patch)
|
||||
|
||||
int hunksStart = 0
|
||||
boolean onlyWhiteSpace = false
|
||||
|
||||
final List<String> newLines = []
|
||||
|
||||
// First two lines are file name ++/-- and we do not care
|
||||
newLines.add(lines[0] + '\n')
|
||||
newLines.add(lines[1] + '\n')
|
||||
|
||||
int i
|
||||
for (i = 2; i < lines.size(); ++i) {
|
||||
def line = lines[i]
|
||||
newLines.add(line + '\n')
|
||||
|
||||
if (HUNK_START_PATTERN.matcher(line).find()) {
|
||||
if (onlyWhiteSpace) {
|
||||
if (!hasS2SArtifact)
|
||||
reporter.report("Patch contains only white space hunk starting at line ${hunksStart + 1}, file: $patchPath")
|
||||
int toRemove = i - hunksStart
|
||||
while (toRemove-- > 0)
|
||||
newLines.remove(newLines.size() - 1)
|
||||
}
|
||||
hunksStart = i
|
||||
onlyWhiteSpace = true
|
||||
continue
|
||||
}
|
||||
|
||||
if (line.startsWithAny('+','-')) {
|
||||
def prefixChange = false
|
||||
def prevLine = lines[i - 1]
|
||||
|
||||
if (line.charAt(0) == (char)'+' && prevLine.charAt(0) == (char)'-') {
|
||||
def prevTrim = prevLine.substring(1).replaceAll("\\s", "")
|
||||
def currTrim = line.substring(1).replaceAll("\\s", "")
|
||||
|
||||
if (prevTrim == currTrim) {
|
||||
prefixChange = true
|
||||
}
|
||||
|
||||
def pMatcher = FIELD_PATTERN.matcher(prevLine)
|
||||
def cMatcher = FIELD_PATTERN.matcher(line)
|
||||
|
||||
if (pMatcher.find() && cMatcher.find() &&
|
||||
pMatcher.group(6) == cMatcher.group(6) && // = ...
|
||||
pMatcher.group(5) == cMatcher.group(5) && // field name
|
||||
pMatcher.group(3) == cMatcher.group(3) && // static
|
||||
(accessChange(pMatcher.group(2), cMatcher.group(2)) || pMatcher.group(4) != cMatcher.group(4))) {
|
||||
reporter.report("Patch contains access changes or final removal at line ${i + 1}, file: $patchPath", false)
|
||||
}
|
||||
|
||||
pMatcher = METHOD_PATTERN.matcher(prevLine)
|
||||
cMatcher = METHOD_PATTERN.matcher(line)
|
||||
|
||||
if (pMatcher.find() && cMatcher.find() &&
|
||||
pMatcher.group(6) == cMatcher.group(6) && // params
|
||||
pMatcher.group(5) == cMatcher.group(5) && // <T> void name
|
||||
pMatcher.group(3) == cMatcher.group(3) && // static
|
||||
(accessChange(pMatcher.group(2), cMatcher.group(2)) || pMatcher.group(4) != cMatcher.group(4))) {
|
||||
reporter.report("Patch contains access changes or final removal at line ${i + 1}, file: $patchPath", false)
|
||||
}
|
||||
|
||||
pMatcher = CLASS_PATTERN.matcher(prevLine)
|
||||
cMatcher = CLASS_PATTERN.matcher(line)
|
||||
|
||||
if (pMatcher.find() && cMatcher.find() &&
|
||||
pMatcher.group(6) == cMatcher.group(6) && // ClassName<> extends ...
|
||||
pMatcher.group(5) == cMatcher.group(5) && // class | interface
|
||||
pMatcher.group(3) == cMatcher.group(3) && // static
|
||||
(accessChange(pMatcher.group(2), cMatcher.group(2)) || pMatcher.group(4) != cMatcher.group(4))) {
|
||||
reporter.report("Patch contains access changes or final removal at line ${i + 1}, file: $patchPath", false)
|
||||
}
|
||||
}
|
||||
|
||||
if (line.charAt(0) == (char)'-' && i + 1 < lines.size()) {
|
||||
final nextLine = lines[i + 1]
|
||||
if (nextLine.charAt(0) == (char)'+') {
|
||||
final nextTrim = nextLine.substring(1).replaceAll("\\s", "")
|
||||
final currTrim = line.substring(1).replaceAll("\\s", "")
|
||||
|
||||
if (nextTrim == currTrim) {
|
||||
prefixChange = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final isWhiteSpaceChange = WHITESPACE_PATTERN.matcher(line).find()
|
||||
|
||||
if (!prefixChange && !isWhiteSpaceChange) {
|
||||
onlyWhiteSpace = hasS2SArtifact && IMPORT_PATTERN.matcher(line).find()
|
||||
} else if (isWhiteSpaceChange) {
|
||||
final prevLineChange = prevLine.startsWithAny('+','-')
|
||||
final nextLineChange = i + 1 < lines.size() && lines[i + 1].startsWithAny('+','-')
|
||||
|
||||
if (!prevLineChange && !nextLineChange) {
|
||||
reporter.report("Patch contains white space change in valid hunk at line ${i + 1}, file: $patchPath \n$prevLine\n$line\n${lines[i+1]}", false)
|
||||
}
|
||||
}
|
||||
|
||||
if (line.contains('\t')) {
|
||||
reporter.report("Patch contains tabs on line ${i + 1}, file: $patchPath")
|
||||
line = line.replaceAll('\t', ' ')
|
||||
newLines.remove(newLines.size() - 1)
|
||||
newLines.add(line + '\n')
|
||||
}
|
||||
|
||||
if (IMPORT_PATTERN.matcher(line).find() && !hasS2SArtifact) {
|
||||
reporter.report("Patch contains import change on line ${i + 1}, file: $patchPath", false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (onlyWhiteSpace) {
|
||||
if (!hasS2SArtifact)
|
||||
reporter.report("Patch contains only white space hunk starting at line ${hunksStart + 1}, file: $patchPath")
|
||||
def toRemove = i - hunksStart;
|
||||
while (toRemove-- > 0)
|
||||
newLines.remove(newLines.size() - 1)
|
||||
}
|
||||
|
||||
if ((reporter.fixed.size() > oldFixedErrors && fix) || hasS2SArtifact) {
|
||||
if (newLines.size() <= 2) {
|
||||
logger.lifecycle("Patch is now empty removing, file: {}", patchPath)
|
||||
Files.delete(patch)
|
||||
}
|
||||
else {
|
||||
if (!hasS2SArtifact)
|
||||
logger.lifecycle("*** Updating patch file. Please run setup then genPatches again. ***")
|
||||
Files.newBufferedWriter(patch).withCloseable {
|
||||
newLines.each { l -> it.write(l) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks.checks
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import net.minecraftforge.forge.tasks.Annotatable
|
||||
import net.minecraftforge.forge.tasks.InheritanceData
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.tasks.InputFile
|
||||
import org.gradle.api.tasks.InputFiles
|
||||
|
||||
@CompileStatic
|
||||
abstract class CheckSAS extends CheckTask {
|
||||
|
||||
@InputFile abstract RegularFileProperty getInheritance()
|
||||
@InputFiles abstract ConfigurableFileCollection getSass()
|
||||
|
||||
@Override
|
||||
void check(Reporter reporter, boolean fix) {
|
||||
final inheritance = InheritanceData.parse(this.inheritance.get().asFile)
|
||||
|
||||
sass.each { f ->
|
||||
final lines = []
|
||||
f.eachLine { line ->
|
||||
if (line[0] == '\t') return // Skip any tabbed lines, those are ones we add
|
||||
final idx = line.indexOf('#')
|
||||
if (idx == 0 || line.isEmpty()) {
|
||||
lines.add(line)
|
||||
return
|
||||
}
|
||||
def comment = idx == -1 ? null : line.substring(idx)
|
||||
if (idx != -1) line = line.substring(0, idx - 1)
|
||||
final spl = (line.trim().replace('(', ' (') + ' ').split(' ', -1)
|
||||
def (String cls, String name, String desc) = [spl[0], spl[1], spl[2]]
|
||||
cls = cls.replaceAll('\\.', '/')
|
||||
|
||||
if (inheritance[cls] === null) {
|
||||
reporter.report("Invalid: $line")
|
||||
} else if (name.isEmpty()) { //Class SAS
|
||||
final toAdd = []
|
||||
final clsSided = isSided(inheritance[cls])
|
||||
boolean sided = clsSided
|
||||
|
||||
/* TODO: MergeTool doesn't do fields
|
||||
if (json[cls]['fields'] != null) {
|
||||
for (entry in json[cls]['fields']) {
|
||||
if (isSided(entry.value)) {
|
||||
sided = true
|
||||
toAdd.add('\t' + cls + ' ' + entry.key)
|
||||
}
|
||||
}
|
||||
} */
|
||||
|
||||
final clsInh = inheritance[cls]
|
||||
if (clsInh.methods) {
|
||||
for (entry in clsInh.methods) {
|
||||
if (isSided(entry.value)) {
|
||||
sided = true
|
||||
toAdd.add('\t' + cls + ' ' + entry.key.replaceAll(' ', ''))
|
||||
findChildMethods(inheritance, cls, entry.key).each { lines.add('\t' + it) }
|
||||
findChildMethods(inheritance, cls, entry.key).each { println(line + ' -- ' + it) }
|
||||
} else if (clsSided) {
|
||||
findChildMethods(inheritance, cls, entry.key).each { lines.add('\t' + it) }
|
||||
findChildMethods(inheritance, cls, entry.key).each { println(line + ' -- ' + it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sided) {
|
||||
lines.add(cls + (comment == null ? '' : ' ' + comment))
|
||||
lines.addAll(toAdd.sort())
|
||||
} else {
|
||||
reporter.report("Invalid: $line")
|
||||
}
|
||||
|
||||
} else if (desc.isEmpty()) { // Fields
|
||||
/* TODO: MergeTool doesn't do fields
|
||||
if (json[cls]['fields'] != null && isSided(json[cls]['fields'][name]))
|
||||
lines.add(cls + ' ' + name + (comment == null ? '' : ' ' + comment))
|
||||
else */
|
||||
reporter.report("Invalid: $line")
|
||||
} else { // Methods
|
||||
final clsInh = inheritance[cls]
|
||||
if (clsInh.methods === null || !isSided(clsInh.methods[name + ' ' + desc]))
|
||||
reporter.report("Invalid: $line")
|
||||
else {
|
||||
lines.add(cls + ' ' + name + desc + (comment == null ? '' : ' ' + comment))
|
||||
findChildMethods(inheritance, cls, name + ' ' + desc).each { println(line + ' -- ' + it) }
|
||||
findChildMethods(inheritance, cls, name + ' ' + desc).each { lines.add('\t' + it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fix) f.text = lines.join('\n')
|
||||
}
|
||||
}
|
||||
|
||||
protected static boolean isSided(Annotatable annotatable) {
|
||||
if (annotatable === null) return false
|
||||
for (ann in annotatable.annotations) {
|
||||
if ('Lnet/minecraftforge/api/distmarker/OnlyIn;' == ann.desc)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
protected static findChildMethods(Map<String, InheritanceData> json, String cls, String desc) {
|
||||
return json.values().findAll{ it.methods !== null && it.methods[desc] !== null && it.methods[desc].override == cls && isSided(it.methods[desc]) }
|
||||
.collect { it.name + ' ' + desc.replace(' ', '') } as TreeSet
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
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
|
||||
import org.gradle.api.logging.LogLevel
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.gradle.api.tasks.TaskContainer
|
||||
import org.gradle.api.tasks.VerificationTask
|
||||
|
||||
@CompileStatic
|
||||
abstract class CheckTask extends DefaultTask implements VerificationTask {
|
||||
@Input
|
||||
abstract Property<CheckMode> getMode()
|
||||
|
||||
private boolean ignoreFailures = false
|
||||
|
||||
@Input
|
||||
@Override
|
||||
boolean getIgnoreFailures() {
|
||||
return ignoreFailures
|
||||
}
|
||||
|
||||
@Override
|
||||
void setIgnoreFailures(boolean ignoreFailures) {
|
||||
this.ignoreFailures = ignoreFailures
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
void run() {
|
||||
Util.init()
|
||||
|
||||
boolean doFix = getMode().get() === CheckMode.FIX
|
||||
final Reporter reporter = new Reporter(doFix)
|
||||
check(reporter, doFix)
|
||||
|
||||
if (reporter.messages) {
|
||||
if (getMode().get() === CheckMode.CHECK) {
|
||||
logger.error("Check task '{}' found errors:\n{}", name, reporter.messages.join('\n'))
|
||||
if (!ignoreFailures) {
|
||||
throw new IllegalArgumentException("${reporter.messages.size()} errors were found!")
|
||||
}
|
||||
} else {
|
||||
if (logger.isEnabled(LogLevel.DEBUG)) {
|
||||
logger.warn("Check task '{}' found {} errors and fixed {}:\n{}", name, reporter.messages.size(), reporter.fixed.size(), reporter.fixed.join('\n'))
|
||||
} else {
|
||||
logger.warn("Check task '{}' found {} errors and fixed {}.", name, reporter.messages.size(), reporter.fixed.size())
|
||||
}
|
||||
|
||||
if (reporter.notFixed) {
|
||||
logger.error('{} errors could not be fixed:\n{}', reporter.notFixed.size(), reporter.notFixed.join('\n'))
|
||||
if (!ignoreFailures) {
|
||||
throw new IllegalArgumentException("${reporter.notFixed.size()} errors which cannot be fixed were found!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract void check(Reporter reporter, boolean fix)
|
||||
|
||||
@CompileStatic
|
||||
static final class Reporter {
|
||||
final boolean trackFixed
|
||||
Reporter(boolean trackFixed) {
|
||||
this.trackFixed = trackFixed
|
||||
}
|
||||
|
||||
public final List<String> messages = []
|
||||
|
||||
public final List<String> fixed = []
|
||||
public final List<String> notFixed = []
|
||||
|
||||
void report(String message, boolean canBeFixed = true) {
|
||||
messages.add(message)
|
||||
|
||||
if (trackFixed) {
|
||||
if (canBeFixed) {
|
||||
fixed.add(message)
|
||||
} else {
|
||||
notFixed.add(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static <T extends CheckTask> void registerTask(TaskContainer tasks, String taskName, @DelegatesTo.Target('type') Class<T> clazz,
|
||||
@DelegatesTo(genericTypeIndex = 0, target = 'type')
|
||||
@ClosureParams(ThirdParam.FirstGenericType) Closure configuration) {
|
||||
taskName = taskName.capitalize()
|
||||
tasks.register("check$taskName", clazz) { CheckTask task ->
|
||||
def castedTask = task as T
|
||||
configuration.setDelegate(castedTask)
|
||||
configuration.call(castedTask)
|
||||
castedTask.mode.set(CheckMode.CHECK)
|
||||
castedTask.group = 'checks'
|
||||
}
|
||||
tasks.named('checkAll').configure { it.dependsOn("check$taskName") }
|
||||
|
||||
tasks.register("checkAndFix$taskName", clazz) { CheckTask task ->
|
||||
def castedTask = task as T
|
||||
configuration.setDelegate(castedTask)
|
||||
configuration.call(castedTask)
|
||||
castedTask.mode.set(CheckMode.FIX)
|
||||
castedTask.group = 'checks'
|
||||
}
|
||||
tasks.named('checkAllAndFix').configure { it.dependsOn("checkAndFix$taskName") }
|
||||
}
|
||||
}
|
||||
@@ -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'
|
||||
}
|
||||
-1100
File diff suppressed because it is too large.
Load diff
@@ -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 += '-Xmx6G' // Needed to make compiling faster, and not run out of heap space in some cases.
|
||||
}
|
||||
|
||||
// Merge the resources and classes into the same directory. We'll need to split them at runtime because
|
||||
// Minecraft and Forge are in the same sourceSet as they are inter dependent.. for now..
|
||||
sourceSets.each {
|
||||
def dir = layout.buildDirectory.dir("classes/java/$it.name")
|
||||
it.output.resourcesDir = dir
|
||||
it.java.destinationDirectory = dir
|
||||
}
|
||||
|
||||
tasks.register('copyEclipseSettings') {
|
||||
doLast {
|
||||
rootProject.fileTree('ide/eclipse/template/.settings/').matching { include '**/*.prefs' }.each { file ->
|
||||
def target = project.file('.settings/' + file.name)
|
||||
def temp = new CleanProperties().load(file)
|
||||
def exst = new CleanProperties().load(target)
|
||||
exst.put('eclipse.preferences.version', '1')
|
||||
exst.putAll(temp)
|
||||
exst.store(target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: [Gradle][IntelliJ] Auto trigger these tasks on import.
|
||||
eclipse {
|
||||
// Run everytime eclipse builds the code
|
||||
//autoBuildTasks writeManifest
|
||||
// Run when importing the project
|
||||
synchronizationTasks generateResources, copyEclipseSettings, eclipseClasspath, eclipseProject
|
||||
}
|
||||
|
||||
idea {
|
||||
module {
|
||||
// IntelliJ IDEA does not do this by itself anymore...
|
||||
downloadJavadoc = true
|
||||
downloadSources = true
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
Contributing to Forge
|
||||
=====================
|
||||
|
||||
1) Keep patches to Minecraft classes together. If you need a lot of things done, you may either add to relevant forge classes or make a new class. Try not to spread out your patches across multiple disjoint lines, as this makes maintenance of your patches difficult.
|
||||
|
||||
2) TODO: Test Mods
|
||||
|
||||
3) Follow the code style of the class you're working in (braces on newlines & spaces instead of tabs in Forge classes, inline brackets in patches, etc).
|
||||
|
||||
For more information, refer to [the wiki][Wiki].
|
||||
|
||||
|
||||
Contributor License Agreement
|
||||
=============================
|
||||
- You grant Forge a license to use your code contributed to the primary codebase (everything **not** under patches) in Forge, under the LGPLv2.1 license.
|
||||
- You assign copyright ownership of your contributions to the patches codebase (everything under patches) to Forge, where it will be licensed under the LGPLv2.1 license.
|
||||
|
||||
"Forge" is Forge Development LLC, a legally incorporated entity in Oregon, USA.
|
||||
|
||||
[Wiki]: https://github.com/MinecraftForge/MinecraftForge/wiki/If-you-want-to-contribute-to-Forge
|
||||
-101
@@ -1,101 +0,0 @@
|
||||

|
||||
|
||||
MinecraftForge
|
||||
=============
|
||||
[][Download]
|
||||
[][Download] [][Discord] [][Patreon]
|
||||
|
||||
Forge is a free, open-source modding API all of your favourite mods use!
|
||||
|
||||
| Version | Support |
|
||||
|--------------|-----------------------------|
|
||||
| All versions | [Supported][Support-Policy] |
|
||||
|
||||
* [Download]
|
||||
* [Forum]
|
||||
* [Discord]
|
||||
* [Documentation]
|
||||
|
||||
# Installing Forge
|
||||
|
||||
Go to [the Forge website][Download]
|
||||
and select the Minecraft version you wish to get Forge for from the list.
|
||||
|
||||
You can download the installer for the *Recommended Build* or the
|
||||
*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!
|
||||
|
||||
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].
|
||||
|
||||
# Contribute to Forge
|
||||
|
||||
If you wish to actually inspect Forge, submit PRs or otherwise work
|
||||
with Forge itself, you're in the right place!
|
||||
|
||||
[See the guide to setting up a Forge workspace][ForgeDev].
|
||||
|
||||
### Pull requests
|
||||
|
||||
[See the "Making Changes and Pull Requests" section in the Forge documentation][Pull-Requests].
|
||||
|
||||
Please read the contributing guidelines found [here][Contributing] before making a pull request.
|
||||
|
||||
### Contributor License Agreement
|
||||
We require all contributors to acknowledge the [Forge Contributor License Agreement][CLA].
|
||||
Please ensure you have a valid email address associated with your GitHub account to do this. If you have previously
|
||||
signed it, you should be OK.
|
||||
|
||||
#### Donate
|
||||
*Forge is a large project with many collaborators working on it around the clock. Forge is and will always remain free
|
||||
to use and modify. However, it costs money to run such a large project as this, so please consider
|
||||
[becoming a patron][Patreon].*
|
||||
|
||||
### Thanks
|
||||
[](https://www.yourkit.com/)
|
||||
|
||||
YourKit for providing us accesss to their [YourKit Java Profiler] which helps identify bottlenecks and places for improvment.
|
||||
|
||||
[Contributing]: ./CONTRIBUTING.md
|
||||
[CLA]: https://cla-assistant.io/MinecraftForge/MinecraftForge
|
||||
|
||||
[Download]: https://files.minecraftforge.net/
|
||||
|
||||
[Adoptium-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/
|
||||
|
||||
[Documentation]: https://docs.minecraftforge.net/
|
||||
[Getting-Started]: https://docs.minecraftforge.net/en/latest/gettingstarted/
|
||||
[ForgeDev]: https://docs.minecraftforge.net/en/latest/forgedev/
|
||||
[Pull-Requests]: https://docs.minecraftforge.net/en/latest/forgedev/#making-changes-and-pull-requests
|
||||
|
||||
[Patreon]: https://www.patreon.com/LexManos
|
||||
[YourKit Java Profiler]: https://www.yourkit.com/java/profiler/
|
||||
-154
@@ -1,154 +0,0 @@
|
||||
Getting Support Surrounding Forge
|
||||
=================================
|
||||
|
||||
**If you do not want to read the following wall of text, no matter its relevance for helping get your issue resolved quickly, please refer to the [Forge Forums][Bug-Reports] for support.**
|
||||
|
||||
Before reporting an issue on this repository's bug tracker, please carefully read this document. There are various resources that are more likely to be able to help you with certain kinds of issues. This document provides instructions on how to determine which place best to report to.
|
||||
|
||||
*Note: While this guide generally is more geared towards end-user problems, modders should also read it before reporting any issue with Forge in order to ensure it actually is one. This does of course not cover feature suggestions.*
|
||||
|
||||
### Contents
|
||||
|
||||
1. [Identifying the Source of a Problem](#identifying-the-source-of-a-problem)
|
||||
1. [Analyzing a Stacktrace](#analyzing-a-stacktrace)
|
||||
2. [Isolating an Issue](#isolating-an-issue)
|
||||
2. [Choosing the Correct Support Channel](#choosing-the-correct-support-channel)
|
||||
1. [Forge Support](#forge-support)
|
||||
2. [Mod Support](#mod-support)
|
||||
3. [Vanilla Support](#vanilla-support)
|
||||
3. [Writing a Helpful Bug Report](#writing-a-helpful-bug-report)
|
||||
1. [Preventing Duplicates and Unnecessary Reports](#preventing-duplicates-and-unnecessary-reports)
|
||||
2. [Required Information](#required-information)
|
||||
3. [Markdown Issue Template](#markdown-issue-template)
|
||||
4. [Waiting for an Issue to Be Resolved](#waiting-for-an-issue-to-be-resolved)
|
||||
|
||||
Identifying the Source of a Problem
|
||||
-----------------------------------
|
||||
|
||||
You're most likely to get fast and valuable responses to your issues if you report them in the correct place. For this, you need to find the root cause of the problem.
|
||||
|
||||
The most important source for this information are the log files Forge creates for every run of the game. You can find them in the `logs` subdirectory of your Minecraft game directory. The most important log file is `latest.log` which contains all of Forge's and any mod's output for `info` of above. `debug.log` contains all the logs in `latest.log` plus the `debug` logs for more detailed debugging.
|
||||
|
||||
If you're faced by a crash, consult these logs files first. Near the end of the log should be a section labelled 'Minecraft Crash Report', and after it, one about `a detailed walkthrough of the error, ...`. In these sections, you'll see a bunch of lines that look like
|
||||
|
||||
```
|
||||
at some.words.related.to.a.mod.MoreWords(MoreWords.java:number)
|
||||
```
|
||||
|
||||
These lines are the most important part of a crash log, called the stacktrace, because they pinpoint the place in the code where something went irrecoverably wrong. Mod authors usually name their code so that these lines describe what the game was doing when it crashed. The further down one of these lines is, the earlier that piece of code ran, so at the bottom you'll see the place where Minecraft started, and at the top is where it stopped. You should use this information to figure out who to report the bug to.
|
||||
|
||||
### Analyzing a Stacktrace
|
||||
|
||||
You can generally tell pretty quickly what caused the error at the head of your stacktrace, simply follow these rules:
|
||||
|
||||
1. If you can find the name of a mod in the trace, that mod most likely is the culprit.
|
||||
- Be aware that you may find multiple mods as participants of a crash. In that case, you can either report to both or try to reason (using the names in the stacktrace) about which mod is at fault.
|
||||
2. If you can only find Forge (usually starting with `net.minecraftforge`) alongside Vanilla (usually starting with `net.minecraft`) references, chances are that you have found a bug in Forge. In this case, use one of the various [support channels](#forge-support) listed below.
|
||||
- The occurrence of a single call to Forge somewhere in the stacktrace *does not* warrant a report to Forge if there are mods present in the crash report.
|
||||
3. If there's no mention of Forge or a mod, but any Vanilla class, you might have found a Vanilla bug. You should probably still report it via the Forge channels first, however, due the nature of modded environments and the rarity of Vanilla crash bugs.
|
||||
4. Always check whether there's a mention of coremods at the beginning of a crash report. If there is and you can't find any clear culprit, consider reporting to these coremods first, since they are free to change any of the code executed in your environment.
|
||||
|
||||
The rules are numbered in decreasing priority, which means that you should always prefer finding a mod in a crash report over directly jumping to conclusions about Forge or even Vanilla.
|
||||
|
||||
### Isolating an Issue
|
||||
|
||||
A significant step of reporting an issue, after having found the involved parties, is reliably reproducing it to identify potential incompatibilities or weird interactions between mods. If you're unable to pinpoint a bug to a set of mods and just randomly report it to any mod, chances are it won't get much attention and hence not a fix.
|
||||
|
||||
Reliable methods of isolating the issue are listed below:
|
||||
|
||||
- **Retrace your last steps before the crash/bug occurred.** It might sound too simple, but a big part of reliably reproducing an issue is determining the steps required to trigger it. If you can't exactly remember what you last did, looking at the stacktrace again can also help you a bit, since there are often descriptive names in there which reflect an action you could take, such as using an item or placing a block.
|
||||
- **Reduce the number of mod interactions.** You can achieve this in multiple ways. One of them is simply disabling any compatibility layers between involved mods via configuration files. The other one, which takes a little more effort, is actually removing mods from your instance. But don't be afraid, you won't have to remove mods one-by-one if you follow these steps (known as a binary search):
|
||||
1. Move half the mods out of your instance (obviously keep those definitely participating in the issue).
|
||||
2. If the issue persists after another try of reproducing it, repeat from step 1 one by halving the mod list again.
|
||||
3. If the issue does not occur anymore without the other half, repeat from step 1 but instead discard the half you just used and split the other one (plus any definitely required mods).
|
||||
4. Repeat these steps until you can't remove any more mods from your instance without also 'solving' the issue. This set of mods can serve as a basis for your issue report.
|
||||
|
||||
Choosing the Correct Support Channel
|
||||
-------------------------------------
|
||||
|
||||
The place you need to report issues to varies depending on the source of the problem. Below you can find a list of support channels you can usually consult for each of the sources listed in the previous section.
|
||||
|
||||
Please note that this list has to be considered incomplete as there is a large number of sub-communities within the Minecraft modding world. You should always prefer channels you know and have access to over any of those listed below which would require extra effort on your part.
|
||||
|
||||
### Forge Support
|
||||
|
||||
If you're certain you've found an issue that is directly caused by Forge, you have the following three platforms available for dealing with it, with decreasing relevance for end-users:
|
||||
|
||||
1. **Forge Forums:** There's a dedicated [Support & Bug Report][Bug-Reports] section on the Forge forums, which is led by team of moderators and volunteers who will try to deal with your reports as quickly as possible. *As a player you should probably report here first.*
|
||||
- For modders, there also is a separate [Modder Support][Modder-Support] section where you can ask all your development-related questions. *You should prefer this section for coding help.*
|
||||
2. **Forge Discord Server:** You can find a lot of help on the official [Discord Server][Discord], especially smaller issues that you quickly need help with. *Be aware that the channel is more suited towards development questions, so while you may get support there as a player, you should almost always prefer the forums.*
|
||||
3. **Forge Issue Tracker:** If you're absolutely certain that you've found an issue in the Forge codebase (e.g. you're a modder yourself and have analyzed the stacktrace and relevant code), don't refrain from reporting it on this repository's [issue tracker](https://github.com/MinecraftForge/MinecraftForge/issues). *Please do not use the issues as a means of getting coding help. For developers, the IRC and the [official documentation][Documentation] are the best sources of information.*
|
||||
|
||||
### Mod Support
|
||||
|
||||
A lot of mods also use GitHub or other source management systems and their integrated issue trackers. The following options will give you the best chances of reporting your issue in the right place:
|
||||
|
||||
- **CurseForge:** Many mods are hosted on the [CurseForge platform](https://minecraft.curseforge.com) nowadays. Chances are you already downloaded them from there or as a part of a modpack hosted there. Projects may specify an issue reporting link on their own page, so simply look for it on the mod's project page and you should be directed to the preferred means of error reporting.
|
||||
- **Modrinth:** Some modders use [Modrinth](https://modrinth.com/mods) to host their mods, but be mindful that not all mods on this platform are for the Forge modloader.
|
||||
- **Searching for a repository directly:** You may need to try and find a repository directly on source control sites. The most common ones are [GitHub](https://github.com), [GitLab](https://gitlab.com), and [BitBucket](https://bitbucket.org). Both come with search capabilities, so simply try to search for the mod's name.
|
||||
|
||||
### Vanilla Support
|
||||
|
||||
Just like Forge or mods, Mojang has its own issue tracker for Vanilla Minecraft. This official [bug tracker](https://bugs.mojang.com/projects/MC/issues/) has the exclusive purpose of managing any concrete issues with the game's code and functionality. It is *not* a place for requesting any features or getting help with playing the game, similar to the Forge issue tracker.
|
||||
|
||||
Writing a Helpful Bug Report
|
||||
----------------------------
|
||||
|
||||
Now that you've identified the source of your issue and decided on the correct support channel, it's time to actually write your report. Before you actually try to blindly create it, however, you should first check if the problem was noticed or even fixed already.
|
||||
|
||||
### Preventing Duplicates and Unnecessary Reports
|
||||
Use the target issue tracker's search functionality to look for issues which have descriptions similar to yours, but make sure to also search for already closed issues. If you can find one, verify it is in fact the same (or a very similar issue) and simply comment on it if you have anything else to contribute to the discussion, such as other steps to reproduce it. *Do not create duplicates of an existing issue, since it only puts extra effort on maintainers to recognize and close/mark them.*
|
||||
|
||||
Additionally, always check the *latest* changelogs of Forge and participating mods to find out if the bug was already noticed and fixed by the developer. Often the maintainer may only support newer versions of the game, so also check the changelogs for versions of Minecraft you don't actually actively play. If the issue was addressed already, *do not report it*, even if the fix only exists for a newer version of the game, since the developer probably had a good reason to do so. If your issue only exists with versions of a mod that are for very old versions of the game, also refrain from reporting them, considering most modders only support the two latest versions of Minecraft, if at all.
|
||||
|
||||
### Required Information
|
||||
If you're certain that your issue went unnoticed up until now, you can get to actually writing up the report. Below you can find a list of the *required* information any bug report should contain:
|
||||
|
||||
- **Minecraft Version:** Although many developers can deduce the Minecraft version in use from the mod's version, it is still an important piece of information to include, since it helps maintainers more easily decide whether to look into your issue at all or whether to close it right away due to being for an outdated version.
|
||||
- **Forge Version:** The Forge API is in constant flux and while breaking changes are limited to major Minecraft updates, new bugs or regressions might creep in during active development or newer versions than a mod was built with might change semantics slightly that manifest themselves for this mod.
|
||||
- **Mod Version:** Just like the Forge and Minecraft version, this is crucial for quickly telling where an issue might come from. *If you're reporting an issue caused by Forge that only occurs with a certain mod installed, include this mod here. No mod version is required if it's a pure Forge bug.*
|
||||
- **Complete List of Mods:** In order to reliably analyze a bug, developers need the exact same context as you do when trying to find its cause. Hence, you should always include a *full list of mods with their corresponding version* in your report. Note that you should probably only include the ones you found by [isolating the issue](#isolating-an-issue).
|
||||
- **Steps to Reproduce:** You should always include a list of steps required to provoke an issue to occur. Without it, developers can only take rough guesses at what might cause the bug. Try to reduce the amount of steps to a bare minimum, to make reproducing the issue easier. If you require a sophisticated setup to get even close to the issue, you might want to consider including your save file in your report.
|
||||
- **Full Log:** This is probably the most important part of your bug report. You *always* have to include the full log (`debug.log` from earlier), since it is the only way of telling what has caused an issue in code. *Do not* paste the full log as plain text in your report, but use [GitHub Gist](https://gist.github.com) instead and link to that from your report.
|
||||
|
||||
### Markdown Issue Template
|
||||
GitHub and BitBucket allow you to use the Markdown format for writing your issues. Below you can find a simple template that you can copy and fill in with your data. `{variables}` are written in braces and are supposed to be replaced by you with the information the variable name indicates. Everything behind two slashes (`//`) has to be interpreted as a comment and should be removed. If you want to include any link, you have to write in the `[title](url)` form. For further information on Markdown, see [this cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet).
|
||||
|
||||
```markdown
|
||||
**Minecraft Version:** {mc version}
|
||||
|
||||
**Forge Version:** {forge version}
|
||||
|
||||
**Mod Version:** {mod version} // Remove this line if you're reporting to Forge and no specific mod is involved
|
||||
|
||||
[**Full Log**]({link to gist/pastebin with full log})
|
||||
|
||||
**Mods in This Instance:**
|
||||
| Name | Version |
|
||||
| ------------------------ | ----------- |
|
||||
| [{mod 1}]({download 1}) | {version 1} | // The download links are entirely optional
|
||||
| {mod 2} | {version 2} |
|
||||
| {mod 3} | {version 3} |
|
||||
| ... | ... |
|
||||
|
||||
**Steps to Reproduce:**
|
||||
1. Do this
|
||||
2. Then do that
|
||||
3. ...
|
||||
```
|
||||
|
||||
*Note: The mod list here is formatted as table for easy reading (when displayed) and to look nice in text. Markdown does not care about any alignment of the spaces, so you can simply write individual rows of the table as* `| {mod name} | {version} |` *without having to care about the spacing.*
|
||||
|
||||
### Waiting for an Issue to Be Resolved
|
||||
After you have written your bug report, it is time to *wait*. Please be *patient* after submitting your issue, since it probably isn't the only one. If there's no response after 5 minutes or even a day of writing it, *do not* try to bump the issue by commenting on it, people *will* read it in due time. The only excuse for bumping a report is if it is a *critical* bug that prevents you from playing the game.
|
||||
|
||||
Every project has a different way of dealing with issues, so don't wonder if there's no comment on it, but somebody "labels" it with certain markers. This helps developers to more easily keep track of bugs and priorities. When any further input from you is required, you will be asked specifically. Do not bug anyone through other channels about resolving your issue.
|
||||
|
||||
Once your issue is closed, you will know whether it was valid or turned out to be unreproducible. If you included all of the data from above in your report, chances are that the latter won't be the case.
|
||||
|
||||
[Bug-Reports]: https://forums.minecraftforge.net/forum/18-support-bug-reports/
|
||||
[Modder-Support]: https://forums.minecraftforge.net/forum/70-modder-support/
|
||||
|
||||
[Discord]: https://discord.minecraftforge.net/
|
||||
|
||||
[Documentation]: https://docs.minecraftforge.net/
|
||||
@@ -1,7 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="164.072 372.466 294.41 46.56">
|
||||
<g fill="#DFA86A">
|
||||
<path d="M266.16 417.99h10.96v-16.94h17.97v-10.27h-17.97v-7.01h19.06V373.5h-30.02zM337.33 378.7c-2.103-2.057-4.629-3.64-7.51-4.704-2.743-1.015-5.786-1.528-9.041-1.528l-.356.002c-3.362 0-6.503.556-9.338 1.651-2.857 1.106-5.357 2.707-7.434 4.761-2.073 2.052-3.714 4.559-4.877 7.45-1.158 2.876-1.748 6.104-1.748 9.59 0 3.417.59 6.582 1.753 9.403 1.163 2.829 2.807 5.297 4.88 7.329 2.073 2.03 4.572 3.621 7.425 4.724 2.834 1.096 5.975 1.651 9.339 1.651h.023c3.402-.043 6.57-.639 9.42-1.772 2.863-1.142 5.375-2.757 7.465-4.804 2.091-2.051 3.742-4.524 4.91-7.358 1.162-2.823 1.75-5.987 1.75-9.402 0-3.487-.588-6.705-1.748-9.568-1.168-2.874-2.82-5.371-4.913-7.425zm-4.644 16.762c0 1.96-.302 3.782-.896 5.413-.587 1.607-1.42 3.011-2.479 4.167-1.05 1.147-2.335 2.065-3.821 2.728-1.479.656-3.164.99-5.008.99-1.84 0-3.514-.333-4.973-.988-1.47-.662-2.747-1.578-3.799-2.729-1.062-1.156-1.895-2.559-2.479-4.167-.595-1.63-.896-3.451-.896-5.413 0-1.801.298-3.497.887-5.044.584-1.536 1.419-2.894 2.479-4.037 1.048-1.132 2.323-2.03 3.792-2.674 1.465-.642 3.145-.968 4.988-.968 1.848 0 3.539.326 5.025.971 1.481.643 2.766 1.541 3.812 2.671 1.062 1.143 1.896 2.501 2.479 4.036.589 1.548.889 3.245.889 5.044zM376.27 395.65c1.902-2.327 2.867-5.221 2.867-8.603 0-2.614-.508-4.84-1.506-6.613-.997-1.762-2.354-3.188-4.035-4.234-1.603-.995-3.445-1.705-5.482-2.114-1.938-.388-3.969-.583-6.037-.583h-16.021v44.487h10.961v-17.282h2.35l9.447 17.282h13.127l-11.199-18.646c2.262-.728 4.116-1.964 5.528-3.694zm-19.254-12.223h4.426c.844 0 1.73.054 2.636.16.784.094 1.503.283 2.132.568.496.225.888.553 1.193.998.283.415.428 1.031.428 1.836 0 .931-.16 1.643-.459 2.06-.34.472-.771.813-1.32 1.046-.686.289-1.459.477-2.301.554-.975.089-1.963.136-2.938.136h-3.795l-.002-7.358z"/>
|
||||
<path d="M394.82 386.38c1.05-1.132 2.325-2.03 3.793-2.674 1.464-.642 3.144-.968 4.988-.968 1.843 0 3.611.311 5.26.921 1.609.599 2.992 1.449 4.107 2.531l1.314 1.278 8.019-8.105-1.454-1.298c-2.355-2.103-5.066-3.586-8.058-4.407-2.879-.789-5.987-1.189-9.247-1.189-3.361 0-6.503.556-9.339 1.651-2.856 1.106-5.355 2.707-7.431 4.761-2.074 2.052-3.716 4.559-4.88 7.45-1.158 2.876-1.746 6.104-1.746 9.59 0 3.417.59 6.582 1.752 9.403 1.164 2.829 2.807 5.297 4.88 7.329 2.073 2.03 4.572 3.621 7.425 4.724 2.832 1.096 5.975 1.651 9.339 1.651 6.501 0 12.475-1.432 17.751-4.25l.979-.522v-23.814h-19.313v10.27h8.354v6.639c-.871.397-1.834.709-2.881.933-1.5.314-3.127.478-4.83.478-1.841 0-3.514-.333-4.973-.988-1.472-.662-2.748-1.578-3.801-2.729-1.06-1.156-1.893-2.559-2.478-4.167-.595-1.63-.896-3.451-.896-5.413 0-1.801.299-3.497.887-5.044.586-1.54 1.421-2.897 2.479-4.041zM437.69 407.72v-7.36h18.72v-10.27h-18.72v-6.32h19.75V373.5h-30.72v44.49h31.76v-10.27z"/>
|
||||
</g>
|
||||
<path fill="#1E2D41" d="m243.63 379.09-37.842-1.852 46.229-.034v-3.727h-52.125l-.001 7.798v6.201c0 .115-1.52-9.144-1.87-11.734h-4.098v13.029c0 .123-1.753-10.888-1.939-12.264H164.07c1.902 1.648 12.401 10.598 19.865 14.28 3.741 1.846 8.33 1.86 12.414 1.974 2.075.06 4.25.217 5.803 1.754 2.255 2.236 2.758 5.704.814 8.33-1.922 2.594-7.335 3.156-7.335 3.156l-4.509 5.531v6.424h10.251l.319-6.348 8.863-6.285c-.946.757-3.058 2.783-6.229 7.666-.717 1.102-1.275 2.306-1.711 3.49 2.238-1.896 6.84-3.194 12.153-3.194 5.307 0 9.903 1.295 12.146 3.188-.437-1.185-.994-2.385-1.709-3.483-3.172-4.883-5.284-6.909-6.229-7.666l8.863 6.285.32 6.348h9.565v-6.424l-4.507-5.531s-6.675-.425-8.423-3.156c-5.032-7.869 2.115-20.082 18.836-23.756z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.5 KiB |
@@ -1,67 +0,0 @@
|
||||
import net.minecraftforge.gradleutils.PomUtils
|
||||
|
||||
plugins {
|
||||
id 'java-library'
|
||||
id 'maven-publish'
|
||||
id 'net.minecraftforge.licenser'
|
||||
id 'net.minecraftforge.gradleutils'
|
||||
}
|
||||
|
||||
apply from: rootProject.file('build_shared.gradle')
|
||||
|
||||
dependencies {
|
||||
compileOnly(libs.nulls)
|
||||
|
||||
api(libs.eventbus)
|
||||
|
||||
implementation(project(':fmlloader'))
|
||||
implementation(libs.commons.io)
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION)
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
tasks.named('jar', Jar).configure {
|
||||
manifest {
|
||||
attributes([
|
||||
'Automatic-Module-Name': 'net.minecraftforge.fmlcore',
|
||||
'FMLModType': 'LIBRARY'
|
||||
] as LinkedHashMap)
|
||||
attributes([
|
||||
'Specification-Title': 'FML',
|
||||
'Specification-Vendor': 'Forge Development LLC',
|
||||
'Specification-Version': '1',
|
||||
'Implementation-Title': 'FML',
|
||||
'Implementation-Version': '1.0',
|
||||
'Implementation-Vendor': 'Forge Development LLC'
|
||||
] as LinkedHashMap, 'net/minecraftforge/fml/')
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
options.compilerArgs << '-Xlint:-unchecked'
|
||||
}
|
||||
|
||||
license {
|
||||
header = rootProject.file('LICENSE-header.txt')
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
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() {}
|
||||
|
||||
/**
|
||||
* 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"));
|
||||
|
||||
/**
|
||||
* @return A supplier of net.minecraftforge.common.MinecraftForge#EVENT_BUS
|
||||
*/
|
||||
public static Supplier<IEventBus> getForgeBus() {
|
||||
return PROVIDER.getForgeBusSupplier();
|
||||
}
|
||||
|
||||
public static Supplier<I18NParser> getMessageParser() {
|
||||
return PROVIDER.getMessageParser();
|
||||
}
|
||||
|
||||
public static Supplier<IConfigEvent.ConfigConfig> getConfigConfiguration() {
|
||||
return PROVIDER.getConfigConfiguration();
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class CrashReportCallables
|
||||
{
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static final List<ISystemReportExtender> crashCallables = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
/**
|
||||
* Register a custom {@link ISystemReportExtender}
|
||||
*/
|
||||
public static void registerCrashCallable(ISystemReportExtender callable)
|
||||
{
|
||||
crashCallables.add(callable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a {@link ISystemReportExtender system report extender} with the given header name and content
|
||||
* generator, which will always be appended to the system report
|
||||
* @param headerName The name of the system report entry
|
||||
* @param reportGenerator The report generator to be called when a crash report is built
|
||||
*/
|
||||
public static void registerCrashCallable(String headerName, Supplier<String> reportGenerator)
|
||||
{
|
||||
registerCrashCallable(new ISystemReportExtender()
|
||||
{
|
||||
@Override
|
||||
public String getLabel()
|
||||
{
|
||||
return headerName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String get()
|
||||
{
|
||||
return reportGenerator.get();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a {@link ISystemReportExtender system report extender} with the given header name and content
|
||||
* generator, which will only be appended to the system report when the given {@link BooleanSupplier} returns true
|
||||
* @param headerName The name of the system report entry
|
||||
* @param reportGenerator The report generator to be called when a crash report is built
|
||||
* @param active The supplier of the flag to be checked when a crash report is built
|
||||
*/
|
||||
public static void registerCrashCallable(String headerName, Supplier<String> reportGenerator, BooleanSupplier active)
|
||||
{
|
||||
registerCrashCallable(new ISystemReportExtender()
|
||||
{
|
||||
@Override
|
||||
public String getLabel()
|
||||
{
|
||||
return headerName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String get()
|
||||
{
|
||||
return reportGenerator.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive()
|
||||
{
|
||||
try
|
||||
{
|
||||
return active.getAsBoolean();
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
LOGGER.warn("CrashCallable '{}' threw an exception while checking the active flag, disabling", headerName, t);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static List<ISystemReportExtender> allCrashCallables()
|
||||
{
|
||||
return List.copyOf(crashCallables);
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import static net.minecraftforge.fml.Logging.LOADING;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.google.common.base.Stopwatch;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Utility for running code on the main launch thread at the next available
|
||||
* opportunity. There is no guaranteed order that work from various mods will be
|
||||
* run, but your own work will be run sequentially.
|
||||
* <p>
|
||||
* <strong>Use of this class after startup is not possible.</strong> At that
|
||||
* point, {@code ReentrantBlockableEventLoop} should be used instead.
|
||||
* <p>
|
||||
* Exceptions from tasks will be handled gracefully, causing a mod loading
|
||||
* error. Tasks that take egregiously long times to run will be logged.
|
||||
*/
|
||||
public class DeferredWorkQueue
|
||||
{
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
|
||||
private static final Map<ModLoadingStage, DeferredWorkQueue> workQueues = new HashMap<>();
|
||||
|
||||
private final ConcurrentLinkedDeque<TaskInfo> tasks = new ConcurrentLinkedDeque<>();
|
||||
private final ModLoadingStage modLoadingStage;
|
||||
|
||||
public DeferredWorkQueue(ModLoadingStage modLoadingStage) {
|
||||
this.modLoadingStage = modLoadingStage;
|
||||
workQueues.put(modLoadingStage, this);
|
||||
}
|
||||
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
public static Optional<DeferredWorkQueue> lookup(Optional<ModLoadingStage> parallelClass) {
|
||||
return Optional.ofNullable(workQueues.get(parallelClass.orElse(null)));
|
||||
}
|
||||
|
||||
public void runTasks() {
|
||||
if (tasks.isEmpty()) return;
|
||||
LOGGER.debug(LOADING, "Dispatching synchronous work for work queue {}: {} jobs", modLoadingStage, tasks.size());
|
||||
RuntimeException aggregate = new RuntimeException();
|
||||
Stopwatch timer = Stopwatch.createStarted();
|
||||
for (TaskInfo t : tasks) {
|
||||
makeRunnable(t, Runnable::run, aggregate);
|
||||
}
|
||||
timer.stop();
|
||||
if (aggregate.getSuppressed().length > 0) {
|
||||
LOGGER.fatal(
|
||||
LOADING,
|
||||
"Synchronous work queue completed exceptionally in {}, see suppressed exceptions for details:",
|
||||
timer,
|
||||
aggregate
|
||||
);
|
||||
throw aggregate;
|
||||
} else {
|
||||
LOGGER.debug(LOADING, "Synchronous work queue completed in {}", timer);
|
||||
}
|
||||
}
|
||||
|
||||
private static void makeRunnable(TaskInfo ti, Executor executor, RuntimeException aggregate) {
|
||||
executor.execute(() -> {
|
||||
Stopwatch timer = Stopwatch.createStarted();
|
||||
ModLoadingContext.get().setActiveContainer(ti.owner);
|
||||
try {
|
||||
ti.future.exceptionally(t -> captureException(ti.owner.getModId(), aggregate, t));
|
||||
ti.task.run();
|
||||
} finally {
|
||||
ModLoadingContext.get().setActiveContainer(null);
|
||||
}
|
||||
timer.stop();
|
||||
if (timer.elapsed(TimeUnit.SECONDS) >= 1) {
|
||||
LOGGER.warn(LOADING, "Mod '{}' took {} to run a deferred task.", ti.owner.getModId(), timer);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static <T> T captureException(String modId, RuntimeException aggregate, Throwable throwable) {
|
||||
if (throwable instanceof CompletionException ce) {
|
||||
throwable = ce.getCause();
|
||||
}
|
||||
aggregate.addSuppressed(throwable);
|
||||
LOGGER.error("Mod '{}' encountered an error in a deferred task:", modId, throwable);
|
||||
return null;
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> enqueueWork(final ModContainer modInfo, final Runnable work) {
|
||||
return enqueueWork(modInfo, taskInfo -> CompletableFuture.runAsync(work, r -> taskInfo.task = r));
|
||||
}
|
||||
|
||||
public <T> CompletableFuture<T> enqueueWork(final ModContainer modInfo, final Supplier<T> work) {
|
||||
return enqueueWork(modInfo, taskInfo -> CompletableFuture.supplyAsync(work, r -> taskInfo.task = r));
|
||||
}
|
||||
|
||||
private <T> CompletableFuture<T> enqueueWork(final ModContainer modInfo, Function<TaskInfo, CompletableFuture<T>> futureGen) {
|
||||
TaskInfo taskInfo = new TaskInfo(modInfo);
|
||||
CompletableFuture<T> future = futureGen.apply(taskInfo);
|
||||
taskInfo.future = future;
|
||||
tasks.add(taskInfo);
|
||||
return future;
|
||||
}
|
||||
|
||||
private static final class TaskInfo {
|
||||
private final ModContainer owner;
|
||||
private Runnable task;
|
||||
private CompletableFuture<?> future;
|
||||
|
||||
private TaskInfo(ModContainer owner) {
|
||||
this.owner = owner;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,273 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.fml.loading.FMLEnvironment;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.invoke.SerializedLambda;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Use to execute code conditionally based on sidedness.
|
||||
* <ul>
|
||||
* <li>When you want to call something on one side and return a result {@link #safeCallWhenOn(Dist, Supplier)}</li>
|
||||
* <li>When you want to call one thing on one side, another thing on the other and return a result {@link #safeRunForDist(Supplier, Supplier)}</li>
|
||||
* <li>When you want to run something on one side {@link #safeRunWhenOn(Dist, Supplier)}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @deprecated Historically, this allowed for safe execution of code on the correct side by exploiting a low-level
|
||||
* detail in Java 8, but in newer versions a simple if check is enough.
|
||||
* <br>
|
||||
* For example:
|
||||
* <pre>{@code
|
||||
* if (FMLEnvironment.dist.isClient()) {
|
||||
* // client code here
|
||||
* }
|
||||
* }</pre>
|
||||
*/
|
||||
@Deprecated(since = "1.21.5", forRemoval = true)
|
||||
public final class DistExecutor
|
||||
{
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
private DistExecutor() {}
|
||||
|
||||
/**
|
||||
* Run the callable in the supplier only on the specified {@link Dist}.
|
||||
* This method is NOT sided-safe and special care needs to be taken in code using this method that implicit class
|
||||
* loading is not triggered by the Callable.
|
||||
*
|
||||
* This method can cause unexpected ClassNotFound exceptions.
|
||||
*
|
||||
* Use {@link #safeCallWhenOn(Dist, Supplier)} where possible.
|
||||
*
|
||||
* @param dist The dist to run on
|
||||
* @param toRun A supplier of the callable to run (Supplier wrapper to ensure classloading only on the appropriate dist)
|
||||
* @param <T> The return type from the callable
|
||||
* @return The callable's result
|
||||
* @deprecated use {@link #safeCallWhenOn(Dist, Supplier)} instead. This remains for advanced use cases.
|
||||
*/
|
||||
@Deprecated
|
||||
public static <T> T callWhenOn(Dist dist, Supplier<Callable<T>> toRun) {
|
||||
return unsafeCallWhenOn(dist, toRun);
|
||||
}
|
||||
|
||||
public static <T> T unsafeCallWhenOn(Dist dist, Supplier<Callable<T>> toRun) {
|
||||
if (dist == FMLEnvironment.dist) {
|
||||
try
|
||||
{
|
||||
return toRun.get().call();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the SafeCallable when on the correct {@link Dist}.
|
||||
*
|
||||
* <strong>The lambda supplied here is required to be a method reference to a method defined in
|
||||
* another class, otherwise an invalid SafeReferent error will be thrown</strong>
|
||||
* @param dist the dist which this will run on
|
||||
* @param toRun the SafeCallable to run and return the result from
|
||||
* @param <T> The type of the SafeCallable
|
||||
* @return the result of the SafeCallable or null if on the wrong side
|
||||
*/
|
||||
public static <T> T safeCallWhenOn(Dist dist, Supplier<SafeCallable<T>> toRun) {
|
||||
validateSafeReferent(toRun);
|
||||
return callWhenOn(dist, toRun::get);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the supplied Runnable on the speicified side. Same warnings apply as {@link #callWhenOn(Dist, Supplier)}.
|
||||
*
|
||||
* This method can cause unexpected ClassNotFound exceptions.
|
||||
*
|
||||
* @see #callWhenOn(Dist, Supplier)
|
||||
* @param dist Dist to run this code on
|
||||
* @param toRun The code to run
|
||||
* @deprecated use {@link #safeRunWhenOn(Dist, Supplier)} where possible. Advanced uses only.
|
||||
*/
|
||||
@Deprecated
|
||||
public static void runWhenOn(Dist dist, Supplier<Runnable> toRun) {
|
||||
unsafeRunWhenOn(dist, toRun);
|
||||
}
|
||||
/**
|
||||
* Runs the supplied Runnable on the speicified side. Same warnings apply as {@link #unsafeCallWhenOn(Dist, Supplier)}.
|
||||
*
|
||||
* This method can cause unexpected ClassNotFoundException problems in common scenarios. Understand the pitfalls of
|
||||
* the way the class verifier works to load classes before using this.
|
||||
*
|
||||
* Use {@link #safeRunWhenOn(Dist, Supplier)} if you can.
|
||||
*
|
||||
* @see #unsafeCallWhenOn(Dist, Supplier)
|
||||
* @param dist Dist to run this code on
|
||||
* @param toRun The code to run
|
||||
*/
|
||||
public static void unsafeRunWhenOn(Dist dist, Supplier<Runnable> toRun) {
|
||||
if (dist == FMLEnvironment.dist) {
|
||||
toRun.get().run();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the supplied SafeRunnable when on the correct Dist.
|
||||
* @param dist The dist to run on
|
||||
* @param toRun The code to run
|
||||
*/
|
||||
public static void safeRunWhenOn(Dist dist, Supplier<SafeRunnable> toRun) {
|
||||
validateSafeReferent(toRun);
|
||||
if (dist == FMLEnvironment.dist) {
|
||||
toRun.get().run();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Executes one of the two suppliers, based on which side is active.
|
||||
*
|
||||
* <p>
|
||||
* Example (replacement for old SidedProxy):<br/>
|
||||
* {@code Proxy p = DistExecutor.runForDist(()->ClientProxy::new, ()->ServerProxy::new);}
|
||||
*
|
||||
* NOTE: the double supplier is required to avoid classloading the secondary target.
|
||||
*
|
||||
* @param clientTarget The supplier supplier to run when on the {@link Dist#CLIENT}
|
||||
* @param serverTarget The supplier supplier to run when on the {@link Dist#DEDICATED_SERVER}
|
||||
* @param <T> The common type to return
|
||||
* @return The returned instance
|
||||
* @deprecated Use {@link #safeRunForDist(Supplier, Supplier)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static <T> T runForDist(Supplier<Supplier<T>> clientTarget, Supplier<Supplier<T>> serverTarget) {
|
||||
return unsafeRunForDist(clientTarget, serverTarget);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsafe version of {@link #safeRunForDist(Supplier, Supplier)}. Use only when you know what you're doing
|
||||
* and understand why the verifier can cause unexpected ClassNotFoundException crashes even when code is apparently
|
||||
* not sided. Ensure you test both sides fully to be confident in using this.
|
||||
*
|
||||
* @param clientTarget The supplier supplier to run when on the {@link Dist#CLIENT}
|
||||
* @param serverTarget The supplier supplier to run when on the {@link Dist#DEDICATED_SERVER}
|
||||
* @param <T> The common type to return
|
||||
* @return The returned instance
|
||||
*/
|
||||
public static <T> T unsafeRunForDist(Supplier<Supplier<T>> clientTarget, Supplier<Supplier<T>> serverTarget) {
|
||||
switch (FMLEnvironment.dist)
|
||||
{
|
||||
case CLIENT:
|
||||
return clientTarget.get().get();
|
||||
case DEDICATED_SERVER:
|
||||
return serverTarget.get().get();
|
||||
default:
|
||||
throw new IllegalArgumentException("UNSIDED?");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Executes one of the two suppliers, based on which side is active.
|
||||
*
|
||||
* <p>
|
||||
* Example (replacement for old SidedProxy):<br/>
|
||||
* {@code Proxy p = DistExecutor.safeRunForDist(()->ClientProxy::new, ()->ServerProxy::new);}
|
||||
*
|
||||
* NOTE: the double supplier is required to avoid classloading the secondary target.
|
||||
*
|
||||
* @param clientTarget The supplier supplier to run when on the {@link Dist#CLIENT}
|
||||
* @param serverTarget The supplier supplier to run when on the {@link Dist#DEDICATED_SERVER}
|
||||
* @param <T> The common type to return
|
||||
* @return The returned instance
|
||||
*/
|
||||
public static <T> T safeRunForDist(Supplier<SafeSupplier<T>> clientTarget, Supplier<SafeSupplier<T>> serverTarget) {
|
||||
validateSafeReferent(clientTarget);
|
||||
validateSafeReferent(serverTarget);
|
||||
switch (FMLEnvironment.dist)
|
||||
{
|
||||
case CLIENT:
|
||||
return clientTarget.get().get();
|
||||
case DEDICATED_SERVER:
|
||||
return serverTarget.get().get();
|
||||
default:
|
||||
throw new IllegalArgumentException("UNSIDED?");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A safe referent. This will assert that it is being called via a separated class method reference. This will
|
||||
* avoid the common pitfalls of {@link #callWhenOn(Dist, Supplier)} above.
|
||||
*
|
||||
* SafeReferents assert that they are defined as a separate method outside the scope of the calling class.
|
||||
*
|
||||
* <strong>Implementations need to be defined in a separate class to the referring site, with appropriate
|
||||
* visibility to be accessible at the callsite (generally, avoid private methods).</strong>
|
||||
*
|
||||
* <p>
|
||||
* Valid:<br/>
|
||||
*
|
||||
* {@code DistExecutor.safeCallWhenOn(Dist.CLIENT, ()->AnotherClass::clientOnlyMethod);}
|
||||
*
|
||||
* <p>
|
||||
* Invalid:<br/>
|
||||
*
|
||||
* {@code DistExecutor.safeCallWhenOn(Dist.CLIENT, ()->()->Minecraft.getInstance().world);}
|
||||
*/
|
||||
public interface SafeReferent {}
|
||||
|
||||
/**
|
||||
* SafeCallable version of {@link SafeReferent}.
|
||||
* @see SafeReferent
|
||||
* @param <T> The return type of the Callable
|
||||
*/
|
||||
public interface SafeCallable<T> extends SafeReferent, Callable<T>, Serializable {}
|
||||
|
||||
/**
|
||||
* SafeSupplier version of {@link SafeReferent}
|
||||
* @param <T> The return type of the Supplier
|
||||
*/
|
||||
public interface SafeSupplier<T> extends SafeReferent, Supplier<T>, Serializable {}
|
||||
|
||||
/**
|
||||
* SafeRunnable version of {@link SafeReferent}
|
||||
*/
|
||||
public interface SafeRunnable extends SafeReferent, Runnable, Serializable {}
|
||||
|
||||
private static final void validateSafeReferent(Supplier<? extends SafeReferent> safeReferentSupplier) {
|
||||
if (FMLEnvironment.production) return;
|
||||
final SafeReferent setter;
|
||||
try {
|
||||
setter = safeReferentSupplier.get();
|
||||
} catch (Exception e) {
|
||||
// Typically a class cast exception, just return out, expected.
|
||||
return;
|
||||
}
|
||||
|
||||
for (Class<?> cl = setter.getClass(); cl != null; cl = cl.getSuperclass()) {
|
||||
try {
|
||||
Method m = cl.getDeclaredMethod("writeReplace");
|
||||
m.setAccessible(true);
|
||||
Object replacement = m.invoke(setter);
|
||||
if (!(replacement instanceof SerializedLambda))
|
||||
break;// custom interface implementation
|
||||
SerializedLambda l = (SerializedLambda) replacement;
|
||||
if (Objects.equals(l.getCapturingClass(), l.getImplClass())) {
|
||||
LOGGER.fatal("Detected unsafe referent usage, please view the code at {}",Thread.currentThread().getStackTrace()[3]);
|
||||
throw new RuntimeException("Unsafe Referent usage found in safe referent method");
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
public interface I18NParser {
|
||||
String parseMessage(String i18nMessage, Object... args);
|
||||
String stripControlCodes(String toStrip);
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import net.minecraftforge.fml.config.IConfigEvent;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public interface IBindingsProvider {
|
||||
Supplier<IEventBus> getForgeBusSupplier();
|
||||
Supplier<I18NParser> getMessageParser();
|
||||
Supplier<IConfigEvent.ConfigConfig> getConfigConfiguration();
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* An extension point for a mod container.
|
||||
*
|
||||
* <p>An extension point can be registered for a mod container using {@link ModContainer#registerExtensionPoint(Class, Supplier)}
|
||||
* and retrieved (if present) using {@link ModContainer#getCustomExtension(Class)}. An extension point allows a mod to
|
||||
* supply an arbitrary value as a record class to another mod or framework through their mod container class, avoiding
|
||||
* the use of {@link InterModComms} or other external frameworks to pass around these values.</p>
|
||||
*
|
||||
* <p>The usual way to declare an extension point is to implement this interface on a record class, with the type
|
||||
* parameter being a reference to the class itself. For example, {@code record MyExtension(...) extends
|
||||
* IExtensionPoint<MyExtension>} would declare an extension point which supplies a {@code MyExtension} object. However,
|
||||
* there is no hard requirement that an extension point's type parameter must be in reference to itself; the type
|
||||
* parameter may reference another record class instead.</p>
|
||||
*
|
||||
* @param <T> the type of the record which is held by the extension point
|
||||
*/
|
||||
@SuppressWarnings("unused") // Type parameter T
|
||||
// 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>
|
||||
{
|
||||
/**
|
||||
* Extension point for the compatibility display test used on the server selection screen.
|
||||
*
|
||||
* <em>Note: "server" and "client" refers to the {@linkplain net.minecraftforge.api.distmarker.Dist#DEDICATED_SERVER dedicated server}
|
||||
* and {@linkplain net.minecraftforge.api.distmarker.Dist#CLIENT game client} physical distributions, rather than the
|
||||
* {@linkplain LogicalSide logical server and client}.</em>
|
||||
*
|
||||
* <p>The {@link Supplier} provides the local compatibility version, which is sent from the server to the client
|
||||
* for multiplayer connections or stored to disk for the world save. The {@link BiPredicate} accepts the remote
|
||||
* compatibility version and a boolean indicating whether the remote version is from the server or a world save,
|
||||
* where {@code true} means it is from the server and {@code false} means it is from the world save. The return
|
||||
* value of the predicate determines whether the remote version is "compatible" for the purposes of the display test.</p>
|
||||
*
|
||||
* <p>The local compatibility version may be of the value {@link DisplayTest#IGNORESERVERONLY},
|
||||
* in which case clients will ignore the mod's presence if it is present on the server but not on the client.
|
||||
* However, the remote version test predicate must still accept this value as a remote version in order to display
|
||||
* as compatible if the mod is present on the client.</p>
|
||||
*
|
||||
* <p><strong>The compatibility display test does not necessarily indicate the success or failure of an actual
|
||||
* connection attempt.</strong> Factors such as display test extension misconfiguration, truncation of ping data,
|
||||
* difference of registry data or network channels between server and client, and others may cause the result of the
|
||||
* compatibility test to not reflect the actual likelihood of a connection successfully being established between
|
||||
* the server and the client.</p>
|
||||
*
|
||||
* <p>An example declaration of a display test extension registration for a regular mod (requires to be present on
|
||||
* server and client) is as follows:</p>
|
||||
* <pre>{@code
|
||||
* String compatibilityVersion = "1"; // Could be linked with a network channel version or mod version
|
||||
* ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class,
|
||||
* () -> new IExtensionPoint.DisplayTest(
|
||||
* () -> compatibilityVersion,
|
||||
* (remoteVersion, isFromServer) -> remoteVersion.equals(compatibilityVersion)
|
||||
* )
|
||||
* );
|
||||
* }</pre>
|
||||
*
|
||||
* <p>An example declaration of a display test extension registration for a <em>server-side-only</em> mod (does not
|
||||
* require to be present on the client) is as follows:</p>
|
||||
* <pre>{@code
|
||||
* ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class,
|
||||
* () -> new IExtensionPoint.DisplayTest(
|
||||
* // Ignore this mod if not present on the client
|
||||
* () -> NetworkConstants.IGNORESERVERONLY,
|
||||
* // If present on the client, accept any version if from a server
|
||||
* (remoteVersion, isFromServer) -> isFromServer
|
||||
* )
|
||||
* );
|
||||
* }</pre>
|
||||
*
|
||||
* <p>An example declaration of a display test extension registration for a <em>client-side-only</em> mod (does not
|
||||
* require to be present on the server) is as follows:</p>
|
||||
* <pre>{@code
|
||||
* ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class,
|
||||
* () -> new IExtensionPoint.DisplayTest(
|
||||
* // Send any version from server to client, since we will be accepting any version as well
|
||||
* () -> "dQw4w9WgXcQ",
|
||||
* // Accept any version on the client, from server or from save
|
||||
* (remoteVersion, isFromServer) -> true
|
||||
* )
|
||||
* );
|
||||
* }</pre>
|
||||
*
|
||||
* @see net.minecraftforge.network.ServerStatusPing
|
||||
* @see net.minecraftforge.client.ForgeHooksClient#processForgeListPingData(net.minecraft.network.protocol.status.ServerStatus, net.minecraft.client.multiplayer.ServerData)
|
||||
*/
|
||||
@SuppressWarnings("JavadocReference") // reference to NetworkConstants, ForgeHooksClient
|
||||
record DisplayTest(Supplier<String> suppliedVersion, BiPredicate<String, Boolean> remoteVersionTest) implements IExtensionPoint<DisplayTest> {
|
||||
public static final String IGNORESERVERONLY = "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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.fml.loading.progress.ProgressMeter;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.ToIntFunction;
|
||||
|
||||
/**
|
||||
* A mod loading state. During mod loading, the mod loader transitions between states in a defined sorted list of states,
|
||||
* grouped into various {@link ModLoadingPhase phases}.
|
||||
*
|
||||
* @see IModStateProvider
|
||||
*/
|
||||
public interface IModLoadingState {
|
||||
/**
|
||||
* {@return the name of this state}
|
||||
*/
|
||||
String name();
|
||||
|
||||
/**
|
||||
* {@return the name of the state immediately previous to this state} This may be a blank name, which indicates this
|
||||
* is either the first mod loading state or an exceptional mod loading state (such as a situation where errors
|
||||
* prevent the loading process from continuing normally).
|
||||
*/
|
||||
String previous();
|
||||
|
||||
/**
|
||||
* {@return the mod loading phase this state belongs to} For exceptional mod loading states, this should be
|
||||
* {@link ModLoadingPhase#ERROR}.
|
||||
*/
|
||||
ModLoadingPhase phase();
|
||||
|
||||
/**
|
||||
* {@return a function returning a human-friendly message for this state}
|
||||
*/
|
||||
Function<ModList, String> message();
|
||||
|
||||
/**
|
||||
* @return a function that computes the size of this transition based on the size of the modlist.
|
||||
* Used to compute progress.
|
||||
*/
|
||||
ToIntFunction<ModList> size();
|
||||
|
||||
/**
|
||||
* {@return an optional runnable, which runs before starting the transition from this state to the next}
|
||||
* @see #buildTransition(Executor, Executor, ProgressMeter, Function, Function)
|
||||
*/
|
||||
Optional<Consumer<ModList>> inlineRunnable();
|
||||
|
||||
/**
|
||||
* Builds the transition task for this state with a blank pre-sync and post-sync task.
|
||||
*
|
||||
* @param <T> a type of event fired on the mod-specific event bus
|
||||
* @param syncExecutor a synchronous executor
|
||||
* @param parallelExecutor a parallel executor
|
||||
* @param progressBar a progress meter for tracking progress
|
||||
* @return a transition task for this state
|
||||
* @see #buildTransition(Executor, Executor, ProgressMeter, Function, Function)
|
||||
*/
|
||||
default <T extends Event & IModBusEvent> Optional<CompletableFuture<Void>> buildTransition(
|
||||
final Executor syncExecutor,
|
||||
final Executor parallelExecutor,
|
||||
final ProgressMeter progressBar
|
||||
) {
|
||||
return buildTransition(syncExecutor, parallelExecutor, progressBar,
|
||||
e -> CompletableFuture.completedFuture(null),
|
||||
e -> CompletableFuture.completedFuture(null)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the transition task for this state. The pre-sync and post-sync task functions allow the transition builder
|
||||
* to run these tasks on the same executor as the actual event dispatch and pre/post hooks.
|
||||
*
|
||||
* @param <T> a type of event fired on the mod-specific event bus
|
||||
* @param syncExecutor a synchronous executor
|
||||
* @param parallelExecutor a parallel executor
|
||||
* @param progressBar a progress meter for tracking progress
|
||||
* @param preSyncTask a function which returns a task to run before event pre-dispatch hook
|
||||
* @param postSyncTask a function which returns a task to run after event post-dispatch hook
|
||||
* @return a transition task for this state
|
||||
*/
|
||||
<T extends Event & IModBusEvent>
|
||||
Optional<CompletableFuture<Void>> buildTransition(final Executor syncExecutor,
|
||||
final Executor parallelExecutor,
|
||||
final ProgressMeter progressBar,
|
||||
final Function<Executor, CompletableFuture<Void>> preSyncTask,
|
||||
final Function<Executor, CompletableFuture<Void>> postSyncTask);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Provides a list of mod loading states which the mod loader may transition between.
|
||||
*
|
||||
* <p>There may be multiple mod state providers in a single application, where all states from each provider is
|
||||
* combined into a single list and ordered.</p>
|
||||
*/
|
||||
public interface IModStateProvider {
|
||||
/**
|
||||
* {@return the list of mod loading states known to this provider}
|
||||
*/
|
||||
List<IModLoadingState> getAllStates();
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.fml.loading.progress.ProgressMeter;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface IModStateTransition {
|
||||
/** Magic value to allow me to optimize the futures list by ignoring the default value without making old methods nullable. */
|
||||
public static final BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> NULL_HOOK = (e, g) -> CompletableFuture.completedFuture(null);
|
||||
|
||||
static IModStateTransition buildNoopTransition() {
|
||||
return ModStateTransitionHelper.NOOP;
|
||||
}
|
||||
|
||||
default CompletableFuture<Void> build(
|
||||
final String name,
|
||||
final Executor syncExecutor,
|
||||
final Executor parallelExecutor,
|
||||
final ProgressMeter progressBar,
|
||||
final Function<Executor, CompletableFuture<Void>> preSyncTask,
|
||||
final Function<Executor, CompletableFuture<Void>> postSyncTask
|
||||
) {
|
||||
return ModStateTransitionHelper.build(this, name, syncExecutor, parallelExecutor, progressBar, preSyncTask, postSyncTask);
|
||||
}
|
||||
|
||||
default BiFunction<ModLoadingStage, Throwable, ModLoadingStage> nextModLoadingStage() {
|
||||
return ModLoadingStage::nextState;
|
||||
}
|
||||
|
||||
/**
|
||||
* This used to allow you to fire multiple events during the transition. However, in doing so it would cause issues with the default
|
||||
* ModContainer's event handlers causing issues such as mod classes being constructed multiple times.
|
||||
*/
|
||||
@Deprecated(since = "1.21.3", forRemoval = true)
|
||||
default Supplier<Stream<EventGenerator<?>>> eventFunctionStream() {
|
||||
return () -> Stream.ofNullable(eventFunction());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
default <T extends Event & IModBusEvent> EventGenerator<T> eventFunction() {
|
||||
return null;
|
||||
}
|
||||
|
||||
ThreadSelector threadSelector();
|
||||
BiFunction<Executor, CompletableFuture<Void>, CompletableFuture<Void>> finalActivityGenerator();
|
||||
|
||||
/**
|
||||
* I think this was meant as a way to do some things for each mod container beforge/after the transition had been sent to the container.
|
||||
* However, the Future returned by this was never linked to the main transition future in any way. Which means that it was run
|
||||
* in parallel and couldn't guarantee the state of the ModContainer.
|
||||
* <p>
|
||||
* Plus all existing code that I could find returned a completedFuture, so I don't think anyone ever used this.
|
||||
* <p>
|
||||
* If I were to add this back it would be a CompletableFuture wrap(ModContainer, CompletableFuture)
|
||||
* <p>
|
||||
* Added magic value NULL_HOOK to allow me to optimize the futures list by ignoring the default value without making this method nullable.
|
||||
*/
|
||||
@Deprecated(since = "1.21.3", forRemoval = true)
|
||||
default BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> preDispatchHook() { return NULL_HOOK; }
|
||||
@Deprecated(since = "1.21.3", forRemoval = true)
|
||||
default BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> postDispatchHook() { return NULL_HOOK; }
|
||||
|
||||
interface EventGenerator<T extends Event & IModBusEvent> extends Function<ModContainer, T> {
|
||||
static <FN extends Event & IModBusEvent> EventGenerator<FN> fromFunction(Function<ModContainer, FN> fn) {
|
||||
return fn::apply;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public interface ISystemReportExtender extends Supplier<String>
|
||||
{
|
||||
String getLabel();
|
||||
|
||||
default boolean isActive()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Spliterator;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
public class InterModComms
|
||||
{
|
||||
public record IMCMessage(String senderModId, String modId, String method, Supplier<?> messageSupplier) {
|
||||
|
||||
/**
|
||||
* Deprecated: use {@link #senderModId()}
|
||||
* @return The modid of the sender. This is supplied by the caller, or by the active mod container context.
|
||||
* Consider it unreliable.
|
||||
*/
|
||||
@Deprecated
|
||||
public final String getSenderModId() {
|
||||
return this.senderModId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated: use {@link #modId()}
|
||||
* @return The modid being sent to.
|
||||
*/
|
||||
@Deprecated
|
||||
public final String getModId() {
|
||||
return this.modId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated: use {@link #method()}
|
||||
* @return The method being sent to.
|
||||
*/
|
||||
@Deprecated
|
||||
public final String getMethod() {
|
||||
return this.method;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param <T> The type of the message.
|
||||
* @return A {@link Supplier} of the message.
|
||||
* Use {@link #messageSupplier()}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Deprecated
|
||||
public final <T> Supplier<T> getMessageSupplier() {
|
||||
return (Supplier<T>) this.messageSupplier;
|
||||
}
|
||||
}
|
||||
|
||||
private static final ConcurrentMap<String, ConcurrentLinkedQueue<IMCMessage>> containerQueues = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Send IMC to remote. Sender will default to the active modcontainer, or minecraft if not.
|
||||
*
|
||||
* @param modId the mod id to send to
|
||||
* @param method the method name to send
|
||||
* @param thing the thing associated with the method name
|
||||
* @return true if the message was enqueued for sending (the target modid is loaded)
|
||||
*/
|
||||
public static boolean sendTo(final String modId, final String method, final Supplier<?> thing) {
|
||||
if (!ModList.get().isLoaded(modId)) return false;
|
||||
containerQueues.computeIfAbsent(modId, k->new ConcurrentLinkedQueue<>()).add(new IMCMessage(ModLoadingContext.get().getActiveContainer().getModId(), modId, method, thing));
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send IMC to remote.
|
||||
*
|
||||
* @param senderModId the mod id you are sending from
|
||||
* @param modId the mod id to send to
|
||||
* @param method the method name to send
|
||||
* @param thing the thing associated with the method name
|
||||
* @return true if the message was enqueued for sending (the target modid is loaded)
|
||||
*/
|
||||
public static boolean sendTo(final String senderModId, final String modId, final String method, final Supplier<?> thing) {
|
||||
if (!ModList.get().isLoaded(modId)) return false;
|
||||
containerQueues.computeIfAbsent(modId, k->new ConcurrentLinkedQueue<>()).add(new IMCMessage(senderModId, modId, method, thing));
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve pending messages for your modid. Use the predicate to filter the method name.
|
||||
*
|
||||
* @param modId the modid you are querying for
|
||||
* @param methodMatcher a predicate for the method you are interested in
|
||||
* @return All messages passing the supplied method predicate
|
||||
*/
|
||||
public static Stream<IMCMessage> getMessages(final String modId, final Predicate<String> methodMatcher) {
|
||||
ConcurrentLinkedQueue<IMCMessage> queue = containerQueues.get(modId);
|
||||
if (queue == null) return Stream.empty();
|
||||
return StreamSupport.stream(new QueueFilteringSpliterator(queue, methodMatcher), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all message for your modid.
|
||||
*
|
||||
* @param modId the modid you are querying for
|
||||
* @return All messages
|
||||
*/
|
||||
public static Stream<IMCMessage> getMessages(final String modId) {
|
||||
return getMessages(modId, s->Boolean.TRUE);
|
||||
}
|
||||
|
||||
private record QueueFilteringSpliterator(
|
||||
ConcurrentLinkedQueue<IMCMessage> queue,
|
||||
Predicate<String> methodFilter,
|
||||
Iterator<IMCMessage> iterator
|
||||
) implements Spliterator<IMCMessage> {
|
||||
|
||||
public QueueFilteringSpliterator(final ConcurrentLinkedQueue<IMCMessage> queue, final Predicate<String> methodFilter) {
|
||||
this(queue, methodFilter, queue.iterator());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int characteristics() {
|
||||
return Spliterator.CONCURRENT | Spliterator.NONNULL | Spliterator.ORDERED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long estimateSize() {
|
||||
return queue.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean tryAdvance(final Consumer<? super IMCMessage> action)
|
||||
{
|
||||
IMCMessage next;
|
||||
do
|
||||
{
|
||||
if (!iterator.hasNext())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
next = this.iterator.next();
|
||||
}
|
||||
while (!methodFilter.test(next.method));
|
||||
action.accept(next);
|
||||
this.iterator.remove();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spliterator<IMCMessage> trySplit() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class LoadingFailedException extends RuntimeException {
|
||||
private final List<ModLoadingException> loadingExceptions;
|
||||
|
||||
public LoadingFailedException(final List<ModLoadingException> loadingExceptions) {
|
||||
this.loadingExceptions = loadingExceptions;
|
||||
}
|
||||
|
||||
public List<ModLoadingException> getErrors() {
|
||||
return this.loadingExceptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return "Loading errors encountered: " + this.loadingExceptions.stream().map(ModLoadingException::getMessage).collect(Collectors.joining(",\n\t", "[\n\t", "\n]"));
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import org.apache.logging.log4j.Marker;
|
||||
import org.apache.logging.log4j.MarkerManager;
|
||||
|
||||
public class Logging
|
||||
{
|
||||
// Lots of markers
|
||||
public static final Marker CORE = MarkerManager.getMarker("CORE");
|
||||
public static final Marker LOADING = MarkerManager.getMarker("LOADING");
|
||||
public static final Marker SCAN = MarkerManager.getMarker("SCAN");
|
||||
public static final Marker SPLASH = MarkerManager.getMarker("SPLASH");
|
||||
public static final Marker CAPABILITIES = MarkerManager.getMarker("CAPABILITIES");
|
||||
public static final Marker MODELLOADING = MarkerManager.getMarker("MODELLOADING");
|
||||
public static final Marker FORGEMOD = MarkerManager.getMarker("FORGEMOD").addParents(LOADING);
|
||||
|
||||
// --log CORE:+DEBUG,SCAN:-OFF
|
||||
// forge log debug 5
|
||||
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
|
||||
/**
|
||||
* A logical side of the Minecraft game.
|
||||
* <p>
|
||||
* The {@linkplain Dist#CLIENT client distribution} has a copy of the logical client and the logical server, while the
|
||||
* {@linkplain Dist#DEDICATED_SERVER dedicated server distribution} only holds
|
||||
* the logical server.
|
||||
*
|
||||
* @see Dist
|
||||
*/
|
||||
public enum LogicalSide {
|
||||
/**
|
||||
* The logical client of the Minecraft game, which interfaces with the player's inputs and renders the player's
|
||||
* viewpoint.
|
||||
* <p>
|
||||
* The logical client is only shipped with the client distribution of the game.
|
||||
*
|
||||
* @see Dist#CLIENT
|
||||
*/
|
||||
CLIENT,
|
||||
|
||||
/**
|
||||
* The logical server of the Minecraft game, responsible for connecting to clients and running the simulation logic
|
||||
* on the level.
|
||||
* <p>
|
||||
* The logical server is shipped with both client and dedicated server distributions. The client distribution runs
|
||||
* the logical server for singleplayer mode and LAN play.
|
||||
*
|
||||
* @see Dist#DEDICATED_SERVER
|
||||
*/
|
||||
SERVER;
|
||||
|
||||
/**
|
||||
* {@return if this logical side is the server}
|
||||
*/
|
||||
public boolean isServer() {
|
||||
return !isClient();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return if the logical side is the client}
|
||||
*/
|
||||
public boolean isClient() {
|
||||
return this == CLIENT;
|
||||
}
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.config.IConfigEvent;
|
||||
import net.minecraftforge.fml.config.ModConfig;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* The container that wraps around mods in the system.
|
||||
* <p>
|
||||
* The philosophy is that individual mod implementation technologies should not
|
||||
* impact the actual loading and management of mod code. This class provides
|
||||
* a mechanism by which we can wrap actual mod code so that the loader and other
|
||||
* facilities can treat mods at arms length.
|
||||
* </p>
|
||||
*/
|
||||
public abstract class ModContainer {
|
||||
protected final String modId;
|
||||
protected final String namespace;
|
||||
protected final IModInfo modInfo;
|
||||
protected ModLoadingStage modLoadingStage;
|
||||
protected Supplier<?> contextExtension;
|
||||
protected final Map<ModLoadingStage, Runnable> activityMap = new EnumMap<>(ModLoadingStage.class);
|
||||
protected final Map<Class<? extends IExtensionPoint<?>>, Supplier<?>> extensionPoints = new IdentityHashMap<>();
|
||||
protected final EnumMap<ModConfig.Type, ModConfig> configs = new EnumMap<>(ModConfig.Type.class);
|
||||
final Set<ModContainer> dependencies = new HashSet<>();
|
||||
/**
|
||||
* If you want to handle the event, override {@link #dispatchConfigEvent(IConfigEvent)}
|
||||
*/
|
||||
@Deprecated(since = "1.21.3", forRemoval = true)
|
||||
protected Optional<Consumer<IConfigEvent>> configHandler = Optional.empty();
|
||||
|
||||
public ModContainer(IModInfo info) {
|
||||
this.modId = info.getModId();
|
||||
// TODO: Currently not reading namespace from configuration..
|
||||
this.namespace = this.modId;
|
||||
this.modInfo = info;
|
||||
this.modLoadingStage = ModLoadingStage.CONSTRUCT;
|
||||
|
||||
final String displayTestString = info.getConfig().<String>getConfigElement("displayTest")
|
||||
.orElseGet(() -> info.getOwningFile().isClientSideOnly() ? "IGNORE_ALL_VERSION" : "MATCH_VERSION");
|
||||
Supplier<IExtensionPoint.DisplayTest> displayTestSupplier = switch (displayTestString) {
|
||||
case "MATCH_VERSION" -> // default displaytest checks for version string match
|
||||
() -> new IExtensionPoint.DisplayTest(() -> this.modInfo.getVersion().toString(),
|
||||
(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;
|
||||
case "IGNORE_ALL_VERSION" -> // Ignores all information and provides no information
|
||||
IExtensionPoint.DisplayTest.IGNORE_ALL_VERSION;
|
||||
case "NONE" -> null; // NO display test at all - use this if you're going to do your own display test
|
||||
default -> // any other value throws an exception
|
||||
throw new IllegalArgumentException("Invalid displayTest value supplied in mods.toml");
|
||||
};
|
||||
if (displayTestSupplier != null)
|
||||
registerExtensionPoint(IExtensionPoint.DisplayTest.class, displayTestSupplier);
|
||||
else
|
||||
extensionPoints.remove(IExtensionPoint.DisplayTest.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Errored container state, used for filtering. Does nothing.
|
||||
*/
|
||||
ModContainer() {
|
||||
this.modLoadingStage = ModLoadingStage.ERROR;
|
||||
modId = "BROKEN";
|
||||
namespace = "BROKEN";
|
||||
modInfo = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the modid for this mod
|
||||
*/
|
||||
public final String getModId() {
|
||||
return modId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the resource prefix for the mod
|
||||
*/
|
||||
public final String getNamespace() {
|
||||
return namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The current loading stage for this mod
|
||||
*/
|
||||
public ModLoadingStage getCurrentState() {
|
||||
return modLoadingStage;
|
||||
}
|
||||
|
||||
public IModInfo getModInfo() {
|
||||
return modInfo;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends Record> Optional<T> getCustomExtension(Class<? extends IExtensionPoint<T>> point) {
|
||||
return Optional.ofNullable((T)extensionPoints.getOrDefault(point,()-> null).get());
|
||||
}
|
||||
|
||||
public <T extends Record & IExtensionPoint<T>> void registerExtensionPoint(Class<? extends IExtensionPoint<T>> point, Supplier<T> extension) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this mod match the supplied mod?
|
||||
*
|
||||
* @param mod to compare
|
||||
* @return if the mod matches
|
||||
*/
|
||||
public abstract boolean matches(Object mod);
|
||||
|
||||
/**
|
||||
* @return the mod object instance
|
||||
*/
|
||||
public abstract Object getMod();
|
||||
|
||||
/**
|
||||
* Accept an arbitrary event for processing by the mod. Probably posted to an event bus in the lower level container.
|
||||
* @param e Event to accept
|
||||
*/
|
||||
protected <T extends Event & IModBusEvent> void acceptEvent(T e) {}
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.forgespi.language.IModFileInfo;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import net.minecraftforge.forgespi.language.ModFileScanData;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
|
||||
import net.minecraftforge.forgespi.locating.IModFile;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Master list of all mods - game-side version. This is classloaded in the game scope and
|
||||
* can dispatch game level events as a result.
|
||||
*/
|
||||
public class ModList {
|
||||
private static ModList INSTANCE;
|
||||
private final List<IModFileInfo> modFiles;
|
||||
private final List<IModInfo> sortedList;
|
||||
private final Map<String, IModFileInfo> fileById;
|
||||
private List<ModContainer> mods;
|
||||
private Map<String, ModContainer> indexedMods;
|
||||
private List<ModFileScanData> modFileScanData;
|
||||
private List<ModContainer> sortedContainers;
|
||||
|
||||
private ModList(final List<ModFile> modFiles, final List<ModInfo> sortedList) {
|
||||
this.modFiles = modFiles.stream().map(ModFile::getModFileInfo).toList();
|
||||
this.sortedList = sortedList.stream().map(IModInfo.class::cast).toList();
|
||||
var byId = new HashMap<String, IModFileInfo>();
|
||||
for (var file : this.modFiles) {
|
||||
for (var mod : file.getMods())
|
||||
byId.put(mod.getModId(), mod.getOwningFile());
|
||||
}
|
||||
this.fileById = Collections.unmodifiableMap(byId);
|
||||
CrashReportCallables.registerCrashCallable("Mod List", this::crashReport);
|
||||
}
|
||||
|
||||
private String getModContainerState(String modId) {
|
||||
return getModContainerById(modId).map(ModContainer::getCurrentState).map(Object::toString).orElse("NONE");
|
||||
}
|
||||
|
||||
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()),
|
||||
((ModFileInfo)mf.getModFileInfo()).getCodeSigningFingerprint().orElse("NOSIGNATURE"));
|
||||
}
|
||||
|
||||
private String crashReport() {
|
||||
return "\n"+applyForEachModFile(this::fileToLine).collect(Collectors.joining("\n\t\t", "\t\t", ""));
|
||||
}
|
||||
|
||||
public static ModList of(List<ModFile> modFiles, List<ModInfo> sortedList) {
|
||||
INSTANCE = new ModList(modFiles, sortedList);
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public static ModList get() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public List<IModFileInfo> getModFiles() {
|
||||
return modFiles;
|
||||
}
|
||||
|
||||
public IModFileInfo getModFileById(String modid) {
|
||||
return this.fileById.get(modid);
|
||||
}
|
||||
|
||||
void setLoadedMods(final List<ModContainer> modContainers) {
|
||||
this.mods = modContainers;
|
||||
this.sortedContainers = modContainers.stream().sorted(Comparator.comparingInt(c -> sortedList.indexOf(c.getModInfo()))).toList();
|
||||
this.indexedMods = modContainers.stream().collect(Collectors.toMap(ModContainer::getModId, Function.identity()));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Optional<T> getModObjectById(String modId) {
|
||||
return getModContainerById(modId).map(ModContainer::getMod).map(o -> (T) o);
|
||||
}
|
||||
|
||||
public Optional<? extends ModContainer> getModContainerById(String modId) {
|
||||
return Optional.ofNullable(this.indexedMods.get(modId));
|
||||
}
|
||||
|
||||
public Optional<? extends ModContainer> getModContainerByObject(Object obj) {
|
||||
return mods.stream().filter(mc -> mc.getMod() == obj).findFirst();
|
||||
}
|
||||
|
||||
public List<IModInfo> getMods() {
|
||||
return this.sortedList;
|
||||
}
|
||||
|
||||
public boolean isLoaded(String modTarget) {
|
||||
return this.indexedMods.containsKey(modTarget);
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return mods.size();
|
||||
}
|
||||
|
||||
public List<ModFileScanData> getAllScanData() {
|
||||
if (modFileScanData == null) {
|
||||
modFileScanData = this.sortedList.stream().
|
||||
map(IModInfo::getOwningFile).
|
||||
filter(Objects::nonNull).
|
||||
map(IModFileInfo::getFile).
|
||||
distinct().
|
||||
map(IModFile::getScanResult).
|
||||
collect(Collectors.toList());
|
||||
}
|
||||
return modFileScanData;
|
||||
|
||||
}
|
||||
|
||||
public void forEachModFile(Consumer<IModFile> fileConsumer) {
|
||||
modFiles.stream().map(IModFileInfo::getFile).forEach(fileConsumer);
|
||||
}
|
||||
|
||||
public <T> Stream<T> applyForEachModFile(Function<IModFile, T> function) {
|
||||
return modFiles.stream().map(IModFileInfo::getFile).map(function);
|
||||
}
|
||||
|
||||
public void forEachModContainer(BiConsumer<String, ModContainer> modContainerConsumer) {
|
||||
indexedMods.forEach(modContainerConsumer);
|
||||
}
|
||||
|
||||
public void forEachModInOrder(Consumer<ModContainer> containerConsumer) {
|
||||
this.sortedContainers.forEach(containerConsumer);
|
||||
}
|
||||
|
||||
public List<ModContainer> getLoadedMods() {
|
||||
return this.sortedContainers;
|
||||
}
|
||||
|
||||
public <T> Stream<T> applyForEachModContainer(Function<ModContainer, T> function) {
|
||||
return indexedMods.values().stream().map(function);
|
||||
}
|
||||
}
|
||||
@@ -1,434 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.fml.loading.FMLEnvironment;
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
import net.minecraftforge.fml.loading.ImmediateWindowHandler;
|
||||
import net.minecraftforge.fml.loading.LoadingModList;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.InvalidModIdentifier;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
|
||||
import net.minecraftforge.fml.loading.progress.ProgressMeter;
|
||||
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import net.minecraftforge.forgespi.language.IModLanguageProvider;
|
||||
import net.minecraftforge.forgespi.locating.ForgeFeature;
|
||||
import net.minecraftforge.forgespi.locating.IModFile;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import static net.minecraftforge.fml.Logging.CORE;
|
||||
import static net.minecraftforge.fml.Logging.LOADING;
|
||||
|
||||
/**
|
||||
* Loads mods.
|
||||
*
|
||||
* Dispatch cycle is seen in {@code #loadMods()} and {@code #finishMods()}
|
||||
*
|
||||
* Overall sequence for loadMods is:
|
||||
* <dl>
|
||||
* <dt>CONSTRUCT</dt>
|
||||
* <dd>Constructs the mod instance. Mods can typically setup basic environment such as Event listeners
|
||||
* and Configuration specifications here.</dd>
|
||||
* <dt>Automated dispatches</dt>
|
||||
* <dd>Dispatches automated elements : {@code net.minecraftforge.fml.common.Mod.EventBusSubscriber},
|
||||
* {@code net.minecraftforge.event.RegistryEvent}, {@code net.minecraftforge.common.capabilities.CapabilityInject}
|
||||
* and others</dd>
|
||||
* <dt>CONFIG_LOAD</dt>
|
||||
* <dd>Dispatches ConfigLoadEvent to mods</dd>
|
||||
* <dt>COMMON_SETUP</dt>
|
||||
* <dd>Dispatches {@code net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent} to mods</dd>
|
||||
* <dt>SIDED_SETUP</dt>
|
||||
* <dd>Dispatches {@code net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent} or
|
||||
* {@code net.minecraftforge.fml.event.lifecycle.FMLDedicatedServerSetupEvent} to mods</dd>
|
||||
* </dl>
|
||||
*
|
||||
* Overall sequence for finishMods is:
|
||||
* <dl>
|
||||
* <dt>ENQUEUE_IMC</dt>
|
||||
* <dd>Dispatches {@code net.minecraftforge.fml.event.lifecycle.InterModEnqueueEvent} to mods,
|
||||
* for enqueuing {@link InterModComms} messages for other mods to receive subsequently</dd>
|
||||
* <dt>PROCESS_IMC</dt>
|
||||
* <dd>Dispatches {@code net.minecraftforge.fml.event.lifecycle.InterModProcessEvent} to mods,
|
||||
* for processing {@link InterModComms} messages received from other mods prior to this event</dd>
|
||||
* <dt>COMPLETE</dt>
|
||||
* <dd>Dispatches {@code net.minecraftforge.fml.event.lifecycle.FMLLoadCompleteEvent} to mods,
|
||||
* and completes the mod loading sequence.</dd>
|
||||
* </dl>
|
||||
*/
|
||||
public class ModLoader {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
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 final Consumer<String> statusConsumer = StartupNotificationManager.modLoaderConsumer().orElse(msg -> {});
|
||||
private final Set<IModLoadingState> completedStates = new HashSet<>();
|
||||
private ModList modList;
|
||||
|
||||
private ModLoader() {
|
||||
this.loadingModList = FMLLoader.getLoadingModList();
|
||||
this.loadingExceptions = this.loadingModList.getErrors().stream()
|
||||
.flatMap(ModLoadingException::fromEarlyException)
|
||||
.toList();
|
||||
this.loadingWarnings = this.loadingModList.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()
|
||||
.filter(ModFileInfo::missingLicense)
|
||||
.filter(modFileInfo -> modFileInfo.getMods().stream().noneMatch(this.erroredModInfos::contains)) //Ignore files where any other mod already encountered an error
|
||||
.map(modFileInfo -> new ModLoadingException(null, ModLoadingStage.VALIDATE, "fml.modloading.missinglicense", null, modFileInfo.getFile()))
|
||||
.forEach(this.loadingExceptions::add);
|
||||
this.stateManager = new ModStateManager();
|
||||
CrashReportCallables.registerCrashCallable("ModLauncher", FMLLoader::getLauncherInfo);
|
||||
CrashReportCallables.registerCrashCallable("ModLauncher launch target", FMLLoader::launcherHandlerName);
|
||||
CrashReportCallables.registerCrashCallable("ModLauncher naming", FMLLoader::getNaming);
|
||||
CrashReportCallables.registerCrashCallable("ModLauncher services", ModLoader::computeModLauncherServiceList);
|
||||
CrashReportCallables.registerCrashCallable("FML Language Providers", ModLoader::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 static String computeModLauncherServiceList() {
|
||||
final List<Map<String, String>> mods = FMLLoader.modLauncherModList();
|
||||
return "\n"+mods.stream().map(mod->mod.getOrDefault("file","nofile")+
|
||||
" "+mod.getOrDefault("name", "missing")+
|
||||
" "+mod.getOrDefault("type","NOTYPE")+
|
||||
" "+mod.getOrDefault("description", "")).
|
||||
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() {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run on the primary starting thread by ClientModLoader and ServerModLoader
|
||||
* @param syncExecutor An executor to run tasks on the main thread
|
||||
* @param parallelExecutor An executor to run tasks on a parallel loading thread pool
|
||||
* @param periodicTask Optional periodic task to perform on the main thread while other activities run
|
||||
*/
|
||||
public void gatherAndInitializeMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
|
||||
ForgeFeature.registerFeature("javaVersion", ForgeFeature.VersionFeatureTest.forVersionString(IModInfo.DependencySide.BOTH, System.getProperty("java.version")));
|
||||
ForgeFeature.registerFeature("openGLVersion", ForgeFeature.VersionFeatureTest.forVersionString(IModInfo.DependencySide.CLIENT, ImmediateWindowHandler.getGLVersion()));
|
||||
loadingStateValid = true;
|
||||
FMLLoader.backgroundScanHandler.waitForScanToComplete(periodicTask);
|
||||
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());
|
||||
statusConsumer.accept("ERROR DURING MOD LOADING");
|
||||
modList.setLoadedMods(Collections.emptyList());
|
||||
loadingStateValid = false;
|
||||
throw new LoadingFailedException(loadingExceptions);
|
||||
}
|
||||
List<? extends ForgeFeature.Bound> failedBounds = loadingModList.getMods().stream()
|
||||
.map(ModInfo::getForgeFeatures)
|
||||
.flatMap(List::stream)
|
||||
.filter(bound -> !ForgeFeature.testFeature(FMLEnvironment.dist, bound))
|
||||
.toList();
|
||||
|
||||
if (!failedBounds.isEmpty()) {
|
||||
LOGGER.fatal(CORE, "Failed to validate feature bounds for mods: {}", failedBounds);
|
||||
statusConsumer.accept("ERROR DURING MOD LOADING");
|
||||
modList.setLoadedMods(Collections.emptyList());
|
||||
loadingStateValid = false;
|
||||
throw new LoadingFailedException(failedBounds.stream()
|
||||
.map(fb -> new ModLoadingException(fb.modInfo(), ModLoadingStage.CONSTRUCT, "fml.modloading.feature.missing", null, fb, ForgeFeature.featureValue(fb)))
|
||||
.toList());
|
||||
}
|
||||
|
||||
final var modContainers = new HashMap<String, ModContainer>();
|
||||
for (var file : loadingModList.getModFiles()) {
|
||||
var containers = this.buildMods(file.getFile());
|
||||
for (var container : containers)
|
||||
modContainers.put(container.getModId(), container);
|
||||
}
|
||||
|
||||
if (!loadingExceptions.isEmpty()) {
|
||||
LOGGER.fatal(CORE, "Failed to initialize mod containers", loadingExceptions.getFirst());
|
||||
statusConsumer.accept("ERROR DURING MOD LOADING");
|
||||
modList.setLoadedMods(Collections.emptyList());
|
||||
loadingStateValid = false;
|
||||
throw new LoadingFailedException(loadingExceptions);
|
||||
}
|
||||
|
||||
// Gather all dependencies, so we can make sure they have finished their transition events before we fire ours
|
||||
for (var mod : modContainers.values()) {
|
||||
for (var dep : mod.getModInfo().getDependencies()) {
|
||||
var target = modContainers.get(dep.getModId());
|
||||
if (target == null)
|
||||
continue;
|
||||
|
||||
switch (dep.getOrdering()) {
|
||||
case AFTER:
|
||||
mod.dependencies.add(target);
|
||||
break;
|
||||
case BEFORE:
|
||||
target.dependencies.add(mod);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
modList.setLoadedMods(modContainers.values().stream().toList());
|
||||
this.modList = modList;
|
||||
var stateList = stateManager.getStates(ModLoadingPhase.GATHER);
|
||||
var progress = StartupMessageManager.addProgressBar("Mod Gather", stateList.stream().mapToInt(mls -> mls.size().applyAsInt(this.modList)).sum());
|
||||
for (IModLoadingState mls : stateList) {
|
||||
dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress);
|
||||
}
|
||||
progress.complete();
|
||||
}
|
||||
|
||||
public void loadMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
|
||||
var stateList = stateManager.getStates(ModLoadingPhase.LOAD);
|
||||
var progress = StartupMessageManager.addProgressBar("Mod Loading", stateList.stream().mapToInt(mls -> mls.size().applyAsInt(modList)).sum());
|
||||
for (IModLoadingState mls : stateList) {
|
||||
dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress);
|
||||
}
|
||||
progress.complete();
|
||||
}
|
||||
|
||||
public void finishMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
|
||||
var stateList = stateManager.getStates(ModLoadingPhase.COMPLETE);
|
||||
var progress = StartupMessageManager.addProgressBar("Mod Complete", stateList.stream().mapToInt(mls -> mls.size().applyAsInt(modList)).sum());
|
||||
for (IModLoadingState mls : stateList) {
|
||||
dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress);
|
||||
}
|
||||
statusConsumer.accept(String.format("Mod loading complete - %d mods loaded", this.modList.size()));
|
||||
progress.complete();
|
||||
}
|
||||
|
||||
private void dispatchAndHandleError(IModLoadingState state, ModWorkManager.DrivenExecutor syncExecutor, Executor parallelExecutor, final Runnable ticker, final ProgressMeter progressBar) {
|
||||
if (!isLoadingStateValid()) {
|
||||
LOGGER.error("Cowardly refusing to process mod state change request from {}", state);
|
||||
return;
|
||||
}
|
||||
progressBar.label(progressBar.name() + " working");
|
||||
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);
|
||||
|
||||
completedStates.add(state);
|
||||
}
|
||||
|
||||
private void handleInlineTransition(final Consumer<ModList> transition, final IModLoadingState state, final ModWorkManager.DrivenExecutor syncExecutor, final Runnable ticker) {
|
||||
var pb = StartupMessageManager.addProgressBar("State transition " +state.name()+" running", 0);
|
||||
syncExecutor.drive(ticker);
|
||||
transition.accept(this.modList);
|
||||
syncExecutor.drive(ticker);
|
||||
pb.complete();
|
||||
syncExecutor.drive(ticker);
|
||||
}
|
||||
|
||||
private void waitForTransition(final IModLoadingState state, final ModWorkManager.DrivenExecutor syncExecutor, final Runnable ticker, final CompletableFuture<Void> transition) {
|
||||
while (!transition.isDone()) {
|
||||
syncExecutor.drive(ticker);
|
||||
}
|
||||
|
||||
try {
|
||||
transition.join();
|
||||
} catch (CompletionException e) {
|
||||
loadingStateValid = false;
|
||||
Throwable t = e.getCause();
|
||||
boolean hasNotModLoadingEx = Arrays.stream(t.getSuppressed())
|
||||
.anyMatch(obj -> !(obj instanceof ModLoadingException));
|
||||
if (hasNotModLoadingEx) {
|
||||
LOGGER.fatal("Encountered non-modloading exceptions!", e);
|
||||
statusConsumer.accept("ERROR DURING MOD LOADING");
|
||||
throw e;
|
||||
}
|
||||
|
||||
final List<ModLoadingException> modLoadingExceptions = Arrays.stream(t.getSuppressed())
|
||||
.filter(ModLoadingException.class::isInstance)
|
||||
.map(ModLoadingException.class::cast)
|
||||
.toList();
|
||||
LOGGER.fatal(LOADING,"Failed to complete lifecycle event {}, {} errors found", state.name(), modLoadingExceptions.size());
|
||||
statusConsumer.accept("ERROR DURING MOD LOADING");
|
||||
throw new LoadingFailedException(modLoadingExceptions);
|
||||
}
|
||||
}
|
||||
|
||||
private List<ModContainer> buildMods(final IModFile modFile) {
|
||||
final Map<String, IModInfo> modInfoMap = new HashMap<>();
|
||||
for (var mod : modFile.getModFileInfo().getMods())
|
||||
modInfoMap.put(mod.getModId(), mod);
|
||||
|
||||
LOGGER.trace(LOADING, "ModContainer is {}", ModContainer.class.getClassLoader());
|
||||
final List<ModContainer> containers = new ArrayList<>();
|
||||
for (var entry : modFile.getScanResult().getTargets().entrySet()) {
|
||||
var container = buildModContainerFromTOML(modFile, modInfoMap, entry.getKey(), entry.getValue());
|
||||
if (container != null)
|
||||
containers.add(container);
|
||||
}
|
||||
|
||||
if (containers.size() != modInfoMap.size()) {
|
||||
var modIds = modInfoMap.values().stream().map(IModInfo::getModId).sorted().toList();
|
||||
var containerIds = containers.stream().map(c -> c != null ? c.getModId() : "(null)").sorted().toList();
|
||||
|
||||
LOGGER.fatal(LOADING, "File {} constructed {} mods: {}, but had {} mods specified: {}",
|
||||
modFile.getFilePath(),
|
||||
containers.size(), containerIds,
|
||||
modInfoMap.size(), modIds);
|
||||
|
||||
var missingClasses = new ArrayList<>(modIds);
|
||||
missingClasses.removeAll(containerIds);
|
||||
LOGGER.fatal(LOADING, "The following classes are missing, but are reported in the mods.toml: {}", missingClasses);
|
||||
|
||||
var missingMods = new ArrayList<>(containerIds);
|
||||
missingMods.removeAll(modIds);
|
||||
LOGGER.fatal(LOADING, "The following mods are missing, but have classes in the jar: {}", missingMods);
|
||||
|
||||
loadingExceptions.add(new ModLoadingException(null, ModLoadingStage.CONSTRUCT, "fml.modloading.missingclasses", null, modFile.getFilePath()));
|
||||
}
|
||||
|
||||
// remove errored mod containers
|
||||
return containers.stream().filter(mc -> mc.modLoadingStage != ModLoadingStage.ERROR).toList();
|
||||
}
|
||||
|
||||
private ModContainer buildModContainerFromTOML(final IModFile modFile, final Map<String, IModInfo> modInfoMap,
|
||||
final String modId, final IModLanguageProvider.IModLanguageLoader languageLoader
|
||||
) {
|
||||
try {
|
||||
IModInfo info = modInfoMap.get(modId);
|
||||
// throw a missing metadata error if there is no matching modid in the modInfoMap from the mods.toml file
|
||||
if (info == null)
|
||||
throw new ModLoadingException(null, ModLoadingStage.CONSTRUCT, "fml.modloading.missingmetadata", null, modId);
|
||||
|
||||
return languageLoader.loadMod(info, modFile.getScanResult(), FMLLoader.getGameLayer());
|
||||
} catch (ModLoadingException mle) {
|
||||
// exceptions are caught and added to the error list for later handling
|
||||
loadingExceptions.add(mle);
|
||||
// return an errored container instance here, because we tried and failed building a container.
|
||||
return new ErroredModContainer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If the current mod loading state is valid. Use if you interact with vanilla systems directly during loading
|
||||
* 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;
|
||||
}
|
||||
|
||||
public boolean hasCompletedState(final String stateName) {
|
||||
IModLoadingState state = stateManager.findState(stateName);
|
||||
return completedStates.contains(state);
|
||||
}
|
||||
|
||||
public <T extends Event & IModBusEvent> void runEventGenerator(Function<ModContainer, T> generator) {
|
||||
if (!loadingStateValid) {
|
||||
LOGGER.error("Cowardly refusing to send event generator to a broken mod state");
|
||||
return;
|
||||
}
|
||||
ModList.get().forEachModInOrder(mc -> mc.acceptEvent(generator.apply(mc)));
|
||||
}
|
||||
|
||||
public <T extends Event & IModBusEvent> void postEvent(T e) {
|
||||
if (!loadingStateValid) {
|
||||
LOGGER.error("Cowardly refusing to send event {} to a broken mod state", e.getClass().getName());
|
||||
return;
|
||||
}
|
||||
ModList.get().forEachModInOrder(mc -> mc.acceptEvent(e));
|
||||
}
|
||||
|
||||
public <T extends Event & IModBusEvent> T postEventWithReturn(T e) {
|
||||
if (!loadingStateValid) {
|
||||
LOGGER.error("Cowardly refusing to send event {} to a broken mod state", e.getClass().getName());
|
||||
return e;
|
||||
}
|
||||
ModList.get().forEachModInOrder(mc -> mc.acceptEvent(e));
|
||||
return e;
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
public <T extends Event & IModBusEvent> void postEventWrapContainerInModOrder(T event) {
|
||||
postEventWithWrapInModOrder(event,
|
||||
(mc, e) -> ModLoadingContext.get().setActiveContainer(mc),
|
||||
(mc, e) -> ModLoadingContext.get().setActiveContainer(null)
|
||||
);
|
||||
}
|
||||
|
||||
public <T extends Event & IModBusEvent> void postEventWithWrapInModOrder(T e, BiConsumer<ModContainer, T> pre, BiConsumer<ModContainer, T> post) {
|
||||
if (!loadingStateValid) {
|
||||
LOGGER.error("Cowardly refusing to send event {} to a broken mod state", e.getClass().getName());
|
||||
return;
|
||||
}
|
||||
ModList.get().forEachModInOrder(mc -> {
|
||||
pre.accept(mc, e);
|
||||
mc.acceptEvent(e);
|
||||
post.accept(mc, e);
|
||||
});
|
||||
}
|
||||
|
||||
public List<ModLoadingWarning> getWarnings() {
|
||||
return ImmutableList.copyOf(this.loadingWarnings);
|
||||
}
|
||||
|
||||
public void addWarning(ModLoadingWarning warning) {
|
||||
this.loadingWarnings.add(warning);
|
||||
}
|
||||
|
||||
private static boolean runningDataGen = false;
|
||||
|
||||
public static boolean isDataGenRunning () {
|
||||
return runningDataGen;
|
||||
}
|
||||
|
||||
private static class ErroredModContainer extends ModContainer {
|
||||
public ErroredModContainer() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(final Object mod) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getMod() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraftforge.fml.config.IConfigSpec;
|
||||
import net.minecraftforge.fml.config.ModConfig;
|
||||
import org.slf4j.Logger;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class ModLoadingContext {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static final ThreadLocal<ModLoadingContext> context = ThreadLocal.withInitial(ModLoadingContext::new);
|
||||
private ModContainer activeContainer;
|
||||
private Object languageExtension;
|
||||
|
||||
/**
|
||||
* @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")
|
||||
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
|
||||
* @param extension An extension operator
|
||||
* @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));
|
||||
}
|
||||
|
||||
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());
|
||||
return;
|
||||
}
|
||||
|
||||
getContainer().addConfig(new ModConfig(type, spec, getContainer()));
|
||||
}
|
||||
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
||||
getContainer().addConfig(new ModConfig(type, spec, getContainer(), fileName));
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T extension() {
|
||||
return (T)languageExtension;
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import com.google.common.collect.Streams;
|
||||
import net.minecraftforge.fml.loading.EarlyLoadingException;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* General purpose mod loading error message
|
||||
*/
|
||||
public class ModLoadingException extends RuntimeException
|
||||
{
|
||||
private static final long serialVersionUID = 2048947398536935507L;
|
||||
/**
|
||||
* Mod Info for mod with issue
|
||||
*/
|
||||
private final IModInfo modInfo;
|
||||
/**
|
||||
* The stage where this error was encountered
|
||||
*/
|
||||
private final ModLoadingStage errorStage;
|
||||
|
||||
/**
|
||||
* I18N message to use for display
|
||||
*/
|
||||
private final String i18nMessage;
|
||||
|
||||
/**
|
||||
* Context for message display
|
||||
*/
|
||||
private final List<Object> context;
|
||||
|
||||
public ModLoadingException(final IModInfo modInfo, final ModLoadingStage errorStage, final String i18nMessage, final Throwable originalException, Object... context) {
|
||||
super("Mod Loading Exception", originalException);
|
||||
this.modInfo = modInfo;
|
||||
this.errorStage = errorStage;
|
||||
this.i18nMessage = i18nMessage;
|
||||
this.context = Arrays.asList(context);
|
||||
}
|
||||
|
||||
static Stream<ModLoadingException> fromEarlyException(final EarlyLoadingException e) {
|
||||
return e.getAllData().stream().map(ed->new ModLoadingException(ed.getModInfo(), ModLoadingStage.VALIDATE, ed.getI18message(), e.getCause(), ed.getArgs()));
|
||||
}
|
||||
|
||||
public String getI18NMessage() {
|
||||
return i18nMessage;
|
||||
}
|
||||
|
||||
public Object[] getContext() {
|
||||
return context.toArray();
|
||||
}
|
||||
|
||||
public String formatToString() {
|
||||
return Bindings.getMessageParser().get().parseMessage(i18nMessage, Streams.concat(Stream.of(modInfo, errorStage, getCause()), context.stream()).toArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return formatToString();
|
||||
}
|
||||
|
||||
public IModInfo getModInfo() {
|
||||
return modInfo;
|
||||
}
|
||||
public String getCleanMessage() {
|
||||
return Bindings.getMessageParser().get().stripControlCodes(formatToString());
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
/**
|
||||
* Phases of mod loading, for grouping mod loading states.
|
||||
*/
|
||||
public enum ModLoadingPhase {
|
||||
/**
|
||||
* Special phase for exceptional situations and error handling, where mod loading cannot continue normally.
|
||||
*
|
||||
* <p>There is conventionally only one state with this phase.</p>
|
||||
*/
|
||||
ERROR, // Special state for error handling
|
||||
/**
|
||||
* Phase for discovering and gathering mods for loading.
|
||||
*/
|
||||
GATHER,
|
||||
/**
|
||||
* Phase for the loading of mods found from mod discovery.
|
||||
*
|
||||
* @see #GATHER
|
||||
*/
|
||||
LOAD,
|
||||
/**
|
||||
* Phase after mod loading has completed, for post-loading tasks.
|
||||
*
|
||||
* @see #LOAD
|
||||
*/
|
||||
COMPLETE,
|
||||
/**
|
||||
* Marker phase for the last state in the full mod loading process.
|
||||
*/
|
||||
DONE
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
/**
|
||||
* Mod loading stage of mod containers during the mod loading process. These will have a corresponding {@link ModLoadingState}
|
||||
* in the basic mod loading process provided by FML.
|
||||
*
|
||||
* <p>Each mod loading stage has a global {@link DeferredWorkQueue}, which is populated during the execution of the state
|
||||
* associated with this stage and emptied at the end of the state's execution.</p>
|
||||
*/
|
||||
public enum ModLoadingStage
|
||||
{
|
||||
/**
|
||||
* Special stage for exceptional situations and error handling.
|
||||
*/
|
||||
ERROR,
|
||||
/**
|
||||
* Validation of the mod list.
|
||||
* TODO: figure out where this is used and why this exists instead of CONSTRUCT being the first normal stage
|
||||
*/
|
||||
VALIDATE,
|
||||
/**
|
||||
* Default stage of mod containers after construction.
|
||||
*/
|
||||
CONSTRUCT,
|
||||
/**
|
||||
* Common (non-side-specific) setup and initialization.
|
||||
*/
|
||||
COMMON_SETUP,
|
||||
/**
|
||||
* Side-specific setup and initialization.
|
||||
*
|
||||
* @see net.minecraftforge.api.distmarker.Dist
|
||||
*/
|
||||
SIDED_SETUP,
|
||||
/**
|
||||
* Stage for enqueuing {@link net.minecraftforge.fml.InterModComms} messages for later processing.
|
||||
*/
|
||||
ENQUEUE_IMC,
|
||||
/**
|
||||
* Stage for processing received messages though {@link net.minecraftforge.fml.InterModComms}.
|
||||
*/
|
||||
PROCESS_IMC,
|
||||
/**
|
||||
* Marks the completion of mod loading for this container.
|
||||
*/
|
||||
COMPLETE,
|
||||
/**
|
||||
* Marks the completion of the full mod loading process.
|
||||
*/
|
||||
DONE;
|
||||
|
||||
private final DeferredWorkQueue deferredWorkQueue;
|
||||
|
||||
ModLoadingStage() {
|
||||
deferredWorkQueue = new DeferredWorkQueue(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the next stage after this stage, or {@link #ERROR} if the exception is not {@code null}}
|
||||
*
|
||||
* @param exception the exception that occurred during this stage, may be {@code null}
|
||||
*/
|
||||
ModLoadingStage nextState(Throwable exception) {
|
||||
return exception != null ? ERROR : values()[this.ordinal()+1];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return this stage, or {@link #ERROR} if the exception is not {@code null}}
|
||||
*
|
||||
* @param exception the exception that occurred during this stage, may be {@code null}
|
||||
*/
|
||||
public ModLoadingStage currentState(Throwable exception) {
|
||||
return exception != null ? ERROR : this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the deferred work queue for this stage}
|
||||
*/
|
||||
public DeferredWorkQueue getDeferredWorkQueue() {
|
||||
return deferredWorkQueue;
|
||||
}
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.fml.loading.progress.ProgressMeter;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.ToIntFunction;
|
||||
|
||||
/**
|
||||
* Implementation of the {@link IModLoadingState} interface.
|
||||
*
|
||||
* @param name the name of this state
|
||||
* @param previous the name of the state immediately previous to this state
|
||||
* @param message a function returning a human-friendly message for this state
|
||||
* @param phase the mod loading phase this state belongs to
|
||||
* @param inlineRunnable an optional runnable, which runs before starting the transition from this state to the next
|
||||
* @param transition optional state transition information
|
||||
*/
|
||||
public record ModLoadingState(
|
||||
String name,
|
||||
String previous,
|
||||
Function<ModList, String> message,
|
||||
ToIntFunction<ModList> size,
|
||||
ModLoadingPhase phase,
|
||||
Optional<Consumer<ModList>> inlineRunnable,
|
||||
Optional<IModStateTransition> transition
|
||||
) implements IModLoadingState {
|
||||
@Override
|
||||
public <T extends Event & IModBusEvent> Optional<CompletableFuture<Void>> buildTransition(
|
||||
final Executor syncExecutor,
|
||||
final Executor parallelExecutor,
|
||||
final ProgressMeter progressBar,
|
||||
final Function<Executor, CompletableFuture<Void>> preSyncTask,
|
||||
final Function<Executor, CompletableFuture<Void>> postSyncTask
|
||||
) {
|
||||
var transition = this.transition.orElse(null);
|
||||
return transition == null
|
||||
? Optional.empty()
|
||||
: Optional.ofNullable(transition.build(name, syncExecutor, parallelExecutor, progressBar, preSyncTask, postSyncTask));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return an empty mod loading state} The mod loading state has a blank human-readable message, no inline runnable,
|
||||
* and no state transition information.
|
||||
*
|
||||
* @param name the name of the state
|
||||
* @param previous the name of the immediately previous state to this state
|
||||
* @param phase the mod loading phase the state belongs to
|
||||
*
|
||||
* @deprecated Use the builder
|
||||
*/
|
||||
@Deprecated(since = "1.21.3", forRemoval = true)
|
||||
public static ModLoadingState empty(final String name, final String previous, final ModLoadingPhase phase) {
|
||||
return of(name, phase).after(previous).empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a mod loading state with state transition information and a default human-friendly message of
|
||||
* {@code Processing transition [name]}.
|
||||
*
|
||||
* @param name the name of the state
|
||||
* @param previous the name of the immediately previous state to this state
|
||||
* @param phase the mod loading phase the state belongs to
|
||||
* @param transition the state transition information
|
||||
*
|
||||
* @deprecated Use the builder
|
||||
*/
|
||||
@Deprecated(since = "1.21.3", forRemoval = true)
|
||||
public static ModLoadingState withTransition(final String name, final String previous, final ModLoadingPhase phase,
|
||||
final IModStateTransition transition) {
|
||||
return of(name, phase).after(previous).withTransition(transition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a mod loading state with state transition information and a custom human-friendly message function.
|
||||
*
|
||||
* @param name the name of the state
|
||||
* @param previous the name of the immediately previous state to this state
|
||||
* @param message a function returning a human-friendly message for this state
|
||||
* @param phase the mod loading phase the state belongs to
|
||||
* @param transition the state transition information
|
||||
* @return a mod loading state with state transition information and a custom message
|
||||
*
|
||||
* @deprecated Use the builder
|
||||
*/
|
||||
@Deprecated(since = "1.21.3", forRemoval = true)
|
||||
public static ModLoadingState withTransition(final String name, final String previous,
|
||||
final Function<ModList, String> message, final ModLoadingPhase phase,
|
||||
final IModStateTransition transition) {
|
||||
return of(name, phase).after(previous).message(message).withTransition(transition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a mod loading state with an inline runnable and a default human-friendly message of {@code Processing
|
||||
* work [name]}.
|
||||
*
|
||||
* @param name the name of the state
|
||||
* @param previous the name of the immediately previous state to this state
|
||||
* @param phase the mod loading phase the state belongs to
|
||||
* @param inline an optional runnable, which runs before starting the transition from this state to the next
|
||||
* @return a mod loading state with an inline runnable and default message
|
||||
*
|
||||
* @deprecated Use the builder
|
||||
*/
|
||||
@Deprecated(since = "1.21.3", forRemoval = true)
|
||||
public static ModLoadingState withInline(final String name, final String previous, final ModLoadingPhase phase,
|
||||
final Consumer<ModList> inline) {
|
||||
return of(name, phase).after(previous).withInline(inline);
|
||||
}
|
||||
|
||||
public static Builder of(final String name, final ModLoadingPhase phase) {
|
||||
return new Builder(name, phase);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private final String name;
|
||||
private final ModLoadingPhase phase;
|
||||
private String after;
|
||||
private Function<ModList, String> message = null;
|
||||
private ToIntFunction<ModList> size = null;
|
||||
|
||||
private Builder(final String name, final ModLoadingPhase phase) {
|
||||
this.name = name;
|
||||
this.phase = phase;
|
||||
}
|
||||
|
||||
public Builder after(final ModLoadingState value) { return after(value.name()); }
|
||||
public Builder after(final String value) {
|
||||
this.after = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder message(final String value) { return message(ml -> value); }
|
||||
public Builder message(final Function<ModList, String> value) {
|
||||
this.message = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder size(final int value) { return size(ml -> value); }
|
||||
public Builder size(final ToIntFunction<ModList> value) {
|
||||
this.size = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ModLoadingState empty() {
|
||||
return new ModLoadingState(name, after, message != null ? message : ml -> "", size != null ? size : ml -> 0, phase, Optional.empty(), Optional.empty());
|
||||
}
|
||||
|
||||
public ModLoadingState withTransition(final IModStateTransition transition) {
|
||||
return new ModLoadingState(name, after, message != null ? message : ml -> "Processing transition " + name, size != null ? size : ModList::size, phase, Optional.empty(), Optional.of(transition));
|
||||
}
|
||||
|
||||
public ModLoadingState withInline(final Consumer<ModList> inline) {
|
||||
return new ModLoadingState(name, after, message != null ? message : ml -> "Processing work " + name, size != null ? size : ml -> 0, phase, Optional.of(inline), Optional.empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import com.google.common.collect.Streams;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class ModLoadingWarning {
|
||||
/**
|
||||
* Mod Info for mod with warning
|
||||
*/
|
||||
private final IModInfo modInfo;
|
||||
/**
|
||||
* The stage where this warning was encountered
|
||||
*/
|
||||
private final ModLoadingStage warningStage;
|
||||
|
||||
/**
|
||||
* I18N message to use for display
|
||||
*/
|
||||
private final String i18nMessage;
|
||||
|
||||
/**
|
||||
* Context for message display
|
||||
*/
|
||||
private final List<Object> context;
|
||||
|
||||
public ModLoadingWarning(final IModInfo modInfo, final ModLoadingStage warningStage, final String i18nMessage, Object... context) {
|
||||
this.modInfo = modInfo;
|
||||
this.warningStage = warningStage;
|
||||
this.i18nMessage = i18nMessage;
|
||||
this.context = Arrays.asList(context);
|
||||
}
|
||||
|
||||
public String formatToString() {
|
||||
return Bindings.getMessageParser().get().parseMessage(i18nMessage, Streams.concat(Stream.of(modInfo, warningStage), context.stream()).toArray());
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import com.google.common.graph.GraphBuilder;
|
||||
import cpw.mods.modlauncher.util.ServiceLoaderUtils;
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
import net.minecraftforge.fml.loading.toposort.TopologicalSort;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
public class ModStateManager {
|
||||
private final EnumMap<ModLoadingPhase, List<IModLoadingState>> stateMap;
|
||||
|
||||
public ModStateManager() {
|
||||
final var sp = ServiceLoader.load(FMLLoader.getGameLayer(), IModStateProvider.class);
|
||||
this.stateMap = ServiceLoaderUtils.streamWithErrorHandling(sp, sce->{})
|
||||
.map(IModStateProvider::getAllStates)
|
||||
.flatMap(List::stream)
|
||||
.collect(Collectors.groupingBy(IModLoadingState::phase, ()->new EnumMap<>(ModLoadingPhase.class), Collectors.toUnmodifiableList()));
|
||||
}
|
||||
|
||||
public List<IModLoadingState> getStates(final ModLoadingPhase phase) {
|
||||
var nodes = stateMap.get(phase);
|
||||
var lookup = nodes.stream().collect(Collectors.toMap(IModLoadingState::name, Function.identity()));
|
||||
|
||||
var graph = GraphBuilder.directed().allowsSelfLoops(false).<IModLoadingState>build();
|
||||
var dummy = ModLoadingState.empty("", "", phase);
|
||||
nodes.forEach(graph::addNode);
|
||||
graph.addNode(dummy);
|
||||
for (IModLoadingState node : nodes) {
|
||||
graph.putEdge(lookup.getOrDefault(node.previous(), dummy), node);
|
||||
}
|
||||
return TopologicalSort.topologicalSort(graph, Comparator.comparingInt(nodes::indexOf)).stream().filter(st->st!=dummy).toList();
|
||||
}
|
||||
|
||||
public IModLoadingState findState(final String stateName) {
|
||||
return stateMap.values()
|
||||
.stream()
|
||||
.flatMap(List::stream)
|
||||
.filter(mls -> mls.name().equals(stateName))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalArgumentException("Unknown IModLoadingState: " + stateName));
|
||||
}
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.IModStateTransition.EventGenerator;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.fml.loading.progress.ProgressMeter;
|
||||
|
||||
@ApiStatus.Internal
|
||||
class ModStateTransitionHelper {
|
||||
static final IModStateTransition NOOP = new NoopTransition();
|
||||
record NoopTransition() implements IModStateTransition {
|
||||
@Override
|
||||
public ThreadSelector threadSelector() {
|
||||
return ThreadSelector.SYNC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BiFunction<Executor, CompletableFuture<Void>, CompletableFuture<Void>> finalActivityGenerator() {
|
||||
return (e, t) -> t.thenApplyAsync(Function.identity(), e);
|
||||
}
|
||||
}
|
||||
|
||||
static <V> CompletionStage<Void> completableFutureFromExceptionList(List<FutureResult<V>> t) {
|
||||
if (t.stream().noneMatch(e -> e.exception() != null)) {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
} else {
|
||||
final var throwables = t.stream().map(FutureResult::exception).filter(e -> e != null).toList();
|
||||
CompletableFuture<Void> cf = new CompletableFuture<>();
|
||||
final RuntimeException accumulator = new RuntimeException();
|
||||
cf.completeExceptionally(accumulator);
|
||||
for (Throwable exception : throwables) {
|
||||
if (exception instanceof CompletionException) {
|
||||
exception = exception.getCause();
|
||||
}
|
||||
if (exception.getSuppressed().length != 0) {
|
||||
for (Throwable throwable : exception.getSuppressed()) {
|
||||
accumulator.addSuppressed(throwable);
|
||||
}
|
||||
} else {
|
||||
accumulator.addSuppressed(exception);
|
||||
}
|
||||
}
|
||||
return cf;
|
||||
}
|
||||
}
|
||||
|
||||
record FutureResult<V>(V value, Throwable exception){}
|
||||
|
||||
static <V> CompletableFuture<List<FutureResult<V>>> gather(Collection<? extends CompletableFuture<? extends V>> futures) {
|
||||
var list = new ArrayList<FutureResult<V>>(futures.size());
|
||||
var results = new CompletableFuture[futures.size()];
|
||||
|
||||
for (var future : futures) {
|
||||
int i = list.size();
|
||||
list.add(null);
|
||||
results[i] = future.whenComplete((result, exception) -> list.set(i, new FutureResult<>(result, exception)));
|
||||
}
|
||||
|
||||
return CompletableFuture.allOf(results).handle((r, th)->null).thenApply(res -> list);
|
||||
}
|
||||
|
||||
private static <T extends Event & IModBusEvent> void addCompletableFutureTaskForModDispatch(
|
||||
final IModStateTransition transition,
|
||||
final Executor executor,
|
||||
final List<CompletableFuture<Void>> completableFutures,
|
||||
final ProgressMeter progressBar,
|
||||
final EventGenerator<T> eventGenerator,
|
||||
final BiFunction<ModLoadingStage, Throwable, ModLoadingStage> nextState
|
||||
) {
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
var preDispatchHook = getHook(transition.preDispatchHook(), executor, eventGenerator);
|
||||
if (preDispatchHook != null)
|
||||
completableFutures.add(preDispatchHook);
|
||||
|
||||
var modFutures = new LinkedHashMap<String, CompletableFuture<Void>>();
|
||||
for (var mod : ModList.get().getLoadedMods()) {
|
||||
|
||||
CompletableFuture<Void> parent = null;
|
||||
if (mod.dependencies.isEmpty()) {
|
||||
parent = CompletableFuture.allOf();
|
||||
} else {
|
||||
var deps = new CompletableFuture[mod.dependencies.size()];
|
||||
int idx = 0;
|
||||
for (var depContainer : mod.dependencies) {
|
||||
var future = modFutures.get(depContainer.getModId());
|
||||
if (future == null)
|
||||
throw new IllegalStateException("Could not find dependency future " + depContainer.getModId() + " for " + mod.getModId());
|
||||
deps[idx++] = future;
|
||||
}
|
||||
parent = CompletableFuture.allOf(deps);
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
var dispatch = parent
|
||||
.thenRunAsync(() -> {
|
||||
ModLoadingContext.get().setActiveContainer(mod);
|
||||
var handler = mod.activityMap.get(mod.modLoadingStage);
|
||||
if (handler != null)
|
||||
handler.run();
|
||||
mod.acceptEvent(eventGenerator.apply(mod));
|
||||
}, executor)
|
||||
.whenComplete((mc, exception) -> {
|
||||
mod.modLoadingStage = nextState.apply(mod.modLoadingStage, exception);
|
||||
progressBar.increment();
|
||||
ModLoadingContext.get().setActiveContainer(null);
|
||||
});
|
||||
|
||||
modFutures.put(mod.getModId(), dispatch);
|
||||
}
|
||||
|
||||
var dispatch = gather(modFutures.values()).thenComposeAsync(ModStateTransitionHelper::completableFutureFromExceptionList, executor);
|
||||
completableFutures.add(dispatch);
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
var postDispatchHook = getHook(transition.preDispatchHook(), executor, eventGenerator);
|
||||
if (postDispatchHook != null)
|
||||
completableFutures.add(postDispatchHook);
|
||||
}
|
||||
|
||||
private static <T extends Event & IModBusEvent> CompletableFuture<Void> getHook(BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> hook, Executor executor, EventGenerator<T> eventGenerator) {
|
||||
if (hook == null || hook == IModStateTransition.NULL_HOOK) return null;
|
||||
@SuppressWarnings("unchecked")
|
||||
var hookTyped = (BiFunction<Executor, EventGenerator<T>, CompletableFuture<Void>>)hook;
|
||||
return hookTyped.apply(executor, eventGenerator);
|
||||
}
|
||||
|
||||
static <T extends Event & IModBusEvent> CompletableFuture<Void> build(
|
||||
final IModStateTransition transition,
|
||||
final String name,
|
||||
final Executor syncExecutor,
|
||||
final Executor parallelExecutor,
|
||||
final ProgressMeter progressBar,
|
||||
final Function<Executor, CompletableFuture<Void>> preSyncTask,
|
||||
final Function<Executor, CompletableFuture<Void>> postSyncTask
|
||||
) {
|
||||
List<CompletableFuture<Void>> futures = new ArrayList<>();
|
||||
final var executor = transition.threadSelector().apply(syncExecutor, parallelExecutor);
|
||||
|
||||
@SuppressWarnings({ "removal", "unchecked" })
|
||||
var events = transition.eventFunctionStream().get().map(f -> (EventGenerator<T>)f).toList();
|
||||
for (int x = 0; x < events.size(); x++) {
|
||||
var gen = events.get(x);
|
||||
BiFunction<ModLoadingStage, Throwable, ModLoadingStage> state = x == events.size() - 1
|
||||
? transition.nextModLoadingStage()
|
||||
: ModLoadingStage::currentState;
|
||||
addCompletableFutureTaskForModDispatch(transition, executor, futures, progressBar, gen, state);
|
||||
}
|
||||
|
||||
final CompletableFuture<Void> preSyncTaskCF = preSyncTask.apply(syncExecutor);
|
||||
final CompletableFuture<Void> eventDispatchCF = gather(futures).thenCompose(ModStateTransitionHelper::completableFutureFromExceptionList);
|
||||
final CompletableFuture<Void> postEventDispatchCF = preSyncTaskCF
|
||||
.thenApplyAsync(n -> {
|
||||
progressBar.label(progressBar.name() + ": dispatching " + name);
|
||||
return null;
|
||||
}, parallelExecutor)
|
||||
.thenComposeAsync(n -> eventDispatchCF, parallelExecutor)
|
||||
.thenApply(r -> {
|
||||
postSyncTask.apply(syncExecutor);
|
||||
return null;
|
||||
});
|
||||
return transition.finalActivityGenerator().apply(syncExecutor, postEventDispatchCF);
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.fml.loading.FMLConfig;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
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();
|
||||
|
||||
default void drive(Runnable ticker) {
|
||||
if (!selfDriven()) {
|
||||
ticker.run();
|
||||
while (true) {
|
||||
if (!driveOne()) break;
|
||||
}
|
||||
} else {
|
||||
// park for a bit so other threads can schedule
|
||||
LockSupport.parkNanos(PARK_TIME);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private record SyncExecutor(ConcurrentLinkedDeque<Runnable> tasks) implements DrivenExecutor {
|
||||
public SyncExecutor() {
|
||||
this(new ConcurrentLinkedDeque<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean driveOne() {
|
||||
final Runnable task = tasks.pollFirst();
|
||||
if (task != null) {
|
||||
task.run();
|
||||
}
|
||||
return task != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean selfDriven() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(final Runnable command) {
|
||||
tasks.addLast(command);
|
||||
}
|
||||
}
|
||||
|
||||
private record WrappingExecutor(Executor wrapped) implements DrivenExecutor {
|
||||
@Override
|
||||
public boolean selfDriven() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean driveOne() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(final Runnable command) {
|
||||
wrapped.execute(command);
|
||||
}
|
||||
}
|
||||
|
||||
private static final SyncExecutor syncExecutor = new SyncExecutor();
|
||||
|
||||
public static DrivenExecutor syncExecutor() {
|
||||
return syncExecutor;
|
||||
}
|
||||
|
||||
public static DrivenExecutor wrappedExecutor(Executor executor) {
|
||||
return new WrappingExecutor(executor);
|
||||
}
|
||||
|
||||
public static Executor parallelExecutor() {
|
||||
return LazyInit.PARALLEL_EXECUTOR;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,227 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public final class OptionalMod<T>
|
||||
{
|
||||
private final String modId;
|
||||
private T value;
|
||||
private boolean searched;
|
||||
|
||||
public static <M> OptionalMod<M> of(final String modId) {
|
||||
return new OptionalMod<>(modId);
|
||||
}
|
||||
|
||||
private static final OptionalMod<?> EMPTY = new OptionalMod<>(true);
|
||||
private static <T> OptionalMod<T> empty() {
|
||||
@SuppressWarnings("unchecked")
|
||||
OptionalMod<T> t = (OptionalMod<T>) EMPTY;
|
||||
return t;
|
||||
}
|
||||
|
||||
private OptionalMod(boolean searched) {
|
||||
this.searched = searched;
|
||||
this.modId = "";
|
||||
}
|
||||
|
||||
private OptionalMod(String modId)
|
||||
{
|
||||
this.modId = modId;
|
||||
}
|
||||
|
||||
private T getValue()
|
||||
{
|
||||
if (!searched) {
|
||||
this.value = ModList.get().<T>getModObjectById(this.modId).orElse(null);
|
||||
searched = true;
|
||||
}
|
||||
return this.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* If a mod is present in this {@code OptionalMod}, returns the value,
|
||||
* otherwise throws {@code NoSuchElementException}.
|
||||
*
|
||||
* @return the modobject held by this {@code OptionalMod}
|
||||
* @throws NoSuchElementException if there is no modobject present
|
||||
*
|
||||
* @see Optional#isPresent()
|
||||
*/
|
||||
public T get() {
|
||||
if (getValue() == null) {
|
||||
throw new NoSuchElementException("No value present");
|
||||
}
|
||||
return getValue();
|
||||
}
|
||||
|
||||
public String getModId() {
|
||||
return this.modId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if there is a mod object present, otherwise {@code false}.
|
||||
*
|
||||
* @return {@code true} if there is a mod object present, otherwise {@code false}
|
||||
*/
|
||||
public boolean isPresent() {
|
||||
return getValue() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* If a mod object is present, invoke the specified consumer with the object,
|
||||
* otherwise do nothing.
|
||||
*
|
||||
* @param consumer block to be executed if a mod object is present
|
||||
* @throws NullPointerException if mod object is present and {@code consumer} is
|
||||
* null
|
||||
*/
|
||||
public void ifPresent(Consumer<? super T> consumer) {
|
||||
if (getValue() != null)
|
||||
consumer.accept(getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* If a mod object is present, and the mod object matches the given predicate,
|
||||
* return an {@code OptionalMod} describing the value, otherwise return an
|
||||
* empty {@code OptionalMod}.
|
||||
*
|
||||
* @param predicate a predicate to apply to the mod object, if present
|
||||
* @return an {@code OptionalMod} describing the value of this {@code OptionalMod}
|
||||
* if a mod object is present and the mod object matches the given predicate,
|
||||
* otherwise an empty {@code OptionalMod}
|
||||
* @throws NullPointerException if the predicate is null
|
||||
*/
|
||||
public OptionalMod<T> filter(Predicate<? super T> predicate) {
|
||||
Objects.requireNonNull(predicate);
|
||||
if (!isPresent())
|
||||
return this;
|
||||
else
|
||||
return predicate.test(getValue()) ? this : empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* If a mod object is present, apply the provided mapping function to it,
|
||||
* and if the result is non-null, return an {@code Optional} describing the
|
||||
* result. Otherwise return an empty {@code Optional}.
|
||||
*
|
||||
* @apiNote This method supports post-processing on optional values, without
|
||||
* the need to explicitly check for a return status.
|
||||
*
|
||||
* @param <U> The type of the result of the mapping function
|
||||
* @param mapper a mapping function to apply to the mod object, if present
|
||||
* @return an {@code Optional} describing the result of applying a mapping
|
||||
* function to the mod object of this {@code OptionalMod}, if a mod object is present,
|
||||
* otherwise an empty {@code Optional}
|
||||
* @throws NullPointerException if the mapping function is null
|
||||
*/
|
||||
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
|
||||
Objects.requireNonNull(mapper);
|
||||
if (!isPresent())
|
||||
return Optional.empty();
|
||||
else {
|
||||
return Optional.ofNullable(mapper.apply(getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If a value is present, apply the provided {@code Optional}-bearing
|
||||
* mapping function to it, return that result, otherwise return an empty
|
||||
* {@code Optional}. This method is similar to {@link #map(Function)},
|
||||
* but the provided mapper is one whose result is already an {@code Optional},
|
||||
* and if invoked, {@code flatMap} does not wrap it with an additional
|
||||
* {@code Optional}.
|
||||
*
|
||||
* @param <U> The type parameter to the {@code Optional} returned by
|
||||
* @param mapper a mapping function to apply to the mod object, if present
|
||||
* the mapping function
|
||||
* @return the result of applying an {@code Optional}-bearing mapping
|
||||
* function to the value of this {@code Optional}, if a value is present,
|
||||
* otherwise an empty {@code Optional}
|
||||
* @throws NullPointerException if the mapping function is null or returns
|
||||
* a null result
|
||||
*/
|
||||
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
|
||||
Objects.requireNonNull(mapper);
|
||||
if (!isPresent())
|
||||
return Optional.empty();
|
||||
else {
|
||||
return Objects.requireNonNull(mapper.apply(getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mod object if present, otherwise return {@code other}.
|
||||
*
|
||||
* @param other the mod object to be returned if there is no mod object present, may
|
||||
* be null
|
||||
* @return the mod object, if present, otherwise {@code other}
|
||||
*/
|
||||
public T orElse(T other) {
|
||||
return getValue() != null ? getValue() : other;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mod object if present, otherwise invoke {@code other} and return
|
||||
* the result of that invocation.
|
||||
*
|
||||
* @param other a {@code Supplier} whose result is returned if no mod object
|
||||
* is present
|
||||
* @return the mod object if present otherwise the result of {@code other.get()}
|
||||
* @throws NullPointerException if mod object is not present and {@code other} is
|
||||
* null
|
||||
*/
|
||||
public T orElseGet(Supplier<? extends T> other) {
|
||||
return getValue() != null ? getValue() : other.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the contained mod object, if present, otherwise throw an exception
|
||||
* to be created by the provided supplier.
|
||||
*
|
||||
* @apiNote A method reference to the exception constructor with an empty
|
||||
* argument list can be used as the supplier. For example,
|
||||
* {@code IllegalStateException::new}
|
||||
*
|
||||
* @param <X> Type of the exception to be thrown
|
||||
* @param exceptionSupplier The supplier which will return the exception to
|
||||
* be thrown
|
||||
* @return the present mod object
|
||||
* @throws X if there is no mod object present
|
||||
* @throws NullPointerException if no mod object is present and
|
||||
* {@code exceptionSupplier} is null
|
||||
*/
|
||||
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
|
||||
if (getValue() != null) {
|
||||
return getValue();
|
||||
} else {
|
||||
throw exceptionSupplier.get();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj) return true;
|
||||
if (obj instanceof OptionalMod<?> optionalMod) {
|
||||
return Objects.equals(optionalMod.modId, modId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hashCode(modId);
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.fml.loading.progress.ProgressMeter;
|
||||
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class StartupMessageManager {
|
||||
public static void addModMessage(final String message) {
|
||||
StartupNotificationManager.addModMessage(message);
|
||||
}
|
||||
|
||||
public static Optional<Consumer<String>> modLoaderConsumer() {
|
||||
return StartupNotificationManager.modLoaderConsumer();
|
||||
}
|
||||
|
||||
public static Optional<Consumer<String>> mcLoaderConsumer() {
|
||||
return StartupNotificationManager.mcLoaderConsumer();
|
||||
}
|
||||
|
||||
public static ProgressMeter addProgressBar(final String barName, final int count) {
|
||||
return StartupNotificationManager.addProgressBar(barName, count);
|
||||
}
|
||||
|
||||
public static ProgressMeter prependProgressBar(final String barName, final int count) {
|
||||
return StartupNotificationManager.prependProgressBar(barName, count);
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.BinaryOperator;
|
||||
|
||||
public enum ThreadSelector implements BinaryOperator<Executor> {
|
||||
SYNC((sync, parallel) -> sync),
|
||||
PARALLEL((sync, parallel) -> parallel);
|
||||
|
||||
private final BinaryOperator<Executor> selector;
|
||||
|
||||
ThreadSelector(final BinaryOperator<Executor> selector) {
|
||||
this.selector = selector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Executor apply(final Executor sync, final Executor parallel) {
|
||||
return this.selector.apply(sync, parallel);
|
||||
}
|
||||
}
|
||||
@@ -1,239 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import net.minecraftforge.fml.loading.FMLConfig;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.maven.artifact.versioning.ComparableVersion;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import static net.minecraftforge.fml.VersionChecker.Status.*;
|
||||
|
||||
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 {
|
||||
PENDING(),
|
||||
FAILED(),
|
||||
UP_TO_DATE(),
|
||||
OUTDATED(3, true, true),
|
||||
AHEAD(),
|
||||
BETA(),
|
||||
BETA_OUTDATED(6, true, true);
|
||||
|
||||
private final int sheetOffset;
|
||||
private final boolean draw, animated;
|
||||
|
||||
Status() {
|
||||
this(0, false, false);
|
||||
}
|
||||
|
||||
Status(int sheetOffset, boolean draw, boolean animated) {
|
||||
this.sheetOffset = sheetOffset;
|
||||
this.draw = draw;
|
||||
this.animated = animated;
|
||||
}
|
||||
|
||||
public int getSheetOffset() {
|
||||
return sheetOffset;
|
||||
}
|
||||
|
||||
public boolean shouldDraw() {
|
||||
return draw;
|
||||
}
|
||||
|
||||
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") {
|
||||
private HttpClient client;
|
||||
private String mcVersion;
|
||||
private String agent;
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the response body as a String for the given URL while following redirects
|
||||
*/
|
||||
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++) {
|
||||
var request = HttpRequest.newBuilder()
|
||||
.uri(currentUrl.toURI())
|
||||
.timeout(Duration.ofSeconds(HTTP_TIMEOUT_SECS))
|
||||
.setHeader("Accept-Encoding", "gzip")
|
||||
.setHeader("User-Agent", userAgent)
|
||||
.GET()
|
||||
.build();
|
||||
|
||||
final HttpResponse<InputStream> response = client.send(request, HttpResponse.BodyHandlers.ofInputStream());
|
||||
|
||||
int responseCode = response.statusCode();
|
||||
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);
|
||||
continue;
|
||||
}
|
||||
|
||||
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"));
|
||||
}
|
||||
return bodyStr;
|
||||
}
|
||||
throw new IOException("Too many redirects while trying to fetch " + url);
|
||||
}
|
||||
|
||||
private void process(IModInfo mod) {
|
||||
Status status = PENDING;
|
||||
ComparableVersion target = null;
|
||||
Map<ComparableVersion, String> changes = null;
|
||||
String display_url = null;
|
||||
try {
|
||||
if (mod.getUpdateURL().isEmpty()) return;
|
||||
URL url = mod.getUpdateURL().get();
|
||||
LOGGER.info("[{}] Starting version check at {}", mod.getModId(), url.toString());
|
||||
|
||||
String data = openUrlString(url, mod);
|
||||
|
||||
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");
|
||||
|
||||
String rec = promos.get(mcVersion + "-recommended");
|
||||
String lat = promos.get(mcVersion + "-latest");
|
||||
ComparableVersion current = new ComparableVersion(mod.getVersion().toString());
|
||||
|
||||
if (rec != null) {
|
||||
ComparableVersion recommended = new ComparableVersion(rec);
|
||||
int diff = recommended.compareTo(current);
|
||||
|
||||
if (diff == 0)
|
||||
status = UP_TO_DATE;
|
||||
else if (diff < 0) {
|
||||
status = AHEAD;
|
||||
if (lat != null) {
|
||||
ComparableVersion latest = new ComparableVersion(lat);
|
||||
if (current.compareTo(latest) < 0) {
|
||||
status = OUTDATED;
|
||||
target = latest;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
status = OUTDATED;
|
||||
target = recommended;
|
||||
}
|
||||
} else if (lat != null) {
|
||||
ComparableVersion latest = new ComparableVersion(lat);
|
||||
status = current.compareTo(latest) < 0 ? BETA_OUTDATED : BETA;
|
||||
target = latest;
|
||||
} else
|
||||
status = BETA;
|
||||
|
||||
LOGGER.info("[{}] Found status: {} Current: {} Target: {}", mod.getModId(), status, current, target);
|
||||
|
||||
changes = new LinkedHashMap<>();
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> tmp = (Map<String, String>)json.get(mcVersion);
|
||||
if (tmp != null) {
|
||||
List<ComparableVersion> ordered = new ArrayList<>();
|
||||
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) {
|
||||
changes.put(ver, tmp.get(ver.toString()));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("Failed to process update information", e);
|
||||
status = FAILED;
|
||||
}
|
||||
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() {
|
||||
List<IModInfo> ret = new LinkedList<>();
|
||||
for (IModInfo info : ModList.get().getMods()) {
|
||||
if (info.getUpdateURL().isPresent())
|
||||
ret.add(info);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static final Map<IModInfo, CheckResult> RESULTS = new ConcurrentHashMap<>();
|
||||
private static final CheckResult PENDING_CHECK = new CheckResult(PENDING, null, null, null);
|
||||
|
||||
public static CheckResult getResult(IModInfo mod) {
|
||||
return RESULTS.getOrDefault(mod, PENDING_CHECK);
|
||||
}
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.config;
|
||||
|
||||
import com.electronwill.nightconfig.core.ConfigFormat;
|
||||
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
|
||||
import com.electronwill.nightconfig.core.file.FileWatcher;
|
||||
import com.electronwill.nightconfig.core.io.ParsingException;
|
||||
import com.electronwill.nightconfig.core.io.WritingMode;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraftforge.fml.loading.FMLConfig;
|
||||
import net.minecraftforge.fml.loading.FMLPaths;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static net.minecraftforge.fml.config.ConfigTracker.CONFIG;
|
||||
|
||||
public class ConfigFileTypeHandler {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static final 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());
|
||||
final CommentedFileConfig configData = CommentedFileConfig.builder(configPath).sync().
|
||||
preserveInsertionOrder().
|
||||
autosave().
|
||||
onFileNotFound((newfile, configFormat)-> setupConfigFile(c, newfile, configFormat)).
|
||||
writingMode(WritingMode.REPLACE).
|
||||
build();
|
||||
LOGGER.debug(CONFIG, "Built TOML config for {}", configPath);
|
||||
try
|
||||
{
|
||||
configData.load();
|
||||
}
|
||||
catch (ParsingException ex)
|
||||
{
|
||||
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);
|
||||
return configData;
|
||||
};
|
||||
}
|
||||
|
||||
public void unload(Path configBasePath, ModConfig config) {
|
||||
Path configPath = configBasePath.resolve(config.getFileName());
|
||||
try {
|
||||
this.getWatcher().removeWatch(configPath);
|
||||
} catch (RuntimeException e) {
|
||||
LOGGER.error("Failed to remove config {} from tracker!", configPath, 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());
|
||||
}
|
||||
Path p = defaultConfigPath.resolve(modConfig.getFileName());
|
||||
if (Files.exists(p)) {
|
||||
LOGGER.info(CONFIG, "Loading default config file from path {}", p);
|
||||
Files.copy(p, file);
|
||||
} else {
|
||||
Files.createFile(file);
|
||||
conf.initEmptyFile(file);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void backUpConfig(final CommentedFileConfig commentedFileConfig)
|
||||
{
|
||||
backUpConfig(commentedFileConfig, 5); //TODO: Think of a way for mods to set their own preference (include a sanity check as well, no disk stuffing)
|
||||
}
|
||||
|
||||
public static void backUpConfig(final CommentedFileConfig commentedFileConfig, final int maxBackups)
|
||||
{
|
||||
Path bakFileLocation = commentedFileConfig.getNioPath().getParent();
|
||||
String bakFileName = FilenameUtils.removeExtension(commentedFileConfig.getFile().getName());
|
||||
String bakFileExtension = FilenameUtils.getExtension(commentedFileConfig.getFile().getName()) + ".bak";
|
||||
Path bakFile = bakFileLocation.resolve(bakFileName + "-1" + "." + bakFileExtension);
|
||||
try
|
||||
{
|
||||
for(int i = maxBackups; i > 0; i--)
|
||||
{
|
||||
Path oldBak = bakFileLocation.resolve(bakFileName + "-" + i + "." + bakFileExtension);
|
||||
if(Files.exists(oldBak))
|
||||
{
|
||||
if(i >= maxBackups)
|
||||
Files.delete(oldBak);
|
||||
else
|
||||
Files.move(oldBak, bakFileLocation.resolve(bakFileName + "-" + (i + 1) + "." + bakFileExtension));
|
||||
}
|
||||
}
|
||||
Files.copy(commentedFileConfig.getNioPath(), bakFile);
|
||||
}
|
||||
catch (IOException exception)
|
||||
{
|
||||
LOGGER.warn(CONFIG, "Failed to back up config file {}", commentedFileConfig.getNioPath(), exception);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ConfigFileTypeHandler[" + (type != null ? type : "UNKNOWN") + "]";
|
||||
}
|
||||
|
||||
private record ConfigWatcher(
|
||||
ModConfig modConfig,
|
||||
CommentedFileConfig commentedFileConfig,
|
||||
ClassLoader realClassLoader
|
||||
) implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
// Force the regular classloader onto the special thread
|
||||
Thread.currentThread().setContextClassLoader(realClassLoader);
|
||||
if (!this.modConfig.getSpec().isCorrecting()) {
|
||||
try
|
||||
{
|
||||
this.commentedFileConfig.load();
|
||||
if(!this.modConfig.getSpec().isCorrect(commentedFileConfig))
|
||||
{
|
||||
LOGGER.warn(CONFIG, "Configuration file {} is not correct. Correcting", commentedFileConfig.getFile().getAbsolutePath());
|
||||
ConfigFileTypeHandler.backUpConfig(commentedFileConfig);
|
||||
this.modConfig.getSpec().correct(commentedFileConfig);
|
||||
commentedFileConfig.save();
|
||||
}
|
||||
}
|
||||
catch (ParsingException ex)
|
||||
{
|
||||
throw new ConfigLoadingException(modConfig, ex);
|
||||
}
|
||||
LOGGER.debug(CONFIG, "Config file {} changed, sending notifies", this.modConfig.getFileName());
|
||||
this.modConfig.getSpec().afterReload();
|
||||
this.modConfig.fireEvent(IConfigEvent.reloading(this.modConfig));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class ConfigLoadingException extends RuntimeException
|
||||
{
|
||||
public ConfigLoadingException(ModConfig config, Exception cause)
|
||||
{
|
||||
super("Failed loading config file " + config.getFileName() + " of type " + config.getType() + " for modid " + config.getModId(), cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
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;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class ConfigTracker {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
static final Marker CONFIG = MarkerFactory.getMarker("CONFIG");
|
||||
public static final ConfigTracker INSTANCE = new ConfigTracker();
|
||||
private final ConcurrentHashMap<String, ModConfig> fileMap;
|
||||
private final EnumMap<ModConfig.Type, Set<ModConfig>> configSets;
|
||||
private final ConcurrentHashMap<String, Map<ModConfig.Type, ModConfig>> configsByMod;
|
||||
|
||||
private ConfigTracker() {
|
||||
this.fileMap = new ConcurrentHashMap<>();
|
||||
this.configSets = new EnumMap<>(ModConfig.Type.class);
|
||||
this.configsByMod = new ConcurrentHashMap<>();
|
||||
this.configSets.put(ModConfig.Type.CLIENT, Collections.synchronizedSet(new LinkedHashSet<>()));
|
||||
this.configSets.put(ModConfig.Type.COMMON, Collections.synchronizedSet(new LinkedHashSet<>()));
|
||||
// this.configSets.put(ModConfig.Type.PLAYER, new ConcurrentSkipListSet<>());
|
||||
this.configSets.put(ModConfig.Type.SERVER, Collections.synchronizedSet(new LinkedHashSet<>()));
|
||||
}
|
||||
|
||||
void trackConfig(final ModConfig config) {
|
||||
if (this.fileMap.containsKey(config.getFileName())) {
|
||||
LOGGER.error(CONFIG,"Detected config file conflict {} between {} and {}", config.getFileName(), this.fileMap.get(config.getFileName()).getModId(), config.getModId());
|
||||
throw new RuntimeException("Config conflict detected!");
|
||||
}
|
||||
this.fileMap.put(config.getFileName(), config);
|
||||
this.configSets.get(config.getType()).add(config);
|
||||
this.configsByMod.computeIfAbsent(config.getModId(), (k)->new EnumMap<>(ModConfig.Type.class)).put(config.getType(), config);
|
||||
LOGGER.debug(CONFIG, "Config file {} for {} tracking", config.getFileName(), config.getModId());
|
||||
}
|
||||
|
||||
public void loadConfigs(ModConfig.Type type, Path configBasePath) {
|
||||
LOGGER.debug(CONFIG, "Loading configs type {}", type);
|
||||
for (ModConfig config : this.configSets.get(type)) {
|
||||
openConfig(config, configBasePath);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: [FML] This is only called for the server (outside of forceUnload)
|
||||
// rethink config implementation for eventual FML rewrite
|
||||
public void unloadConfigs(ModConfig.Type type, Path configBasePath) {
|
||||
LOGGER.debug(CONFIG, "Unloading configs type {}", type);
|
||||
for (ModConfig config : this.configSets.get(type)) {
|
||||
closeConfig(config, configBasePath);
|
||||
}
|
||||
ConfigFileTypeHandler.get(type).stopWatcher();
|
||||
}
|
||||
|
||||
// If there is a better way to do this, please tell me. Because this is FUCKED
|
||||
public void forceUnload() {
|
||||
// This is how ModStateProvider handles loading configs. So we're doing the same but with unloadConfigs instead
|
||||
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> this.unloadConfigs(ModConfig.Type.CLIENT, FMLPaths.CONFIGDIR.get()));
|
||||
this.unloadConfigs(ModConfig.Type.COMMON, FMLPaths.CONFIGDIR.get());
|
||||
|
||||
// just in case server watcher is still alive somehow...
|
||||
ConfigFileTypeHandler.get(ModConfig.Type.SERVER).stopWatcher();
|
||||
}
|
||||
|
||||
private static void openConfig(final ModConfig config, final Path configBasePath) {
|
||||
LOGGER.trace(CONFIG, "Loading config file type {} at {} for {}", config.getType(), config.getFileName(), config.getModId());
|
||||
final CommentedFileConfig configData = config.getHandler().reader(configBasePath).apply(config);
|
||||
config.setConfigData(configData);
|
||||
config.fireEvent(IConfigEvent.loading(config));
|
||||
config.save();
|
||||
}
|
||||
|
||||
private static void closeConfig(final ModConfig config, final Path configBasePath) {
|
||||
if (config.getConfigData() != null) {
|
||||
LOGGER.trace(CONFIG, "Closing config file type {} at {} for {}", config.getType(), config.getFileName(), config.getModId());
|
||||
// stop the filewatcher before we save the file and close it, so reload doesn't fire
|
||||
config.getHandler().unload(configBasePath, config);
|
||||
var unloading = IConfigEvent.unloading(config);
|
||||
if (unloading != null)
|
||||
config.fireEvent(unloading);
|
||||
config.save();
|
||||
config.setConfigData(null);
|
||||
}
|
||||
}
|
||||
|
||||
public void loadDefaultServerConfigs() {
|
||||
configSets.get(ModConfig.Type.SERVER).forEach(modConfig -> {
|
||||
final CommentedConfig commentedConfig = CommentedConfig.inMemory();
|
||||
modConfig.getSpec().correct(commentedConfig);
|
||||
modConfig.setConfigData(commentedConfig);
|
||||
modConfig.fireEvent(IConfigEvent.loading(modConfig));
|
||||
});
|
||||
}
|
||||
|
||||
public String getConfigFileName(String modId, ModConfig.Type type) {
|
||||
return Optional.ofNullable(configsByMod.getOrDefault(modId, Collections.emptyMap()).getOrDefault(type, null)).
|
||||
map(ModConfig::getFullPath).map(Object::toString).orElse(null);
|
||||
}
|
||||
|
||||
public Map<ModConfig.Type, Set<ModConfig>> configSets() {
|
||||
return configSets;
|
||||
}
|
||||
|
||||
public ConcurrentHashMap<String, ModConfig> fileMap() {
|
||||
return fileMap;
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.config;
|
||||
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.Bindings;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface IConfigEvent {
|
||||
record ConfigConfig(Function<ModConfig, IConfigEvent> loading, Function<ModConfig, IConfigEvent> reloading, @Nullable Function<ModConfig, IConfigEvent> unloading) {}
|
||||
|
||||
ConfigConfig CONFIGCONFIG = Bindings.getConfigConfiguration().get();
|
||||
|
||||
static IConfigEvent reloading(ModConfig modConfig) {
|
||||
return CONFIGCONFIG.reloading().apply(modConfig);
|
||||
}
|
||||
static IConfigEvent loading(ModConfig modConfig) {
|
||||
return CONFIGCONFIG.loading().apply(modConfig);
|
||||
}
|
||||
@Nullable static IConfigEvent unloading(ModConfig modConfig) {
|
||||
return CONFIGCONFIG.unloading() == null ? null : CONFIGCONFIG.unloading().apply(modConfig);
|
||||
}
|
||||
ModConfig getConfig();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
default <T extends Event & IConfigEvent> T self() {
|
||||
return (T) this;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.config;
|
||||
|
||||
import com.electronwill.nightconfig.core.CommentedConfig;
|
||||
import com.electronwill.nightconfig.core.UnmodifiableConfig;
|
||||
|
||||
public interface IConfigSpec<T extends IConfigSpec<T>> extends UnmodifiableConfig {
|
||||
@SuppressWarnings("unchecked")
|
||||
default T self() {
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
void acceptConfig(CommentedConfig data);
|
||||
|
||||
boolean isCorrecting();
|
||||
|
||||
boolean isCorrect(CommentedConfig commentedFileConfig);
|
||||
|
||||
int correct(CommentedConfig commentedFileConfig);
|
||||
|
||||
void afterReload();
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.config;
|
||||
|
||||
import com.electronwill.nightconfig.core.CommentedConfig;
|
||||
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
|
||||
import com.electronwill.nightconfig.toml.TomlFormat;
|
||||
import net.minecraftforge.fml.ModContainer;
|
||||
import net.minecraftforge.fml.loading.StringUtils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
public class ModConfig
|
||||
{
|
||||
private final Type type;
|
||||
private final IConfigSpec<?> spec;
|
||||
private final String fileName;
|
||||
private final ModContainer container;
|
||||
private final ConfigFileTypeHandler configHandler;
|
||||
private CommentedConfig configData;
|
||||
|
||||
public ModConfig(final Type type, final IConfigSpec<?> spec, final ModContainer container, final String fileName) {
|
||||
this.type = type;
|
||||
this.spec = spec;
|
||||
this.fileName = fileName;
|
||||
this.container = container;
|
||||
this.configHandler = ConfigFileTypeHandler.get(type);
|
||||
ConfigTracker.INSTANCE.trackConfig(this);
|
||||
}
|
||||
|
||||
public ModConfig(final Type type, final IConfigSpec<?> spec, final ModContainer activeContainer) {
|
||||
this(type, spec, activeContainer, defaultConfigName(type, activeContainer.getModId()));
|
||||
}
|
||||
|
||||
private static String defaultConfigName(Type type, String modId) {
|
||||
// config file name would be "forge-client.toml" and "forge-server.toml"
|
||||
return String.format(Locale.ROOT, "%s-%s.toml", modId, type.extension());
|
||||
}
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public ConfigFileTypeHandler getHandler() {
|
||||
return configHandler;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends IConfigSpec<T>> IConfigSpec<T> getSpec() {
|
||||
return (IConfigSpec<T>) spec;
|
||||
}
|
||||
|
||||
public String getModId() {
|
||||
return container.getModId();
|
||||
}
|
||||
|
||||
public CommentedConfig getConfigData() {
|
||||
return this.configData;
|
||||
}
|
||||
|
||||
void setConfigData(final CommentedConfig configData) {
|
||||
this.configData = configData;
|
||||
this.spec.acceptConfig(this.configData);
|
||||
}
|
||||
|
||||
void fireEvent(final IConfigEvent configEvent) {
|
||||
this.container.dispatchConfigEvent(configEvent);
|
||||
}
|
||||
|
||||
public void save() {
|
||||
((CommentedFileConfig)this.configData).save();
|
||||
}
|
||||
|
||||
public Path getFullPath() {
|
||||
return ((CommentedFileConfig)this.configData).getNioPath();
|
||||
}
|
||||
|
||||
public void acceptSyncedConfig(byte[] bytes) {
|
||||
setConfigData(TomlFormat.instance().createParser().parse(new ByteArrayInputStream(bytes)));
|
||||
fireEvent(IConfigEvent.reloading(this));
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
/**
|
||||
* Common mod config for configuration that needs to be loaded on both environments.
|
||||
* Loaded on both servers and clients.
|
||||
* Stored in the global config directory.
|
||||
* Not synced.
|
||||
* Suffix is "-common" by default.
|
||||
*/
|
||||
COMMON,
|
||||
/**
|
||||
* Client config is for configuration affecting the ONLY client state such as graphical options.
|
||||
* Only loaded on the client side.
|
||||
* Stored in the global config directory.
|
||||
* Not synced.
|
||||
* Suffix is "-client" by default.
|
||||
*/
|
||||
CLIENT,
|
||||
// /**
|
||||
// * Player type config is configuration that is associated with a player.
|
||||
// * Preferences around machine states, for example.
|
||||
// */
|
||||
// PLAYER,
|
||||
/**
|
||||
* Server type config is configuration that is associated with a server instance.
|
||||
* Only loaded during server startup.
|
||||
* Stored in a server/save specific "serverconfig" directory.
|
||||
* Synced to clients during connection.
|
||||
* Suffix is "-server" by default.
|
||||
*/
|
||||
SERVER;
|
||||
|
||||
public String extension() {
|
||||
return StringUtils.toLowerCase(name());
|
||||
}
|
||||
}
|
||||
}
|
||||
Loaded 100 of 4244 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user