mirror of
https://github.com/MinecraftForge/MinecraftForge.git
synced 2026-11-14 01:04:24 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f31519b6bb | ||
|
|
f715248a1e | ||
|
|
e1bc30a04b | ||
|
|
85ab903cd1 | ||
|
|
530c7c4eb5 | ||
|
|
3c740e488d | ||
|
|
03d07a9621 | ||
|
|
90dc29b1ef | ||
|
|
eae9d72dd8 | ||
|
|
7ac8903a3b | ||
|
|
bd96ff8777 | ||
|
|
ebfd92e0e1 | ||
|
|
43288f79e3 | ||
|
|
dbd7da6885 | ||
|
|
bb98165ee8 | ||
|
|
6a1f1acc42 | ||
|
|
a982445295 | ||
|
|
89919519ac | ||
|
|
4d592ba5ac | ||
|
|
5657e6542b | ||
|
|
c1c327ce11 | ||
|
|
142f07cbad | ||
|
|
c4f18dfa4b | ||
|
|
9f5a6d716e | ||
|
|
984cedda5c | ||
|
|
cc4a431711 | ||
|
|
ee07bcac57 | ||
|
|
b1cc694453 | ||
|
|
838ab9e1a5 | ||
|
|
99a7fcd8bd | ||
|
|
3535dcdbb3 | ||
|
|
d7ccecefe0 | ||
|
|
d3e9cfaaba | ||
|
|
7134b93427 | ||
|
|
61694447b2 | ||
|
|
561f3ee534 | ||
|
|
d0fea9cf76 | ||
|
|
dd866ed53e | ||
|
|
54f5e1213a | ||
|
|
9d4f283e6b | ||
|
|
28d4205a97 | ||
|
|
d0d1f1d782 | ||
|
|
397ceac0b6 | ||
|
|
1b7665cdaa | ||
|
|
70f96ef64e | ||
|
|
0cf4446352 | ||
|
|
b8270cd9ff | ||
|
|
e1fa61080a | ||
|
|
381998c35c | ||
|
|
11ed2500a6 | ||
|
|
a647237db4 | ||
|
|
e0dccae041 | ||
|
|
e7a5c58907 | ||
|
|
e094eee54d | ||
|
|
a6adc58655 | ||
|
|
d31ea640f9 | ||
|
|
8e16765438 | ||
|
|
73b0088cb4 | ||
|
|
203fbbb742 | ||
|
|
46d78c8210 | ||
|
|
cfba4381b6 | ||
|
|
7113701322 | ||
|
|
73e02c6add | ||
|
|
7f2b85d2ff | ||
|
|
099d121c2c | ||
|
|
a6978eed68 | ||
|
|
309116520b | ||
|
|
3e358acbe1 | ||
|
|
c243adf6aa | ||
|
|
5d0047753d | ||
|
|
aa6f268c74 | ||
|
|
d21556a4da | ||
|
|
d4c998e311 | ||
|
|
25727a0e93 | ||
|
|
80286594c5 | ||
|
|
870e3ebcab | ||
|
|
a4c26c9b2b | ||
|
|
45b00b0004 | ||
|
|
4b1c5ddb2c | ||
|
|
0966109602 | ||
|
|
b31006534a | ||
|
|
740a5d5039 | ||
|
|
817e20821d | ||
|
|
fdb22d02cc | ||
|
|
f09fd1f05b | ||
|
|
ed2ba0f52c | ||
|
|
b04118982f | ||
|
|
d44d0b8edc | ||
|
|
6d7fda6a9b | ||
|
|
0303ca651e | ||
|
|
8445f73b2d | ||
|
|
c7446a5180 | ||
|
|
5c15aa3322 | ||
|
|
588466f5e4 | ||
|
|
470f03b2f9 | ||
|
|
d2367d81fc | ||
|
|
6f9f0f0cfa | ||
|
|
16defac82e | ||
|
|
16483d4ea1 | ||
|
|
28909812bb | ||
|
|
e5580b84b7 | ||
|
|
607ca5beea | ||
|
|
744906e33a | ||
|
|
c1557ec3c2 | ||
|
|
4ac1a3dedf | ||
|
|
a26f952212 | ||
|
|
83ee68c0b0 | ||
|
|
a5ff932125 | ||
|
|
0d820eddd4 | ||
|
|
83c651ce39 | ||
|
|
7cd88944d4 | ||
|
|
dff52ecd71 | ||
|
|
da34d97d9d | ||
|
|
a27405b8d1 | ||
|
|
7a443276a5 | ||
|
|
e34090c809 | ||
|
|
72e8ae252c | ||
|
|
347c480bdc | ||
|
|
e0b9474621 | ||
|
|
8ad1e899a3 | ||
|
|
3b6bb5edbc | ||
|
|
c4e7d694c1 | ||
|
|
e00de1a326 | ||
|
|
e9ee999558 | ||
|
|
a1aacf25fe | ||
|
|
0c2f3e5523 | ||
|
|
a6978cffbf | ||
|
|
2332ecf984 | ||
|
|
9cd73c84a6 | ||
|
|
ab544a78d3 | ||
|
|
5b0abf97c8 | ||
|
|
587b684035 | ||
|
|
90dafb0df0 | ||
|
|
aff05a4cf3 | ||
|
|
1ddac54256 | ||
|
|
fcf9088aa5 | ||
|
|
374dbc7a4b | ||
|
|
d25ba7adc8 | ||
|
|
05c88ae33a | ||
|
|
e2d564d32a | ||
|
|
225092130e | ||
|
|
eefca22aa3 | ||
|
|
19bbb88e52 | ||
|
|
18886e5128 | ||
|
|
47dfa83cff | ||
|
|
0940c532f2 | ||
|
|
d80f106140 | ||
|
|
516b35ce57 | ||
|
|
be60ddbfca | ||
|
|
af6e848227 | ||
|
|
dd139115ed | ||
|
|
145df60bec | ||
|
|
f7cca8fb88 | ||
|
|
1f59b3f9fe | ||
|
|
084effdadf | ||
|
|
40a7998281 | ||
|
|
4786fa6f89 | ||
|
|
9291b865c7 | ||
|
|
52eca96fdc | ||
|
|
4649f7af36 | ||
|
|
3edefbe33e |
No files matched your search
@@ -21,3 +21,6 @@ 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
|
||||
|
||||
+9
-4
@@ -13,6 +13,9 @@
|
||||
/classes
|
||||
*.iml
|
||||
|
||||
# We however WANT the ide folder as it has tempaltes/configs for developing Forge.
|
||||
!ide/**
|
||||
|
||||
#gradle
|
||||
/build
|
||||
/.gradle
|
||||
@@ -23,7 +26,6 @@
|
||||
/projects/mcp/
|
||||
/projects/clean/
|
||||
/projects/forge/
|
||||
/projects/fmlonly/
|
||||
#/patches/*
|
||||
|
||||
#occupational hazards
|
||||
@@ -32,9 +34,10 @@
|
||||
/projects/**/run/
|
||||
/projects/**/*.launch
|
||||
/repo/
|
||||
src/generated/resources/.cache/
|
||||
src/generated_test/resources/.cache/
|
||||
src/generated_mdk/resources/.cache/
|
||||
src/*/generated/**/.cache/
|
||||
# Generated by gradle every import
|
||||
**/src/main/resources/META-INF/MANIFEST.MF
|
||||
/fmlloader/src/main/resources/forge_version.json
|
||||
|
||||
#Patch rejects
|
||||
#/patches-/
|
||||
@@ -47,3 +50,5 @@ changelog.txt
|
||||
*.7z
|
||||
/logs/
|
||||
|
||||
/ForgeRoot.ipr
|
||||
/ForgeRoot.iws
|
||||
Vendored
-15
@@ -47,8 +47,6 @@ project {
|
||||
""".trimIndent(), label = "The branch specification of the repository", description = "By default all main branches are build by the configuration. Modify this value to adapt the branches build.", display = ParameterDisplay.HIDDEN, allowEmpty = true)
|
||||
text("github_repository_name", "MinecraftForge", label = "The github repository name. Used to connect to it in VCS Roots.", description = "This is the repository slug on github. So for example `MinecraftForge` or `MinecraftForge`. It is interpolated into the global VCS Roots.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
|
||||
text("env.PUBLISHED_JAVA_ARTIFACT_ID", "forge", label = "Published artifact id", description = "The maven coordinate artifact id that has been published by this build. Can not be empty.", allowEmpty = false)
|
||||
text("env.PUBLISHED_JAVA_FML_ARTIFACT_ID", "fmlonly", label = "Published fmlonly artifact id", description = "The maven coordinate artifact id for fml only that has been published by this build. Can not be empty.", allowEmpty = false)
|
||||
text("env.PUBLISHED_JAVA_FML_ARTIFACT_VERSION", "0.0.0-SNAPSHOT", label = "Published fmlonly artifact version", description = "The version for fml only that has been published by this build. Can not be empty.", allowEmpty = false)
|
||||
text("env.PUBLISHED_JAVA_GROUP", "net.minecraftforge", label = "Published group", description = "The maven coordinate group that has been published by this build. Can not be empty.", allowEmpty = false)
|
||||
//These are references and not actually keys
|
||||
password("env.CROWDIN_KEY", "credentialsJSON:a3102dbe-805d-4177-9f54-3d2c2eb08fd5", display = ParameterDisplay.HIDDEN)
|
||||
@@ -76,19 +74,6 @@ object Build : BuildType({
|
||||
id("MinecraftForge_MinecraftForge__Build")
|
||||
name = "Build"
|
||||
description = "Builds and Publishes the main branches of the project."
|
||||
|
||||
features {
|
||||
feature {
|
||||
id = "trigger_fml_only_files_generator"
|
||||
type = "triggerBuildFeature"
|
||||
param("triggers", "MinecraftForge_FilesGenerator_GeneratePages")
|
||||
param("parameters", """
|
||||
env.PUBLISHED_JAVA_FML_ARTIFACT_ID~env.PUBLISHED_JAVA_ARTIFACT_ID
|
||||
env.PUBLISHED_JAVA_FML_ARTIFACT_VERSION~env.PUBLISHED_JAVA_ARTIFACT_VERSION
|
||||
env.PUBLISHED_JAVA_GROUP
|
||||
""".trimIndent())
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
object BuildSecondaryBranches : BuildType({
|
||||
|
||||
+4
-4
@@ -3,7 +3,7 @@ Minecraft Forge: Credits/Thank You
|
||||
Forge is a set of tools and modifications to the Minecraft base game code to assist
|
||||
mod developers in creating new and exciting content. It has been in development for
|
||||
several years now, but I would like to take this time thank a few people who have
|
||||
helped it along it's way.
|
||||
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
|
||||
@@ -11,7 +11,7 @@ of me taking over the project, who knows what Minecraft modding would be today.
|
||||
|
||||
Secondly, someone who has worked with me, and developed some of the core features
|
||||
that allow modding to be as functional, and as simple as it is, cpw. For developing
|
||||
FML, which stabelized the client and server modding ecosystem. As well as the base
|
||||
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
|
||||
@@ -45,11 +45,11 @@ 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-
|
||||
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.
|
||||
http://www.minecraftforge.net/
|
||||
https://minecraftforge.net/
|
||||
|
||||
Additionally, it contains an implementation of topological sort based on that
|
||||
published at http://keithschwarz.com/interesting/code/?dir=topological-sort
|
||||
|
||||
Vendored
-125
@@ -1,125 +0,0 @@
|
||||
@Library('forge-shared-library')_
|
||||
|
||||
pipeline {
|
||||
options {
|
||||
disableConcurrentBuilds()
|
||||
}
|
||||
agent {
|
||||
docker {
|
||||
image 'gradle:7-jdk16'
|
||||
//args '-v forgegc:/home/gradle/.gradle_cache/'
|
||||
}
|
||||
}
|
||||
environment {
|
||||
GRADLE_ARGS = '--no-daemon --console=plain' // No daemon for now as FG3 kinda derps. //'-Dorg.gradle.daemon.idletimeout=5000'
|
||||
DISCORD_WEBHOOK = credentials('forge-discord-jenkins-webhook')
|
||||
DISCORD_PREFIX = "Job: Forge Branch: ${BRANCH_NAME} Build: #${BUILD_NUMBER}"
|
||||
JENKINS_HEAD = 'https://wiki.jenkins-ci.org/download/attachments/2916393/headshot.png'
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('notify_start') {
|
||||
when {
|
||||
not {
|
||||
changeRequest()
|
||||
}
|
||||
}
|
||||
steps {
|
||||
discordSend(
|
||||
title: "${DISCORD_PREFIX} Started",
|
||||
successful: true,
|
||||
result: 'ABORTED', //White border
|
||||
thumbnail: JENKINS_HEAD,
|
||||
webhookURL: DISCORD_WEBHOOK
|
||||
)
|
||||
}
|
||||
}
|
||||
/*
|
||||
stage('pull_cache') {
|
||||
steps {
|
||||
sh 'cp -r /home/gradle/.gradle_cache/jdks/ /home/gradle/.gradle/jdks/'
|
||||
sh 'cp -r /home/gradle/.gradle_cache/wrapper/ /home/gradle/.gradle/wrapper/'
|
||||
sh 'cp -r /home/gradle/.gradle_cache/caches/ /home/gradle/.gradle/caches/'
|
||||
}
|
||||
}
|
||||
*/
|
||||
stage('setup') {
|
||||
steps {
|
||||
withGradle {
|
||||
sh './gradlew ${GRADLE_ARGS} --refresh-dependencies --continue setup'
|
||||
}
|
||||
script {
|
||||
env.MYVERSION = sh(returnStdout: true, script: './gradlew :forge:properties -q | grep "^version:" | cut -d" " -f2').trim()
|
||||
env.FMLONLY_VERSION = sh(returnStdout: true, script: './gradlew :fmlonly:properties -q | grep "^version:" | cut -d" " -f2').trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('changelog') {
|
||||
when {
|
||||
not {
|
||||
changeRequest()
|
||||
}
|
||||
}
|
||||
steps {
|
||||
writeChangelog(currentBuild, 'build/changelog.txt')
|
||||
}
|
||||
}
|
||||
stage('publish') {
|
||||
when {
|
||||
not {
|
||||
changeRequest()
|
||||
}
|
||||
}
|
||||
environment {
|
||||
CROWDIN = credentials('forge-crowdin')
|
||||
KEYSTORE = credentials('forge-jenkins-keystore-old')
|
||||
KEYSTORE_KEYPASS = credentials('forge-jenkins-keystore-old-keypass')
|
||||
KEYSTORE_STOREPASS = credentials('forge-jenkins-keystore-old-keypass')
|
||||
}
|
||||
steps {
|
||||
withCredentials([usernamePassword(credentialsId: 'maven-forge-user', usernameVariable: 'MAVEN_USER', passwordVariable: 'MAVEN_PASSWORD')]) {
|
||||
withGradle {
|
||||
sh './gradlew ${GRADLE_ARGS} publish -PkeystoreKeyPass=${KEYSTORE_KEYPASS} -PkeystoreStorePass=${KEYSTORE_STOREPASS} -Pkeystore=${KEYSTORE} -PcrowdinKey=${CROWDIN}'
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
success {
|
||||
build job: 'filegenerator', parameters: [string(name: 'COMMAND', value: "promote net.minecraftforge:forge ${env.MYVERSION} latest")], propagate: false, wait: false
|
||||
build job: 'filegenerator', parameters: [string(name: 'COMMAND', value: "promote net.minecraftforge:fmlonly ${env.FMLONLY_VERSION} latest")], propagate: false, wait: false
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('test_publish_pr') { //Publish to local repo to test full process, but don't include credentials so it can't sign/publish to maven
|
||||
when {
|
||||
changeRequest()
|
||||
}
|
||||
environment {
|
||||
CROWDIN = credentials('forge-crowdin')
|
||||
}
|
||||
steps {
|
||||
withGradle {
|
||||
sh './gradlew ${GRADLE_ARGS} publish -PcrowdinKey=${CROWDIN}'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
script {
|
||||
archiveArtifacts artifacts: 'projects/forge/build/libs/**/*.*', fingerprint: true, onlyIfSuccessful: true, allowEmptyArchive: true
|
||||
|
||||
if (env.CHANGE_ID == null) { // This is unset for non-PRs
|
||||
discordSend(
|
||||
title: "${DISCORD_PREFIX} Finished ${currentBuild.currentResult}",
|
||||
description: '```\n' + getChanges(currentBuild) + '\n```',
|
||||
successful: currentBuild.resultIsBetterOrEqualTo("SUCCESS"),
|
||||
result: currentBuild.currentResult,
|
||||
thumbnail: JENKINS_HEAD,
|
||||
webhookURL: DISCORD_WEBHOOK
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+16
-1776
File diff suppressed because it is too large.
Load diff
@@ -1,13 +1,13 @@
|
||||
repositories {
|
||||
maven { url = 'https://maven.minecraftforge.net/' }
|
||||
mavenCentral()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.ow2.asm:asm:9.5'
|
||||
implementation 'org.ow2.asm:asm-tree:9.5'
|
||||
implementation 'org.ow2.asm:asm:9.6'
|
||||
implementation 'org.ow2.asm:asm-tree:9.6'
|
||||
implementation 'net.minecraftforge:srgutils:0.5.+'
|
||||
implementation 'commons-io:commons-io:2.12.0'
|
||||
implementation 'commons-io:commons-io:2.13.0'
|
||||
implementation 'com.google.code.gson:gson:2.10.1'
|
||||
implementation 'org.eclipse.jgit:org.eclipse.jgit:6.5.0.202303070854-r'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
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()
|
||||
@InputFile abstract RegularFileProperty getServerBundle()
|
||||
@OutputFile abstract RegularFileProperty getOutput()
|
||||
|
||||
BundleList() {
|
||||
config.setFrom(project.configurations.installer)
|
||||
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")
|
||||
}
|
||||
|
||||
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,6 +1,7 @@
|
||||
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
|
||||
@@ -10,6 +11,7 @@ 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.
|
||||
@@ -24,13 +26,13 @@ abstract class BytecodeFinder extends DefaultTask {
|
||||
protected void exec() {
|
||||
Util.init()
|
||||
|
||||
def outputFile = output.get().asFile
|
||||
var outputFile = output.get().asFile
|
||||
if (outputFile.exists())
|
||||
outputFile.delete()
|
||||
|
||||
pre()
|
||||
|
||||
Util.processClassNodes(jar.get().asFile, this::process)
|
||||
Util.processClassNodes(jar.get().asFile, this.&process)
|
||||
|
||||
post()
|
||||
outputFile.text = new JsonBuilder(getData()).toPrettyString()
|
||||
@@ -38,8 +40,8 @@ abstract class BytecodeFinder extends DefaultTask {
|
||||
|
||||
|
||||
protected process(ClassNode node) {
|
||||
if (node.fields != null) node.fields.each { process(node, it) }
|
||||
if (node.methods != null) node.methods.each { process(node, it) }
|
||||
if (node.fields !== null) node.fields.each { process(node, it) }
|
||||
if (node.methods !== null) node.methods.each { process(node, it) }
|
||||
}
|
||||
|
||||
protected pre() {}
|
||||
|
||||
+2
-2
@@ -12,7 +12,7 @@ abstract class BytecodePredicateFinder extends BytecodeFinder {
|
||||
@Internal
|
||||
abstract Property<Closure<Boolean>> getPredicate()
|
||||
|
||||
private final Set<String> matches = new HashSet()
|
||||
private final Set<String> matches = new HashSet<>()
|
||||
|
||||
@Override
|
||||
protected process(ClassNode parent, MethodNode node) {
|
||||
@@ -27,6 +27,6 @@ abstract class BytecodePredicateFinder extends BytecodeFinder {
|
||||
@Internal
|
||||
@Override
|
||||
protected Object getData() {
|
||||
return matches.toSorted(Comparator.naturalOrder()) ?: {throw new RuntimeException('Failed to find any targets, please ensure that method names and descriptors are correct.')}()
|
||||
return matches.toSorted(Comparator.<String>naturalOrder()) ?: {throw new RuntimeException('Failed to find any targets, please ensure that method names and descriptors are correct.')}()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
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,5 +1,7 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import groovy.transform.stc.FirstParam
|
||||
|
||||
import java.util.function.BiConsumer
|
||||
|
||||
public class ClosureHelper {
|
||||
@@ -22,7 +24,7 @@ public class ClosureHelper {
|
||||
}
|
||||
}
|
||||
|
||||
static <T> T apply(T obj, Closure cl) {
|
||||
static <T> T apply(T obj, @DelegatesTo(value = FirstParam, strategy = Closure.DELEGATE_FIRST) Closure cl) {
|
||||
cl.delegate = obj
|
||||
cl.resolveStrategy = Closure.DELEGATE_FIRST
|
||||
cl()
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.Optional
|
||||
import org.gradle.api.tasks.OutputFile
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
abstract class Crowdin extends DefaultTask {
|
||||
@Input abstract Property<String> getId()
|
||||
@Input @Optional abstract Property<String> getKey()
|
||||
@Input boolean json = true
|
||||
@OutputFile abstract RegularFileProperty getOutput()
|
||||
@OutputFile abstract RegularFileProperty getExport()
|
||||
|
||||
Crowdin() {
|
||||
outputs.upToDateWhen{ false }
|
||||
id.convention('minecraft-forge')
|
||||
output.convention(project.layout.buildDirectory.dir(name).map {it.file("output.zip") })
|
||||
export.convention(project.layout.buildDirectory.dir(name).map {it.file("export.json") })
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
def run() {
|
||||
File outputFile = output.get().asFile
|
||||
File exportFile = export.get().asFile
|
||||
if (outputFile.exists())
|
||||
outputFile.delete()
|
||||
|
||||
if (!key.isPresent())
|
||||
return
|
||||
String key = this.key.get()
|
||||
String id = this.id.get()
|
||||
|
||||
// Force an export
|
||||
new URL("https://api.crowdin.com/api/project/${id}/export?key=${key}").withInputStream { i ->
|
||||
exportFile.withOutputStream { it << i }
|
||||
}
|
||||
|
||||
if (!exportFile.text.contains('success')) {
|
||||
throw new RuntimeException("Crowdin export failed, see ${exportFile} for more info")
|
||||
}
|
||||
|
||||
new URL("https://api.crowdin.com/api/project/${id}/download/all.zip?key=${key}").withInputStream { i ->
|
||||
new ZipInputStream(i).withCloseable { zin ->
|
||||
outputFile.withOutputStream { out ->
|
||||
new ZipOutputStream(out).withCloseable { zout ->
|
||||
ZipEntry zein
|
||||
while ((zein = zin.nextEntry) != null) {
|
||||
if (zein.isDirectory()) {
|
||||
zout.putNextEntry(new ZipEntry(zein.name))
|
||||
} else {
|
||||
// 1.13+ uses json
|
||||
if (zein.name.endsWith('.json') == json) {
|
||||
ZipEntry zeout = new ZipEntry(json ? zein.name.toLowerCase() : zein.name)
|
||||
zeout.time = 1
|
||||
zout.putNextEntry(zeout)
|
||||
zout << zin
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ abstract class DownloadLibraries extends DefaultTask {
|
||||
def run() {
|
||||
Util.init()
|
||||
File outputDir = output.get().asFile
|
||||
def libraries = new ArrayList()
|
||||
var libraries = new ArrayList<String>()
|
||||
|
||||
def json = input.get().asFile.json().libraries.each { lib ->
|
||||
//TODO: Thread?
|
||||
@@ -35,7 +35,7 @@ abstract class DownloadLibraries extends DefaultTask {
|
||||
artifacts.each{ art ->
|
||||
def target = new File(outputDir, art.path)
|
||||
libraries.add(target.absolutePath)
|
||||
if (!target.exists() || !art.sha1.equals(target.sha1())) {
|
||||
if (!target.exists() || art.sha1 != target.sha1()) {
|
||||
project.logger.lifecycle("Downloading ${art.url}")
|
||||
if (!target.parentFile.exists()) {
|
||||
target.parentFile.mkdirs()
|
||||
@@ -43,7 +43,7 @@ abstract class DownloadLibraries extends DefaultTask {
|
||||
new URL(art.url).withInputStream { i ->
|
||||
target.withOutputStream { it << i }
|
||||
}
|
||||
if (!art.sha1.equals(target.sha1())) {
|
||||
if (art.sha1 != target.sha1()) {
|
||||
throw new IllegalStateException("Failed to download ${art.url} to ${target.canonicalPath} SHA Mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
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,20 +1,11 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
import groovy.transform.EqualsAndHashCode
|
||||
|
||||
import java.util.ArrayList
|
||||
import java.util.HashMap
|
||||
import java.util.TreeMap
|
||||
import java.util.TreeSet
|
||||
import java.util.function.BiConsumer
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.tasks.*
|
||||
|
||||
import org.objectweb.asm.Opcodes
|
||||
import org.objectweb.asm.Type
|
||||
import org.objectweb.asm.tree.AbstractInsnNode
|
||||
import org.objectweb.asm.tree.ClassNode
|
||||
import org.objectweb.asm.tree.FieldNode
|
||||
import org.objectweb.asm.tree.MethodNode
|
||||
|
||||
import static org.objectweb.asm.Opcodes.*
|
||||
@@ -34,17 +25,17 @@ abstract class FieldCompareFinder extends BytecodeFinder {
|
||||
|
||||
@Override
|
||||
protected process(ClassNode parent, MethodNode node) {
|
||||
def last = null
|
||||
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)) {
|
||||
if (current.opcode === IF_ACMPEQ || current.opcode === IF_ACMPNE) {
|
||||
if (last !== null && (last.opcode === GETSTATIC || last.opcode === GETFIELD)) {
|
||||
def target = new Search(cls: last.owner, name: last.name)
|
||||
def wanted = fieldsReverse.get(target)
|
||||
def original = fields.get(wanted)
|
||||
def instance = new ObjectTarget(owner: parent.name, name: node.name, desc: node.desc)
|
||||
if (wanted != null && (original.blacklist == null || (!original.blacklist.contains(instance) && !original.blacklist.contains(parentInstance)))) {
|
||||
if (wanted !== null && (original.blacklist === null || (!original.blacklist.contains(instance) && !original.blacklist.contains(parentInstance)))) {
|
||||
targets.computeIfAbsent(wanted, { k -> new TreeSet() }).add(instance)
|
||||
}
|
||||
}
|
||||
@@ -68,9 +59,10 @@ abstract class FieldCompareFinder extends BytecodeFinder {
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
|
||||
@CompileStatic
|
||||
@EqualsAndHashCode(excludes = ['replacement', 'blacklist'])
|
||||
public static class Search {
|
||||
static class Search {
|
||||
@Input
|
||||
String cls
|
||||
|
||||
@@ -89,17 +81,17 @@ abstract class FieldCompareFinder extends BytecodeFinder {
|
||||
return cls + '.' + name
|
||||
}
|
||||
|
||||
def blacklist(def owner, def name, def desc) {
|
||||
if (blacklist == null)
|
||||
blacklist = new HashSet()
|
||||
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(def owner) {
|
||||
def blacklist(String owner) {
|
||||
blacklist(owner, '', '')
|
||||
}
|
||||
}
|
||||
|
||||
def fields(Closure cl) {
|
||||
void fields(Closure cl) {
|
||||
new ClosureHelper(cl, {name, ccl ->
|
||||
def search = ClosureHelper.apply(new Search(), ccl)
|
||||
this.fields.put(name, search)
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.DirectoryProperty
|
||||
import org.gradle.api.tasks.InputDirectory
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
|
||||
import java.nio.file.Files
|
||||
|
||||
abstract class FixPatchImports extends DefaultTask {
|
||||
|
||||
@InputDirectory abstract DirectoryProperty getClean()
|
||||
@InputDirectory abstract DirectoryProperty getPatched()
|
||||
|
||||
@TaskAction
|
||||
void run() {
|
||||
final patchedPath = getPatched().get().asFile.toPath()
|
||||
final cleanPath = getClean().get().asFile.toPath()
|
||||
|
||||
try (final var stream = Files.find(patchedPath, Integer.MAX_VALUE, (path, attr) -> path.toString().endsWith('.java'))) {
|
||||
final itr = stream.iterator()
|
||||
while (itr.hasNext()) {
|
||||
final next = itr.next()
|
||||
final clean = cleanPath.resolve(patchedPath.relativize(next).toString())
|
||||
|
||||
final patchedLines = Files.readAllLines(next)
|
||||
final cleanLines = Files.readAllLines(clean)
|
||||
|
||||
final patchedImport = patchedLines.findLastIndexOf(2) { it.startsWith('import ') }
|
||||
final cleanImport = cleanLines.findLastIndexOf(2) { it.startsWith('import ') }
|
||||
|
||||
if (cleanImport !== patchedImport) {
|
||||
patchedLines.removeIf { it.startsWith('import ') }
|
||||
patchedLines.addAll(2, cleanLines.subList(2, cleanImport + 1))
|
||||
Files.write(next, patchedLines)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +1,158 @@
|
||||
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.provider.SetProperty
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.provider.Property
|
||||
import groovy.json.JsonSlurper
|
||||
|
||||
abstract class InstallerJar extends Zip {
|
||||
@Input @Optional abstract SetProperty<String> getPackedDependencies()
|
||||
@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)
|
||||
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 = 'exclude'
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
}
|
||||
|
||||
if (!System.env.TEAMCITY_VERSION) {
|
||||
dependsOn(project.universalJar)
|
||||
from(project.universalJar) {
|
||||
into '/maven/' + project.group.replace('.', '/') + '/' + project.universalJar.archiveBaseName.get() + '/' + project.version + '/'
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
packedDependencies.get().forEach {
|
||||
def jarTask = project.rootProject.getTasks().findByPath(it)
|
||||
dependsOn(jarTask)
|
||||
from(jarTask) {
|
||||
into '/maven/' + jarTask.project.group.replace('.', '/') + '/' + jarTask.archiveBaseName.get() + '/' + jarTask.project.version + '/'
|
||||
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" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,13 +7,13 @@ import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.provider.SetProperty
|
||||
import org.gradle.api.tasks.*
|
||||
import org.gradle.api.tasks.bundling.AbstractArchiveTask
|
||||
|
||||
import java.nio.file.Files
|
||||
|
||||
abstract class InstallerJson extends DefaultTask {
|
||||
@OutputFile abstract RegularFileProperty getOutput()
|
||||
@InputFiles abstract ConfigurableFileCollection getInput()
|
||||
@Input @Optional abstract SetProperty<String> getPackedDependencies()
|
||||
@Input @Optional final Map<String, Object> libraries = new LinkedHashMap<>()
|
||||
@Input Map<String, Object> json = new LinkedHashMap<>()
|
||||
@InputFile abstract RegularFileProperty getIcon()
|
||||
@@ -23,29 +23,19 @@ abstract class InstallerJson extends DefaultTask {
|
||||
@Input abstract Property<String> getWelcome()
|
||||
|
||||
InstallerJson() {
|
||||
getLauncherJsonName().convention('/version.json')
|
||||
getLogo().convention('/big_logo.png')
|
||||
getMirrors().convention('https://files.minecraftforge.net/mirrors-2.0.json')
|
||||
getWelcome().convention("Welcome to the simple ${project.name.capitalize()} installer.")
|
||||
|
||||
getOutput().convention(project.layout.buildDirectory.file('install_profile.json'))
|
||||
|
||||
['client', 'server'].each { side ->
|
||||
['slim', 'extra'].each { type ->
|
||||
def tsk = project.tasks.getByName("download${side.capitalize()}${type.capitalize()}")
|
||||
dependsOn(tsk)
|
||||
input.from(tsk.output)
|
||||
}
|
||||
def tsk = project.tasks.getByName("create${side.capitalize()}SRG")
|
||||
dependsOn(tsk)
|
||||
input.from(tsk.output)
|
||||
}
|
||||
|
||||
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 {
|
||||
(packedDependencies.get().collect{ project.rootProject.tasks.findByPath(it) } + [project.universalJar]).forEach {
|
||||
dependsOn(it)
|
||||
input.from it.archiveFile
|
||||
[
|
||||
project.tasks.universalJar,
|
||||
project.tasks.serverShimJar
|
||||
].forEach { packed ->
|
||||
dependsOn(packed)
|
||||
input.from packed.archiveFile
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,17 +43,19 @@ abstract class InstallerJson extends DefaultTask {
|
||||
@TaskAction
|
||||
protected void exec() {
|
||||
def libs = libraries
|
||||
for (def child : packedDependencies.get().collect{ project.rootProject.tasks.findByPath(it) } + [project.universalJar]) {
|
||||
def dep = Util.getMavenDep(child)
|
||||
def path = Util.getMavenPath(child)
|
||||
libs.put(dep.toString(), [
|
||||
name: dep,
|
||||
[
|
||||
project.tasks.universalJar,
|
||||
project.tasks.serverShimJar
|
||||
].forEach { AbstractArchiveTask packed ->
|
||||
def info = Util.getMavenInfoFromTask(packed)
|
||||
libs.put(info.name, [
|
||||
name: info.name,
|
||||
downloads: [
|
||||
artifact: [
|
||||
path: path,
|
||||
url: "https://maven.minecraftforge.net/${path}",
|
||||
sha1: child.archiveFile.get().asFile.sha1(),
|
||||
size: child.archiveFile.get().asFile.length()
|
||||
path: info.path,
|
||||
url: "https://maven.minecraftforge.net/$info.path",
|
||||
sha1: packed.archiveFile.get().asFile.sha1(),
|
||||
size: packed.archiveFile.get().asFile.length()
|
||||
]
|
||||
]
|
||||
])
|
||||
|
||||
@@ -6,6 +6,7 @@ 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
|
||||
|
||||
@@ -16,59 +17,78 @@ abstract class LauncherJson extends DefaultTask {
|
||||
@OutputFile abstract RegularFileProperty getOutput()
|
||||
@InputFiles abstract ConfigurableFileCollection getInput()
|
||||
@Input Map<String, Object> json = new LinkedHashMap<>()
|
||||
@Input @Optional abstract SetProperty<String> getPackedDependencies()
|
||||
|
||||
@Internal final vanilla = project.project(':mcp').file('build/mcp/downloadJson/version.json')
|
||||
@Internal final timestamp = iso8601Now()
|
||||
@Internal final comment = [
|
||||
"Please do not automate the download and installation of Forge.",
|
||||
"Our efforts are supported by ads from the download page.",
|
||||
"If you MUST automate this, please consider supporting the project through https://www.patreon.com/LexManos/"
|
||||
]
|
||||
@Internal final id = "${project.rootProject.ext.MC_VERSION}-${project.name}${project.version.substring(project.rootProject.ext.MC_VERSION.length())}"
|
||||
|
||||
LauncherJson() {
|
||||
getOutput().convention(project.layout.buildDirectory.file('version.json'))
|
||||
output.convention(project.layout.buildDirectory.file('libs/version.json'))
|
||||
|
||||
dependsOn(':fmlloader:jar', 'universalJar')
|
||||
getInput().from(project.tasks.universalJar.archiveFile,
|
||||
project.project(':fmlloader').jar.archiveFile,
|
||||
vanilla)
|
||||
|
||||
project.afterEvaluate {
|
||||
packedDependencies.get().forEach {
|
||||
def jarTask = project.rootProject.tasks.findByPath(it)
|
||||
dependsOn(jarTask)
|
||||
input.from jarTask.archiveFile
|
||||
dependsOn(project.tasks.universalJar)
|
||||
input.from(project.tasks.universalJar.archiveFile)
|
||||
input.from(project.configurations.installer)
|
||||
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() {
|
||||
if (!json.libraries)
|
||||
json.libraries = []
|
||||
def libs = [:]
|
||||
getArtifacts(project, project.configurations.installer, false).each { key, lib -> libs[key] = lib }
|
||||
getArtifacts(project, project.configurations.moduleonly, false).each { key, lib -> libs[key] = lib }
|
||||
|
||||
packedDependencies.get().collect{ project.rootProject.tasks.findByPath(it) }.forEach {
|
||||
def path = Util.getMavenPath(it)
|
||||
def key = Util.getMavenDep(it)
|
||||
|
||||
libs[key] = [
|
||||
name: key,
|
||||
downloads: [
|
||||
artifact: [
|
||||
path: path,
|
||||
url: "https://maven.minecraftforge.net/${path}",
|
||||
sha1: it.archiveFile.get().asFile.sha1(),
|
||||
size: it.archiveFile.get().asFile.length()
|
||||
]
|
||||
var 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()
|
||||
]
|
||||
]
|
||||
}
|
||||
libs.each { key, lib -> json.libraries.add(lib) }
|
||||
])
|
||||
|
||||
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.installer).values())
|
||||
Files.writeString(output.get().asFile.toPath(), new JsonBuilder(json).toPrettyString())
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
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
|
||||
@@ -12,6 +13,7 @@ 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') })
|
||||
@@ -24,8 +26,8 @@ abstract class MergeJars extends DefaultTask {
|
||||
try (ZipOutputStream zout = new ZipOutputStream(new FileOutputStream(output.get().asFile))) {
|
||||
for (def jar : jars) {
|
||||
try (ZipInputStream zin = new ZipInputStream(new FileInputStream(jar))) {
|
||||
def entry
|
||||
while ((entry = zin.getNextEntry()) != null) {
|
||||
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)
|
||||
|
||||
@@ -27,6 +27,6 @@ public class ObjectTarget implements Comparable<ObjectTarget> {
|
||||
|
||||
@Override
|
||||
int compareTo(ObjectTarget o) {
|
||||
return toString().compareTo(o.toString())
|
||||
return toString() <=> o.toString()
|
||||
}
|
||||
}
|
||||
+1
-11
@@ -16,7 +16,7 @@ abstract class SetupCheckJarCompatibility extends DefaultTask {
|
||||
SetupCheckJarCompatibility() {
|
||||
group = 'jar compatibility'
|
||||
onlyIf {
|
||||
inputVersion.getOrNull() != null
|
||||
inputVersion.getOrNull() !== null
|
||||
}
|
||||
outputs.upToDateWhen { false } // Never up to date, because this setup task should always run
|
||||
|
||||
@@ -26,16 +26,6 @@ abstract class SetupCheckJarCompatibility extends DefaultTask {
|
||||
@TaskAction
|
||||
void run() {
|
||||
def inputVersion = inputVersion.get()
|
||||
def fmlLibs = project.configurations.detachedConfiguration(project.PACKED_DEPS.collect {
|
||||
def artifactId = it.split(':')[1]
|
||||
return project.dependencies.create("net.minecraftforge:${artifactId}:${inputVersion}")
|
||||
}.toArray(Dependency[]::new))
|
||||
|
||||
project.tasks.named('checkJarCompatibility') {
|
||||
baseLibraries.from(project.provider {
|
||||
fmlLibs.resolvedConfiguration.lenientConfiguration.files
|
||||
})
|
||||
}
|
||||
|
||||
def baseForgeUserdev = project.layout.buildDirectory.dir(name).map { it.file("forge-${inputVersion}-userdev.jar") }.get().asFile
|
||||
project.rootProject.extensions.download.run {
|
||||
|
||||
@@ -2,159 +2,224 @@ 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.io.File
|
||||
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.Date
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.Semaphore
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
public class Util {
|
||||
static final ASM_LEVEL = Opcodes.ASM9
|
||||
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, {bytes, size ->
|
||||
md.update(bytes, 0, size)
|
||||
}
|
||||
return md.digest().collect {String.format "%02x", it}.join()
|
||||
}
|
||||
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.json = { -> new JsonSlurper().parseText(delegate.text) }
|
||||
File.metaClass.getJson = { return delegate.exists() ? new JsonSlurper().parse(delegate) : [:] }
|
||||
File.metaClass.setJson = { json -> delegate.text = new JsonBuilder(json).toPrettyString() }
|
||||
|
||||
Date.metaClass.iso8601 = { ->
|
||||
def format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
|
||||
def result = format.format(delegate)
|
||||
return result[0..21] + ':' + result[22..-1]
|
||||
}
|
||||
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 ->
|
||||
def lst = new ArrayList()
|
||||
def x = 0, idx
|
||||
def tmp = delegate
|
||||
while ((idx = tmp.lastIndexOf(del)) != -1 && (limit == -1 || x++ < limit)) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
public static String[] getClasspath(project, libs, artifact) {
|
||||
def ret = []
|
||||
artifactTree(project, artifact).each { key, lib ->
|
||||
libs[lib.name] = lib
|
||||
if (lib.name != artifact)
|
||||
ret.add(lib.name)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
public static def getArtifacts(project, config, classifiers) {
|
||||
def ret = [:]
|
||||
config.resolvedConfiguration.resolvedArtifacts.each {
|
||||
def art = [
|
||||
group: it.moduleVersion.id.group,
|
||||
name: it.moduleVersion.id.name,
|
||||
version: it.moduleVersion.id.version,
|
||||
classifier: it.classifier,
|
||||
extension: it.extension,
|
||||
file: it.file
|
||||
]
|
||||
def key = art.group + ':' + art.name
|
||||
def folder = "${art.group.replace('.', '/')}/${art.name}/${art.version}/"
|
||||
def filename = "${art.name}-${art.version}"
|
||||
if (art.classifier != null)
|
||||
filename += "-${art.classifier}"
|
||||
filename += ".${art.extension}"
|
||||
def path = "${folder}${filename}"
|
||||
def url = "https://libraries.minecraft.net/${path}"
|
||||
if (!checkExists(url)) {
|
||||
url = "https://maven.minecraftforge.net/${path}"
|
||||
}
|
||||
ret[key] = [
|
||||
name: "${art.group}:${art.name}:${art.version}" + (art.classifier == null ? '' : ":${art.classifier}") + (art.extension == 'jar' ? '' : "@${art.extension}"),
|
||||
downloads: [
|
||||
artifact: [
|
||||
path: path,
|
||||
url: url,
|
||||
sha1: sha1(art.file),
|
||||
size: art.file.length()
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
public static def getMavenPath(task) {
|
||||
def classifier = task.archiveClassifier.get()
|
||||
def dep = "${task.project.group}:${task.project.name}:${task.project.version}" + (classifier == '' ? '' : ':' + classifier)
|
||||
return "${task.project.group.replace('.', '/')}/${task.project.name}/${task.project.version}/${task.project.name}-${task.project.version}".toString() + (classifier == '' ? '' : '-' + classifier) + '.jar'
|
||||
}
|
||||
|
||||
public static def getMavenDep(task) {
|
||||
def classifier = task.archiveClassifier.get()
|
||||
return "${task.project.group}:${task.project.name}:${task.project.version}" + (classifier == '' ? '' : ':' + classifier)
|
||||
static String[] getClasspath(Project project, Map libs, String artifact) {
|
||||
def ret = []
|
||||
artifactTree(project, artifact).each { key, lib ->
|
||||
libs[lib.name] = lib
|
||||
if (lib.name != artifact)
|
||||
ret.add(lib.name)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
public static def iso8601Now() { new Date().iso8601() }
|
||||
@CompileStatic
|
||||
static Map getArtifacts(Configuration config) {
|
||||
var ret = [:]
|
||||
var semaphore = new Semaphore(1, true)
|
||||
config.resolvedConfiguration.resolvedArtifacts.parallelStream().forEachOrdered(dep -> {
|
||||
var info = getMavenInfoFromDep(dep)
|
||||
var domain = 'libraries.minecraft.net'
|
||||
var url = "https://$domain/$info.path"
|
||||
if (!checkExists(url))
|
||||
url.values[0] = 'maven.minecraftforge.net'
|
||||
|
||||
public static def sha1(file) {
|
||||
MessageDigest md = MessageDigest.getInstance('SHA-1')
|
||||
file.eachByte 4096, {bytes, size ->
|
||||
md.update(bytes, 0, size)
|
||||
}
|
||||
return md.digest().collect {String.format "%02x", it}.join()
|
||||
}
|
||||
var sha1 = sha1(dep.file)
|
||||
|
||||
private static def artifactTree(project, artifact) {
|
||||
if (!project.ext.has('tree_resolver'))
|
||||
project.ext.tree_resolver = 1
|
||||
def cfg = project.configurations.create('tree_resolver_' + project.ext.tree_resolver++)
|
||||
def dep = project.dependencies.create(artifact)
|
||||
cfg.dependencies.add(dep)
|
||||
def files = cfg.resolve()
|
||||
return getArtifacts(project, cfg, true)
|
||||
}
|
||||
semaphore.acquire()
|
||||
ret[info.key] = [
|
||||
name: info.name,
|
||||
downloads: [
|
||||
artifact: [
|
||||
path: info.path,
|
||||
url: url.toString(),
|
||||
sha1: sha1,
|
||||
size: dep.file.length()
|
||||
]
|
||||
]
|
||||
]
|
||||
semaphore.release()
|
||||
})
|
||||
return ret
|
||||
}
|
||||
|
||||
private static boolean checkExists(url) {
|
||||
try {
|
||||
def code = new URL(url).openConnection().with {
|
||||
requestMethod = 'HEAD'
|
||||
connect()
|
||||
responseCode
|
||||
}
|
||||
return code == 200
|
||||
} catch (Exception e) {
|
||||
if (e.toString().contains('unable to find valid certification path to requested target'))
|
||||
throw new RuntimeException('Failed to connect to ' + url + ': Missing certificate root authority, try updating java')
|
||||
throw e
|
||||
}
|
||||
}
|
||||
@CompileStatic
|
||||
static Map 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
|
||||
])
|
||||
}
|
||||
|
||||
static String getLatestForgeVersion(mcVersion) {
|
||||
@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)
|
||||
}
|
||||
|
||||
static void processClassNodes(File file, Closure process) {
|
||||
@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) {
|
||||
while ((zein = zin.nextEntry) !== null) {
|
||||
if (zein.name.endsWith('.class')) {
|
||||
def node = new ClassNode(ASM_LEVEL)
|
||||
var node = new ClassNode(ASM_LEVEL)
|
||||
new ClassReader(zin).accept(node, 0)
|
||||
process(node)
|
||||
}
|
||||
@@ -162,4 +227,4 @@ public class Util {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
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
|
||||
@@ -28,8 +29,8 @@ abstract class ValidateDeprecations extends DefaultTask {
|
||||
|
||||
@TaskAction
|
||||
protected void exec() {
|
||||
def mcVer = MinecraftVersion.from(mcVersion.get())
|
||||
def errors = []
|
||||
var mcVer = MinecraftVersion.from(mcVersion.get())
|
||||
List<String> errors = []
|
||||
|
||||
Util.processClassNodes(input.get().asFile) {
|
||||
processNode(mcVer, errors, it)
|
||||
@@ -65,7 +66,7 @@ abstract class ValidateDeprecations extends DefaultTask {
|
||||
}
|
||||
}
|
||||
|
||||
private static void processAnnotations(AnnotationNode annotation, MinecraftVersion mcVer, List<String> errors, Closure context) {
|
||||
private static void processAnnotations(AnnotationNode annotation, MinecraftVersion mcVer, List<String> errors, Closure<String> context) {
|
||||
def values = annotation.values
|
||||
if (values === null)
|
||||
return
|
||||
@@ -73,7 +74,7 @@ abstract class ValidateDeprecations extends DefaultTask {
|
||||
int since = values.indexOf('since')
|
||||
if (annotation.desc == 'Ljava/lang/Deprecated;' && forRemoval !== -1 && since !== -1 && values.size() >= 4 && values[forRemoval + 1] === true) {
|
||||
def oldVersion = MinecraftVersion.from(values[since + 1])
|
||||
def split = ValidateDeprecations.splitDots(oldVersion.toString())
|
||||
int[] split = ValidateDeprecations.splitDots(oldVersion.toString())
|
||||
if (split.length < 2)
|
||||
return
|
||||
def removeVersion = MinecraftVersion.from("${split[0]}.${split[1] + 1}")
|
||||
@@ -82,6 +83,7 @@ abstract class ValidateDeprecations extends DefaultTask {
|
||||
}
|
||||
}
|
||||
|
||||
@CompileStatic
|
||||
private static int[] splitDots(String version) {
|
||||
String[] pts = version.split('\\.')
|
||||
int[] values = new int[pts.length]
|
||||
|
||||
@@ -26,12 +26,12 @@ abstract class CheckExcs extends CheckTask {
|
||||
excs.each { f ->
|
||||
final lines = []
|
||||
f.eachLine { line ->
|
||||
def idx = line.indexOf('#')
|
||||
if (idx == 0 || line.isEmpty()) {
|
||||
int idx = line.indexOf('#')
|
||||
if (idx === 0 || line.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (idx != -1) line = line.substring(0, idx - 1)
|
||||
if (idx !== -1) line = line.substring(0, idx - 1)
|
||||
|
||||
if (!line.contains('=')) {
|
||||
reporter.report("Invalid: $line")
|
||||
@@ -58,6 +58,8 @@ abstract class CheckExcs extends CheckTask {
|
||||
return
|
||||
}
|
||||
lines.add(line)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (fix) f.text = lines.sort().join('\n')
|
||||
|
||||
@@ -6,4 +6,6 @@ import groovy.transform.CompileStatic
|
||||
enum CheckMode {
|
||||
CHECK,
|
||||
FIX
|
||||
|
||||
CheckMode() {}
|
||||
}
|
||||
@@ -36,13 +36,13 @@ abstract class CheckPatches extends CheckTask {
|
||||
}
|
||||
}
|
||||
|
||||
def accessChange(previous, current) {
|
||||
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) {
|
||||
final oldFixedErrors = reporter.fixed.size()
|
||||
int oldFixedErrors = reporter.fixed.size()
|
||||
|
||||
final lines = Files.readAllLines(patch)
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ abstract class CheckSAS extends CheckTask {
|
||||
}
|
||||
}
|
||||
|
||||
protected static isSided(Annotatable annotatable) {
|
||||
protected static boolean isSided(Annotatable annotatable) {
|
||||
if (annotatable === null) return false
|
||||
for (ann in annotatable.annotations) {
|
||||
if ('Lnet/minecraftforge/api/distmarker/OnlyIn;' == ann.desc)
|
||||
@@ -104,7 +104,7 @@ abstract class CheckSAS extends CheckTask {
|
||||
}
|
||||
|
||||
protected static findChildMethods(Map<String, InheritanceData> json, String cls, String desc) {
|
||||
return json.values().findAll{ it.methods != null && it.methods[desc] != null && it.methods[desc].override == cls && isSided(it.methods[desc]) }
|
||||
return json.values().findAll{ it.methods !== null && it.methods[desc] !== null && it.methods[desc].override == cls && isSided(it.methods[desc]) }
|
||||
.collect { it.name + ' ' + desc.replace(' ', '') } as TreeSet
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
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
|
||||
@@ -33,7 +35,7 @@ abstract class CheckTask extends DefaultTask implements VerificationTask {
|
||||
void run() {
|
||||
Util.init()
|
||||
|
||||
final doFix = getMode().get() === CheckMode.FIX
|
||||
boolean doFix = getMode().get() === CheckMode.FIX
|
||||
final Reporter reporter = new Reporter(doFix)
|
||||
check(reporter, doFix)
|
||||
|
||||
@@ -88,7 +90,8 @@ abstract class CheckTask extends DefaultTask implements VerificationTask {
|
||||
}
|
||||
|
||||
static <T extends CheckTask> void registerTask(TaskContainer tasks, String taskName, @DelegatesTo.Target('type') Class<T> clazz,
|
||||
@DelegatesTo(genericTypeIndex = 0, target = 'type') Closure configuration) {
|
||||
@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
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
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'
|
||||
}
|
||||
+1094
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,84 @@
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
+26
-7
@@ -2,16 +2,15 @@
|
||||
|
||||
MinecraftForge
|
||||
=============
|
||||
[][Download]
|
||||
[][Download]
|
||||
[][Download] [][Discord] [][Patreon]
|
||||
|
||||
Forge is a free, open-source modding API all of your favourite mods use!
|
||||
|
||||
| Version | Support |
|
||||
|---------| ------------- |
|
||||
| 1.20.x | Active |
|
||||
| 1.19.x | Legacy |
|
||||
| Version | Support |
|
||||
|--------------|-----------------------------|
|
||||
| All versions | [Supported][Support-Policy] |
|
||||
|
||||
* [Download]
|
||||
* [Forum]
|
||||
@@ -31,6 +30,19 @@ You can download the installer for the *Recommended Build* or the
|
||||
|
||||
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].
|
||||
@@ -63,8 +75,15 @@ Please ensure you have a valid email address associated with your GitHub account
|
||||
|
||||
[Download]: https://files.minecraftforge.net/
|
||||
|
||||
[Adoptium-21]: https://adoptium.net/temurin/releases/?version=21
|
||||
[Adoptium-17]: https://adoptium.net/temurin/releases/?version=17
|
||||
[Adoptium-16]: https://adoptium.net/temurin/releases/?version=16
|
||||
[Adoptium-8]: https://adoptium.net/temurin/releases/?version=8
|
||||
[Jarfix]: https://johann.loefflmann.net/en/software/jarfix/index.html
|
||||
|
||||
[Forum]: https://forums.minecraftforge.net/
|
||||
[Support-Forum]: https://forums.minecraftforge.net/forum/18-support-bug-reports/
|
||||
[Support-Policy]: https://forums.minecraftforge.net/topic/144690-new-tiered-support-policy
|
||||
|
||||
[Discord]: https://discord.minecraftforge.net/
|
||||
|
||||
@@ -73,4 +92,4 @@ Please ensure you have a valid email address associated with your GitHub account
|
||||
[ForgeDev]: https://docs.minecraftforge.net/en/latest/forgedev/
|
||||
[Pull-Requests]: https://docs.minecraftforge.net/en/latest/forgedev/#making-changes-and-pull-requests
|
||||
|
||||
[Patreon]: https://www.patreon.com/MinecraftForge
|
||||
[Patreon]: https://www.patreon.com/LexManos
|
||||
+44
-51
@@ -1,74 +1,67 @@
|
||||
plugins {
|
||||
id 'com.github.ben-manes.versions'
|
||||
id 'org.javamodularity.moduleplugin' version '1.8.3' apply false
|
||||
id 'org.cadixdev.licenser'
|
||||
}
|
||||
apply plugin: 'java-library'
|
||||
apply plugin: 'jacoco'
|
||||
apply plugin: 'org.javamodularity.moduleplugin'
|
||||
import net.minecraftforge.gradleutils.PomUtils
|
||||
|
||||
dependencyUpdates.rejectVersionIf { isNonStable(it.candidate.version) }
|
||||
plugins {
|
||||
id 'java-library'
|
||||
id 'maven-publish'
|
||||
id 'net.minecraftforge.licenser'
|
||||
id 'net.minecraftforge.gradleutils'
|
||||
}
|
||||
|
||||
apply from: rootProject.file('build_shared.gradle')
|
||||
|
||||
dependencies {
|
||||
compileOnly("org.jetbrains:annotations:${JETBRAINS_ANNOTATIONS_VERSION}")
|
||||
compileOnly(libs.nulls)
|
||||
|
||||
api("net.minecraftforge:eventbus:${EVENTBUS_VERSION}")
|
||||
api(libs.eventbus)
|
||||
|
||||
implementation(project(':fmlloader'))
|
||||
implementation("commons-io:commons-io:${COMMONS_IO_VERSION}")
|
||||
implementation(libs.commons.io)
|
||||
}
|
||||
|
||||
task sourcesJar(type: Jar) {
|
||||
archiveClassifier = 'sources'
|
||||
from sourceSets.main.allSource
|
||||
java {
|
||||
toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION)
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
ext {
|
||||
MANIFESTS = [
|
||||
'': [
|
||||
'FMLModType': 'LIBRARY',
|
||||
'Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"),
|
||||
'Git-Commit': GIT_INFO.abbreviatedId,
|
||||
'Git-Branch': GIT_INFO.branch,
|
||||
'Build-Number': "${System.getenv('BUILD_NUMBER')?:0}"
|
||||
] as LinkedHashMap,
|
||||
'net/minecraftforge/fml/loading/': [
|
||||
'Specification-Title': 'FMLLoader',
|
||||
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 Loader',
|
||||
'Implementation-Title': 'FML',
|
||||
'Implementation-Version': '1.0',
|
||||
'Implementation-Vendor': 'Forge'
|
||||
] as LinkedHashMap
|
||||
]
|
||||
}
|
||||
|
||||
jar.doFirst {
|
||||
manifest.attributes['Automatic-Module-Name'] = project.name
|
||||
MANIFESTS.each { pkg, values ->
|
||||
if (pkg == '')
|
||||
manifest.attributes(values)
|
||||
else
|
||||
manifest.attributes(values, pkg)
|
||||
'Implementation-Vendor': 'Forge Development LLC'
|
||||
] as LinkedHashMap, 'net/minecraftforge/fml/')
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
options.compilerArgs << '-Xlint:unchecked'
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
options.compilerArgs << '-Xlint:-unchecked'
|
||||
}
|
||||
|
||||
license {
|
||||
header = rootProject.file('LICENSE-header.txt')
|
||||
|
||||
include 'net/minecraftforge/'
|
||||
}
|
||||
|
||||
artifacts {
|
||||
archives jar
|
||||
archives sourcesJar
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
publishing.publications.mavenJava(MavenPublication) {
|
||||
from components.java
|
||||
artifact sourcesJar
|
||||
}
|
||||
@@ -5,36 +5,39 @@
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import cpw.mods.modlauncher.util.ServiceLoaderUtils;
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import net.minecraftforge.fml.config.IConfigEvent;
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
|
||||
import java.util.ServiceConfigurationError;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Used to allow access to certain things from the game layer
|
||||
*/
|
||||
public class Bindings {
|
||||
private static final Bindings INSTANCE = new Bindings();
|
||||
private Bindings() {}
|
||||
|
||||
private final IBindingsProvider provider;
|
||||
|
||||
private Bindings() {
|
||||
final var providers = ServiceLoaderUtils.streamServiceLoader(()->ServiceLoader.load(FMLLoader.getGameLayer(), IBindingsProvider.class), sce->{}).toList();
|
||||
if (providers.size() != 1) {
|
||||
throw new IllegalStateException("Could not find bindings provider");
|
||||
}
|
||||
this.provider = providers.get(0);
|
||||
}
|
||||
/**
|
||||
* 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 INSTANCE.provider.getForgeBusSupplier();
|
||||
return PROVIDER.getForgeBusSupplier();
|
||||
}
|
||||
|
||||
public static Supplier<I18NParser> getMessageParser() {
|
||||
return INSTANCE.provider.getMessageParser();
|
||||
return PROVIDER.getMessageParser();
|
||||
}
|
||||
|
||||
public static Supplier<IConfigEvent.ConfigConfig> getConfigConfiguration() {
|
||||
return INSTANCE.provider.getConfigConfiguration();
|
||||
return PROVIDER.getConfigConfiguration();
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import java.util.function.Supplier;
|
||||
* @param <T> the type of the record which is held by the extension point
|
||||
*/
|
||||
@SuppressWarnings("unused") // Type parameter T
|
||||
// TODO: Complete re-write, this hasnt actually be used in a generic way by mods and we could make it a lot simpler.
|
||||
public interface IExtensionPoint<T extends Record>
|
||||
{
|
||||
/**
|
||||
@@ -40,7 +41,7 @@ public interface IExtensionPoint<T extends Record>
|
||||
* where {@code true} means it is from the server and {@code false} means it is from the world save. The return
|
||||
* value of the predicate determines whether the remote version is "compatible" for the purposes of the display test.</p>
|
||||
*
|
||||
* <p>The local compatibility version may be of the value {@link net.minecraftforge.network.NetworkConstants#IGNORESERVERONLY},
|
||||
* <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>
|
||||
@@ -94,6 +95,27 @@ public interface IExtensionPoint<T extends Record>
|
||||
*/
|
||||
@SuppressWarnings("JavadocReference") // reference to NetworkConstants, ForgeHooksClient
|
||||
record DisplayTest(Supplier<String> suppliedVersion, BiPredicate<String, Boolean> remoteVersionTest) implements IExtensionPoint<DisplayTest> {
|
||||
public static final String IGNORESERVERONLY = "OHNOES\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31\uD83D\uDE31";
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@@ -58,15 +59,16 @@ public abstract class ModContainer
|
||||
this.modInfo = info;
|
||||
this.modLoadingStage = ModLoadingStage.CONSTRUCT;
|
||||
|
||||
final String displayTestString = info.getConfig().<String>getConfigElement("displayTest").orElse("MATCH_VERSION"); // missing defaults to DEFAULT type
|
||||
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
|
||||
() -> new IExtensionPoint.DisplayTest(() -> IExtensionPoint.DisplayTest.IGNORESERVERONLY, (incoming, isNetwork) -> true);
|
||||
IExtensionPoint.DisplayTest.IGNORE_SERVER_VERSION;
|
||||
case "IGNORE_ALL_VERSION" -> // Ignores all information and provides no information
|
||||
() -> new IExtensionPoint.DisplayTest(() -> "", (incoming, isNetwork) -> true);
|
||||
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");
|
||||
@@ -145,6 +147,22 @@ public abstract class ModContainer
|
||||
extensionPoints.put(point, extension);
|
||||
}
|
||||
|
||||
public void registerDisplayTest(IExtensionPoint.DisplayTest displayTest) {
|
||||
registerExtensionPoint(IExtensionPoint.DisplayTest.class, () -> displayTest);
|
||||
}
|
||||
|
||||
public void registerDisplayTest(Supplier<IExtensionPoint.DisplayTest> displayTest) {
|
||||
registerExtensionPoint(IExtensionPoint.DisplayTest.class, displayTest);
|
||||
}
|
||||
|
||||
public void registerDisplayTest(String version, BiPredicate<String, Boolean> remoteVersionTest) {
|
||||
registerDisplayTest(new IExtensionPoint.DisplayTest(version, remoteVersionTest));
|
||||
}
|
||||
|
||||
public void registerDisplayTest(Supplier<String> suppliedVersion, BiPredicate<String, Boolean> remoteVersionTest) {
|
||||
registerDisplayTest(new IExtensionPoint.DisplayTest(suppliedVersion, remoteVersionTest));
|
||||
}
|
||||
|
||||
public void addConfig(final ModConfig modConfig) {
|
||||
configs.put(modConfig.getType(), modConfig);
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ import java.util.stream.Stream;
|
||||
*/
|
||||
public class ModList
|
||||
{
|
||||
private static Logger LOGGER = LogManager.getLogger();
|
||||
//private static final Logger LOGGER = LogManager.getLogger();
|
||||
private static ModList INSTANCE;
|
||||
private final List<IModFileInfo> modFiles;
|
||||
private final List<IModInfo> sortedList;
|
||||
@@ -120,14 +120,14 @@ public class ModList
|
||||
return executor -> gather(
|
||||
this.mods.stream()
|
||||
.map(mod -> ModContainer.buildTransitionHandler(mod, eventGenerator, progressBar, stateChange, executor))
|
||||
.collect(Collectors.toList()))
|
||||
.thenComposeAsync(ModList::completableFutureFromExceptionList, executor);
|
||||
.toList()
|
||||
).thenComposeAsync(ModList::completableFutureFromExceptionList, executor);
|
||||
}
|
||||
static CompletionStage<Void> completableFutureFromExceptionList(List<? extends Map.Entry<?, Throwable>> t) {
|
||||
if (t.stream().noneMatch(e->e.getValue()!=null)) {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
} else {
|
||||
final List<Throwable> throwables = t.stream().filter(e -> e.getValue() != null).map(Map.Entry::getValue).collect(Collectors.toList());
|
||||
final List<Throwable> throwables = t.stream().filter(e -> e.getValue() != null).map(Map.Entry::getValue).toList();
|
||||
CompletableFuture<Void> cf = new CompletableFuture<>();
|
||||
final RuntimeException accumulator = new RuntimeException();
|
||||
cf.completeExceptionally(accumulator);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Streams;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.fml.loading.FMLEnvironment;
|
||||
@@ -32,6 +33,7 @@ import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static net.minecraftforge.fml.Logging.CORE;
|
||||
import static net.minecraftforge.fml.Logging.LOADING;
|
||||
@@ -91,13 +93,13 @@ public class ModLoader
|
||||
{
|
||||
INSTANCE = this;
|
||||
this.loadingModList = FMLLoader.getLoadingModList();
|
||||
this.loadingExceptions = FMLLoader.getLoadingModList().getErrors().stream()
|
||||
this.loadingExceptions = this.loadingModList.getErrors().stream()
|
||||
.flatMap(ModLoadingException::fromEarlyException)
|
||||
.collect(Collectors.toList());
|
||||
this.loadingWarnings = FMLLoader.getLoadingModList().getBrokenFiles().stream()
|
||||
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());
|
||||
FMLLoader.getLoadingModList().getModFiles().stream()
|
||||
this.loadingModList.getModFiles().stream()
|
||||
.filter(ModFileInfo::missingLicense)
|
||||
.filter(modFileInfo -> modFileInfo.getMods().stream().noneMatch(thisModInfo -> this.loadingExceptions.stream().map(ModLoadingException::getModInfo).anyMatch(otherInfo -> otherInfo == thisModInfo))) //Ignore files where any other mod already encountered an error
|
||||
.map(modFileInfo -> new ModLoadingException(null, ModLoadingStage.VALIDATE, "fml.modloading.missinglicense", null, modFileInfo.getFile()))
|
||||
@@ -228,10 +230,9 @@ public class ModLoader
|
||||
} catch (CompletionException e) {
|
||||
loadingStateValid = false;
|
||||
Throwable t = e.getCause();
|
||||
final List<Throwable> notModLoading = Arrays.stream(t.getSuppressed())
|
||||
.filter(obj -> !(obj instanceof ModLoadingException))
|
||||
.collect(Collectors.toList());
|
||||
if (!notModLoading.isEmpty()) {
|
||||
boolean hasNotModLoadingEx = Arrays.stream(t.getSuppressed())
|
||||
.anyMatch(obj -> !(obj instanceof ModLoadingException));
|
||||
if (hasNotModLoadingEx) {
|
||||
LOGGER.fatal("Encountered non-modloading exceptions!", e);
|
||||
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
|
||||
throw e;
|
||||
@@ -257,10 +258,10 @@ public class ModLoader
|
||||
.stream()
|
||||
.map(e -> buildModContainerFromTOML(modFile, modInfoMap, e))
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
.toList();
|
||||
if (containers.size() != modInfoMap.size()) {
|
||||
var modIds = modInfoMap.values().stream().map(IModInfo::getModId).sorted().collect(Collectors.toList());
|
||||
var containerIds = containers.stream().map(c -> c != null ? c.getModId() : "(null)").sorted().collect(Collectors.toList());
|
||||
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(),
|
||||
@@ -278,7 +279,7 @@ public class ModLoader
|
||||
loadingExceptions.add(new ModLoadingException(null, ModLoadingStage.CONSTRUCT, "fml.modloading.missingclasses", null, modFile.getFilePath()));
|
||||
}
|
||||
// remove errored mod containers
|
||||
return containers.stream().filter(mc -> mc.modLoadingStage != ModLoadingStage.ERROR).collect(Collectors.toList());
|
||||
return containers.stream().filter(mc -> mc.modLoadingStage != ModLoadingStage.ERROR).toList();
|
||||
}
|
||||
|
||||
private ModContainer buildModContainerFromTOML(final IModFile modFile, final Map<String, IModInfo> modInfoMap, final Map.Entry<String, ? extends IModLanguageProvider.IModLanguageLoader> idToProviderEntry) {
|
||||
|
||||
@@ -8,38 +8,54 @@ package net.minecraftforge.fml;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraftforge.fml.config.IConfigSpec;
|
||||
import net.minecraftforge.fml.config.ModConfig;
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class ModLoadingContext
|
||||
{
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static final ThreadLocal<ModLoadingContext> context = ThreadLocal.withInitial(ModLoadingContext::new);
|
||||
private ModContainer activeContainer;
|
||||
private Object languageExtension;
|
||||
private ModLoadingStage stage;
|
||||
|
||||
/**
|
||||
* @deprecated Use the context provided by your language loader in your mod's constructor.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "1.21.1")
|
||||
public static ModLoadingContext get() {
|
||||
return context.get();
|
||||
}
|
||||
|
||||
private ModContainer activeContainer;
|
||||
|
||||
/**
|
||||
* @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(forRemoval = true, since = "1.21.1")
|
||||
public String getActiveNamespace() {
|
||||
return activeContainer == null ? "minecraft" : activeContainer.getNamespace();
|
||||
}
|
||||
|
||||
public ModContainer getContainer() {
|
||||
return getActiveContainer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an {@link IExtensionPoint} with the mod container.
|
||||
* @param point The extension point to register
|
||||
@@ -47,29 +63,67 @@ public class ModLoadingContext
|
||||
* @param <T> The type signature of the extension operator
|
||||
*/
|
||||
public <T extends Record & IExtensionPoint<T>> void registerExtensionPoint(Class<? extends IExtensionPoint<T>> point, Supplier<T> extension) {
|
||||
getActiveContainer().registerExtensionPoint(point, 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, getActiveContainer().getModId());
|
||||
LOGGER.debug("Attempted to register an empty config for type {} on mod {}", type, getContainer().getModId());
|
||||
return;
|
||||
}
|
||||
|
||||
getActiveContainer().addConfig(new ModConfig(type, spec, getActiveContainer()));
|
||||
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, getActiveContainer().getModId(), fileName);
|
||||
LOGGER.debug("Attempted to register an empty config for type {} on mod {} using file name {}", type, getContainer().getModId(), fileName);
|
||||
return;
|
||||
}
|
||||
|
||||
getActiveContainer().addConfig(new ModConfig(type, spec, getActiveContainer(), fileName));
|
||||
getContainer().addConfig(new ModConfig(type, spec, getContainer(), fileName));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -12,8 +12,7 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class ModLoadingWarning
|
||||
{
|
||||
public class ModLoadingWarning {
|
||||
/**
|
||||
* Mod Info for mod with warning
|
||||
*/
|
||||
|
||||
@@ -7,7 +7,6 @@ package net.minecraftforge.fml;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import net.minecraftforge.fml.loading.FMLConfig;
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@@ -35,81 +34,72 @@ import java.util.zip.GZIPInputStream;
|
||||
|
||||
import static net.minecraftforge.fml.VersionChecker.Status.*;
|
||||
|
||||
public class VersionChecker
|
||||
{
|
||||
public class VersionChecker {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
private static final int MAX_HTTP_REDIRECTS = Integer.getInteger("http.maxRedirects", 20);
|
||||
private static final int HTTP_TIMEOUT_SECS = Integer.getInteger("http.timeoutSecs", 15);
|
||||
|
||||
public enum Status
|
||||
{
|
||||
public enum Status {
|
||||
PENDING(),
|
||||
FAILED(),
|
||||
UP_TO_DATE(),
|
||||
OUTDATED(3, true),
|
||||
OUTDATED(3, true, true),
|
||||
AHEAD(),
|
||||
BETA(),
|
||||
BETA_OUTDATED(6, true);
|
||||
BETA_OUTDATED(6, true, true);
|
||||
|
||||
final int sheetOffset;
|
||||
final boolean draw, animated;
|
||||
private final int sheetOffset;
|
||||
private final boolean draw, animated;
|
||||
|
||||
Status()
|
||||
{
|
||||
Status() {
|
||||
this(0, false, false);
|
||||
}
|
||||
|
||||
Status(int sheetOffset)
|
||||
{
|
||||
this(sheetOffset, true, false);
|
||||
}
|
||||
|
||||
Status(int sheetOffset, boolean animated)
|
||||
{
|
||||
this(sheetOffset, true, animated);
|
||||
}
|
||||
|
||||
Status(int sheetOffset, boolean draw, boolean animated)
|
||||
{
|
||||
Status(int sheetOffset, boolean draw, boolean animated) {
|
||||
this.sheetOffset = sheetOffset;
|
||||
this.draw = draw;
|
||||
this.animated = animated;
|
||||
}
|
||||
|
||||
public int getSheetOffset()
|
||||
{
|
||||
public int getSheetOffset() {
|
||||
return sheetOffset;
|
||||
}
|
||||
|
||||
public boolean shouldDraw()
|
||||
{
|
||||
public boolean shouldDraw() {
|
||||
return draw;
|
||||
}
|
||||
|
||||
public boolean isAnimated()
|
||||
{
|
||||
public boolean isAnimated() {
|
||||
return animated;
|
||||
}
|
||||
|
||||
public boolean isOutdated() {
|
||||
return this == OUTDATED || this == BETA_OUTDATED;
|
||||
}
|
||||
}
|
||||
|
||||
public record CheckResult(VersionChecker.Status status, ComparableVersion target, Map<ComparableVersion, String> changes, String url) {}
|
||||
|
||||
public static void startVersionCheck()
|
||||
{
|
||||
new Thread("Forge Version Check")
|
||||
{
|
||||
public static void startVersionCheck() {
|
||||
new Thread("Forge Version Check") {
|
||||
private HttpClient client;
|
||||
private String mcVersion;
|
||||
private String agent;
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
if (!FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.VERSION_CHECK))
|
||||
{
|
||||
public void run() {
|
||||
if (!FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.VERSION_CHECK)) {
|
||||
LOGGER.info("Global Forge version check system disabled, no further processing.");
|
||||
return;
|
||||
}
|
||||
|
||||
var mc = ModList.get().getModContainerById("minecraft");
|
||||
var forge = ModList.get().getModContainerById("forge");
|
||||
this.mcVersion = mc.get().getModInfo().getVersion().toString();
|
||||
|
||||
this.agent = "Java-http-client/" + System.getProperty("java.version")
|
||||
+ " MinecraftForge/" + mcVersion + '-' + forge.get().getModInfo().getVersion();
|
||||
|
||||
client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(HTTP_TIMEOUT_SECS)).build();
|
||||
gatherMods().forEach(this::process);
|
||||
}
|
||||
@@ -119,15 +109,9 @@ public class VersionChecker
|
||||
*/
|
||||
private String openUrlString(URL url, IModInfo mod) throws IOException, URISyntaxException, InterruptedException {
|
||||
URL currentUrl = url;
|
||||
var userAgent = this.agent + ' ' + mod.getModId() + '/' + mod.getVersion();
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("Java-http-client/").append(System.getProperty("java.version")).append(' ');
|
||||
sb.append("MinecraftForge/").append(FMLLoader.versionInfo().mcAndForgeVersion()).append(' ');
|
||||
sb.append(mod.getModId()).append('/').append(mod.getVersion());
|
||||
final String userAgent = sb.toString();
|
||||
|
||||
for (int redirects = 0; redirects < MAX_HTTP_REDIRECTS; redirects++)
|
||||
{
|
||||
for (int redirects = 0; redirects < MAX_HTTP_REDIRECTS; redirects++) {
|
||||
var request = HttpRequest.newBuilder()
|
||||
.uri(currentUrl.toURI())
|
||||
.timeout(Duration.ofSeconds(HTTP_TIMEOUT_SECS))
|
||||
@@ -139,8 +123,7 @@ public class VersionChecker
|
||||
final HttpResponse<InputStream> response = client.send(request, HttpResponse.BodyHandlers.ofInputStream());
|
||||
|
||||
int responseCode = response.statusCode();
|
||||
if (responseCode >= 300 && responseCode <= 399)
|
||||
{
|
||||
if (responseCode >= 300 && responseCode <= 399) {
|
||||
String newLocation = response.headers().firstValue("Location")
|
||||
.orElseThrow(() -> new IOException("Got a 3xx response code but Location header was null while trying to fetch " + url));
|
||||
currentUrl = new URL(currentUrl, newLocation);
|
||||
@@ -150,26 +133,22 @@ public class VersionChecker
|
||||
final boolean isGzipEncoded = response.headers().firstValue("Content-Encoding").orElse("").equals("gzip");
|
||||
|
||||
final String bodyStr;
|
||||
try (InputStream inStream = isGzipEncoded ? new GZIPInputStream(response.body()) : response.body())
|
||||
{
|
||||
try (var bufferedReader = new BufferedReader(new InputStreamReader(inStream)))
|
||||
{
|
||||
bodyStr = bufferedReader.lines().collect(Collectors.joining("\n"));
|
||||
}
|
||||
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)
|
||||
{
|
||||
private void process(IModInfo mod) {
|
||||
Status status = PENDING;
|
||||
ComparableVersion target = null;
|
||||
Map<ComparableVersion, String> changes = null;
|
||||
String display_url = null;
|
||||
try
|
||||
{
|
||||
try {
|
||||
if (mod.getUpdateURL().isEmpty()) return;
|
||||
URL url = mod.getUpdateURL().get();
|
||||
LOGGER.info("[{}] Starting version check at {}", mod.getModId(), url.toString());
|
||||
@@ -178,54 +157,40 @@ public class VersionChecker
|
||||
|
||||
LOGGER.debug("[{}] Received version check data:\n{}", mod.getModId(), data);
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> json = new Gson().fromJson(data, Map.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> promos = (Map<String, String>)json.get("promos");
|
||||
display_url = (String)json.get("homepage");
|
||||
|
||||
var mcVersion = FMLLoader.versionInfo().mcVersion();
|
||||
String rec = promos.get(mcVersion + "-recommended");
|
||||
String lat = promos.get(mcVersion + "-latest");
|
||||
ComparableVersion current = new ComparableVersion(mod.getVersion().toString());
|
||||
|
||||
if (rec != null)
|
||||
{
|
||||
if (rec != null) {
|
||||
ComparableVersion recommended = new ComparableVersion(rec);
|
||||
int diff = recommended.compareTo(current);
|
||||
|
||||
if (diff == 0)
|
||||
status = UP_TO_DATE;
|
||||
else if (diff < 0)
|
||||
{
|
||||
else if (diff < 0) {
|
||||
status = AHEAD;
|
||||
if (lat != null)
|
||||
{
|
||||
if (lat != null) {
|
||||
ComparableVersion latest = new ComparableVersion(lat);
|
||||
if (current.compareTo(latest) < 0)
|
||||
{
|
||||
if (current.compareTo(latest) < 0) {
|
||||
status = OUTDATED;
|
||||
target = latest;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
status = OUTDATED;
|
||||
target = recommended;
|
||||
}
|
||||
}
|
||||
else if (lat != null)
|
||||
{
|
||||
} else if (lat != null) {
|
||||
ComparableVersion latest = new ComparableVersion(lat);
|
||||
if (current.compareTo(latest) < 0)
|
||||
status = BETA_OUTDATED;
|
||||
else
|
||||
status = BETA;
|
||||
status = current.compareTo(latest) < 0 ? BETA_OUTDATED : BETA;
|
||||
target = latest;
|
||||
}
|
||||
else
|
||||
} else
|
||||
status = BETA;
|
||||
|
||||
LOGGER.info("[{}] Found status: {} Current: {} Target: {}", mod.getModId(), status, current, target);
|
||||
@@ -233,38 +198,30 @@ public class VersionChecker
|
||||
changes = new LinkedHashMap<>();
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> tmp = (Map<String, String>)json.get(mcVersion);
|
||||
if (tmp != null)
|
||||
{
|
||||
if (tmp != null) {
|
||||
List<ComparableVersion> ordered = new ArrayList<>();
|
||||
for (String key : tmp.keySet())
|
||||
{
|
||||
for (String key : tmp.keySet()) {
|
||||
ComparableVersion ver = new ComparableVersion(key);
|
||||
if (ver.compareTo(current) > 0 && (target == null || ver.compareTo(target) < 1))
|
||||
{
|
||||
ordered.add(ver);
|
||||
}
|
||||
}
|
||||
Collections.sort(ordered);
|
||||
|
||||
for (ComparableVersion ver : ordered)
|
||||
{
|
||||
for (ComparableVersion ver : ordered) {
|
||||
changes.put(ver, tmp.get(ver.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("Failed to process update information", e);
|
||||
status = FAILED;
|
||||
}
|
||||
results.put(mod, new CheckResult(status, target, changes, display_url));
|
||||
RESULTS.put(mod, new CheckResult(status, target, changes, display_url));
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
// Gather a list of mods that have opted in to this update system by providing a URL.
|
||||
private static List<IModInfo> gatherMods()
|
||||
{
|
||||
private static List<IModInfo> gatherMods() {
|
||||
List<IModInfo> ret = new LinkedList<>();
|
||||
for (IModInfo info : ModList.get().getMods()) {
|
||||
if (info.getUpdateURL().isPresent())
|
||||
@@ -273,12 +230,10 @@ public class VersionChecker
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static Map<IModInfo, CheckResult> results = new ConcurrentHashMap<>();
|
||||
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);
|
||||
public static CheckResult getResult(IModInfo mod) {
|
||||
return RESULTS.getOrDefault(mod, PENDING_CHECK);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,14 +1,15 @@
|
||||
import net.minecraftforge.gradleutils.PomUtils
|
||||
|
||||
plugins {
|
||||
id 'com.github.ben-manes.versions'
|
||||
id 'org.javamodularity.moduleplugin' version '1.8.7' apply false
|
||||
id 'org.cadixdev.licenser'
|
||||
id 'java-library'
|
||||
id 'maven-publish'
|
||||
id 'net.minecraftforge.licenser'
|
||||
id 'net.minecraftforge.gradleutils'
|
||||
}
|
||||
apply plugin: 'java-library'
|
||||
apply plugin: 'jacoco'
|
||||
apply plugin: 'org.javamodularity.moduleplugin'
|
||||
|
||||
apply from: rootProject.file('build_shared.gradle')
|
||||
|
||||
import org.gradle.internal.os.OperatingSystem
|
||||
|
||||
switch (OperatingSystem.current()) {
|
||||
case OperatingSystem.LINUX:
|
||||
project.ext.lwjglNatives = "natives-linux"
|
||||
@@ -21,22 +22,18 @@ switch (OperatingSystem.current()) {
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
dependencyUpdates.rejectVersionIf { isNonStable(it.candidate.version) }
|
||||
|
||||
java.withSourcesJar()
|
||||
java {
|
||||
toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION)
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly('org.jetbrains:annotations:23.0.0')
|
||||
compileOnly(libs.nulls)
|
||||
implementation(project(':fmlloader'))
|
||||
implementation(project(':fmlcore'))
|
||||
implementation('org.lwjgl:lwjgl:3.3.1')
|
||||
implementation('org.lwjgl:lwjgl-glfw:3.3.1')
|
||||
implementation('org.lwjgl:lwjgl-opengl:3.3.1')
|
||||
implementation('org.lwjgl:lwjgl-stb:3.3.1')
|
||||
implementation('org.lwjgl:lwjgl-tinyfd:3.3.1')
|
||||
implementation(libs.bundles.lwjgl)
|
||||
implementation('org.slf4j:slf4j-api:1.8.0-beta4')
|
||||
implementation("net.sf.jopt-simple:jopt-simple:${JOPT_SIMPLE_VERSION}")
|
||||
implementation(libs.jopt.simple)
|
||||
testImplementation('org.junit.jupiter:junit-jupiter-api:5.8.2')
|
||||
testImplementation('org.powermock:powermock-core:2.0.9')
|
||||
testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine:5.8.2')
|
||||
@@ -47,47 +44,49 @@ dependencies {
|
||||
testRuntimeOnly("org.lwjgl:lwjgl-stb::$lwjglNatives")
|
||||
}
|
||||
|
||||
test {
|
||||
tasks.named('test', Test).configure {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
ext {
|
||||
MANIFESTS = [
|
||||
'': [
|
||||
'Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"),
|
||||
'Git-Commit': GIT_INFO.abbreviatedId,
|
||||
'Git-Branch': GIT_INFO.branch,
|
||||
'Build-Number': "${System.getenv('BUILD_NUMBER')?:0}",
|
||||
] as LinkedHashMap,
|
||||
'net/minecraftforge/fml/earlydisplay/': [
|
||||
'Specification-Title': 'FMLEarlyDisplay',
|
||||
tasks.named('jar', Jar).configure {
|
||||
manifest {
|
||||
attributes([
|
||||
'Automatic-Module-Name': 'net.minecraftforge.earlydisplay',
|
||||
'Forge-Module-Layer': 'boot'
|
||||
] as LinkedHashMap)
|
||||
attributes([
|
||||
'Specification-Title': 'FML Early Display',
|
||||
'Specification-Vendor': 'Forge Development LLC',
|
||||
'Specification-Version': '1',
|
||||
'Implementation-Title': 'FML Early Display',
|
||||
'Implementation-Version': '1.0',
|
||||
'Implementation-Vendor': 'Forge'
|
||||
] as LinkedHashMap
|
||||
]
|
||||
}
|
||||
|
||||
jar.doFirst {
|
||||
MANIFESTS.each { pkg, values ->
|
||||
if (pkg == '')
|
||||
manifest.attributes(values)
|
||||
else
|
||||
manifest.attributes(values, pkg)
|
||||
'Implementation-Vendor': 'Forge Development LLC',
|
||||
'Implementation-Version': '1.0'
|
||||
] as LinkedHashMap, 'net/minecraftforge/fml/earlydisplay/')
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
options.compilerArgs << '-Xlint:unchecked'
|
||||
}
|
||||
|
||||
license {
|
||||
header = rootProject.file('LICENSE-header.txt')
|
||||
include 'net/minecraftforge/'
|
||||
}
|
||||
|
||||
publishing.publications.mavenJava(MavenPublication) {
|
||||
from components.java
|
||||
}
|
||||
publishing {
|
||||
publications.register('mavenJava', MavenPublication).configure {
|
||||
from components.java
|
||||
artifactId = 'fmlearlydisplay'
|
||||
pom {
|
||||
name = project.name
|
||||
description = 'A pretty looking, but prone to erroring screen displayed during Forge loading.'
|
||||
url = 'https://github.com/MinecraftForge/MinecraftForge'
|
||||
PomUtils.setGitHubDetails(pom, 'MinecraftForge')
|
||||
license PomUtils.Licenses.LGPLv2_1
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven gradleutils.publishingForgeMaven
|
||||
}
|
||||
}
|
||||
+82
-52
@@ -14,9 +14,7 @@ import net.minecraftforge.fml.loading.ImmediateWindowProvider;
|
||||
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.lwjgl.PointerBuffer;
|
||||
import org.lwjgl.glfw.GLFWImage;
|
||||
import org.lwjgl.glfw.GLFWVidMode;
|
||||
import org.lwjgl.stb.STBImage;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
import org.lwjgl.util.tinyfd.TinyFileDialogs;
|
||||
@@ -30,9 +28,8 @@ import java.io.InputStreamReader;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.net.URI;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
@@ -40,15 +37,16 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.IntConsumer;
|
||||
import java.util.function.IntSupplier;
|
||||
import java.util.function.LongSupplier;
|
||||
@@ -78,7 +76,7 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger("EARLYDISPLAY");
|
||||
private final AtomicBoolean animationTimerTrigger = new AtomicBoolean(true);
|
||||
|
||||
private ColourScheme colourScheme;
|
||||
private ColourScheme colourScheme = ColourScheme.RED;
|
||||
private ElementShader elementShader;
|
||||
|
||||
private RenderElement.DisplayContext context;
|
||||
@@ -86,6 +84,7 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
private int framecount;
|
||||
private EarlyFramebuffer framebuffer;
|
||||
private ScheduledFuture<?> windowTick;
|
||||
private ScheduledFuture<?> initializationFuture;
|
||||
|
||||
private PerformanceInfo performanceInfo;
|
||||
private ScheduledFuture<?> performanceTick;
|
||||
@@ -111,11 +110,13 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
public String name() {
|
||||
return "fmlearlywindow";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Runnable initialize(String[] arguments) {
|
||||
String mcVersion = FMLLoader.versionInfo().mcVersion();
|
||||
String forgeVersion = FMLLoader.versionInfo().forgeVersion();
|
||||
|
||||
final OptionParser parser = new OptionParser();
|
||||
var mcversionopt = parser.accepts("fml.mcVersion").withRequiredArg().ofType(String.class);
|
||||
var forgeversionopt = parser.accepts("fml.forgeVersion").withRequiredArg().ofType(String.class);
|
||||
var widthopt = parser.accepts("width")
|
||||
.withRequiredArg().ofType(Integer.class)
|
||||
.defaultsTo(FMLConfig.getIntConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_WIDTH));
|
||||
@@ -130,25 +131,28 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
FMLConfig.updateConfig(FMLConfig.ConfigValue.EARLY_WINDOW_WIDTH, winWidth);
|
||||
FMLConfig.updateConfig(FMLConfig.ConfigValue.EARLY_WINDOW_HEIGHT, winHeight);
|
||||
fbScale = FMLConfig.getIntConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_FBSCALE);
|
||||
if (System.getenv("FML_EARLY_WINDOW_DARK")!= null) {
|
||||
if (System.getenv("FML_EARLY_WINDOW_DARK") != null) {
|
||||
this.colourScheme = ColourScheme.BLACK;
|
||||
} else {
|
||||
try {
|
||||
var optionLines = Files.readAllLines(FMLPaths.GAMEDIR.get().resolve(Paths.get("options.txt")));
|
||||
var options = optionLines.stream().map(l -> l.split(":")).filter(a -> a.length == 2).collect(Collectors.toMap(a -> a[0], a -> a[1]));
|
||||
var colourScheme = Boolean.parseBoolean(options.getOrDefault("darkMojangStudiosBackground", "false"));
|
||||
this.colourScheme = colourScheme ? ColourScheme.BLACK : ColourScheme.RED;
|
||||
} catch (IOException ioe) {
|
||||
// No options
|
||||
this.colourScheme = ColourScheme.RED; // default to red colourscheme
|
||||
// check the options file for the colour scheme
|
||||
var optionLines = Files.readAllLines(FMLPaths.GAMEDIR.get().resolve(Path.of("options.txt")));
|
||||
var keyName = "darkMojangStudiosBackground:";
|
||||
for (String line : optionLines) {
|
||||
if (line.startsWith(keyName)) {
|
||||
this.colourScheme = line.startsWith("true", keyName.length()) ? ColourScheme.BLACK : ColourScheme.RED;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
this.colourScheme = ColourScheme.RED; // fallback to red colourScheme
|
||||
}
|
||||
}
|
||||
this.maximized = parsed.has(maximizedopt) || FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_MAXIMIZED);
|
||||
|
||||
var forgeVersion = parsed.valueOf(forgeversionopt);
|
||||
StartupNotificationManager.modLoaderConsumer().ifPresent(c->c.accept("Forge loading "+ forgeVersion));
|
||||
StartupNotificationManager.modLoaderConsumer().ifPresent(c->c.accept("Forge loading " + forgeVersion));
|
||||
performanceInfo = new PerformanceInfo();
|
||||
return start(parsed.valueOf(mcversionopt), forgeVersion);
|
||||
return start(mcVersion, forgeVersion);
|
||||
}
|
||||
|
||||
private static final long MINFRAMETIME = TimeUnit.MILLISECONDS.toNanos(10); // This is the FPS cap on the window - note animation is capped at 20FPS via the tickTimer
|
||||
@@ -225,12 +229,12 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
LOGGER.error("Crash during font initialization", t);
|
||||
crashElegantly("An error occurred initializing a font for rendering. "+t.getMessage());
|
||||
}
|
||||
this.elements = new ArrayList<>(Arrays.asList(
|
||||
RenderElement.anvil(font),
|
||||
RenderElement.logMessageOverlay(font),
|
||||
RenderElement.forgeVersionOverlay(font, mcVersion+"-"+forgeVersion.split("-")[0]),
|
||||
RenderElement.performanceBar(font),
|
||||
RenderElement.progressBars(font)
|
||||
this.elements = new ArrayList<>(List.of(
|
||||
RenderElement.anvil(font),
|
||||
RenderElement.logMessageOverlay(font),
|
||||
RenderElement.forgeVersionOverlay(font, mcVersion + "-" + forgeVersion),
|
||||
RenderElement.performanceBar(font),
|
||||
RenderElement.progressBars(font)
|
||||
));
|
||||
|
||||
var date = Calendar.getInstance();
|
||||
@@ -264,7 +268,6 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
public void render(int alpha) {
|
||||
var currentVAO = glGetInteger(GL_VERTEX_ARRAY_BINDING);
|
||||
var currentFB = glGetInteger(GL_READ_FRAMEBUFFER_BINDING);
|
||||
glfwSwapInterval(0);
|
||||
glViewport(0, 0, this.context.scaledWidth(), this.context.scaledHeight());
|
||||
RenderElement.globalAlpha = alpha;
|
||||
framebuffer.activate();
|
||||
@@ -283,11 +286,12 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
public Runnable start(@Nullable String mcVersion, final String forgeVersion) {
|
||||
renderScheduler = Executors.newSingleThreadScheduledExecutor(r -> {
|
||||
final var thread = Executors.defaultThreadFactory().newThread(r);
|
||||
thread.setName("EarlyDisplay");
|
||||
thread.setDaemon(true);
|
||||
return thread;
|
||||
});
|
||||
initWindow(mcVersion);
|
||||
renderScheduler.schedule(() -> initRender(mcVersion, forgeVersion), 1, TimeUnit.MILLISECONDS);
|
||||
this.initializationFuture = renderScheduler.schedule(() -> initRender(mcVersion, forgeVersion), 1, TimeUnit.MILLISECONDS);
|
||||
return this::periodicTick;
|
||||
}
|
||||
|
||||
@@ -360,14 +364,15 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_NATIVE_CONTEXT_API);
|
||||
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
|
||||
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
|
||||
if (mcVersion != null) {
|
||||
// this emulates what we would get without early progress window
|
||||
// as vanilla never sets these, so GLFW uses the first window title
|
||||
// set them explicitly to avoid it using "FML early loading progress" as the class
|
||||
String vanillaWindowTitle = "Minecraft* " + mcVersion;
|
||||
glfwWindowHintString(GLFW_X11_CLASS_NAME, vanillaWindowTitle);
|
||||
glfwWindowHintString(GLFW_X11_INSTANCE_NAME, vanillaWindowTitle);
|
||||
}
|
||||
|
||||
String vanillaWindowTitle = "Minecraft* ";
|
||||
if (mcVersion != null) vanillaWindowTitle += mcVersion;
|
||||
|
||||
// this emulates what we would get without early progress window
|
||||
// as vanilla never sets these, so GLFW uses the first window title
|
||||
// set them explicitly to avoid it using "FML early loading progress" as the class
|
||||
glfwWindowHintString(GLFW_X11_CLASS_NAME, vanillaWindowTitle);
|
||||
glfwWindowHintString(GLFW_X11_INSTANCE_NAME, vanillaWindowTitle);
|
||||
|
||||
long primaryMonitor = glfwGetPrimaryMonitor();
|
||||
if (primaryMonitor == 0) {
|
||||
@@ -389,7 +394,8 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
}, 10, TimeUnit.SECONDS);
|
||||
int versidx = 0;
|
||||
var skipVersions = FMLConfig.<String>getListConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_SKIP_GL_VERSIONS);
|
||||
final String[] lastGLError=new String[GL_VERSIONS.length];
|
||||
boolean showHelpLog = FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_LOG_HELP_MSG);
|
||||
final String[] lastGLError = new String[GL_VERSIONS.length];
|
||||
do {
|
||||
final var glVersionToTry = GL_VERSIONS[versidx][0] + "." + GL_VERSIONS[versidx][1];
|
||||
if (skipVersions.contains(glVersionToTry)) {
|
||||
@@ -398,11 +404,23 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
continue;
|
||||
}
|
||||
LOGGER.info("Trying GL version " + glVersionToTry);
|
||||
if (showHelpLog && versidx == 0) {
|
||||
LOGGER.info("""
|
||||
If this message is the only thing at the bottom of your log before a crash, you probably have a driver issue.
|
||||
|
||||
Possible solutions:
|
||||
A) Make sure Minecraft is set to prefer high performance graphics in the OS and/or driver control panel
|
||||
B) Check for driver updates on the graphics brand's website
|
||||
C) Try reinstalling your graphics drivers
|
||||
D) If still not working after trying all of the above, ask for further help on the Forge forums or Discord
|
||||
|
||||
You can safely ignore this message if the game starts up successfully.""");
|
||||
}
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, GL_VERSIONS[versidx][0]); // we try our versions one at a time
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, GL_VERSIONS[versidx][1]);
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
||||
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
|
||||
window = glfwCreateWindow(winWidth, winHeight, "Minecraft: Forge Loading...", 0L, 0L);
|
||||
window = glfwCreateWindow(winWidth, winHeight, vanillaWindowTitle, 0L, 0L);
|
||||
var erridx = versidx;
|
||||
handleLastGLFWError((error, description) -> lastGLError[erridx] = String.format("Trying %d.%d: GLFW error: [0x%X]%s", GL_VERSIONS[erridx][0], GL_VERSIONS[erridx][1], error, description));
|
||||
if (lastGLError[versidx] != null) {
|
||||
@@ -430,6 +448,9 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
this.glVersion = gotVersion;
|
||||
this.window = window;
|
||||
|
||||
if (showHelpLog)
|
||||
FMLConfig.updateConfig(FMLConfig.ConfigValue.EARLY_WINDOW_LOG_HELP_MSG, false);
|
||||
|
||||
int[] x = new int[1];
|
||||
int[] y = new int[1];
|
||||
glfwGetMonitorPos(primaryMonitor, x, y);
|
||||
@@ -447,19 +468,19 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
glfwSetWindowPos(window, (vidmode.width() - this.winWidth) / 2 + monitorX, (vidmode.height() - this.winHeight) / 2 + monitorY);
|
||||
|
||||
// Attempt setting the icon
|
||||
int[] channels = new int[1];
|
||||
try (var glfwImgBuffer = GLFWImage.create(MemoryUtil.getAllocator().malloc(GLFWImage.SIZEOF), 1)) {
|
||||
final ByteBuffer imgBuffer;
|
||||
try (GLFWImage glfwImages = GLFWImage.malloc()) {
|
||||
imgBuffer = STBHelper.loadImageFromClasspath("forge_logo.png", 20000, x, y, channels);
|
||||
glfwImgBuffer.put(glfwImages.set(x[0], y[0], imgBuffer));
|
||||
glfwSetWindowIcon(window, glfwImgBuffer);
|
||||
STBImage.stbi_image_free(imgBuffer);
|
||||
}
|
||||
} catch (NullPointerException e) {
|
||||
System.err.println("Failed to load forge logo");
|
||||
}
|
||||
handleLastGLFWError((error, description) -> LOGGER.debug(String.format("Suppressing GLFW icon error: [0x%X]%s", error, description)));
|
||||
// int[] channels = new int[1];
|
||||
// try (var glfwImgBuffer = GLFWImage.create(MemoryUtil.getAllocator().malloc(GLFWImage.SIZEOF), 1)) {
|
||||
// final ByteBuffer imgBuffer;
|
||||
// try (GLFWImage glfwImages = GLFWImage.malloc()) {
|
||||
// imgBuffer = STBHelper.loadImageFromClasspath("forge_logo.png", 20000, x, y, channels);
|
||||
// glfwImgBuffer.put(glfwImages.set(x[0], y[0], imgBuffer));
|
||||
// glfwSetWindowIcon(window, glfwImgBuffer);
|
||||
// STBImage.stbi_image_free(imgBuffer);
|
||||
// }
|
||||
// } catch (NullPointerException e) {
|
||||
// System.err.println("Failed to load forge logo");
|
||||
// }
|
||||
// handleLastGLFWError((error, description) -> LOGGER.debug(String.format("Suppressing GLFW icon error: [0x%X]%s", error, description)));
|
||||
|
||||
glfwSetFramebufferSizeCallback(window, this::fbResize);
|
||||
glfwSetWindowPosCallback(window, this::winMove);
|
||||
@@ -518,6 +539,16 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
* @return the Window we own.
|
||||
*/
|
||||
public long setupMinecraftWindow(final IntSupplier width, final IntSupplier height, final Supplier<String> title, final LongSupplier monitorSupplier) {
|
||||
// wait for the window to actually be initialized
|
||||
try {
|
||||
this.initializationFuture.get(30, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (TimeoutException e) {
|
||||
Thread.dumpStack();
|
||||
crashElegantly("We seem to be having trouble initializing the window, waited for 30 seconds");
|
||||
}
|
||||
|
||||
// we have to spin wait for the window ticker
|
||||
ImmediateWindowHandler.updateProgress("Initializing Game Graphics");
|
||||
while (!this.windowTick.isDone()) {
|
||||
@@ -584,8 +615,7 @@ public class DisplayWindow implements ImmediateWindowProvider {
|
||||
var fm = layer.findModule("forge").orElseThrow();
|
||||
getClass().getModule().addReads(fm);
|
||||
var clz = FMLLoader.getGameLayer().findModule("forge").map(l->Class.forName(l, "net.minecraftforge.client.loading.ForgeLoadingOverlay")).orElseThrow();
|
||||
var methods = Arrays.stream(clz.getMethods()).filter(m-> Modifier.isStatic(m.getModifiers())).collect(Collectors.toMap(Method::getName, Function.identity()));
|
||||
loadingOverlay = methods.get("newInstance");
|
||||
loadingOverlay = Arrays.stream(clz.getDeclaredMethods()).filter(m-> Modifier.isStatic(m.getModifiers()) && m.getName().equals("newInstance")).findFirst().orElseThrow();
|
||||
}
|
||||
|
||||
public int getFramebufferTextureId() {
|
||||
|
||||
+17
-8
@@ -6,35 +6,44 @@
|
||||
package net.minecraftforge.fml.earlydisplay;
|
||||
|
||||
import com.sun.management.OperatingSystemMXBean;
|
||||
import net.minecraftforge.fml.loading.FMLConfig;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.MemoryMXBean;
|
||||
import java.lang.management.MemoryUsage;
|
||||
|
||||
public class PerformanceInfo {
|
||||
private static final MemoryMXBean MEMORY_BEAN = ManagementFactory.getMemoryMXBean();
|
||||
|
||||
private final boolean showCPUUsage;
|
||||
private final OperatingSystemMXBean osBean;
|
||||
private final MemoryMXBean memoryBean;
|
||||
float memory;
|
||||
|
||||
private float memory;
|
||||
private String text;
|
||||
|
||||
PerformanceInfo() {
|
||||
osBean = ManagementFactory.getPlatformMXBean(OperatingSystemMXBean.class);
|
||||
memoryBean = ManagementFactory.getMemoryMXBean();
|
||||
showCPUUsage = FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_SHOW_CPU);
|
||||
osBean = showCPUUsage ? ManagementFactory.getPlatformMXBean(OperatingSystemMXBean.class) : null;
|
||||
}
|
||||
|
||||
void update() {
|
||||
final MemoryUsage heapusage = memoryBean.getHeapMemoryUsage();
|
||||
final MemoryUsage heapusage = MEMORY_BEAN.getHeapMemoryUsage();
|
||||
memory = (float) heapusage.getUsed() / heapusage.getMax();
|
||||
|
||||
if (!showCPUUsage) {
|
||||
text = "Memory: %d/%dMB (%.1f%%)".formatted(heapusage.getUsed() >>> 20, heapusage.getMax() >>> 20, memory * 100f);
|
||||
return;
|
||||
}
|
||||
|
||||
var cpuLoad = osBean.getProcessCpuLoad();
|
||||
String cpuText;
|
||||
if (cpuLoad == -1) {
|
||||
cpuText = String.format("*CPU: %.1f%%", osBean.getCpuLoad() * 100f);
|
||||
cpuText = "*CPU: %.1f%%".formatted(osBean.getCpuLoad() * 100f);
|
||||
} else {
|
||||
cpuText = String.format("CPU: %.1f%%", cpuLoad * 100f);
|
||||
cpuText = "CPU: %.1f%%".formatted(cpuLoad * 100f);
|
||||
}
|
||||
|
||||
text = String.format("Heap: %d/%d MB (%.1f%%) OffHeap: %d MB %s", heapusage.getUsed() >> 20, heapusage.getMax() >> 20, memory * 100.0, memoryBean.getNonHeapMemoryUsage().getUsed() >> 20, cpuText);
|
||||
text = "Memory: %d/%dMB (%.1f%%) %s".formatted(heapusage.getUsed() >>> 20, heapusage.getMax() >>> 20, memory * 100f, cpuText);
|
||||
}
|
||||
|
||||
String text() {
|
||||
|
||||
+8
-2
@@ -34,11 +34,14 @@ public class RenderElement {
|
||||
};
|
||||
}
|
||||
}
|
||||
@FunctionalInterface
|
||||
interface TextureRenderer {
|
||||
void accept(SimpleBufferBuilder bb, DisplayContext context, int[] size, int frame);
|
||||
}
|
||||
@FunctionalInterface
|
||||
interface Initializer extends Supplier<Renderer> {}
|
||||
|
||||
@FunctionalInterface
|
||||
interface TextGenerator {
|
||||
void accept(SimpleBufferBuilder bb, SimpleFont fh, DisplayContext ctx);
|
||||
}
|
||||
@@ -201,14 +204,17 @@ public class RenderElement {
|
||||
bar.then(label).accept(buffer, context, frameNumber);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface ColourFunction {
|
||||
int colour(int frame);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface ProgressDisplay {
|
||||
float[] progress(int frame);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface BarPosition {
|
||||
int[] location(DisplayContext context);
|
||||
}
|
||||
@@ -280,7 +286,7 @@ public class RenderElement {
|
||||
if (num < min) {
|
||||
return min;
|
||||
} else {
|
||||
return num > max ? max : num;
|
||||
return Math.min(num, max);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,7 +294,7 @@ public class RenderElement {
|
||||
if (num < min) {
|
||||
return min;
|
||||
} else {
|
||||
return num > max ? max : num;
|
||||
return Math.min(num, max);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+82
-73
@@ -1,97 +1,106 @@
|
||||
plugins {
|
||||
id 'com.github.ben-manes.versions'
|
||||
id 'org.javamodularity.moduleplugin' version '1.8.3' apply false
|
||||
id 'org.cadixdev.licenser'
|
||||
}
|
||||
apply plugin: 'java-library'
|
||||
apply plugin: 'jacoco'
|
||||
apply plugin: 'org.javamodularity.moduleplugin'
|
||||
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')
|
||||
|
||||
dependencyUpdates.rejectVersionIf { isNonStable(it.candidate.version) }
|
||||
configurations.forEach{ it.transitive = false }
|
||||
|
||||
dependencies {
|
||||
compileOnly("org.jetbrains:annotations:${JETBRAINS_ANNOTATIONS_VERSION}")
|
||||
compileOnly(libs.nulls)
|
||||
|
||||
api("org.ow2.asm:asm:${ASM_VERSION}")
|
||||
api("org.ow2.asm:asm-tree:${ASM_VERSION}")
|
||||
api("org.ow2.asm:asm-commons:${ASM_VERSION}")
|
||||
api("net.minecraftforge:forgespi:${SPI_VERSION}")
|
||||
api("net.minecraftforge:mergetool:${MERGETOOL_VERSION}:api")
|
||||
api("org.apache.logging.log4j:log4j-api:${LOG4J_VERSION}")
|
||||
api("org.slf4j:slf4j-api:${SLF4J_API_VERSION}")
|
||||
api("com.google.guava:guava:${GUAVA_VERSION}")
|
||||
api("com.google.code.gson:gson:${GSON_VERSION}")
|
||||
api("org.apache.maven:maven-artifact:${APACHE_MAVEN_ARTIFACT_VERSION}")
|
||||
api("org.apache.commons:commons-lang3:${APACHE_COMMONS_LANG3_VERSION}")
|
||||
api("com.electronwill.night-config:core:${NIGHTCONFIG_VERSION}")
|
||||
api("com.electronwill.night-config:toml:${NIGHTCONFIG_VERSION}")
|
||||
api("cpw.mods:modlauncher:${MODLAUNCHER_VERSION}")
|
||||
api("net.minecraftforge:coremods:${COREMODS_VERSION}")
|
||||
api("com.mojang:logging:${MOJANG_LOGGING_VERSION}")
|
||||
api "net.minecraftforge:JarJarSelector:${JARJAR_VERSION}"
|
||||
api "net.minecraftforge:JarJarMetadata:${JARJAR_VERSION}"
|
||||
api(libs.bundles.asm) // Needed by all the black magic
|
||||
api(libs.forgespi)
|
||||
api(libs.mergetool.api)
|
||||
api(libs.log4j.api)
|
||||
api(libs.slf4j.api)
|
||||
api(libs.guava)
|
||||
api(libs.gson)
|
||||
api(libs.maven.artifact)
|
||||
api(libs.apache.commons)
|
||||
api(libs.bundles.night.config)
|
||||
api(libs.modlauncher)
|
||||
api(libs.coremods)
|
||||
api(libs.mojang.logging)
|
||||
api(libs.jarjar.selector)
|
||||
api(libs.jarjar.meta)
|
||||
|
||||
implementation("net.sf.jopt-simple:jopt-simple:${JOPT_SIMPLE_VERSION}")
|
||||
implementation("cpw.mods:securejarhandler:${SECUREJARHANDLER_VERSION}")
|
||||
implementation("net.minecraftforge:accesstransformers:${ACCESSTRANSFORMERS_VERSION}")
|
||||
implementation("net.minecrell:terminalconsoleappender:${TERMINALCONSOLEAPPENDER_VERSION}")
|
||||
// implementation("org.jline:jline:${JLINE_VERSION}")
|
||||
implementation(libs.jopt.simple)
|
||||
implementation(libs.securemodules)
|
||||
implementation(libs.accesstransformers)
|
||||
implementation(libs.terminalconsoleappender)
|
||||
implementation(libs.jimfs)
|
||||
|
||||
implementation("org.apache.logging.log4j:log4j-core:${LOG4J_VERSION}")
|
||||
annotationProcessor("org.apache.logging.log4j:log4j-core:${LOG4J_VERSION}")
|
||||
implementation(libs.log4j.core)
|
||||
annotationProcessor(libs.log4j.core)
|
||||
|
||||
testCompileOnly("org.jetbrains:annotations:${JETBRAINS_ANNOTATIONS_VERSION}")
|
||||
testRuntimeOnly("cpw.mods:bootstraplauncher:${BOOTSTRAPLAUNCHER_VERSION}")
|
||||
testCompileOnly(libs.nulls)
|
||||
testRuntimeOnly(libs.bootstrap)
|
||||
}
|
||||
|
||||
task sourcesJar(type: Jar) {
|
||||
archiveClassifier = 'sources'
|
||||
from sourceSets.main.allSource
|
||||
java {
|
||||
toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION)
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
ext {
|
||||
MANIFESTS = [
|
||||
'/': [
|
||||
'Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"),
|
||||
'Git-Commit': GIT_INFO.abbreviatedId,
|
||||
'Git-Branch': GIT_INFO.branch,
|
||||
'Build-Number': "${System.getenv('BUILD_NUMBER')?:0}",
|
||||
] as LinkedHashMap,
|
||||
'net/minecraftforge/fml/loading/': [
|
||||
'Specification-Title': 'FMLLoader',
|
||||
'Specification-Vendor': 'Forge Development LLC',
|
||||
'Specification-Version': '1',
|
||||
'Implementation-Title': 'FML Loader',
|
||||
'Implementation-Version': '1.0',
|
||||
'Implementation-Vendor': 'Forge'
|
||||
] as LinkedHashMap
|
||||
]
|
||||
}
|
||||
|
||||
jar.doFirst {
|
||||
manifest.attributes['Automatic-Module-Name'] = project.name
|
||||
MANIFESTS.each { pkg, values ->
|
||||
manifest.attributes(values, pkg)
|
||||
tasks.named('jar', Jar).configure {
|
||||
manifest {
|
||||
attributes([
|
||||
'Automatic-Module-Name': 'net.minecraftforge.fmlloader',
|
||||
'Forge-Module-Layer': 'boot'
|
||||
] as LinkedHashMap)
|
||||
attributes([
|
||||
'Specification-Title': 'FMLLoader',
|
||||
'Specification-Vendor': 'Forge Development LLC',
|
||||
'Specification-Version': '1',
|
||||
'Implementation-Title': 'FMLLoader',
|
||||
'Implementation-Vendor': 'Forge Development LLC',
|
||||
'Implementation-Version': FORGE_VERSION
|
||||
] as LinkedHashMap, 'net/minecraftforge/fml/loading/')
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
options.compilerArgs << '-Xlint:unchecked'
|
||||
}
|
||||
|
||||
license {
|
||||
header = rootProject.file('LICENSE-header.txt')
|
||||
|
||||
include 'net/minecraftforge/'
|
||||
}
|
||||
|
||||
artifacts {
|
||||
archives jar
|
||||
archives sourcesJar
|
||||
publishing {
|
||||
publications.register('mavenJava', MavenPublication).configure {
|
||||
from components.java
|
||||
artifactId = 'fmlloader'
|
||||
pom {
|
||||
name = project.name
|
||||
description = 'Modifactions to Minecraft to enable mod developers.'
|
||||
url = 'https://github.com/MinecraftForge/MinecraftForge'
|
||||
PomUtils.setGitHubDetails(pom, 'MinecraftForge')
|
||||
license PomUtils.Licenses.LGPLv2_1
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven gradleutils.publishingForgeMaven
|
||||
}
|
||||
}
|
||||
|
||||
publishing.publications.mavenJava(MavenPublication) {
|
||||
from components.java
|
||||
artifact sourcesJar
|
||||
}
|
||||
tasks.register('writeForgeVersionJson') {
|
||||
doLast {
|
||||
file('src/main/resources/forge_version.json').json = [
|
||||
forge: FORGE_VERSION,
|
||||
mc: MC_VERSION,
|
||||
mcp: MCP_VERSION
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named('generateResources').configure {
|
||||
dependsOn('writeForgeVersionJson')
|
||||
}
|
||||
+13
-6
@@ -36,9 +36,9 @@ import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
|
||||
*/
|
||||
public class CapabilityTokenSubclass implements ILaunchPluginService {
|
||||
|
||||
private final String FUNC_NAME = "getType";
|
||||
private final String FUNC_DESC = "()Ljava/lang/String;";
|
||||
private final String CAP_INJECT = "net/minecraftforge/common/capabilities/CapabilityToken"; //Don't directly reference this to prevent class loading.
|
||||
private static final String FUNC_NAME = "getType";
|
||||
private static final String FUNC_DESC = "()Ljava/lang/String;";
|
||||
private static final String CAP_INJECT = "net/minecraftforge/common/capabilities/CapabilityToken"; //Don't directly reference this to prevent class loading.
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
@@ -51,7 +51,14 @@ public class CapabilityTokenSubclass implements ILaunchPluginService {
|
||||
@Override
|
||||
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty)
|
||||
{
|
||||
return isEmpty ? NAY : YAY;
|
||||
if (isEmpty)
|
||||
return NAY;
|
||||
|
||||
String internalName = classType.getInternalName();
|
||||
if (internalName.startsWith("net/minecraft/") || internalName.startsWith("com/mojang/"))
|
||||
return NAY;
|
||||
|
||||
return YAY;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -75,7 +82,7 @@ public class CapabilityTokenSubclass implements ILaunchPluginService {
|
||||
SignatureReader reader = new SignatureReader(classNode.signature); // Having a node version of this would probably be useful.
|
||||
reader.accept(new SignatureVisitor(Opcodes.ASM9)
|
||||
{
|
||||
Deque<String> stack = new ArrayDeque<>();
|
||||
final Deque<String> stack = new ArrayDeque<>();
|
||||
|
||||
@Override
|
||||
public void visitClassType(final String name)
|
||||
@@ -113,7 +120,7 @@ public class CapabilityTokenSubclass implements ILaunchPluginService {
|
||||
}
|
||||
}
|
||||
|
||||
private static class Holder {
|
||||
private static final class Holder {
|
||||
String value;
|
||||
}
|
||||
|
||||
|
||||
+30
-9
@@ -8,6 +8,7 @@ package net.minecraftforge.fml.common.asm;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -35,7 +36,11 @@ public class ObjectHolderDefinalize implements ILaunchPluginService {
|
||||
new VanillaObjectHolderData("net.minecraft.core.particles.ParticleTypes", "particle_type", "net.minecraft.core.particles.ParticleType"),
|
||||
new VanillaObjectHolderData("net.minecraft.sounds.SoundEvents", "sound_event", "net.minecraft.sounds.SoundEvent")
|
||||
).collect(Collectors.toMap(VanillaObjectHolderData::holderClass, Function.identity()));
|
||||
private final String OBJECT_HOLDER = "Lnet/minecraftforge/registries/ObjectHolder;"; //Don't directly reference this to prevent class loading.
|
||||
private static final Set<String> VANILLA_OBJECT_HOLDER_CLASSES = VANILLA_OBJECT_HOLDERS.keySet().stream()
|
||||
.map(it -> it.replace('.', '/'))
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
private static final String OBJECT_HOLDER = "Lnet/minecraftforge/registries/ObjectHolder;"; //Don't directly reference this to prevent class loading.
|
||||
private static final int PUBLIC_STATIC_FINAL_FLAGS = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL;
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
@@ -46,16 +51,33 @@ public class ObjectHolderDefinalize implements ILaunchPluginService {
|
||||
private static final EnumSet<Phase> NAY = EnumSet.noneOf(Phase.class);
|
||||
|
||||
@Override
|
||||
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty)
|
||||
{
|
||||
return isEmpty ? NAY : YAY;
|
||||
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty) {
|
||||
if (isEmpty)
|
||||
return NAY;
|
||||
|
||||
// Let's skip processing classes that definitely don't have object holders
|
||||
String internalName = classType.getInternalName();
|
||||
|
||||
// Forge classes that aren't in the debug package (that package is used for tests)
|
||||
if (internalName.startsWith("net/minecraftforge/") && !internalName.substring(18).contains("debug"))
|
||||
return NAY;
|
||||
|
||||
// Vanilla classes that don't have object holders
|
||||
if (internalName.startsWith("com/mojang/"))
|
||||
return NAY;
|
||||
|
||||
// ...except specific ones we added to the VANILLA_OBJECT_HOLDERS map
|
||||
if (internalName.startsWith("net/minecraft/") && !VANILLA_OBJECT_HOLDER_CLASSES.contains(internalName))
|
||||
return NAY;
|
||||
|
||||
return YAY;
|
||||
}
|
||||
|
||||
private boolean hasHolder(List<AnnotationNode> lst)
|
||||
{
|
||||
private boolean hasHolder(List<AnnotationNode> lst) {
|
||||
return lst != null && lst.stream().anyMatch(n -> n.desc.equals(OBJECT_HOLDER));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private String getValue(List<AnnotationNode> lst)
|
||||
{
|
||||
AnnotationNode ann = lst.stream().filter(n -> n.desc.equals(OBJECT_HOLDER)).findFirst().get();
|
||||
@@ -75,10 +97,9 @@ public class ObjectHolderDefinalize implements ILaunchPluginService {
|
||||
{
|
||||
final AtomicBoolean changes = new AtomicBoolean();
|
||||
//Must be public static finals, and non-array objects
|
||||
final int flags = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL;
|
||||
|
||||
//Fix Annotated Fields before injecting from class level
|
||||
classNode.fields.stream().filter(f -> ((f.access & flags) == flags) && f.desc.startsWith("L") && hasHolder(f.visibleAnnotations)).forEach(f ->
|
||||
classNode.fields.stream().filter(f -> ((f.access & PUBLIC_STATIC_FINAL_FLAGS) == PUBLIC_STATIC_FINAL_FLAGS) && f.desc.startsWith("L") && hasHolder(f.visibleAnnotations)).forEach(f ->
|
||||
{
|
||||
int prev = f.access;
|
||||
f.access &= ~Opcodes.ACC_FINAL; //Strip final
|
||||
@@ -88,7 +109,7 @@ public class ObjectHolderDefinalize implements ILaunchPluginService {
|
||||
|
||||
if (VANILLA_OBJECT_HOLDERS.containsKey(classType.getClassName())) //Class level, de-finalize all fields and add @ObjectHolder to them!
|
||||
{
|
||||
classNode.fields.stream().filter(f -> ((f.access & flags) == flags) && f.desc.startsWith("L")).forEach(f ->
|
||||
classNode.fields.stream().filter(f -> ((f.access & PUBLIC_STATIC_FINAL_FLAGS) == PUBLIC_STATIC_FINAL_FLAGS) && f.desc.startsWith("L")).forEach(f ->
|
||||
{
|
||||
int prev = f.access;
|
||||
f.access &= ~Opcodes.ACC_FINAL;
|
||||
|
||||
+29
-26
@@ -20,8 +20,6 @@ import org.objectweb.asm.tree.MethodNode;
|
||||
|
||||
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.Marker;
|
||||
import org.slf4j.MarkerFactory;
|
||||
|
||||
/**
|
||||
* Modifies specified enums to allow runtime extension by making the $VALUES field non-final and
|
||||
@@ -30,15 +28,16 @@ import org.slf4j.MarkerFactory;
|
||||
public class RuntimeEnumExtender implements ILaunchPluginService {
|
||||
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private final Type STRING = Type.getType(String.class);
|
||||
private final Type ENUM = Type.getType(Enum.class);
|
||||
private final Type MARKER_IFACE = Type.getType("Lnet/minecraftforge/common/IExtensibleEnum;");
|
||||
private final Type ARRAY_UTILS = Type.getType("Lorg/apache/commons/lang3/ArrayUtils;"); //Don't directly reference this to prevent class loading.
|
||||
private final String ADD_DESC = Type.getMethodDescriptor(Type.getType(Object[].class), Type.getType(Object[].class), Type.getType(Object.class));
|
||||
private final Type UNSAFE_HACKS = Type.getType("Lnet/minecraftforge/fml/unsafe/UnsafeHacks;"); //Again, not direct reference to prevent class loading.
|
||||
private final String CLEAN_DESC = Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Class.class));
|
||||
private final String NAME_DESC = Type.getMethodDescriptor(STRING);
|
||||
private final String EQUALS_DESC = Type.getMethodDescriptor(Type.BOOLEAN_TYPE, STRING);
|
||||
private static final Type STRING = Type.getType(String.class);
|
||||
private static final Type ENUM = Type.getType(Enum.class);
|
||||
private static final Type MARKER_IFACE = Type.getType("Lnet/minecraftforge/common/IExtensibleEnum;");
|
||||
private static final Type ARRAY_UTILS = Type.getType("Lorg/apache/commons/lang3/ArrayUtils;"); //Don't directly reference this to prevent class loading.
|
||||
private static final String ADD_DESC = Type.getMethodDescriptor(Type.getType(Object[].class), Type.getType(Object[].class), Type.getType(Object.class));
|
||||
private static final Type UNSAFE_HACKS = Type.getType("Lnet/minecraftforge/fml/unsafe/UnsafeHacks;"); //Again, not direct reference to prevent class loading.
|
||||
private static final String CLEAN_DESC = Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Class.class));
|
||||
private static final String NAME_DESC = Type.getMethodDescriptor(STRING);
|
||||
private static final String EQUALS_DESC = Type.getMethodDescriptor(Type.BOOLEAN_TYPE, STRING);
|
||||
private static final int FLAGS = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC;
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
@@ -49,9 +48,15 @@ public class RuntimeEnumExtender implements ILaunchPluginService {
|
||||
private static final EnumSet<Phase> NAY = EnumSet.noneOf(Phase.class);
|
||||
|
||||
@Override
|
||||
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty)
|
||||
{
|
||||
return isEmpty ? NAY : YAY;
|
||||
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty) {
|
||||
if (isEmpty)
|
||||
return NAY;
|
||||
|
||||
String internalName = classType.getInternalName();
|
||||
if (internalName.startsWith("net/minecraftforge/") || internalName.startsWith("com/mojang/"))
|
||||
return NAY;
|
||||
|
||||
return YAY;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -60,25 +65,24 @@ public class RuntimeEnumExtender implements ILaunchPluginService {
|
||||
if ((classNode.access & Opcodes.ACC_ENUM) == 0)
|
||||
return ComputeFlags.NO_REWRITE;
|
||||
|
||||
Type array = Type.getType("[" + classType.getDescriptor());
|
||||
final int flags = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC;
|
||||
|
||||
FieldNode values = classNode.fields.stream().filter(f -> f.desc.contentEquals(array.getDescriptor()) && ((f.access & flags) == flags)).findFirst().orElse(null);
|
||||
|
||||
if (!classNode.interfaces.contains(MARKER_IFACE.getInternalName())) {
|
||||
if (!classNode.interfaces.contains(MARKER_IFACE.getInternalName()))
|
||||
return ComputeFlags.NO_REWRITE;
|
||||
}
|
||||
|
||||
Type array = Type.getType("[" + classType.getDescriptor());
|
||||
String arrayDesc = array.getDescriptor();
|
||||
|
||||
FieldNode values = classNode.fields.stream().filter(f -> f.desc.equals(arrayDesc) && ((f.access & FLAGS) == FLAGS)).findFirst().orElse(null);
|
||||
|
||||
//Static methods named "create" with first argument as a string
|
||||
List<MethodNode> candidates = classNode.methods.stream()
|
||||
.filter(m -> ((m.access & Opcodes.ACC_STATIC) != 0) && m.name.equals("create"))
|
||||
.collect(Collectors.toList());
|
||||
.toList();
|
||||
|
||||
if (candidates.isEmpty()) {
|
||||
throw new IllegalStateException("IExtensibleEnum has no candidate factory methods: " + classType.getClassName());
|
||||
}
|
||||
|
||||
candidates.forEach(mtd ->
|
||||
for (var mtd : candidates)
|
||||
{
|
||||
Type[] args = Type.getArgumentTypes(mtd.desc);
|
||||
if (args.length == 0 || !args[0].equals(STRING)) {
|
||||
@@ -110,8 +114,7 @@ public class RuntimeEnumExtender implements ILaunchPluginService {
|
||||
Type[] ctrArgs = new Type[args.length + 1];
|
||||
ctrArgs[0] = STRING;
|
||||
ctrArgs[1] = Type.INT_TYPE;
|
||||
for (int x = 1; x < args.length; x++)
|
||||
ctrArgs[1 + x] = args[x];
|
||||
System.arraycopy(args, 1, ctrArgs, 2, args.length - 1);
|
||||
|
||||
String desc = Type.getMethodDescriptor(Type.VOID_TYPE, ctrArgs);
|
||||
|
||||
@@ -233,7 +236,7 @@ public class RuntimeEnumExtender implements ILaunchPluginService {
|
||||
ins.load(vars, classType);
|
||||
ins.areturn(classType);
|
||||
}
|
||||
});
|
||||
}
|
||||
return ComputeFlags.COMPUTE_FRAMES;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,15 +5,13 @@
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
public class BackgroundWaiter {
|
||||
private static ExecutorService runner = Executors.newSingleThreadExecutor();
|
||||
private static final ExecutorService runner = Executors.newSingleThreadExecutor();
|
||||
|
||||
public static void runAndTick(Runnable r, Runnable tick) {
|
||||
ImmediateWindowHandler.updateProgress("Loading bootstrap resources");
|
||||
|
||||
-61
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import cpw.mods.modlauncher.api.NamedPath;
|
||||
import cpw.mods.modlauncher.serviceapi.ITransformerDiscoveryService;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
public class ClasspathTransformerDiscoverer implements ITransformerDiscoveryService {
|
||||
|
||||
private final List<Path> legacyClasspath = Arrays.stream(System.getProperty("legacyClassPath", "").split(File.pathSeparator)).map(Path::of).toList();
|
||||
|
||||
@Override
|
||||
public List<NamedPath> candidates(Path gameDirectory) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NamedPath> candidates(final Path gameDirectory, final String launchTarget) {
|
||||
if (launchTarget != null && launchTarget.contains("dev")) {
|
||||
this.scan(gameDirectory);
|
||||
}
|
||||
return List.copyOf(found);
|
||||
}
|
||||
|
||||
private final static List<NamedPath> found = new ArrayList<>();
|
||||
|
||||
public static List<Path> allExcluded() {
|
||||
return found.stream().map(np->np.paths()[0]).toList();
|
||||
}
|
||||
|
||||
private void scan(final Path gameDirectory) {
|
||||
try {
|
||||
locateTransformers("META-INF/services/cpw.mods.modlauncher.api.ITransformationService");
|
||||
locateTransformers("META-INF/services/net.minecraftforge.forgespi.locating.IModLocator");
|
||||
} catch (IOException e) {
|
||||
LogManager.getLogger().error("Error during discovery of transform services from the classpath", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void locateTransformers(String resource) throws IOException {
|
||||
final Enumeration<URL> resources = ClassLoader.getSystemClassLoader().getResources(resource);
|
||||
while (resources.hasMoreElements()) {
|
||||
URL url = resources.nextElement();
|
||||
Path path = ClasspathLocatorUtils.findJarPathFor(resource, url.toString(), url);
|
||||
if (legacyClasspath.stream().anyMatch(path::equals) || !Files.exists(path) || Files.isDirectory(path))
|
||||
continue;
|
||||
found.add(new NamedPath(path.toUri().toString(), path));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,8 @@ import java.util.List;
|
||||
* or server.
|
||||
*/
|
||||
public class EarlyLoadingException extends RuntimeException {
|
||||
private static final long serialVersionUID = 2302192216172508006L;
|
||||
|
||||
public static class ExceptionData {
|
||||
private final IModInfo modInfo;
|
||||
private final String i18message;
|
||||
|
||||
@@ -36,13 +36,15 @@ public class FMLConfig
|
||||
EARLY_WINDOW_FBSCALE("earlyWindowFBScale", 1, "Early window framebuffer scale"),
|
||||
EARLY_WINDOW_MAXIMIZED("earlyWindowMaximized", Boolean.FALSE, "Early window starts maximized"),
|
||||
EARLY_WINDOW_SKIP_GL_VERSIONS("earlyWindowSkipGLVersions", List.of(), "Skip specific GL versions, may help with buggy graphics card drivers"),
|
||||
EARLY_WINDOW_SQUIR("earlyWindowSquir", Boolean.FALSE, "Squir?")
|
||||
EARLY_WINDOW_SQUIR("earlyWindowSquir", Boolean.FALSE, "Squir?"),
|
||||
EARLY_WINDOW_SHOW_CPU("earlyWindowShowCPU", Boolean.FALSE, "Whether to show CPU usage stats in early window"),
|
||||
EARLY_WINDOW_LOG_HELP_MSG("earlyWindowLogHelpMessage", Boolean.TRUE, "Whether to log a help message on first attempt, to aid troubleshooting. This setting should automatically disable itself after a successful launch"),
|
||||
DETECT_NON_FORGE_MODS_TOML("detectNonForgeModsToml", Boolean.TRUE, "Attempt to detect non-Forge mods from their mods.toml to show a friendlier error message. This is a best-effort feature - disable if it causes issues."),
|
||||
;
|
||||
|
||||
private final String entry;
|
||||
private final Object defaultValue;
|
||||
private final String comment;
|
||||
private final Class<?> valueType;
|
||||
private final Function<Object, Object> entryFunction;
|
||||
|
||||
ConfigValue(final String entry, final Object defaultValue, final String comment) {
|
||||
@@ -52,7 +54,6 @@ public class FMLConfig
|
||||
this.entry = entry;
|
||||
this.defaultValue = defaultValue;
|
||||
this.comment = comment;
|
||||
this.valueType = defaultValue.getClass();
|
||||
this.entryFunction = entryFunction;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,15 +8,12 @@ package net.minecraftforge.fml.loading;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import cpw.mods.modlauncher.Launcher;
|
||||
import cpw.mods.modlauncher.api.*;
|
||||
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
|
||||
import cpw.mods.modlauncher.util.ServiceLoaderUtils;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.BackgroundScanHandler;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModDiscoverer;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModValidator;
|
||||
import net.minecraftforge.accesstransformer.service.AccessTransformerService;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
|
||||
import net.minecraftforge.fml.loading.targets.CommonLaunchHandler;
|
||||
import net.minecraftforge.forgespi.Environment;
|
||||
import net.minecraftforge.forgespi.coremod.ICoreModProvider;
|
||||
@@ -25,27 +22,29 @@ import org.slf4j.Logger;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ServiceConfigurationError;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
|
||||
import static net.minecraftforge.fml.loading.LogMarkers.SCAN;
|
||||
|
||||
public class FMLLoader
|
||||
{
|
||||
public class FMLLoader {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static AccessTransformerService accessTransformer;
|
||||
private static ModDiscoverer modDiscoverer;
|
||||
private static ICoreModProvider coreModProvider;
|
||||
private static ILaunchPluginService eventBus;
|
||||
private static LanguageLoadingProvider languageLoadingProvider;
|
||||
private static Dist dist;
|
||||
private static String naming;
|
||||
private static LoadingModList loadingModList;
|
||||
private static RuntimeDistCleaner runtimeDistCleaner;
|
||||
private static Path gamePath;
|
||||
private static VersionInfo versionInfo;
|
||||
private static final VersionInfo versionInfo = VersionInfo.detect();
|
||||
private static String launchHandlerName;
|
||||
private static CommonLaunchHandler commonLaunchHandler;
|
||||
public static Runnable progressWindowTick;
|
||||
@@ -54,80 +53,83 @@ public class FMLLoader
|
||||
private static boolean production;
|
||||
private static IModuleLayerManager moduleLayerManager;
|
||||
|
||||
static void onInitialLoad(IEnvironment environment, Set<String> otherServices) throws IncompatibleEnvironmentException
|
||||
{
|
||||
final String version = LauncherVersion.getVersion();
|
||||
LOGGER.debug(CORE,"FML {} loading", version);
|
||||
final Package modLauncherPackage = ITransformationService.class.getPackage();
|
||||
LOGGER.debug(CORE,"FML found ModLauncher version : {}", modLauncherPackage.getImplementationVersion());
|
||||
if (!modLauncherPackage.isCompatibleWith("4.0")) {
|
||||
LOGGER.error(CORE, "Found incompatible ModLauncher specification : {}, version {} from {}", modLauncherPackage.getSpecificationVersion(), modLauncherPackage.getImplementationVersion(), modLauncherPackage.getImplementationVendor());
|
||||
throw new IncompatibleEnvironmentException("Incompatible modlauncher found "+modLauncherPackage.getSpecificationVersion());
|
||||
}
|
||||
static void onInitialLoad(IEnvironment env, Set<String> otherServices) throws IncompatibleEnvironmentException {
|
||||
LOGGER.debug(CORE, "Detected version data : {}", versionInfo);
|
||||
LOGGER.debug(CORE, "FML {} loading", LauncherVersion.getVersion());
|
||||
|
||||
accessTransformer = (AccessTransformerService) environment.findLaunchPlugin("accesstransformer").orElseThrow(()-> {
|
||||
LOGGER.error(CORE, "Access Transformer library is missing, we need this to run");
|
||||
return new IncompatibleEnvironmentException("Missing AccessTransformer, cannot run");
|
||||
});
|
||||
// Allows us to communicate properties with other services through ModLauncher
|
||||
setupBlackboardKeys();
|
||||
|
||||
final Package atPackage = accessTransformer.getClass().getPackage();
|
||||
LOGGER.debug(CORE,"FML found AccessTransformer version : {}", atPackage.getImplementationVersion());
|
||||
if (!atPackage.isCompatibleWith("1.0")) {
|
||||
LOGGER.error(CORE, "Found incompatible AccessTransformer specification : {}, version {} from {}", atPackage.getSpecificationVersion(), atPackage.getImplementationVersion(), atPackage.getImplementationVendor());
|
||||
throw new IncompatibleEnvironmentException("Incompatible accesstransformer found "+atPackage.getSpecificationVersion());
|
||||
}
|
||||
|
||||
eventBus = environment.findLaunchPlugin("eventbus").orElseThrow(()-> {
|
||||
LOGGER.error(CORE, "Event Bus library is missing, we need this to run");
|
||||
return new IncompatibleEnvironmentException("Missing EventBus, cannot run");
|
||||
});
|
||||
|
||||
final Package eventBusPackage = eventBus.getClass().getPackage();
|
||||
LOGGER.debug(CORE,"FML found EventBus version : {}", eventBusPackage.getImplementationVersion());
|
||||
if (!eventBusPackage.isCompatibleWith("1.0")) {
|
||||
LOGGER.error(CORE, "Found incompatible EventBus specification : {}, version {} from {}", eventBusPackage.getSpecificationVersion(), eventBusPackage.getImplementationVersion(), eventBusPackage.getImplementationVendor());
|
||||
throw new IncompatibleEnvironmentException("Incompatible eventbus found "+eventBusPackage.getSpecificationVersion());
|
||||
}
|
||||
|
||||
runtimeDistCleaner = (RuntimeDistCleaner)environment.findLaunchPlugin("runtimedistcleaner").orElseThrow(()-> {
|
||||
LOGGER.error(CORE, "Dist Cleaner is missing, we need this to run");
|
||||
return new IncompatibleEnvironmentException("Missing DistCleaner, cannot run!");
|
||||
});
|
||||
LOGGER.debug(CORE, "Found Runtime Dist Cleaner");
|
||||
|
||||
var coreModProviders = ServiceLoaderUtils.streamWithErrorHandling(ServiceLoader.load(FMLLoader.class.getModule().getLayer(), ICoreModProvider.class), sce -> LOGGER.error(CORE, "Failed to load a coremod library, expect problems", sce)).toList();
|
||||
|
||||
if (coreModProviders.isEmpty()) {
|
||||
LOGGER.error(CORE, "Found no coremod provider. Cannot run");
|
||||
throw new IncompatibleEnvironmentException("No coremod library found");
|
||||
} else if (coreModProviders.size() > 1) {
|
||||
LOGGER.error(CORE, "Found multiple coremod providers : {}. Cannot run", coreModProviders.stream().map(p -> p.getClass().getName()).collect(Collectors.toList()));
|
||||
throw new IncompatibleEnvironmentException("Multiple coremod libraries found");
|
||||
}
|
||||
|
||||
coreModProvider = coreModProviders.get(0);
|
||||
final Package coremodPackage = coreModProvider.getClass().getPackage();
|
||||
LOGGER.debug(CORE,"FML found CoreMod version : {}", coremodPackage.getImplementationVersion());
|
||||
|
||||
|
||||
LOGGER.debug(CORE, "Found ForgeSPI package implementation version {}", Environment.class.getPackage().getImplementationVersion());
|
||||
LOGGER.debug(CORE, "Found ForgeSPI package specification {}", Environment.class.getPackage().getSpecificationVersion());
|
||||
if (Integer.parseInt(Environment.class.getPackage().getSpecificationVersion()) < 2) {
|
||||
LOGGER.error(CORE, "Found an out of date ForgeSPI implementation: {}, loading cannot continue", Environment.class.getPackage().getSpecificationVersion());
|
||||
throw new IncompatibleEnvironmentException("ForgeSPI is out of date, we cannot continue");
|
||||
}
|
||||
checkPackage(ITransformationService.class, "4.0", "ModLauncher");
|
||||
accessTransformer = getPlugin(env, "accesstransformer", "1.0", "AccessTransformer");
|
||||
/*eventBus =*/ getPlugin(env, "eventbus", "1.0", "EventBus");
|
||||
runtimeDistCleaner = getPlugin(env, "runtimedistcleaner", "1.0", "RuntimeDistCleaner");
|
||||
coreModProvider = getSingleService(ICoreModProvider.class, "CoreMod");
|
||||
LOGGER.debug(CORE,"FML found CoreMod version : {}", JarVersionLookupHandler.getInfo(coreModProvider.getClass()).impl().version().orElse("MISSING"));
|
||||
checkPackage(Environment.class, "2.0", "ForgeSPI");
|
||||
|
||||
try {
|
||||
Class.forName("com.electronwill.nightconfig.core.Config", false, environment.getClass().getClassLoader());
|
||||
Class.forName("com.electronwill.nightconfig.toml.TomlFormat", false, environment.getClass().getClassLoader());
|
||||
Class.forName("com.electronwill.nightconfig.core.Config", false, env.getClass().getClassLoader());
|
||||
Class.forName("com.electronwill.nightconfig.toml.TomlFormat", false, env.getClass().getClassLoader());
|
||||
} catch (ClassNotFoundException e) {
|
||||
LOGGER.error(CORE, "Failed to load NightConfig");
|
||||
throw new IncompatibleEnvironmentException("Missing NightConfig");
|
||||
}
|
||||
}
|
||||
|
||||
static void setupLaunchHandler(final IEnvironment environment, final Map<String, Object> arguments)
|
||||
{
|
||||
private static <T> T getPlugin(IEnvironment env, String id, String version, String name) throws IncompatibleEnvironmentException {
|
||||
@SuppressWarnings("unchecked")
|
||||
var plugin = (T)env.findLaunchPlugin(id).orElseThrow(() -> {
|
||||
LOGGER.error(CORE, "{} library is missing, we need this to run", name);
|
||||
return new IncompatibleEnvironmentException("Missing " + name + ", cannot run");
|
||||
});
|
||||
checkPackage(plugin.getClass(), version, name);
|
||||
return plugin;
|
||||
}
|
||||
|
||||
private static void checkPackage(Class<?> cls, String version, String name) throws IncompatibleEnvironmentException {
|
||||
var pkg = cls.getPackage();
|
||||
var info = JarVersionLookupHandler.getInfo(pkg);
|
||||
LOGGER.debug(CORE, "Found {} version: {}", name, info.impl().version().orElse("MISSING"));
|
||||
|
||||
if (!pkg.isCompatibleWith(version)) {
|
||||
LOGGER.error(CORE, "Found incompatible {} specification: {}, version {} from {}", name,
|
||||
info.spec().version().orElse("MISSING"),
|
||||
info.impl().version().orElse("MISSING"),
|
||||
info.impl().vendor().orElse("MISSING")
|
||||
);
|
||||
throw new IncompatibleEnvironmentException("Incompatible " + name + " found " + info.spec().version().orElse("MISSING"));
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> T getSingleService(Class<T> clazz, String name) throws IncompatibleEnvironmentException {
|
||||
var providers = new ArrayList<T>();
|
||||
for (var itr = ServiceLoader.load(FMLLoader.class.getModule().getLayer(), clazz).iterator(); itr.hasNext(); ) {
|
||||
try {
|
||||
providers.add(itr.next());
|
||||
} catch (ServiceConfigurationError e) {
|
||||
LOGGER.error(CORE, "Failed to load a " + name + " library, expect problems", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (providers.isEmpty()) {
|
||||
LOGGER.error(CORE, "Found no {} provider. Cannot run", name);
|
||||
throw new IncompatibleEnvironmentException("No " + name + " library found");
|
||||
} else if (providers.size() > 1) {
|
||||
LOGGER.error(CORE, "Found multiple {} providers: {}. Cannot run", name, providers.stream().map(p -> p.getClass().getName()).toList());
|
||||
throw new IncompatibleEnvironmentException("Multiple " + name + " libraries found");
|
||||
}
|
||||
|
||||
return providers.get(0);
|
||||
}
|
||||
|
||||
private static void setupBlackboardKeys() {
|
||||
LOGGER.debug(CORE, "Requesting CoreMods to not apply the fix for ASMAPI.findFirstInstructionBefore by default");
|
||||
var blackboardKey = TypesafeMap.Key.getOrCreate(Launcher.INSTANCE.blackboard(), "coremods.use_old_findFirstInstructionBefore", Boolean.class);
|
||||
Launcher.INSTANCE.blackboard().<Boolean>computeIfAbsent(blackboardKey, k -> true);
|
||||
}
|
||||
|
||||
static void setupLaunchHandler(final IEnvironment environment, final Map<String, Object> arguments) {
|
||||
final String launchTarget = environment.getProperty(IEnvironment.Keys.LAUNCHTARGET.get()).orElse("MISSING");
|
||||
arguments.put("launchTarget", launchTarget);
|
||||
final Optional<ILaunchHandlerService> launchHandler = environment.findLaunchHandler(launchTarget);
|
||||
@@ -137,6 +139,7 @@ public class FMLLoader
|
||||
throw new RuntimeException("Missing launch handler: " + launchTarget);
|
||||
}
|
||||
|
||||
// TODO: [FML][Loader] What the fuck is the point of using a service if you require a specific concrete class
|
||||
if (!(launchHandler.get() instanceof CommonLaunchHandler)) {
|
||||
LOGGER.error(CORE, "Incompatible Launch handler found - type {}, cannot continue", launchHandler.get().getClass().getName());
|
||||
throw new RuntimeException("Incompatible launch handler found");
|
||||
@@ -149,16 +152,12 @@ public class FMLLoader
|
||||
dist = commonLaunchHandler.getDist();
|
||||
production = commonLaunchHandler.isProduction();
|
||||
|
||||
versionInfo = new VersionInfo(arguments);
|
||||
|
||||
accessTransformer.getExtension().accept(Pair.of(naming, "srg"));
|
||||
|
||||
LOGGER.debug(CORE,"Received command line version data : {}", versionInfo);
|
||||
|
||||
runtimeDistCleaner.getExtension().accept(dist);
|
||||
}
|
||||
public static List<ITransformationService.Resource> beginModScan(final Map<String,?> arguments)
|
||||
{
|
||||
|
||||
public static List<ITransformationService.Resource> beginModScan(final Map<String,?> arguments) {
|
||||
LOGGER.debug(SCAN,"Scanning for Mod Locators");
|
||||
modDiscoverer = new ModDiscoverer(arguments);
|
||||
modValidator = modDiscoverer.discoverMods();
|
||||
@@ -178,8 +177,7 @@ public class FMLLoader
|
||||
return coreModProvider;
|
||||
}
|
||||
|
||||
public static LanguageLoadingProvider getLanguageLoadingProvider()
|
||||
{
|
||||
public static LanguageLoadingProvider getLanguageLoadingProvider() {
|
||||
return languageLoadingProvider;
|
||||
}
|
||||
|
||||
@@ -191,31 +189,26 @@ public class FMLLoader
|
||||
return commonLaunchHandler;
|
||||
}
|
||||
|
||||
public static void addAccessTransformer(Path atPath, ModFile modName)
|
||||
{
|
||||
public static void addAccessTransformer(Path atPath, ModFile modName) {
|
||||
LOGGER.debug(SCAN, "Adding Access Transformer in {}", modName.getFilePath());
|
||||
accessTransformer.offerResource(atPath, modName.getFileName());
|
||||
}
|
||||
|
||||
public static Dist getDist()
|
||||
{
|
||||
public static Dist getDist() {
|
||||
return dist;
|
||||
}
|
||||
|
||||
public static void beforeStart(ModuleLayer gameLayer)
|
||||
{
|
||||
public static void beforeStart(ModuleLayer gameLayer) {
|
||||
ImmediateWindowHandler.acceptGameLayer(gameLayer);
|
||||
ImmediateWindowHandler.updateProgress("Launching minecraft");
|
||||
progressWindowTick.run();
|
||||
}
|
||||
|
||||
public static LoadingModList getLoadingModList()
|
||||
{
|
||||
public static LoadingModList getLoadingModList() {
|
||||
return loadingModList;
|
||||
}
|
||||
|
||||
public static Path getGamePath()
|
||||
{
|
||||
public static Path getGamePath() {
|
||||
return gamePath;
|
||||
}
|
||||
|
||||
@@ -232,7 +225,7 @@ public class FMLLoader
|
||||
}
|
||||
|
||||
public static List<Map<String, String>> modLauncherModList() {
|
||||
return Launcher.INSTANCE.environment().getProperty(IEnvironment.Keys.MODLIST.get()).orElseGet(Collections::emptyList);
|
||||
return Launcher.INSTANCE.environment().getProperty(IEnvironment.Keys.MODLIST.get()).orElse(List.of());
|
||||
}
|
||||
|
||||
public static String launcherHandlerName() {
|
||||
|
||||
@@ -7,7 +7,6 @@ package net.minecraftforge.fml.loading;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import cpw.mods.modlauncher.api.*;
|
||||
import joptsimple.ArgumentAcceptingOptionSpec;
|
||||
import joptsimple.OptionSpecBuilder;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
|
||||
import net.minecraftforge.forgespi.Environment;
|
||||
@@ -15,7 +14,6 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -24,36 +22,18 @@ import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
|
||||
|
||||
public class FMLServiceProvider implements ITransformationService
|
||||
{
|
||||
public class FMLServiceProvider implements ITransformationService {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private ArgumentAcceptingOptionSpec<String> modsOption;
|
||||
private ArgumentAcceptingOptionSpec<String> modListsOption;
|
||||
private ArgumentAcceptingOptionSpec<String> mavenRootsOption;
|
||||
private ArgumentAcceptingOptionSpec<String> forgeOption;
|
||||
private ArgumentAcceptingOptionSpec<String> mcOption;
|
||||
private ArgumentAcceptingOptionSpec<String> forgeGroupOption;
|
||||
private ArgumentAcceptingOptionSpec<String> mcpOption;
|
||||
private ArgumentAcceptingOptionSpec<String> mappingsOption;
|
||||
private List<String> modsArgumentList;
|
||||
private List<String> modListsArgumentList;
|
||||
private List<String> mavenRootsArgumentList;
|
||||
private String targetForgeVersion;
|
||||
private String targetMcVersion;
|
||||
private String targetMcpVersion;
|
||||
private String targetMcpMappings;
|
||||
private String targetForgeGroup;
|
||||
private Map<String, Object> arguments;
|
||||
|
||||
public FMLServiceProvider()
|
||||
{
|
||||
final String markerselection = System.getProperty("forge.logging.markers", "");
|
||||
Arrays.stream(markerselection.split(",")).forEach(marker -> System.setProperty("forge.logging.marker." + marker.toLowerCase(Locale.ROOT), "ACCEPT"));
|
||||
public FMLServiceProvider() {
|
||||
var markers = System.getProperty("forge.logging.markers", "").split(",");
|
||||
for (var marker : markers)
|
||||
System.setProperty("forge.logging.marker." + marker.toLowerCase(Locale.ROOT), "ACCEPT");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name()
|
||||
{
|
||||
public String name() {
|
||||
return "fml";
|
||||
}
|
||||
|
||||
@@ -66,14 +46,6 @@ public class FMLServiceProvider implements ITransformationService
|
||||
LOGGER.debug(CORE, "Preparing ModFile");
|
||||
environment.computePropertyIfAbsent(Environment.Keys.MODFILEFACTORY.get(), k->ModFile::new);
|
||||
arguments = new HashMap<>();
|
||||
arguments.put("modLists", modListsArgumentList);
|
||||
arguments.put("mods", modsArgumentList);
|
||||
arguments.put("mavenRoots", mavenRootsArgumentList);
|
||||
arguments.put("forgeVersion", targetForgeVersion);
|
||||
arguments.put("forgeGroup", targetForgeGroup);
|
||||
arguments.put("mcVersion", targetMcVersion);
|
||||
arguments.put("mcpVersion", targetMcpVersion);
|
||||
arguments.put("mcpMappings", targetMcpMappings);
|
||||
LOGGER.debug(CORE, "Preparing launch handler");
|
||||
FMLLoader.setupLaunchHandler(environment, arguments);
|
||||
FMLEnvironment.setupInteropEnvironment(environment);
|
||||
@@ -92,45 +64,22 @@ public class FMLServiceProvider implements ITransformationService
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoad(IEnvironment environment, Set<String> otherServices) throws IncompatibleEnvironmentException
|
||||
{
|
||||
// LOGGER.debug("Injecting tracing printstreams for STDOUT/STDERR.");
|
||||
// System.setOut(new TracingPrintStream(LogManager.getLogger("STDOUT"), System.out));
|
||||
// System.setErr(new TracingPrintStream(LogManager.getLogger("STDERR"), System.err));
|
||||
public void onLoad(IEnvironment environment, Set<String> otherServices) throws IncompatibleEnvironmentException {
|
||||
FMLLoader.onInitialLoad(environment, otherServices);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void arguments(BiFunction<String, String, OptionSpecBuilder> argumentBuilder)
|
||||
{
|
||||
forgeOption = argumentBuilder.apply("forgeVersion", "Forge Version number").withRequiredArg().ofType(String.class).required();
|
||||
forgeGroupOption = argumentBuilder.apply("forgeGroup", "Forge Group (for testing)").withRequiredArg().ofType(String.class).defaultsTo("net.minecraftforge");
|
||||
mcOption = argumentBuilder.apply("mcVersion", "Minecraft Version number").withRequiredArg().ofType(String.class).required();
|
||||
mcpOption = argumentBuilder.apply("mcpVersion", "MCP Version number").withRequiredArg().ofType(String.class).required();
|
||||
mappingsOption = argumentBuilder.apply("mcpMappings", "MCP Mappings Channel and Version").withRequiredArg().ofType(String.class);
|
||||
modsOption = argumentBuilder.apply("mods", "List of mods to add").withRequiredArg().ofType(String.class).withValuesSeparatedBy(",");
|
||||
modListsOption = argumentBuilder.apply("modLists", "JSON modlists").withRequiredArg().ofType(String.class).withValuesSeparatedBy(",");
|
||||
mavenRootsOption = argumentBuilder.apply("mavenRoots", "Maven root directories").withRequiredArg().ofType(String.class).withValuesSeparatedBy(",");
|
||||
public void arguments(BiFunction<String, String, OptionSpecBuilder> argumentBuilder) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void argumentValues(OptionResult option)
|
||||
{
|
||||
modsArgumentList = option.values(modsOption);
|
||||
modListsArgumentList = option.values(modListsOption);
|
||||
mavenRootsArgumentList = option.values(mavenRootsOption);
|
||||
targetForgeVersion = option.value(forgeOption);
|
||||
targetForgeGroup = option.value(forgeGroupOption);
|
||||
targetMcVersion = option.value(mcOption);
|
||||
targetMcpVersion = option.value(mcpOption);
|
||||
targetMcpMappings = option.value(mappingsOption);
|
||||
public void argumentValues(OptionResult option) {
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public @NotNull List<ITransformer> transformers()
|
||||
{
|
||||
public @NotNull List<ITransformer> transformers() {
|
||||
LOGGER.debug(CORE, "Loading coremod transformers");
|
||||
return new ArrayList<>(FMLLoader.getCoreModProvider().getCoreModTransformers());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class FileUtils
|
||||
{
|
||||
|
||||
public static String fileExtension(final Path path) {
|
||||
String fileName = path.getFileName().toString();
|
||||
int idx = fileName.lastIndexOf('.');
|
||||
if (idx > -1) {
|
||||
return fileName.substring(idx+1);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean matchFileName(String path, String... matches) {
|
||||
// Extract file name from path
|
||||
String name = path.substring(Math.min(path.lastIndexOf(File.separatorChar) + 1, path.length()));
|
||||
// Check if it contains any of the desired keywords
|
||||
for (String match : matches) {
|
||||
if (name.contains(match)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ public class ImmediateWindowHandler {
|
||||
|
||||
private static ProgressMeter earlyProgress;
|
||||
public static void load(final String launchTarget, final String[] arguments) {
|
||||
if (!List.of("forgeclient", "forgeclientuserdev", "forgeclientdev").contains(launchTarget)) {
|
||||
if (!launchTarget.contains("client")) {
|
||||
provider = new DummyProvider();
|
||||
LOGGER.info("ImmediateWindowProvider not loading because launch target is {}", launchTarget);
|
||||
} else if (!FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_CONTROL)) {
|
||||
@@ -32,15 +32,16 @@ public class ImmediateWindowHandler {
|
||||
} else {
|
||||
final var providername = FMLConfig.getConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_PROVIDER);
|
||||
LOGGER.info("Loading ImmediateWindowProvider {}", providername);
|
||||
final var maybeProvider = ServiceLoader.load(ImmediateWindowProvider.class)
|
||||
.stream()
|
||||
.map(ServiceLoader.Provider::get)
|
||||
.filter(p -> Objects.equals(p.name(), providername))
|
||||
.findFirst();
|
||||
provider = maybeProvider.or(() -> {
|
||||
for (var service : ServiceLoader.load(ImmediateWindowProvider.class)) {
|
||||
if (providername.equals(service.name())) {
|
||||
provider = service;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (provider == null) {
|
||||
LOGGER.info("Failed to find ImmediateWindowProvider {}, disabling", providername);
|
||||
return Optional.of(new DummyProvider());
|
||||
}).orElseThrow();
|
||||
provider = new DummyProvider();
|
||||
}
|
||||
}
|
||||
// Only update config if the provider isn't the dummy provider
|
||||
if (!Objects.equals(provider.name(), "dummyprovider"))
|
||||
|
||||
+42
-1
@@ -11,31 +11,72 @@ import java.util.Optional;
|
||||
* Finds Version data from a package, with possible default values
|
||||
*/
|
||||
public class JarVersionLookupHandler {
|
||||
public record Version(Optional<String> title, Optional<String> vendor, Optional<String> version) {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" +
|
||||
"title=" + title.orElse(null) +
|
||||
", vendor=" + vendor.orElse(null) +
|
||||
", version=" + version.orElse(null) +
|
||||
"]";
|
||||
}
|
||||
}
|
||||
public record Info(Version spec, Version impl) {}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static Info getInfo(String pkgName) {
|
||||
return getInfo(Package.getPackage(pkgName));
|
||||
}
|
||||
|
||||
public static Info getInfo(Class<?> clazz) {
|
||||
return getInfo(clazz.getPackage());
|
||||
}
|
||||
|
||||
public static Info getInfo(Package pkg) {
|
||||
return new Info(
|
||||
new Version(
|
||||
Optional.ofNullable(pkg.getSpecificationTitle()),
|
||||
Optional.ofNullable(pkg.getSpecificationVendor()),
|
||||
Optional.ofNullable(pkg.getSpecificationVersion())
|
||||
),
|
||||
new Version(
|
||||
Optional.ofNullable(pkg.getImplementationTitle()),
|
||||
Optional.ofNullable(pkg.getImplementationVendor()),
|
||||
Optional.ofNullable(pkg.getImplementationVersion())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@Deprecated(forRemoval = true, since = "1.20.2")
|
||||
public static Optional<String> getImplementationVersion(final String pkgName) {
|
||||
// Note that with Java 9, you'll probably want the module's version data, hence pulling this out
|
||||
final String pkgVersion = Package.getPackage(pkgName).getImplementationVersion();
|
||||
return Optional.ofNullable(pkgVersion);
|
||||
}
|
||||
|
||||
@Deprecated(forRemoval = true, since = "1.20.2")
|
||||
public static Optional<String> getSpecificationVersion(final String pkgName) {
|
||||
// Note that with Java 9, you'll probably want the module's version data, hence pulling this out
|
||||
final String pkgVersion = Package.getPackage(pkgName).getSpecificationVersion();
|
||||
return Optional.ofNullable(pkgVersion);
|
||||
}
|
||||
|
||||
@Deprecated(forRemoval = true, since = "1.20.2")
|
||||
public static Optional<String> getImplementationVersion(final Class<?> clazz) {
|
||||
// With java 9 we'll use the module's version if it exists in preference.
|
||||
final String pkgVersion = clazz.getPackage().getImplementationVersion();
|
||||
return Optional.ofNullable(pkgVersion);
|
||||
}
|
||||
|
||||
@Deprecated(forRemoval = true, since = "1.20.2")
|
||||
public static Optional<String> getImplementationTitle(final Class<?> clazz) {
|
||||
// With java 9 we'll use the module's version if it exists in preference.
|
||||
final String pkgVersion = clazz.getPackage().getImplementationTitle();
|
||||
return Optional.ofNullable(pkgVersion);
|
||||
}
|
||||
|
||||
|
||||
@Deprecated(forRemoval = true, since = "1.20.2")
|
||||
public static Optional<String> getSpecificationVersion(final Class<?> clazz) {
|
||||
// With java 9 we'll use the module's version if it exists in preference.
|
||||
final String pkgVersion = clazz.getPackage().getSpecificationVersion();
|
||||
|
||||
+81
-107
@@ -7,27 +7,19 @@ package net.minecraftforge.fml.loading;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import cpw.mods.modlauncher.Launcher;
|
||||
import cpw.mods.modlauncher.api.IModuleLayerManager;
|
||||
import cpw.mods.modlauncher.util.ServiceLoaderUtils;
|
||||
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
|
||||
import cpw.mods.modlauncher.api.IModuleLayerManager.Layer;
|
||||
import net.minecraftforge.forgespi.language.IModLanguageProvider;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ExplodedDirectoryLocator;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
|
||||
import org.apache.maven.artifact.versioning.ArtifactVersion;
|
||||
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
|
||||
import org.apache.maven.artifact.versioning.VersionRange;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ServiceConfigurationError;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
@@ -36,115 +28,97 @@ import java.util.stream.Stream;
|
||||
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
|
||||
import static net.minecraftforge.fml.loading.LogMarkers.LOADING;
|
||||
|
||||
public class LanguageLoadingProvider
|
||||
{
|
||||
public class LanguageLoadingProvider {
|
||||
public void forEach(Consumer<IModLanguageProvider> consumer) {
|
||||
providers.forEach(consumer);
|
||||
}
|
||||
|
||||
public <T> Stream<T> applyForEach(Function<IModLanguageProvider, T> function) {
|
||||
return providers.stream().map(function);
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
loadLanguageProviders();
|
||||
}
|
||||
|
||||
/** This doesn't actually do anything with the argument, so just call reload(). Not sure when cpw broke that. */
|
||||
@Deprecated(forRemoval = true, since = "1.20.2")
|
||||
public void addAdditionalLanguages(List<ModFile> modFiles) {
|
||||
reload();
|
||||
}
|
||||
|
||||
/*==========================================================================*
|
||||
* INTERNAL SHIT *
|
||||
*==========================================================================*/
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private final List<IModLanguageProvider> languageProviders = new ArrayList<>();
|
||||
private final ServiceLoader<IModLanguageProvider> serviceLoader;
|
||||
private final Map<String, ModLanguageWrapper> languageProviderMap = new HashMap<>();
|
||||
private List<Path> languagePaths = new ArrayList<>();
|
||||
private final List<IModLanguageProvider> providers = new ArrayList<>();
|
||||
private final Map<String, Wrapper> providersByName = new HashMap<>();
|
||||
private static final DefaultArtifactVersion IN_DEV = new DefaultArtifactVersion("0.0-dev");
|
||||
|
||||
public void forEach(final Consumer<IModLanguageProvider> consumer)
|
||||
{
|
||||
languageProviders.forEach(consumer);
|
||||
}
|
||||
|
||||
public <T> Stream<T> applyForEach(final Function<IModLanguageProvider, T> function) {
|
||||
return languageProviders.stream().map(function);
|
||||
}
|
||||
|
||||
private static class ModLanguageWrapper {
|
||||
private final IModLanguageProvider modLanguageProvider;
|
||||
|
||||
private final ArtifactVersion version;
|
||||
public ModLanguageWrapper(IModLanguageProvider modLanguageProvider, ArtifactVersion version)
|
||||
{
|
||||
this.modLanguageProvider = modLanguageProvider;
|
||||
this.version = version;
|
||||
}
|
||||
public ArtifactVersion getVersion()
|
||||
{
|
||||
return version;
|
||||
}
|
||||
|
||||
public IModLanguageProvider getModLanguageProvider()
|
||||
{
|
||||
return modLanguageProvider;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
private record Wrapper(ArtifactVersion version, IModLanguageProvider service) {}
|
||||
|
||||
LanguageLoadingProvider() {
|
||||
var sl = Launcher.INSTANCE.environment().findModuleLayerManager().flatMap(lm->lm.getLayer(IModuleLayerManager.Layer.PLUGIN)).orElseThrow();
|
||||
var sl = Launcher.INSTANCE.environment().findModuleLayerManager().flatMap(lm -> lm.getLayer(Layer.PLUGIN)).orElseThrow();
|
||||
serviceLoader = ServiceLoader.load(sl, IModLanguageProvider.class);
|
||||
loadLanguageProviders();
|
||||
}
|
||||
|
||||
private void loadLanguageProviders() {
|
||||
LOGGER.debug(CORE, "Found {} language providers", ServiceLoaderUtils.streamServiceLoader(()->serviceLoader, sce->LOGGER.error("Problem with language loaders")).count());
|
||||
serviceLoader.forEach(languageProviders::add);
|
||||
ImmediateWindowHandler.updateProgress("Loading language providers");
|
||||
languageProviders.forEach(lp -> {
|
||||
final Path lpPath;
|
||||
try {
|
||||
lpPath = Paths.get(lp.getClass().getProtectionDomain().getCodeSource().getLocation().toURI());
|
||||
} catch (URISyntaxException e) {
|
||||
throw new RuntimeException("Huh?", e);
|
||||
}
|
||||
Optional<String> implementationVersion = JarVersionLookupHandler.getImplementationVersion(lp.getClass());
|
||||
String impl = implementationVersion.orElse(Files.isDirectory(lpPath) ? FMLLoader.versionInfo().forgeVersion().split("\\.")[0] : null);
|
||||
if (impl == null) {
|
||||
LOGGER.error(CORE, "Found unversioned language provider {}", lp.name());
|
||||
throw new RuntimeException("Failed to find implementation version for language provider "+ lp.name());
|
||||
}
|
||||
LOGGER.debug(CORE, "Found language provider {}, version {}", lp.name(), impl);
|
||||
ImmediateWindowHandler.updateProgress("Loaded language provider "+lp.name()+ " " + impl);
|
||||
languageProviderMap.put(lp.name(), new ModLanguageWrapper(lp, new DefaultArtifactVersion(impl)));
|
||||
});
|
||||
}
|
||||
providers.clear();
|
||||
providersByName.clear();
|
||||
|
||||
void addForgeLanguage(final Path forgePath) {
|
||||
if (!languageProviderMap.containsKey("javafml")) {
|
||||
LOGGER.debug(CORE,"Adding forge as a language from {}", forgePath.toString());
|
||||
addLanguagePaths(Stream.of(forgePath));
|
||||
serviceLoader.reload();
|
||||
loadLanguageProviders();
|
||||
} else {
|
||||
LOGGER.debug(CORE, "Skipping adding forge jar - javafml is already present");
|
||||
}
|
||||
}
|
||||
|
||||
private void addLanguagePaths(final Stream<Path> langPaths) {
|
||||
languageProviders.clear();
|
||||
languageProviderMap.clear();
|
||||
// langPaths.peek(languagePaths::add).map(Path::toFile).map(File::toURI).map(rethrowFunction(URI::toURL)).forEach(languageClassLoader::addURL);
|
||||
}
|
||||
|
||||
public void addAdditionalLanguages(List<ModFile> modFiles)
|
||||
{
|
||||
if (modFiles==null) return;
|
||||
Stream<Path> langPaths = modFiles.stream().map(ModFile::getFilePath);
|
||||
addLanguagePaths(langPaths);
|
||||
serviceLoader.reload();
|
||||
loadLanguageProviders();
|
||||
}
|
||||
|
||||
Stream<Path> getLibraries() {
|
||||
return languagePaths.stream();
|
||||
}
|
||||
|
||||
public IModLanguageProvider findLanguage(ModFile mf, String modLoader, VersionRange modLoaderVersion) {
|
||||
final String languageFileName = mf.getProvider() instanceof ExplodedDirectoryLocator ? "in-development" : mf.getFileName();
|
||||
final ModLanguageWrapper mlw = languageProviderMap.get(modLoader);
|
||||
if (mlw == null) {
|
||||
LOGGER.error(LOADING,"Missing language {} version {} wanted by {}", modLoader, modLoaderVersion, languageFileName);
|
||||
throw new EarlyLoadingException("Missing language "+modLoader, null, Collections.singletonList(new EarlyLoadingException.ExceptionData("fml.language.missingversion", modLoader, modLoaderVersion, languageFileName, "null")));
|
||||
}
|
||||
if (!VersionSupportMatrix.testVersionSupportMatrix(modLoaderVersion, modLoader, "languageloader", (llid, range) -> range.containsVersion(mlw.getVersion()))) {
|
||||
LOGGER.error(LOADING,"Missing language {} version {} wanted by {}, found {}", modLoader, modLoaderVersion, languageFileName, mlw.getVersion());
|
||||
throw new EarlyLoadingException("Missing language "+ modLoader + " matching range "+modLoaderVersion + " found "+mlw.getVersion(), null, Collections.singletonList(new EarlyLoadingException.ExceptionData("fml.language.missingversion", modLoader, modLoaderVersion, languageFileName, mlw.getVersion())));
|
||||
for (var itr = serviceLoader.iterator(); itr.hasNext(); ) {
|
||||
try {
|
||||
var srvc = itr.next();
|
||||
providers.add(srvc);
|
||||
} catch (ServiceConfigurationError sce) {
|
||||
LOGGER.error(CORE, "Failed to load IModLauguageProvider Service: " + sce.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return mlw.getModLanguageProvider();
|
||||
LOGGER.debug(CORE, "Found {} language providers", providers.size());
|
||||
|
||||
ImmediateWindowHandler.updateProgress("Loading language providers");
|
||||
for (var provider : providers) {
|
||||
var info = JarVersionLookupHandler.getInfo(provider.getClass());
|
||||
|
||||
if (info.impl().version().isEmpty() && FMLEnvironment.production) {
|
||||
LOGGER.error(CORE, "Found unversioned language provider {}: {}", provider.name(), provider.getClass().getName());
|
||||
throw new RuntimeException("Failed to find implementation version for language provider " + provider.name() + " of type " + provider.getClass().getName());
|
||||
}
|
||||
|
||||
var version = info.impl().version().map(DefaultArtifactVersion::new).orElse(IN_DEV);
|
||||
|
||||
LOGGER.debug(CORE, "Found language provider {}, version {}", provider.name(), version);
|
||||
ImmediateWindowHandler.updateProgress("Loaded language provider " + provider.name() + " " + version);
|
||||
providersByName.put(provider.name(), new Wrapper(version, provider));
|
||||
}
|
||||
}
|
||||
|
||||
public IModLanguageProvider findLanguage(ModFile mod, String language, VersionRange versionRange) {
|
||||
var consumer = mod.getFileName();
|
||||
var wrapper = providersByName.get(language);
|
||||
|
||||
if (wrapper == null) {
|
||||
LOGGER.error(LOADING, "Missing language {} version {} wanted by {}", language, versionRange, consumer);
|
||||
var data = new EarlyLoadingException.ExceptionData("fml.language.missingversion", language, versionRange, consumer, "null");
|
||||
throw new EarlyLoadingException("Missing language " + language, null, List.of(data));
|
||||
}
|
||||
|
||||
var version = wrapper.version();
|
||||
if (version == IN_DEV) {
|
||||
LOGGER.debug(LOADING, "Language Provider {} version is unknown, but {} was requested by {}. Proceeding as this is development environement", language, versionRange, consumer);
|
||||
return wrapper.service();
|
||||
}
|
||||
|
||||
if (!versionRange.containsVersion(version)) {
|
||||
LOGGER.error(LOADING, "Missing language {} version {} wanted by {}, found {}", language, versionRange, consumer, version);
|
||||
var data = new EarlyLoadingException.ExceptionData("fml.language.missingversion", language, versionRange, consumer, version);
|
||||
throw new EarlyLoadingException("Missing language " + language + " matching range " + versionRange + " found " + version, null, List.of(data));
|
||||
}
|
||||
|
||||
return wrapper.service();
|
||||
}
|
||||
}
|
||||
@@ -8,21 +8,19 @@ package net.minecraftforge.fml.loading;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
|
||||
|
||||
public class LauncherVersion {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static final String launcherVersion;
|
||||
|
||||
static {
|
||||
String vers = JarVersionLookupHandler.getImplementationVersion(LauncherVersion.class).orElse(System.getenv("LAUNCHER_VERSION"));
|
||||
if (vers == null) throw new RuntimeException("Missing FMLLauncher version, cannot continue");
|
||||
launcherVersion = vers;
|
||||
LOGGER.debug(CORE, "Found FMLLauncher version {}", launcherVersion);
|
||||
var info = JarVersionLookupHandler.getInfo(LauncherVersion.class);
|
||||
if (info.impl().version().isEmpty())
|
||||
throw new IllegalStateException("Failed to find version for package " + LauncherVersion.class.getPackageName() + " This is an invalid environment");
|
||||
launcherVersion = info.impl().version().get();
|
||||
LOGGER.debug(LogMarkers.CORE, "Found FMLLauncher version {}", launcherVersion);
|
||||
}
|
||||
|
||||
public static String getVersion()
|
||||
{
|
||||
public static String getVersion() {
|
||||
return launcherVersion;
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class LibraryFinder {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static Path libsPath;
|
||||
static Path findLibsPath() {
|
||||
if (libsPath == null) {
|
||||
libsPath = Path.of(System.getProperty("libraryDirectory","crazysnowmannonsense/cheezwhizz"));
|
||||
if (!Files.isDirectory(libsPath)) {
|
||||
throw new IllegalStateException("Missing libraryDirectory system property, cannot continue");
|
||||
}
|
||||
}
|
||||
return libsPath;
|
||||
}
|
||||
|
||||
static Path getForgeLibraryPath(final String mcVersion, final String forgeVersion, final String forgeGroup) {
|
||||
Path forgePath = findLibsPath().resolve(MavenCoordinateResolver.get(forgeGroup, "forge", "", "universal", mcVersion+"-"+forgeVersion));
|
||||
LOGGER.debug(LogMarkers.CORE, "Found forge path {} is {}", forgePath, pathStatus(forgePath));
|
||||
return forgePath;
|
||||
}
|
||||
|
||||
static String pathStatus(final Path path) {
|
||||
return Files.exists(path) ? "present" : "missing";
|
||||
}
|
||||
|
||||
static Path[] getMCPaths(final String mcVersion, final String mcpVersion, final String forgeVersion, final String forgeGroup, final String type) {
|
||||
Path srgMcPath = findLibsPath().resolve(MavenCoordinateResolver.get("net.minecraft", type, "", "srg", mcVersion+"-"+mcpVersion));
|
||||
Path mcExtrasPath = findLibsPath().resolve(MavenCoordinateResolver.get("net.minecraft", type, "", "extra", mcVersion+"-"+mcpVersion));
|
||||
Path patchedBinariesPath = findLibsPath().resolve(MavenCoordinateResolver.get(forgeGroup, "forge", "", type, mcVersion+"-"+forgeVersion));
|
||||
LOGGER.debug(LogMarkers.CORE,"SRG MC at {} is {}", srgMcPath.toString(), pathStatus(srgMcPath));
|
||||
LOGGER.debug(LogMarkers.CORE,"MC Extras at {} is {}", mcExtrasPath.toString(), pathStatus(mcExtrasPath));
|
||||
LOGGER.debug(LogMarkers.CORE,"Forge patches at {} is {}", patchedBinariesPath.toString(), pathStatus(patchedBinariesPath));
|
||||
return new Path[] { patchedBinariesPath, mcExtrasPath, srgMcPath };
|
||||
}
|
||||
|
||||
public static Path findPathForMaven(final String group, final String artifact, final String extension, final String classifier, final String version) {
|
||||
return findLibsPath().resolve(MavenCoordinateResolver.get(group, artifact, extension, classifier, version));
|
||||
}
|
||||
public static Path findPathForMaven(final String maven) {
|
||||
return findLibsPath().resolve(MavenCoordinateResolver.get(maven));
|
||||
}
|
||||
}
|
||||
+14
-3
@@ -7,10 +7,11 @@ package net.minecraftforge.fml.loading;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import cpw.mods.jarhandling.SecureJar;
|
||||
import cpw.mods.modlauncher.Launcher;
|
||||
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
|
||||
import cpw.mods.modlauncher.api.NamedPath;
|
||||
import cpw.mods.modlauncher.serviceapi.ITransformerDiscoveryService;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -19,10 +20,9 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.zip.ZipFile;
|
||||
import java.util.Set;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public class ModDirTransformerDiscoverer implements ITransformerDiscoveryService {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static final Set<String> SERVICES = Set.of(
|
||||
@@ -79,4 +79,15 @@ public class ModDirTransformerDiscoverer implements ITransformerDiscoveryService
|
||||
.filter(SERVICES::contains)
|
||||
.forEach(s -> found.add(new NamedPath(s, path)));
|
||||
}
|
||||
|
||||
public static boolean isServiceProvider(Path path) {
|
||||
var jar = SecureJar.from(path);
|
||||
|
||||
for (var providers : jar.moduleDataProvider().descriptor().provides()) {
|
||||
if (SERVICES.contains(providers.service()))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLStreamHandler;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
|
||||
|
||||
public class ModJarURLHandler extends URLStreamHandler
|
||||
{
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
// modjar://modid/path/to/file
|
||||
@Override
|
||||
protected URLConnection openConnection(URL url) {
|
||||
return new ModJarURLConnection(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int hashCode(final URL u) {
|
||||
return Objects.hash(u.getHost(), u.getFile());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean equals(final URL u1, final URL u2) {
|
||||
return Objects.equals(u1.getProtocol(), u2.getProtocol())
|
||||
&& Objects.equals(u1.getHost(), u2.getHost())
|
||||
&& Objects.equals(u1.getFile(), u2.getFile());
|
||||
}
|
||||
|
||||
static class ModJarURLConnection extends URLConnection {
|
||||
private Path resource;
|
||||
private String modpath;
|
||||
private String modid;
|
||||
private Optional<Manifest> manifest;
|
||||
|
||||
public ModJarURLConnection(final URL url) {
|
||||
super(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect()
|
||||
{
|
||||
// if (resource == null) {
|
||||
// modid = url.getHost();
|
||||
// // trim first char
|
||||
// modpath = url.getPath().substring(1);
|
||||
// resource = FMLLoader.getLoadingModList().getModFileById(modid).getFile().findResource(modpath);
|
||||
// manifest = FMLLoader.getLoadingModList().getModFileById(modid).getManifest();
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException
|
||||
{
|
||||
connect();
|
||||
LOGGER.trace(CORE, "Loading modjar URL {} got resource {} {}", url, resource, resource != null ? Files.exists(resource) : "missing");
|
||||
return Files.newInputStream(resource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getContentLengthLong() {
|
||||
try {
|
||||
connect();
|
||||
return Files.size(resource);
|
||||
} catch (IOException e) {
|
||||
return -1L;
|
||||
}
|
||||
}
|
||||
|
||||
// Used to cache protectiondomains by "top level object" aka the modid
|
||||
@Override
|
||||
public URL getURL() {
|
||||
return LamdbaExceptionUtils.uncheck(()->new URL("modjar://"+modid));
|
||||
}
|
||||
|
||||
public Optional<Manifest> getManifest() {
|
||||
return manifest;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,9 +8,6 @@ package net.minecraftforge.fml.loading;
|
||||
import com.google.common.graph.GraphBuilder;
|
||||
import com.google.common.graph.MutableGraph;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import cpw.mods.jarhandling.SecureJar;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator;
|
||||
import net.minecraftforge.forgespi.language.IModFileInfo;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import net.minecraftforge.fml.loading.EarlyLoadingException.ExceptionData;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
|
||||
@@ -22,10 +19,14 @@ import org.apache.maven.artifact.versioning.ArtifactVersion;
|
||||
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Function;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -53,6 +54,9 @@ public class ModSorter
|
||||
ms.buildUniqueList();
|
||||
} catch (EarlyLoadingException e) {
|
||||
// We cannot build any list with duped mods. We have to abort immediately and report it
|
||||
// Note this will never actually throw an error because the duplicate checks are done in ModDiscovererer before we get to this phase
|
||||
// So all this is really doing is wasting time.
|
||||
// But i'm leaving it here until I rewrite all of cpw's mod loading code because its such a clusterfuck.
|
||||
return LoadingModList.of(ms.systemMods, ms.systemMods.stream().map(mf->(ModInfo)mf.getModInfos().get(0)).collect(toList()), e);
|
||||
}
|
||||
// try and validate dependencies
|
||||
@@ -72,7 +76,6 @@ public class ModSorter
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
private void sort()
|
||||
{
|
||||
// lambdas are identity based, so sorting them is impossible unless you hold reference to them
|
||||
@@ -121,7 +124,6 @@ public class ModSorter
|
||||
.collect(toList());
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
private void addDependency(MutableGraph<ModFileInfo> topoGraph, IModInfo.ModVersion dep)
|
||||
{
|
||||
final ModFileInfo self = (ModFileInfo)dep.getOwner().getOwningFile();
|
||||
@@ -133,6 +135,7 @@ public class ModSorter
|
||||
switch (dep.getOrdering()) {
|
||||
case BEFORE -> topoGraph.putEdge(self, target);
|
||||
case AFTER -> topoGraph.putEdge(target, self);
|
||||
default -> {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,27 +155,13 @@ public class ModSorter
|
||||
));
|
||||
}
|
||||
|
||||
private void detectSystemMods(final Map<String, List<ModFile>> modFilesByFirstId)
|
||||
{
|
||||
private void detectSystemMods(final Map<String, List<ModFile>> modFilesByFirstId) {
|
||||
// Capture system mods (ex. MC, Forge) here, so we can keep them for later
|
||||
final Set<String> systemMods = new HashSet<>();
|
||||
// The minecraft mod is always a system mod
|
||||
systemMods.add("minecraft");
|
||||
// Find mod file from MinecraftLocator to define the system mods
|
||||
modFiles.stream()
|
||||
.filter(modFile -> modFile.getProvider().getClass() == MinecraftLocator.class)
|
||||
.map(ModFile::getSecureJar)
|
||||
.map(SecureJar::moduleDataProvider)
|
||||
.map(SecureJar.ModuleDataProvider::getManifest)
|
||||
.map(Manifest::getMainAttributes)
|
||||
.map(mf -> mf.getValue("FML-System-Mods"))
|
||||
.filter(Objects::nonNull)
|
||||
.findFirst()
|
||||
.ifPresent(value -> systemMods.addAll(Arrays.asList(value.split(","))));
|
||||
var systemMods = List.of("minecraft", "forge");
|
||||
LOGGER.debug("Configured system mods: {}", systemMods);
|
||||
|
||||
this.systemMods = new ArrayList<>();
|
||||
for (String systemMod : systemMods) {
|
||||
for (var systemMod : systemMods) {
|
||||
var container = modFilesByFirstId.get(systemMod);
|
||||
if (container != null && !container.isEmpty()) {
|
||||
LOGGER.debug("Found system mod: {}", systemMod);
|
||||
|
||||
@@ -147,7 +147,7 @@ public class RuntimeDistCleaner implements ILaunchPluginService
|
||||
return ret;
|
||||
}
|
||||
|
||||
private boolean remove(final List<AnnotationNode> anns, final String side)
|
||||
private static boolean remove(final List<AnnotationNode> anns, final String side)
|
||||
{
|
||||
return unpack(anns).stream().
|
||||
filter(ann->Objects.equals(ann.desc, ONLYIN)).
|
||||
@@ -169,9 +169,15 @@ public class RuntimeDistCleaner implements ILaunchPluginService
|
||||
private static final EnumSet<Phase> NAY = EnumSet.noneOf(Phase.class);
|
||||
|
||||
@Override
|
||||
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty)
|
||||
{
|
||||
return isEmpty ? NAY : YAY;
|
||||
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty) {
|
||||
if (isEmpty)
|
||||
return NAY;
|
||||
|
||||
String internalName = classType.getInternalName();
|
||||
if (internalName.startsWith("net/minecraftforge/"))
|
||||
return NAY;
|
||||
|
||||
return YAY;
|
||||
}
|
||||
|
||||
private static class LambdaGatherer extends MethodVisitor {
|
||||
|
||||
@@ -5,42 +5,37 @@
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
|
||||
import org.apache.commons.lang3.text.StrLookup;
|
||||
import org.apache.commons.lang3.text.StrSubstitutor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class StringSubstitutor
|
||||
{
|
||||
private static final Map<String,String> globals = ImmutableMap.of(
|
||||
"mcVersion", FMLLoader.versionInfo().mcVersion(),
|
||||
"forgeVersion", FMLLoader.versionInfo().forgeVersion()
|
||||
);
|
||||
import org.apache.commons.lang3.text.StrLookup;
|
||||
import org.apache.commons.lang3.text.StrSubstitutor;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class StringSubstitutor {
|
||||
public static String replace(final String in, final ModFile file) {
|
||||
return new StrSubstitutor(getStringLookup(file)).replace(in);
|
||||
}
|
||||
|
||||
private static StrLookup<String> getStringLookup(final ModFile file) {
|
||||
return new StrLookup<String>()
|
||||
{
|
||||
var globals = Map.of(
|
||||
"mcVersion", FMLLoader.versionInfo().mcVersion(),
|
||||
"forgeVersion", FMLLoader.versionInfo().forgeVersion()
|
||||
);
|
||||
return new StrLookup<String>() {
|
||||
@Override
|
||||
public String lookup(String key)
|
||||
{
|
||||
final String[] parts = key.split("\\.");
|
||||
if (parts.length == 1) return key;
|
||||
final String pfx = parts[0];
|
||||
public String lookup(String key) {
|
||||
var parts = key.split("\\.");
|
||||
if (parts.length == 1)
|
||||
return key;
|
||||
var pfx = parts[0];
|
||||
|
||||
if ("global".equals(pfx))
|
||||
{
|
||||
return globals.get(parts[1]);
|
||||
}
|
||||
else if ("file".equals(pfx) && file != null)
|
||||
{
|
||||
return String.valueOf(file.getSubstitutionMap().get().get(parts[1]));
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5,18 +5,45 @@
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import java.util.Map;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/** TODO: [FML][Loader] Remove VersionInfo - we shouldn't need to base any decisions on forge or mc version info at the loader level. So once I get a proper replacement i'm gunna kill this. */
|
||||
public record VersionInfo(String forgeVersion, String mcVersion, String mcpVersion, String forgeGroup) {
|
||||
VersionInfo(Map<String, ?> arguments) {
|
||||
this((String) arguments.get("forgeVersion"), (String) arguments.get("mcVersion"), (String) arguments.get("mcpVersion"), (String) arguments.get("forgeGroup"));
|
||||
}
|
||||
|
||||
public String mcAndForgeVersion() {
|
||||
return mcVersion+"-"+forgeVersion;
|
||||
return mcVersion + "-" + forgeVersion;
|
||||
}
|
||||
|
||||
public String mcAndMCPVersion() {
|
||||
return mcVersion + "-" + mcpVersion;
|
||||
}
|
||||
|
||||
/*==========================================================================*
|
||||
* INTERNAL SHIT *
|
||||
*==========================================================================*/
|
||||
private static final Gson GSON = new GsonBuilder().create();
|
||||
private static <T> T readJson(String path, Class<T> type) {
|
||||
try (var is = VersionInfo.class.getResourceAsStream(path)) {
|
||||
if (is == null)
|
||||
return null;
|
||||
|
||||
try (var reader = new InputStreamReader(is)) {
|
||||
return GSON.fromJson(reader, type);
|
||||
}
|
||||
} catch (IOException | JsonSyntaxException e) {
|
||||
throw new IllegalStateException("Failed to parse version info from " + path , e);
|
||||
}
|
||||
}
|
||||
|
||||
static VersionInfo detect() {
|
||||
var forge = readJson("/forge_version.json", JSON.class);
|
||||
if (forge == null)
|
||||
throw new IllegalStateException("Failed to find /forge_version.json This is a critical issue");
|
||||
return new VersionInfo(forge.forge(), forge.mc(), forge.mcp(), "net.minecraftforge");
|
||||
}
|
||||
private record JSON(String forge, String mcp, String mc) {}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ import java.util.function.BiPredicate;
|
||||
public class VersionSupportMatrix {
|
||||
private static final HashMap<String, List<ArtifactVersion>> overrideVersions = new HashMap<>();
|
||||
static {
|
||||
final ArtifactVersion version = new DefaultArtifactVersion(FMLLoader.versionInfo().mcVersion());
|
||||
final ArtifactVersion version = new DefaultArtifactVersion(FMLLoader.versionInfo().mcVersion());
|
||||
if (MavenVersionAdapter.createFromVersionSpec("[1.19.2]").containsVersion(version)) {
|
||||
// 1.19.2 is Compatible with 1.19.1
|
||||
add("languageloader.javafml", "42");
|
||||
@@ -31,7 +31,7 @@ public class VersionSupportMatrix {
|
||||
}
|
||||
public static <T> boolean testVersionSupportMatrix(VersionRange declaredRange, String lookupId, String type, BiPredicate<String, VersionRange> standardLookup) {
|
||||
if (standardLookup.test(lookupId, declaredRange)) return true;
|
||||
List<ArtifactVersion> custom = overrideVersions.get(type +"." +lookupId);
|
||||
List<ArtifactVersion> custom = overrideVersions.get(type + "." + lookupId);
|
||||
return custom == null ? false : custom.stream().anyMatch(declaredRange::containsVersion);
|
||||
}
|
||||
}
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraftforge.forgespi.locating.IDependencyLocator;
|
||||
import net.minecraftforge.forgespi.locating.IModFile;
|
||||
import net.minecraftforge.forgespi.locating.ModFileLoadingException;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
public abstract class AbstractJarFileDependencyLocator extends AbstractJarFileModProvider implements IDependencyLocator
|
||||
{
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
|
||||
protected Optional<InputStream> loadResourceFromModFile(final IModFile modFile, final Path path) {
|
||||
try {
|
||||
return Optional.of(Files.newInputStream(modFile.findResource(path.toString())));
|
||||
}
|
||||
catch (final FileNotFoundException e) {
|
||||
LOGGER.debug("Failed to load resource {} from {}, it does not contain dependency information.", path, modFile.getFileName());
|
||||
return Optional.empty();
|
||||
}
|
||||
catch (final Exception e) {
|
||||
LOGGER.error("Failed to load resource {} from mod {}, cause {}", path, modFile.getFileName(), e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
protected Optional<IModFile> loadModFileFrom(final IModFile file, final Path path) {
|
||||
try {
|
||||
final Path pathInModFile = file.findResource(path.toString());
|
||||
return Optional.of(createMod(pathInModFile).file());
|
||||
}
|
||||
catch (Exception e) {
|
||||
LOGGER.error("Failed to load mod file {} from {}", path, file.getFileName());
|
||||
throw new ModFileLoadingException("Failed to load mod file "+file.getFileName());
|
||||
}
|
||||
}
|
||||
|
||||
protected String identifyMod(final IModFile modFile) {
|
||||
return modFile.getFileName();
|
||||
}
|
||||
}
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import net.minecraftforge.forgespi.locating.IModLocator;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public abstract class AbstractJarFileModLocator extends AbstractJarFileModProvider implements IModLocator
|
||||
{
|
||||
@Override
|
||||
public List<IModLocator.ModFileOrException> scanMods()
|
||||
{
|
||||
return scanCandidates().map(this::createMod).toList();
|
||||
}
|
||||
|
||||
public abstract Stream<Path> scanCandidates();
|
||||
}
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import cpw.mods.jarhandling.SecureJar;
|
||||
import net.minecraftforge.fml.loading.LogMarkers;
|
||||
import net.minecraftforge.forgespi.locating.IModFile;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public abstract class AbstractJarFileModProvider extends AbstractModProvider
|
||||
{
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
|
||||
@Override
|
||||
public void scanFile(final IModFile file, final Consumer<Path> pathConsumer) {
|
||||
LOGGER.debug(LogMarkers.SCAN,"Scan started: {}", file);
|
||||
final Function<Path, SecureJar.Status> status = p->file.getSecureJar().verifyPath(p);
|
||||
try (Stream<Path> files = Files.find(file.getSecureJar().getRootPath(), Integer.MAX_VALUE, (p, a) -> p.getNameCount() > 0 && p.getFileName().toString().endsWith(".class"))) {
|
||||
file.setSecurityStatus(files.peek(pathConsumer).map(status).reduce((s1, s2)-> SecureJar.Status.values()[Math.min(s1.ordinal(), s2.ordinal())]).orElse(SecureJar.Status.INVALID));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
LOGGER.debug(LogMarkers.SCAN,"Scan finished: {}", file);
|
||||
}
|
||||
}
|
||||
+73
-42
@@ -16,54 +16,61 @@ import net.minecraftforge.forgespi.locating.IModFile;
|
||||
import net.minecraftforge.forgespi.locating.IModLocator;
|
||||
import net.minecraftforge.forgespi.locating.IModProvider;
|
||||
import net.minecraftforge.forgespi.locating.ModFileLoadingException;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
public abstract class AbstractModProvider implements IModProvider
|
||||
{
|
||||
@ApiStatus.Internal
|
||||
public abstract class AbstractModProvider implements IModProvider {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
protected static final String MODS_TOML = "META-INF/mods.toml";
|
||||
protected static final String MANIFEST = "META-INF/MANIFEST.MF";
|
||||
|
||||
protected IModLocator.ModFileOrException createMod(Path... path) {
|
||||
protected IModLocator.ModFileOrException createMod(Path path) {
|
||||
return this.createMod(path, getDefaultJarModType());
|
||||
}
|
||||
|
||||
protected IModLocator.ModFileOrException createMod(Path path, String defaultType) {
|
||||
var mjm = new ModJarMetadata();
|
||||
var sj = SecureJar.from(
|
||||
Manifest::new,
|
||||
jar -> jar.moduleDataProvider().findFile(MODS_TOML).isPresent() ? mjm : JarMetadata.from(jar, path),
|
||||
(root, p) -> true,
|
||||
path
|
||||
jar -> jar.moduleDataProvider().findFile(MODS_TOML).isPresent() ? mjm : JarMetadata.from(jar, path),
|
||||
path
|
||||
);
|
||||
|
||||
IModFile mod;
|
||||
var type = sj.moduleDataProvider().getManifest().getMainAttributes().getValue(ModFile.TYPE);
|
||||
if (type == null) {
|
||||
type = getDefaultJarModType();
|
||||
}
|
||||
if (type == null)
|
||||
type = defaultType;
|
||||
|
||||
if (sj.moduleDataProvider().findFile(MODS_TOML).isPresent()) {
|
||||
LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", MODS_TOML, type, path);
|
||||
mod = new ModFile(sj, this, ModFileParser::modsTomlParser);
|
||||
if (ModFileInfo.DETECT_NON_FORGE_MODS_TOML && mod.getModFileInfo().getFileProperties().containsKey(ModFileInfo.NOT_A_FORGE_MOD_PROP)) {
|
||||
LOGGER.error(LogMarkers.SCAN, "Unable to load file \"{}\" because its mods.toml is requesting an invalid javafml loaderVersion (use \"*\" if you want to allow all versions) and is missing a forge modId dependency declaration (see the sample mods.toml in the MDK).", path);
|
||||
return new IModLocator.ModFileOrException(null, new ModFileLoadingException("File \"%s\" is not a Forge mod and cannot be loaded. Look for a Forge version of this mod or consider alternative mods.".formatted(mod.getFileName())));
|
||||
}
|
||||
} else if (type != null) {
|
||||
LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", MANIFEST, type, path);
|
||||
LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", JarFile.MANIFEST_NAME, type, path);
|
||||
mod = new ModFile(sj, this, this::manifestParser, type);
|
||||
} else {
|
||||
return new IModLocator.ModFileOrException(null, new ModFileLoadingException("Invalid mod file found "+ Arrays.toString(path)));
|
||||
}
|
||||
} else
|
||||
return new IModLocator.ModFileOrException(null, new ModFileLoadingException("Invalid mod file found " + path));
|
||||
|
||||
mjm.setModFile(mod);
|
||||
return new IModLocator.ModFileOrException(mod, null);
|
||||
}
|
||||
|
||||
protected IModFileInfo manifestParser(final IModFile mod) {
|
||||
Function<String, Optional<String>> cfg = name -> Optional.ofNullable(mod.getSecureJar().moduleDataProvider().getManifest().getMainAttributes().getValue(name));
|
||||
var license = cfg.apply("LICENSE").orElse("");
|
||||
var mf = mod.getSecureJar().moduleDataProvider().getManifest().getMainAttributes();
|
||||
var license = mf.getValue("LICENSE");
|
||||
var dummy = new IConfigurable() {
|
||||
@Override
|
||||
public <T> Optional<T> getConfigElement(String... key) {
|
||||
@@ -75,7 +82,7 @@ public abstract class AbstractModProvider implements IModProvider
|
||||
}
|
||||
};
|
||||
|
||||
return new DefaultModFileInfo(mod, license, dummy);
|
||||
return new DefaultModFileInfo(mod, license == null ? "" : license, dummy);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -83,41 +90,65 @@ public abstract class AbstractModProvider implements IModProvider
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initArguments(final Map<String, ?> arguments) {
|
||||
}
|
||||
|
||||
protected String getDefaultJarModType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scanFile(IModFile file, Consumer<Path> pathConsumer) {
|
||||
LOGGER.debug(LogMarkers.SCAN, "Scan started: {}", file);
|
||||
var jar = file.getSecureJar();
|
||||
var root = jar.getRootPath();
|
||||
Consumer<Path> consumer = pathConsumer;
|
||||
|
||||
//TODO: [SecureJar] Actually redesign this to have a proper validation.
|
||||
var holder = new Holder<SecureJar.Status>();
|
||||
holder.value = SecureJar.Status.NONE;
|
||||
if (jar.hasSecurityData()) {
|
||||
consumer = path -> {
|
||||
pathConsumer.accept(path);
|
||||
var status = jar.verifyPath(path);
|
||||
if (status.ordinal() < holder.value.ordinal())
|
||||
holder.value = status;
|
||||
};
|
||||
}
|
||||
|
||||
try (var files = Files.walk(root)) {
|
||||
files
|
||||
.filter(p -> p.toString().endsWith(".class"))
|
||||
.forEach(consumer);
|
||||
|
||||
file.setSecurityStatus(holder.value);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
LOGGER.debug(LogMarkers.SCAN, "Scan finished: {}", file);
|
||||
}
|
||||
|
||||
private static final class Holder<T> {
|
||||
T value;
|
||||
}
|
||||
|
||||
private record DefaultModFileInfo(IModFile mod, String license, IConfigurable configurable) implements IModFileInfo, IConfigurable {
|
||||
@Override
|
||||
public <T> Optional<T> getConfigElement(final String... strings)
|
||||
{
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends IConfigurable> getConfigList(final String... strings)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override public <T> Optional<T> getConfigElement(final String... strings) { return Optional.empty(); }
|
||||
@Override public List<? extends IConfigurable> getConfigList(final String... strings) { return null; }
|
||||
@Override public List<IModInfo> getMods() { return Collections.emptyList(); }
|
||||
@Override public List<LanguageSpec> requiredLanguageLoaders() { return Collections.emptyList(); }
|
||||
@Override public boolean showAsResourcePack() { return false; }
|
||||
@Override public boolean showAsDataPack() { return false; }
|
||||
@Override public Map<String, Object> getFileProperties() { return Collections.emptyMap(); }
|
||||
@Override
|
||||
public String getLicense() { return license; }
|
||||
|
||||
@Override public String getLicense() { return license; }
|
||||
@Override public IModFile getFile() { return mod; }
|
||||
@Override public IConfigurable getConfig() { return configurable; }
|
||||
|
||||
// These Should never be called as it's only called from ModJarMetadata.version and we bypass that
|
||||
@Override public String moduleName() { return mod.getSecureJar().name(); }
|
||||
@Override public String versionString() { return null; }
|
||||
@Override public List<String> usesServices() { return null; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "IModFileInfo(" + mod.getFilePath() + ")";
|
||||
}
|
||||
@Override public String toString() { return "IModFileInfo(" + mod.getFilePath() + ")"; }
|
||||
}
|
||||
}
|
||||
-12
@@ -9,13 +9,11 @@ import com.mojang.logging.LogUtils;
|
||||
import net.minecraftforge.fml.loading.ImmediateWindowHandler;
|
||||
import net.minecraftforge.fml.loading.LoadingModList;
|
||||
import net.minecraftforge.fml.loading.LogMarkers;
|
||||
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
|
||||
import net.minecraftforge.forgespi.language.ModFileScanData;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
@@ -32,9 +30,6 @@ public class BackgroundScanHandler
|
||||
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private final ExecutorService modContentScanner;
|
||||
private final List<ModFile> pendingFiles;
|
||||
private final List<ModFile> scannedFiles;
|
||||
private final List<ModFile> allFiles;
|
||||
private final List<ModFile> modFiles;
|
||||
private ScanStatus status;
|
||||
private LoadingModList loadingModList;
|
||||
@@ -46,9 +41,6 @@ public class BackgroundScanHandler
|
||||
thread.setDaemon(true);
|
||||
return thread;
|
||||
});
|
||||
scannedFiles = new ArrayList<>();
|
||||
pendingFiles = new ArrayList<>();
|
||||
allFiles = new ArrayList<>();
|
||||
status = ScanStatus.NOT_STARTED;
|
||||
}
|
||||
|
||||
@@ -63,8 +55,6 @@ public class BackgroundScanHandler
|
||||
}
|
||||
status = ScanStatus.RUNNING;
|
||||
ImmediateWindowHandler.updateProgress("Scanning mod candidates");
|
||||
allFiles.add(file);
|
||||
pendingFiles.add(file);
|
||||
final CompletableFuture<ModFileScanData> future = CompletableFuture.supplyAsync(file::compileContent, modContentScanner)
|
||||
.whenComplete(file::setScanResult)
|
||||
.whenComplete((r,t)-> this.addCompletedFile(file,r,t));
|
||||
@@ -76,8 +66,6 @@ public class BackgroundScanHandler
|
||||
status = ScanStatus.ERRORED;
|
||||
LOGGER.error(LogMarkers.SCAN,"An error occurred scanning file {}", file, throwable);
|
||||
}
|
||||
pendingFiles.remove(file);
|
||||
scannedFiles.add(file);
|
||||
}
|
||||
|
||||
public void setLoadingModList(LoadingModList loadingModList)
|
||||
|
||||
+77
-54
@@ -5,90 +5,113 @@
|
||||
|
||||
package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import net.minecraftforge.fml.loading.ClasspathLocatorUtils;
|
||||
import com.mojang.logging.LogUtils;
|
||||
|
||||
import net.minecraftforge.fml.loading.LogMarkers;
|
||||
import net.minecraftforge.fml.loading.ModDirTransformerDiscoverer;
|
||||
import net.minecraftforge.forgespi.locating.IModLocator;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
public class ClasspathLocator extends AbstractJarFileModLocator
|
||||
{
|
||||
@ApiStatus.Internal
|
||||
public class ClasspathLocator extends AbstractModProvider implements IModLocator {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private final List<Path> legacyClasspath = Arrays.stream(System.getProperty("legacyClassPath", "").split(File.pathSeparator)).map(Path::of).toList();
|
||||
private boolean enabled = false;
|
||||
private static final Attributes.Name MOD_TYPE = new Attributes.Name("FMLModType");
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "userdev classpath";
|
||||
return "classpath";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Path> scanCandidates() {
|
||||
if (!enabled)
|
||||
return Stream.of();
|
||||
public List<ModFileOrException> scanMods() {
|
||||
//var cl = ClassLoader.getSystemClassLoader();
|
||||
var cl = getClass().getClassLoader();
|
||||
|
||||
try {
|
||||
var claimed = new ArrayList<>(legacyClasspath);
|
||||
var paths = Stream.<Path>builder();
|
||||
// Find 'minecraft' during dev time this is also Forge. So skip it.
|
||||
// Can return null for dedicated server as Minecraft isn't on the classpath
|
||||
// On the client it doesn't matter as it's the vanilla jar so wouldnt have mods.toml in it.
|
||||
var minecraft = getPathFromResource(cl, "net/minecraft/obfuscate/DontObfuscate.class");
|
||||
|
||||
findPaths(claimed, MODS_TOML).forEach(paths::add);
|
||||
findPaths(claimed, MANIFEST).forEach(paths::add);
|
||||
|
||||
return paths.build();
|
||||
} catch (IOException e) {
|
||||
LOGGER.error(LogMarkers.SCAN, "Error trying to find resources", e);
|
||||
throw new RuntimeException(e);
|
||||
var claimed = new HashSet<Path>();
|
||||
var tomls = getUrls(cl, MODS_TOML);
|
||||
for (var url : tomls) {
|
||||
var path = getPath(url, MODS_TOML);
|
||||
if (!path.equals(minecraft))
|
||||
claimed.add(path);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Path> findPaths(List<Path> claimed, String resource) throws IOException {
|
||||
var ret = new ArrayList<Path>();
|
||||
final Enumeration<URL> resources = ClassLoader.getSystemClassLoader().getResources(resource);
|
||||
while (resources.hasMoreElements()) {
|
||||
URL url = resources.nextElement();
|
||||
Path path = ClasspathLocatorUtils.findJarPathFor(resource, resource, url);
|
||||
if (claimed.stream().anyMatch(path::equals) || !Files.exists(path) || Files.isDirectory(path))
|
||||
var manifests = getUrls(cl, JarFile.MANIFEST_NAME);
|
||||
for (var url : manifests) {
|
||||
var path = getPath(url, JarFile.MANIFEST_NAME);
|
||||
if (claimed.contains(path))
|
||||
continue;
|
||||
ret.add(path);
|
||||
|
||||
try (var is = url.openStream()) {
|
||||
var mf = new Manifest(is);
|
||||
var type = mf.getMainAttributes().getValue(MOD_TYPE);
|
||||
if (type != null)
|
||||
claimed.add(path);
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn(LogMarkers.SCAN, "Error reading manifest from: " + url, e);
|
||||
}
|
||||
}
|
||||
|
||||
var ret = new ArrayList<ModFileOrException>();
|
||||
for (var path : claimed) {
|
||||
// Filter out anything found by the ServiceLoader
|
||||
if (!ModDirTransformerDiscoverer.isServiceProvider(path))
|
||||
ret.add(createMod(path));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initArguments(Map<String, ?> arguments) {
|
||||
var launchTarget = (String) arguments.get("launchTarget");
|
||||
enabled = launchTarget != null && launchTarget.contains("dev");
|
||||
}
|
||||
|
||||
private Path findJarPathFor(final String resourceName, final String jarName, final URL resource) {
|
||||
private List<URL> getUrls(ClassLoader cl, String resource) {
|
||||
try {
|
||||
Path path;
|
||||
final URI uri = resource.toURI();
|
||||
if (uri.getScheme().equals("jar") && uri.getRawSchemeSpecificPart().contains("!/")) {
|
||||
int lastExcl = uri.getRawSchemeSpecificPart().lastIndexOf("!/");
|
||||
path = Paths.get(new URI(uri.getRawSchemeSpecificPart().substring(0, lastExcl)));
|
||||
} else {
|
||||
path = Paths.get(new URI("file://"+uri.getRawSchemeSpecificPart().substring(0, uri.getRawSchemeSpecificPart().length()-resourceName.length())));
|
||||
var lst = Collections.list(cl.getResources(resource));
|
||||
if (LOGGER.isDebugEnabled(LogMarkers.SCAN)) {
|
||||
Collections.sort(lst, (a, b) -> a.toString().compareTo(b.toString()));
|
||||
LOGGER.debug(LogMarkers.SCAN, "Scanning Classloader: {} for {}", cl, resource);
|
||||
for (var url : lst)
|
||||
LOGGER.debug(LogMarkers.SCAN, "\t{}", url);
|
||||
}
|
||||
//LOGGER.debug(CORE, "Found JAR {} at path {}", jarName, path.toString());
|
||||
return path;
|
||||
} catch (NullPointerException | URISyntaxException e) {
|
||||
LOGGER.error(LogMarkers.SCAN, "Failed to find JAR for class {} - {}", resourceName, jarName);
|
||||
throw new RuntimeException("Unable to locate "+resourceName+" - "+jarName, e);
|
||||
return lst;
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn(LogMarkers.SCAN, "Error finding resources for: " + resource, e);
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
private static Path getPath(URL url, String resource) {
|
||||
var str = url.toString();
|
||||
int len = resource.length();
|
||||
if ("jar".equalsIgnoreCase(url.getProtocol())) {
|
||||
str = url.getFile();
|
||||
len += 2;
|
||||
}
|
||||
str = str.substring(0, str.length() - len);
|
||||
var path = Paths.get(URI.create(str));
|
||||
return path;
|
||||
}
|
||||
|
||||
private static Path getPathFromResource(ClassLoader cl, String resource) {
|
||||
var url = cl.getResource(resource);
|
||||
if (url == null)
|
||||
return null;
|
||||
return getPath(url, resource);
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,9 @@ import java.io.Reader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public class CoreModFile implements ICoreModFile {
|
||||
private final Path internalPath;
|
||||
private final ModFile file;
|
||||
|
||||
-78
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraftforge.fml.loading.LogMarkers;
|
||||
import net.minecraftforge.forgespi.locating.IModFile;
|
||||
import net.minecraftforge.forgespi.locating.IModLocator;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class ExplodedDirectoryLocator implements IModLocator {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
|
||||
public record ExplodedMod(String modid, List<Path> paths) {}
|
||||
|
||||
private final List<ExplodedMod> explodedMods = new ArrayList<>();
|
||||
private final Map<ExplodedMod, IModFile> mods = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public List<IModLocator.ModFileOrException> scanMods() {
|
||||
explodedMods.forEach(explodedMod ->
|
||||
ModJarMetadata.buildFile(this,
|
||||
jar->jar.moduleDataProvider().findFile("/META-INF/mods.toml").isPresent(),
|
||||
(a,b) -> true,
|
||||
explodedMod.paths().toArray(Path[]::new))
|
||||
.ifPresentOrElse(f->mods.put(explodedMod, f), () -> LOGGER.warn(LogMarkers.LOADING, "Failed to find exploded resource mods.toml in directory {}", explodedMod.paths().get(0).toString())));
|
||||
return mods.values().stream().map(mf->new IModLocator.ModFileOrException(mf, null)).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "exploded directory";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scanFile(final IModFile file, final Consumer<Path> pathConsumer) {
|
||||
LOGGER.debug(LogMarkers.SCAN,"Scanning exploded directory {}", file.getFilePath().toString());
|
||||
try (Stream<Path> files = Files.find(file.getSecureJar().getRootPath(), Integer.MAX_VALUE, (p, a) -> p.getNameCount() > 0 && p.getFileName().toString().endsWith(".class"))) {
|
||||
files.forEach(pathConsumer);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
LOGGER.debug(LogMarkers.SCAN,"Exploded directory scan complete {}", file.getFilePath().toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "{ExplodedDir locator}";
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void initArguments(final Map<String, ?> arguments) {
|
||||
final var explodedTargets = ((Map<String, List<ExplodedMod>>) arguments).get("explodedTargets");
|
||||
if (explodedTargets != null && !explodedTargets.isEmpty()) {
|
||||
explodedMods.addAll(explodedTargets);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(final IModFile modFile) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+3
-4
@@ -11,12 +11,11 @@ import net.minecraftforge.forgespi.locating.ModFileLoadingException;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
|
||||
public class InvalidModFileException extends ModFileLoadingException
|
||||
{
|
||||
public class InvalidModFileException extends ModFileLoadingException {
|
||||
private static final long serialVersionUID = 1230464325917450374L;
|
||||
private final IModFileInfo modFileInfo;
|
||||
|
||||
public InvalidModFileException(String message, IModFileInfo modFileInfo)
|
||||
{
|
||||
public InvalidModFileException(String message, IModFileInfo modFileInfo) {
|
||||
super(String.format(Locale.ROOT, "%s (%s)", message, Optional.ofNullable(modFileInfo).map(mf->mf.getFile().getFileName()).orElse("MISSING FILE NAME")));
|
||||
this.modFileInfo = modFileInfo;
|
||||
}
|
||||
|
||||
+5
-3
@@ -12,10 +12,12 @@ import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import static cpw.mods.modlauncher.api.LamdbaExceptionUtils.*;
|
||||
|
||||
// TODO: [FML][Loader] By the time this is reached, most stuff this checks for is already filtered out - needs fixing.
|
||||
public enum InvalidModIdentifier {
|
||||
|
||||
OLDFORGE(filePresent("mcmod.info")),
|
||||
@@ -23,9 +25,9 @@ public enum InvalidModIdentifier {
|
||||
LITELOADER(filePresent("litemod.json")),
|
||||
OPTIFINE(filePresent("optifine/Installer.class")),
|
||||
BUKKIT(filePresent("plugin.yml")),
|
||||
INVALIDZIP((f,zf) -> !zf.isPresent());
|
||||
INVALIDZIP((f,zf) -> zf.isEmpty()); // note: only this one INVALIDZIP check is ran until the todo on this class is fixed
|
||||
|
||||
private BiPredicate<Path, Optional<ZipFile>> ident;
|
||||
private final BiPredicate<Path, Optional<ZipFile>> ident;
|
||||
|
||||
InvalidModIdentifier(BiPredicate<Path, Optional<ZipFile>> identifier)
|
||||
{
|
||||
@@ -40,7 +42,7 @@ public enum InvalidModIdentifier {
|
||||
public static Optional<String> identifyJarProblem(Path path)
|
||||
{
|
||||
Optional<ZipFile> zfo = optionalFromException(() -> new ZipFile(path.toFile()));
|
||||
Optional<String> result = Arrays.stream(values()).
|
||||
Optional<String> result = Stream.of(INVALIDZIP).
|
||||
filter(i -> i.ident.test(path, zfo)).
|
||||
map(InvalidModIdentifier::getReason).
|
||||
findAny();
|
||||
|
||||
+68
-53
@@ -10,17 +10,21 @@ import com.google.common.collect.Lists;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraftforge.fml.loading.EarlyLoadingException;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import net.minecraftforge.forgespi.locating.IDependencyLocator;
|
||||
import net.minecraftforge.forgespi.locating.IModFile;
|
||||
import net.minecraftforge.forgespi.locating.ModFileLoadingException;
|
||||
import net.minecraftforge.jarjar.selection.JarSelector;
|
||||
import org.apache.maven.artifact.versioning.ArtifactVersion;
|
||||
import org.apache.maven.artifact.versioning.VersionRange;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -30,26 +34,35 @@ import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class JarInJarDependencyLocator extends AbstractJarFileDependencyLocator
|
||||
{
|
||||
@ApiStatus.Internal
|
||||
public class JarInJarDependencyLocator extends AbstractModProvider implements IDependencyLocator {
|
||||
private static final String COLOR_CODE = "\u00a7";
|
||||
private static final String RESET = COLOR_CODE + "r";
|
||||
private static final String YELLOW = COLOR_CODE + "e";
|
||||
private static final String RED = COLOR_CODE + "4";
|
||||
private static final String GREEN = COLOR_CODE + "2";
|
||||
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
|
||||
@Override
|
||||
public String name()
|
||||
{
|
||||
public String name() {
|
||||
return "JarInJar";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IModFile> scanMods(final Iterable<IModFile> loadedMods)
|
||||
{
|
||||
public List<IModFile> scanMods(Iterable<IModFile> loadedMods) {
|
||||
final List<IModFile> sources = Lists.newArrayList();
|
||||
loadedMods.forEach(sources::add);
|
||||
|
||||
final List<IModFile> dependenciesToLoad = JarSelector.detectAndSelect(sources, this::loadResourceFromModFile, this::loadModFileFrom, this::identifyMod, this::exception);
|
||||
var dependenciesToLoad = JarSelector.detectAndSelect(
|
||||
sources,
|
||||
this::loadResourceFromModFile,
|
||||
this::loadModFileFrom,
|
||||
this::identifyMod,
|
||||
this::exception
|
||||
);
|
||||
|
||||
if (dependenciesToLoad.isEmpty())
|
||||
{
|
||||
if (dependenciesToLoad.isEmpty()) {
|
||||
LOGGER.info("No dependencies to load found. Skipping!");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
@@ -59,43 +72,34 @@ public class JarInJarDependencyLocator extends AbstractJarFileDependencyLocator
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initArguments(final Map<String, ?> arguments)
|
||||
{
|
||||
// NO-OP, for now
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDefaultJarModType()
|
||||
{
|
||||
protected String getDefaultJarModType() {
|
||||
return IModFile.Type.GAMELIBRARY.name();
|
||||
}
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
@Override
|
||||
protected Optional<IModFile> loadModFileFrom(final IModFile file, final Path path)
|
||||
{
|
||||
try
|
||||
{
|
||||
protected Optional<IModFile> loadModFileFrom(IModFile file, Path path) {
|
||||
try {
|
||||
final Path pathInModFile = file.findResource(path.toString());
|
||||
final URI filePathUri = new URI("jij:" + (pathInModFile.toAbsolutePath().toUri().getRawSchemeSpecificPart())).normalize();
|
||||
final Map<String, ?> outerFsArgs = ImmutableMap.of("packagePath", pathInModFile);
|
||||
final FileSystem zipFS = FileSystems.newFileSystem(filePathUri, outerFsArgs);
|
||||
final Path pathInFS = zipFS.getPath("/");
|
||||
return Optional.of(createMod(pathInFS).file());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
final IModFile.Type parentType = file.getType();
|
||||
final String modType;
|
||||
if (parentType == IModFile.Type.LIBRARY || parentType == IModFile.Type.LANGPROVIDER) {
|
||||
modType = IModFile.Type.LIBRARY.name();
|
||||
} else {
|
||||
modType = IModFile.Type.GAMELIBRARY.name();
|
||||
}
|
||||
return Optional.of(createMod(pathInFS, modType).file());
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to load mod file {} from {}", path, file.getFileName());
|
||||
final RuntimeException exception = new ModFileLoadingException("Failed to load mod file " + file.getFileName());
|
||||
exception.initCause(e);
|
||||
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
protected EarlyLoadingException exception(Collection<JarSelector.ResolutionFailureInformation<IModFile>> failedDependencies)
|
||||
{
|
||||
|
||||
protected EarlyLoadingException exception(Collection<JarSelector.ResolutionFailureInformation<IModFile>> failedDependencies) {
|
||||
final List<EarlyLoadingException.ExceptionData> errors = failedDependencies.stream()
|
||||
.filter(entry -> !entry.sources().isEmpty()) //Should never be the case, but just to be sure
|
||||
.map(this::buildExceptionData)
|
||||
@@ -105,8 +109,7 @@ public class JarInJarDependencyLocator extends AbstractJarFileDependencyLocator
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private EarlyLoadingException.ExceptionData buildExceptionData(final JarSelector.ResolutionFailureInformation<IModFile> entry)
|
||||
{
|
||||
private EarlyLoadingException.ExceptionData buildExceptionData(JarSelector.ResolutionFailureInformation<IModFile> entry) {
|
||||
return new EarlyLoadingException.ExceptionData(
|
||||
getErrorTranslationKey(entry),
|
||||
entry.identifier().group() + ":" + entry.identifier().artifact(),
|
||||
@@ -119,16 +122,14 @@ public class JarInJarDependencyLocator extends AbstractJarFileDependencyLocator
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String getErrorTranslationKey(final JarSelector.ResolutionFailureInformation<IModFile> entry)
|
||||
{
|
||||
private String getErrorTranslationKey(JarSelector.ResolutionFailureInformation<IModFile> entry) {
|
||||
return entry.failureReason() == JarSelector.FailureReason.VERSION_RESOLUTION_FAILED ?
|
||||
"fml.dependencyloading.conflictingdependencies" :
|
||||
"fml.dependencyloading.mismatchedcontaineddependencies";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Stream<ModWithVersionRange> getModWithVersionRangeStream(final JarSelector.SourceWithRequestedVersionRange<IModFile> file)
|
||||
{
|
||||
private Stream<ModWithVersionRange> getModWithVersionRangeStream(JarSelector.SourceWithRequestedVersionRange<IModFile> file) {
|
||||
return file.sources()
|
||||
.stream()
|
||||
.map(IModFile::getModFileInfo)
|
||||
@@ -136,23 +137,37 @@ public class JarInJarDependencyLocator extends AbstractJarFileDependencyLocator
|
||||
.map(modInfo -> new ModWithVersionRange(modInfo, file.requestedVersionRange(), file.includedVersion()));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String formatError(final ModWithVersionRange modWithVersionRange)
|
||||
{
|
||||
return "\u00a7e" + modWithVersionRange.modInfo().getModId() + "\u00a7r - \u00a74" + modWithVersionRange.versionRange().toString() + "\u00a74 - \u00a72" + modWithVersionRange.artifactVersion().toString() + "\u00a72";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String identifyMod(final IModFile modFile)
|
||||
{
|
||||
if (modFile.getModFileInfo() == null || modFile.getModInfos().isEmpty())
|
||||
{
|
||||
return modFile.getFileName();
|
||||
protected Optional<InputStream> loadResourceFromModFile(IModFile modFile, Path path) {
|
||||
try {
|
||||
var pathInModFile = modFile.findResource(path.toString());
|
||||
if (!Files.exists(pathInModFile)) {
|
||||
LOGGER.debug("Failed to load resource {} from {}, it does not contain dependency information.", path, modFile.getFileName());
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(Files.newInputStream(pathInModFile));
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to load resource {} from mod {}, cause {}", path, modFile.getFileName(), e);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return modFile.getModInfos().stream().map(IModInfo::getModId).collect(Collectors.joining());
|
||||
}
|
||||
|
||||
private record ModWithVersionRange(IModInfo modInfo, VersionRange versionRange, ArtifactVersion artifactVersion)
|
||||
{}
|
||||
@NotNull
|
||||
private String formatError(ModWithVersionRange modWithVersionRange){
|
||||
return YELLOW + modWithVersionRange.modInfo().getModId() + RESET + " - " +
|
||||
RED + modWithVersionRange.versionRange().toString() + RESET + " - " +
|
||||
GREEN + modWithVersionRange.artifactVersion().toString() + RESET;
|
||||
}
|
||||
|
||||
protected String identifyMod(IModFile modFile) {
|
||||
if (modFile.getModFileInfo() != null && !modFile.getModInfos().isEmpty())
|
||||
return modFile.getModInfos().stream().map(IModInfo::getModId).collect(Collectors.joining());
|
||||
|
||||
var module = modFile.getSecureJar().moduleDataProvider().name();
|
||||
if (module != null && !module.isEmpty())
|
||||
return module;
|
||||
|
||||
return modFile.getFileName();
|
||||
}
|
||||
|
||||
private record ModWithVersionRange(IModInfo modInfo, VersionRange versionRange, ArtifactVersion artifactVersion) {}
|
||||
}
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import net.minecraftforge.fml.loading.FMLPaths;
|
||||
import net.minecraftforge.fml.loading.MavenCoordinateResolver;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class MavenDirectoryLocator extends AbstractJarFileModLocator
|
||||
{
|
||||
private List<Path> modCoords;
|
||||
|
||||
@Override
|
||||
public Stream<Path> scanCandidates() {
|
||||
return modCoords.stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "maven libs";
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "{Maven Directory locator for mods "+this.modCoords+"}";
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void initArguments(final Map<String, ?> arguments) {
|
||||
final List<String> mavenRoots = (List<String>) arguments.get("mavenRoots");
|
||||
final List<Path> mavenRootPaths = mavenRoots.stream().map(n -> FMLPaths.GAMEDIR.get().resolve(n)).collect(Collectors.toList());
|
||||
final List<String> mods = (List<String>) arguments.get("mods");
|
||||
final List<String> listedMods = ModListHandler.processModLists((List<String>) arguments.get("modLists"), mavenRootPaths);
|
||||
|
||||
List<Path> localModCoords = Stream.concat(mods.stream(),listedMods.stream()).map(MavenCoordinateResolver::get).collect(Collectors.toList());
|
||||
// find the modCoords path in each supplied maven path, and turn it into a mod file. (skips not found files)
|
||||
|
||||
this.modCoords = localModCoords.stream().map(mc -> mavenRootPaths.stream().map(root -> root.resolve(mc)).filter(path -> Files.exists(path)).findFirst().orElseThrow(() -> new IllegalArgumentException("Failed to locate requested mod coordinate " + mc))).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
+16
-76
@@ -6,62 +6,36 @@
|
||||
package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import com.electronwill.nightconfig.core.Config;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import cpw.mods.jarhandling.SecureJar;
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
import net.minecraftforge.fml.loading.LogMarkers;
|
||||
import net.minecraftforge.forgespi.language.IModFileInfo;
|
||||
import net.minecraftforge.forgespi.locating.IModFile;
|
||||
import net.minecraftforge.forgespi.locating.IModLocator;
|
||||
import net.minecraftforge.forgespi.locating.ModFileFactory;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class MinecraftLocator extends AbstractModProvider implements IModLocator
|
||||
{
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public class MinecraftLocator extends AbstractModProvider implements IModLocator {
|
||||
@Override
|
||||
public List<IModLocator.ModFileOrException> scanMods() {
|
||||
final var launchHandler = FMLLoader.getLaunchHandler();
|
||||
var baseMC = launchHandler.getMinecraftPaths();
|
||||
var mcjar = ModJarMetadata.buildFile(j->ModFileFactory.FACTORY.build(j, this, this::buildMinecraftTOML), j->true, baseMC.minecraftFilter(), baseMC.minecraftPaths().toArray(Path[]::new)).orElseThrow();
|
||||
var artifacts = baseMC.otherArtifacts().stream()
|
||||
.map(SecureJar::from)
|
||||
.map(sj -> new ModFile(sj, this, ModFileParser::modsTomlParser))
|
||||
.collect(Collectors.<IModFile>toList());
|
||||
var othermods = baseMC.otherModPaths().stream()
|
||||
.map(p -> createMod(p.toArray(Path[]::new)))
|
||||
.filter(Objects::nonNull);
|
||||
artifacts.add(mcjar);
|
||||
var minecraft = FMLLoader.getLaunchHandler().getMinecraftPaths();
|
||||
|
||||
return Stream.concat(artifacts.stream().map(f -> new ModFileOrException(f, null)), othermods).toList();
|
||||
// Minecraft itself.
|
||||
var meta = new ModJarMetadata();
|
||||
var mcjar = SecureJar.from(
|
||||
jar -> meta,
|
||||
minecraft.toArray(Path[]::new)
|
||||
);
|
||||
var mc = ModFileFactory.FACTORY.build(mcjar, this, this::buildMinecraftTOML);
|
||||
meta.setModFile(mc);
|
||||
|
||||
return List.of(new ModFileOrException(mc, null));
|
||||
}
|
||||
|
||||
private IModFileInfo buildMinecraftTOML(final IModFile iModFile) {
|
||||
final ModFile modFile = (ModFile) iModFile;
|
||||
/*
|
||||
final Path mcmodtoml = modFile.findResource("META-INF", "minecraftmod.toml");
|
||||
if (Files.notExists(mcmodtoml)) {
|
||||
LOGGER.fatal(LOADING, "Mod file {} is missing minecraftmod.toml file", modFile.getFilePath());
|
||||
return null;
|
||||
}
|
||||
|
||||
final FileConfig mcmodstomlfile = FileConfig.builder(mcmodtoml).build();
|
||||
mcmodstomlfile.load();
|
||||
mcmodstomlfile.close();
|
||||
*/
|
||||
|
||||
|
||||
// We haven't changed this in years, and I can't be asked right now to special case this one file in the path.
|
||||
final var conf = Config.inMemory();
|
||||
conf.set("modLoader", "minecraft");
|
||||
@@ -76,47 +50,13 @@ public class MinecraftLocator extends AbstractModProvider implements IModLocator
|
||||
mods.set("authors", "MCP: Searge,ProfMobius,IngisKahn,Fesh0r,ZeuX,R4wk,LexManos,Bspkrs");
|
||||
mods.set("description", "Minecraft, decompiled and deobfuscated with MCP technology");
|
||||
conf.set("mods", List.of(mods));
|
||||
/*
|
||||
conf.putAll(mcmodstomlfile);
|
||||
|
||||
final var extralangs = Stream.<IModFileInfo.LanguageSpec>builder();
|
||||
final Path forgemodtoml = modFile.findResource("META-INF", "mods.toml");
|
||||
if (Files.notExists(forgemodtoml)) {
|
||||
LOGGER.info("No forge mods.toml file found, not loading forge mod");
|
||||
} else {
|
||||
final FileConfig forgemodstomlfile = FileConfig.builder(forgemodtoml).build();
|
||||
forgemodstomlfile.load();
|
||||
forgemodstomlfile.close();
|
||||
conf.putAll(forgemodstomlfile);
|
||||
conf.<List<Object>>get("mods").add(0, mcmodstomlfile.<List<Object>>get("mods").get(0)); // Add MC as a sub-mod
|
||||
extralangs.add(new IModFileInfo.LanguageSpec(mcmodstomlfile.get("modLoader"), MavenVersionAdapter.createFromVersionSpec(mcmodstomlfile.get("loaderVersion"))));
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
final NightConfigWrapper configWrapper = new NightConfigWrapper(conf);
|
||||
//final ModFileInfo modFileInfo = new ModFileInfo(modFile, configWrapper, extralangs.build().toList());
|
||||
return new ModFileInfo(modFile, configWrapper, configWrapper::setFile, List.of());
|
||||
var configWrapper = new NightConfigWrapper(conf);
|
||||
return new ModFileInfo((ModFile)iModFile, configWrapper, configWrapper::setFile, List.of());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "minecraft";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scanFile(final IModFile modFile, final Consumer<Path> pathConsumer) {
|
||||
LOGGER.debug(LogMarkers.SCAN, "Scan started: {}", modFile);
|
||||
try (Stream<Path> files = Files.find(modFile.getSecureJar().getRootPath(), Integer.MAX_VALUE, (p, a) -> p.getNameCount() > 0 && p.getFileName().toString().endsWith(".class"))) {
|
||||
files.forEach(pathConsumer);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
LOGGER.debug(LogMarkers.SCAN, "Scan finished: {}", modFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initArguments(final Map<String, ?> arguments) {
|
||||
// no op
|
||||
}
|
||||
}
|
||||
+32
-62
@@ -7,65 +7,42 @@ package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import net.minecraftforge.forgespi.language.ModFileScanData;
|
||||
import net.minecraftforge.forgespi.language.ModFileScanData.EnumData;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
public class ModAnnotation
|
||||
{
|
||||
public static ModFileScanData.AnnotationData fromModAnnotation(final Type clazz, final ModAnnotation annotation) {
|
||||
@ApiStatus.Internal
|
||||
class ModAnnotation {
|
||||
public static ModFileScanData.AnnotationData fromModAnnotation(Type clazz, ModAnnotation annotation) {
|
||||
return new ModFileScanData.AnnotationData(annotation.asmType, annotation.type, clazz, annotation.member, annotation.values);
|
||||
}
|
||||
|
||||
public static class EnumHolder
|
||||
{
|
||||
private final String desc;
|
||||
private final String value;
|
||||
|
||||
public EnumHolder(String desc, String value)
|
||||
{
|
||||
this.desc = desc;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getDesc()
|
||||
{
|
||||
return desc;
|
||||
}
|
||||
|
||||
public String getValue()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
private final ElementType type;
|
||||
private final Type asmType;
|
||||
private final String member;
|
||||
private final Map<String,Object> values = Maps.newHashMap();
|
||||
private final Map<String,Object> values = new HashMap<>();
|
||||
|
||||
private ArrayList<Object> arrayList;
|
||||
private String arrayName;
|
||||
public ModAnnotation(ElementType type, Type asmType, String member)
|
||||
{
|
||||
public ModAnnotation(ElementType type, Type asmType, String member) {
|
||||
this.type = type;
|
||||
this.asmType = asmType;
|
||||
this.member = member;
|
||||
}
|
||||
|
||||
public ModAnnotation(Type asmType, ModAnnotation parent)
|
||||
{
|
||||
this.type = parent.type;
|
||||
this.asmType = asmType;
|
||||
this.member = parent.member;
|
||||
|
||||
public ModAnnotation(Type asmType, ModAnnotation parent) {
|
||||
this(parent.type, asmType, parent.member);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper("Annotation")
|
||||
.add("type",type)
|
||||
.add("name",asmType.getClassName())
|
||||
@@ -74,52 +51,45 @@ public class ModAnnotation
|
||||
.toString();
|
||||
}
|
||||
|
||||
public ElementType getType()
|
||||
{
|
||||
public ElementType getType() {
|
||||
return type;
|
||||
}
|
||||
public Type getASMType()
|
||||
{
|
||||
|
||||
public Type getASMType() {
|
||||
return asmType;
|
||||
}
|
||||
public String getMember()
|
||||
{
|
||||
|
||||
public String getMember() {
|
||||
return member;
|
||||
}
|
||||
public Map<String, Object> getValues()
|
||||
{
|
||||
|
||||
public Map<String, Object> getValues() {
|
||||
return values;
|
||||
}
|
||||
public void addArray(String name)
|
||||
{
|
||||
this.arrayList = Lists.newArrayList();
|
||||
|
||||
public void addArray(String name) {
|
||||
this.arrayList = new ArrayList<>();
|
||||
this.arrayName = name;
|
||||
}
|
||||
public void addProperty(String key, Object value)
|
||||
{
|
||||
|
||||
public void addProperty(String key, Object value) {
|
||||
if (this.arrayList != null)
|
||||
{
|
||||
arrayList.add(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
values.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void addEnumProperty(String key, String enumName, String value)
|
||||
{
|
||||
addProperty(key, new EnumHolder(enumName, value));
|
||||
public void addEnumProperty(String key, String enumName, String value) {
|
||||
addProperty(key, new EnumData(Type.getType(enumName), value));
|
||||
}
|
||||
|
||||
public void endArray()
|
||||
{
|
||||
public void endArray() {
|
||||
values.put(arrayName, arrayList);
|
||||
arrayList = null;
|
||||
}
|
||||
public ModAnnotation addChildAnnotation(String name, String desc)
|
||||
{
|
||||
ModAnnotation child = new ModAnnotation(Type.getType(desc), this);
|
||||
|
||||
public ModAnnotation addChildAnnotation(String name, String desc) {
|
||||
var child = new ModAnnotation(Type.getType(desc), this);
|
||||
addProperty(name, child.getValues());
|
||||
return child;
|
||||
}
|
||||
|
||||
-81
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import org.objectweb.asm.AnnotationVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
public class ModAnnotationVisitor extends AnnotationVisitor
|
||||
{
|
||||
private final ModAnnotation annotation;
|
||||
private LinkedList<ModAnnotation> annotations;
|
||||
private boolean array;
|
||||
private String name;
|
||||
private boolean isSubAnnotation;
|
||||
|
||||
public ModAnnotationVisitor(LinkedList<ModAnnotation> annotations, ModAnnotation annotation)
|
||||
{
|
||||
super(Opcodes.ASM9);
|
||||
this.annotations = annotations;
|
||||
this.annotation = annotation;
|
||||
}
|
||||
|
||||
public ModAnnotationVisitor(LinkedList<ModAnnotation> annotations, ModAnnotation annotation, String name)
|
||||
{
|
||||
this(annotations, annotation);
|
||||
this.array = true;
|
||||
this.name = name;
|
||||
annotation.addArray(name);
|
||||
}
|
||||
|
||||
public ModAnnotationVisitor(LinkedList<ModAnnotation> annotations, ModAnnotation annotation, boolean isSubAnnotation)
|
||||
{
|
||||
this(annotations, annotation);
|
||||
this.isSubAnnotation = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(String key, Object value)
|
||||
{
|
||||
annotation.addProperty(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnum(String name, String desc, String value)
|
||||
{
|
||||
annotation.addEnumProperty(name, desc, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationVisitor visitArray(String name)
|
||||
{
|
||||
return new ModAnnotationVisitor(annotations, annotation, name);
|
||||
}
|
||||
@Override
|
||||
public AnnotationVisitor visitAnnotation(String name, String desc)
|
||||
{
|
||||
ModAnnotation ma = annotations.getFirst();
|
||||
final ModAnnotation childAnnotation = ma.addChildAnnotation(name, desc);
|
||||
annotations.addFirst(childAnnotation);
|
||||
return new ModAnnotationVisitor(annotations, childAnnotation,true);
|
||||
}
|
||||
@Override
|
||||
public void visitEnd()
|
||||
{
|
||||
if (array)
|
||||
{
|
||||
annotation.endArray();
|
||||
}
|
||||
|
||||
if (isSubAnnotation)
|
||||
{
|
||||
ModAnnotation child = annotations.removeFirst();
|
||||
annotations.addLast(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
+90
-28
@@ -6,6 +6,8 @@
|
||||
package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import net.minecraftforge.forgespi.language.ModFileScanData;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.objectweb.asm.AnnotationVisitor;
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.FieldVisitor;
|
||||
@@ -15,58 +17,118 @@ import org.objectweb.asm.Type;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class ModClassVisitor extends ClassVisitor
|
||||
{
|
||||
@ApiStatus.Internal
|
||||
class ModClassVisitor extends ClassVisitor {
|
||||
private Type asmType;
|
||||
private Type asmSuperType;
|
||||
private Set<Type> interfaces;
|
||||
private final LinkedList<ModAnnotation> annotations = new LinkedList<>();
|
||||
public ModClassVisitor()
|
||||
{
|
||||
|
||||
ModClassVisitor() {
|
||||
super(Opcodes.ASM9);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces)
|
||||
{
|
||||
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
|
||||
this.asmType = Type.getObjectType(name);
|
||||
this.asmSuperType = superName != null && superName.length() > 0 ? Type.getObjectType(superName) : null;
|
||||
this.asmSuperType = superName != null && !superName.isEmpty() ? Type.getObjectType(superName) : null;
|
||||
this.interfaces = Stream.of(interfaces).map(Type::getObjectType).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationVisitor visitAnnotation(final String annotationName, final boolean runtimeVisible)
|
||||
{
|
||||
ModAnnotation ann = new ModAnnotation(ElementType.TYPE, Type.getType(annotationName), this.asmType.getClassName());
|
||||
public AnnotationVisitor visitAnnotation(final String annotationName, final boolean runtimeVisible) {
|
||||
var ann = new ModAnnotation(ElementType.TYPE, Type.getType(annotationName), this.asmType.getClassName());
|
||||
annotations.addFirst(ann);
|
||||
return new ModAnnotationVisitor(annotations, ann);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value)
|
||||
{
|
||||
return new ModFieldVisitor(name, annotations);
|
||||
return new ModAnnotationVisitor(ann);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions)
|
||||
{
|
||||
return new ModMethodVisitor(name, desc, annotations);
|
||||
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
|
||||
return new FieldVisitor(Opcodes.ASM9) {
|
||||
@Override
|
||||
public AnnotationVisitor visitAnnotation(String annotationName, boolean runtimeVisible) {
|
||||
var ann = new ModAnnotation(ElementType.FIELD, Type.getType(annotationName), name);
|
||||
annotations.addFirst(ann);
|
||||
return new ModAnnotationVisitor(ann);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void buildData(final Set<ModFileScanData.ClassData> classes, final Set<ModFileScanData.AnnotationData> annotations) {
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
|
||||
return new MethodVisitor(Opcodes.ASM9) {
|
||||
@Override
|
||||
public AnnotationVisitor visitAnnotation(String annotationName, boolean runtimeVisible) {
|
||||
ModAnnotation ann = new ModAnnotation(ElementType.METHOD, Type.getType(annotationName), name + desc);
|
||||
annotations.addFirst(ann);
|
||||
return new ModAnnotationVisitor(ann);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void buildData(final Set<ModFileScanData.ClassData> classes, final Set<ModFileScanData.AnnotationData> baked) {
|
||||
classes.add(new ModFileScanData.ClassData(this.asmType, this.asmSuperType, this.interfaces));
|
||||
final List<ModFileScanData.AnnotationData> collect = this.annotations.stream().
|
||||
filter(ma->ModFileScanData.interestingAnnotations().test(ma.getASMType())).
|
||||
map(a -> ModAnnotation.fromModAnnotation(this.asmType, a)).collect(Collectors.toList());
|
||||
annotations.addAll(collect);
|
||||
for (var a : this.annotations) {
|
||||
//if (ModFileScanData.interestingAnnotations().test(a.getASMType())) // Always true, so who cares. Left as a note to remove this in SPI later.
|
||||
baked.add(ModAnnotation.fromModAnnotation(this.asmType, a));
|
||||
}
|
||||
}
|
||||
|
||||
private class ModAnnotationVisitor extends AnnotationVisitor {
|
||||
private final ModAnnotation annotation;
|
||||
private boolean array;
|
||||
private boolean isSubAnnotation;
|
||||
|
||||
public ModAnnotationVisitor(ModAnnotation annotation) {
|
||||
super(Opcodes.ASM9);
|
||||
this.annotation = annotation;
|
||||
}
|
||||
|
||||
public ModAnnotationVisitor(ModAnnotation annotation, String name) {
|
||||
this(annotation);
|
||||
this.array = true;
|
||||
annotation.addArray(name);
|
||||
}
|
||||
|
||||
public ModAnnotationVisitor(ModAnnotation annotation, boolean isSubAnnotation) {
|
||||
this(annotation);
|
||||
this.isSubAnnotation = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(String key, Object value) {
|
||||
annotation.addProperty(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnum(String name, String desc, String value) {
|
||||
annotation.addEnumProperty(name, desc, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationVisitor visitArray(String name) {
|
||||
return new ModAnnotationVisitor(annotation, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationVisitor visitAnnotation(String name, String desc) {
|
||||
var ma = annotations.getFirst();
|
||||
var childAnnotation = ma.addChildAnnotation(name, desc);
|
||||
annotations.addFirst(childAnnotation);
|
||||
return new ModAnnotationVisitor(childAnnotation, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
if (array)
|
||||
annotation.endArray();
|
||||
|
||||
if (isSubAnnotation)
|
||||
annotations.addLast(annotations.removeFirst());
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
-3
@@ -12,6 +12,7 @@ import cpw.mods.modlauncher.Launcher;
|
||||
import cpw.mods.modlauncher.api.IModuleLayerManager;
|
||||
import cpw.mods.modlauncher.util.ServiceLoaderUtils;
|
||||
import net.minecraftforge.fml.loading.EarlyLoadingException;
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
import net.minecraftforge.fml.loading.ImmediateWindowHandler;
|
||||
import net.minecraftforge.fml.loading.LogMarkers;
|
||||
import net.minecraftforge.fml.loading.UniqueModListBuilder;
|
||||
@@ -21,6 +22,9 @@ import net.minecraftforge.forgespi.language.IModFileInfo;
|
||||
import net.minecraftforge.forgespi.locating.IDependencyLocator;
|
||||
import net.minecraftforge.forgespi.locating.IModFile;
|
||||
import net.minecraftforge.forgespi.locating.IModLocator;
|
||||
|
||||
import net.minecraftforge.forgespi.locating.ModFileLoadingException;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -30,6 +34,7 @@ import java.util.Objects;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public class ModDiscoverer {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private final ServiceLoader<IModLocator> modLocators;
|
||||
@@ -67,6 +72,8 @@ public class ModDiscoverer {
|
||||
List<EarlyLoadingException.ExceptionData> discoveryErrorData = new ArrayList<>();
|
||||
boolean successfullyLoadedMods = true;
|
||||
List<IModFileInfo> brokenFiles = new ArrayList<>();
|
||||
List<ModFileLoadingException> modFileLoadingExceptions = new ArrayList<>();
|
||||
boolean distIsDedicatedServer = FMLLoader.getDist().isDedicatedServer();
|
||||
ImmediateWindowHandler.updateProgress("Discovering mod files");
|
||||
//Loop all mod locators to get the prime mods to load from.
|
||||
for (IModLocator locator : modLocatorList) {
|
||||
@@ -77,7 +84,17 @@ public class ModDiscoverer {
|
||||
var exceptions = candidates.stream().map(IModLocator.ModFileOrException::ex).filter(Objects::nonNull).toList();
|
||||
if (!exceptions.isEmpty()) {
|
||||
LOGGER.debug(LogMarkers.SCAN, "Locator {} found {} invalid mod files", locator, exceptions.size());
|
||||
brokenFiles.addAll(exceptions.stream().map(e->e instanceof InvalidModFileException ime ? ime.getBrokenFile() : null).filter(Objects::nonNull).toList());
|
||||
for (ModFileLoadingException ex : exceptions) {
|
||||
// pipe exception messages through the discoveryErrorData to avoid swallowing some exceptions and improve error messages
|
||||
// (no longer a generic "Invalid mod file" for all InvalidModExceptions - it actually shows the exception message now)
|
||||
if (ex instanceof InvalidModFileException invalidModFileEx) {
|
||||
var modInfo = invalidModFileEx.getBrokenFile();
|
||||
brokenFiles.add(modInfo);
|
||||
discoveryErrorData.add(new EarlyLoadingException.ExceptionData(ex.getMessage(), modInfo));
|
||||
} else {
|
||||
discoveryErrorData.add(new EarlyLoadingException.ExceptionData(ex.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
var locatedFiles = candidates.stream().map(IModLocator.ModFileOrException::file).filter(Objects::nonNull).collect(Collectors.toList());
|
||||
|
||||
@@ -85,8 +102,17 @@ public class ModDiscoverer {
|
||||
if (!badModFiles.isEmpty()) {
|
||||
LOGGER.error(LogMarkers.SCAN, "Locator {} returned {} files which is are not ModFile instances! They will be skipped!", locator, badModFiles.size());
|
||||
brokenFiles.addAll(badModFiles.stream().map(IModFile::getModFileInfo).toList());
|
||||
locatedFiles.removeAll(badModFiles);
|
||||
}
|
||||
locatedFiles.removeAll(badModFiles);
|
||||
|
||||
if (distIsDedicatedServer) {
|
||||
var clientOnlyModFiles = locatedFiles.stream().filter(file -> file.getModFileInfo().isClientSideOnly()).toList();
|
||||
if (!clientOnlyModFiles.isEmpty()) {
|
||||
LOGGER.warn(LogMarkers.SCAN, "Locator {} returned {} files which are client-side-only mods, but we're on a dedicated server. They will be skipped!", locator, clientOnlyModFiles.size());
|
||||
locatedFiles.removeAll(clientOnlyModFiles);
|
||||
}
|
||||
}
|
||||
|
||||
LOGGER.debug(LogMarkers.SCAN, "Locator {} found {} valid mod files", locator, locatedFiles.size());
|
||||
handleLocatedFiles(loadedFiles, locatedFiles);
|
||||
} catch (InvalidModFileException imfe) {
|
||||
@@ -159,7 +185,7 @@ public class ModDiscoverer {
|
||||
|
||||
//Validate the loading. With a deduplicated list, we can now successfully process the artifacts and load
|
||||
//transformer plugins.
|
||||
var validator = new ModValidator(modFilesMap, brokenFiles, discoveryErrorData);
|
||||
var validator = new ModValidator(modFilesMap, brokenFiles, discoveryErrorData, modFileLoadingExceptions);
|
||||
validator.stage1Validation();
|
||||
return validator;
|
||||
}
|
||||
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import org.objectweb.asm.AnnotationVisitor;
|
||||
import org.objectweb.asm.FieldVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.util.LinkedList;
|
||||
|
||||
public class ModFieldVisitor extends FieldVisitor
|
||||
{
|
||||
private final LinkedList<ModAnnotation> annotations;
|
||||
private final String fieldName;
|
||||
|
||||
public ModFieldVisitor(String name, final LinkedList<ModAnnotation> annotations)
|
||||
{
|
||||
super(Opcodes.ASM9);
|
||||
this.fieldName = name;
|
||||
this.annotations = annotations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationVisitor visitAnnotation(String annotationName, boolean runtimeVisible)
|
||||
{
|
||||
ModAnnotation ann = new ModAnnotation(ElementType.FIELD, Type.getType(annotationName), fieldName);
|
||||
annotations.addFirst(ann);
|
||||
return new ModAnnotationVisitor(annotations, ann);
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@ import com.mojang.logging.LogUtils;
|
||||
import cpw.mods.jarhandling.SecureJar;
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
import net.minecraftforge.fml.loading.LogMarkers;
|
||||
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
|
||||
import net.minecraftforge.forgespi.language.IModFileInfo;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import net.minecraftforge.forgespi.language.IModLanguageProvider;
|
||||
@@ -24,6 +23,8 @@ import org.slf4j.Logger;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@@ -36,16 +37,7 @@ import java.util.jar.Attributes;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
public class ModFile implements IModFile {
|
||||
// Mods either must have a mods.toml or a manifest. We can no longer just put any jar on the classpath.
|
||||
@Deprecated(forRemoval = true, since = "1.18")
|
||||
public static final Manifest DEFAULTMANIFEST;
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
|
||||
static {
|
||||
DEFAULTMANIFEST = new Manifest();
|
||||
DEFAULTMANIFEST.getMainAttributes().putValue("FMLModType", "MOD");
|
||||
}
|
||||
|
||||
private final String jarVersion;
|
||||
private final ModFileFactory.ModFileInfoParser parser;
|
||||
private Map<String, Object> fileProperties;
|
||||
@@ -177,9 +169,13 @@ public class ModFile implements IModFile {
|
||||
}
|
||||
|
||||
public void identifyLanguage() {
|
||||
this.loaders = this.modFileInfo.requiredLanguageLoaders().stream()
|
||||
.map(spec-> FMLLoader.getLanguageLoadingProvider().findLanguage(this, spec.languageName(), spec.acceptedVersions()))
|
||||
.toList();
|
||||
var lst = new ArrayList<IModLanguageProvider>(this.modFileInfo.requiredLanguageLoaders().size());
|
||||
var services = FMLLoader.getLanguageLoadingProvider();
|
||||
for (var spec : this.modFileInfo.requiredLanguageLoaders()) {
|
||||
var service = services.findLanguage(this, spec.languageName(), spec.acceptedVersions());
|
||||
lst.add(service);
|
||||
}
|
||||
this.loaders = Collections.unmodifiableList(lst);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -207,8 +203,11 @@ public class ModFile implements IModFile {
|
||||
this.securityStatus = status;
|
||||
}
|
||||
|
||||
public ArtifactVersion getJarVersion()
|
||||
{
|
||||
public SecureJar.Status getSecurityStatus() {
|
||||
return this.securityStatus;
|
||||
}
|
||||
|
||||
public ArtifactVersion getJarVersion() {
|
||||
return new DefaultArtifactVersion(this.jarVersion);
|
||||
}
|
||||
|
||||
|
||||
+55
-5
@@ -8,12 +8,14 @@ package net.minecraftforge.fml.loading.moddiscovery;
|
||||
import com.google.common.base.Strings;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
|
||||
import net.minecraftforge.fml.loading.FMLConfig;
|
||||
import net.minecraftforge.fml.loading.LogMarkers;
|
||||
import net.minecraftforge.fml.loading.StringUtils;
|
||||
import net.minecraftforge.forgespi.language.IConfigurable;
|
||||
import net.minecraftforge.forgespi.language.IModFileInfo;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import net.minecraftforge.forgespi.language.MavenVersionAdapter;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.slf4j.Logger;
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
import java.net.URL;
|
||||
@@ -29,6 +31,7 @@ import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@@ -40,11 +43,22 @@ import java.util.stream.Stream;
|
||||
public class ModFileInfo implements IModFileInfo, IConfigurable
|
||||
{
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static final String MAYBE_NOT_JAVAFML_VER = "[2,)";
|
||||
private static final String JAVAFML = "javafml";
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static final String NOT_A_FORGE_MOD_PROP = "__FORGE__not_a_forge_mod";
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static final boolean DETECT_NON_FORGE_MODS_TOML = FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.DETECT_NON_FORGE_MODS_TOML);
|
||||
|
||||
private final IConfigurable config;
|
||||
private final ModFile modFile;
|
||||
private final URL issueURL;
|
||||
private final List<LanguageSpec> languageSpecs;
|
||||
private final boolean showAsResourcePack;
|
||||
private final boolean clientSideOnly;
|
||||
private final boolean showAsDataPack;
|
||||
private final List<IModInfo> mods;
|
||||
private final Map<String,Object> properties;
|
||||
private final String license;
|
||||
@@ -59,23 +73,47 @@ public class ModFileInfo implements IModFileInfo, IConfigurable
|
||||
var modLoader = config.<String>getConfigElement("modLoader")
|
||||
.orElseThrow(()->new InvalidModFileException("Missing ModLoader in file", this));
|
||||
// as is modloader version
|
||||
var modLoaderVersion = config.<String>getConfigElement("loaderVersion")
|
||||
.map(MavenVersionAdapter::createFromVersionSpec)
|
||||
var modLoaderVerStr = config.<String>getConfigElement("loaderVersion")
|
||||
.orElseThrow(()->new InvalidModFileException("Missing ModLoader version in file", this));
|
||||
var modLoaderVersion = MavenVersionAdapter.createFromVersionSpec(modLoaderVerStr);
|
||||
this.languageSpecs = new ArrayList<>(List.of(new LanguageSpec(modLoader, modLoaderVersion)));
|
||||
|
||||
// if true, this might not be a Forge mod
|
||||
boolean maybeNotAForgeMod = DETECT_NON_FORGE_MODS_TOML
|
||||
&& modLoader.equals(JAVAFML)
|
||||
&& modLoaderVersion.hasRestrictions() // if loaderVersion is not "*"
|
||||
&& modLoaderVerStr.equals(MAYBE_NOT_JAVAFML_VER); // if loaderVersion is exactly "[2,)"
|
||||
|
||||
// the remaining properties are optional with sensible defaults
|
||||
this.license = config.<String>getConfigElement("license")
|
||||
.orElse("");
|
||||
this.showAsResourcePack = config.<Boolean>getConfigElement("showAsResourcePack")
|
||||
.orElse(false);
|
||||
this.clientSideOnly = config.<Boolean>getConfigElement("clientSideOnly")
|
||||
.orElse(false);
|
||||
if (this.clientSideOnly) {
|
||||
// if clientSideOnly, then this is definitely a Forge mod
|
||||
maybeNotAForgeMod = false;
|
||||
}
|
||||
this.showAsDataPack = config.<Boolean>getConfigElement("showAsDataPack")
|
||||
.orElse(false);
|
||||
this.usesServices = config.<List<String>>getConfigElement("services")
|
||||
.orElse(List.of());
|
||||
this.properties = config.<Map<String, Object>>getConfigElement("properties")
|
||||
.orElse(Collections.emptyMap());
|
||||
this.modFile.setFileProperties(this.properties);
|
||||
this.issueURL = config.<String>getConfigElement("issueTrackerURL")
|
||||
.map(StringUtils::toURL)
|
||||
.orElse(null);
|
||||
|
||||
if (maybeNotAForgeMod) {
|
||||
// mark this file as not a Forge mod. Note: this marker may be removed later based on the mod dependencies
|
||||
this.properties = config.<Map<String, Object>>getConfigElement("properties")
|
||||
.orElse(new LinkedHashMap<>());
|
||||
this.properties.put(NOT_A_FORGE_MOD_PROP, true);
|
||||
} else {
|
||||
this.properties = config.<Map<String, Object>>getConfigElement("properties")
|
||||
.orElse(Collections.emptyMap());
|
||||
}
|
||||
this.modFile.setFileProperties(this.properties);
|
||||
|
||||
final List<? extends IConfigurable> modConfigs = config.getConfigList("mods");
|
||||
if (modConfigs.isEmpty())
|
||||
{
|
||||
@@ -126,6 +164,18 @@ public class ModFileInfo implements IModFileInfo, IConfigurable
|
||||
return this.showAsResourcePack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClientSideOnly()
|
||||
{
|
||||
return this.clientSideOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean showAsDataPack()
|
||||
{
|
||||
return this.showAsDataPack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> getConfigElement(final String... key)
|
||||
{
|
||||
|
||||
+2
-2
@@ -13,6 +13,7 @@ import net.minecraftforge.fml.loading.LogMarkers;
|
||||
import net.minecraftforge.forgespi.language.IModFileInfo;
|
||||
import net.minecraftforge.forgespi.locating.IModFile;
|
||||
import net.minecraftforge.forgespi.locating.ModFileFactory;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -22,7 +23,6 @@ import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ModFileParser {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
@@ -31,7 +31,7 @@ public class ModFileParser {
|
||||
return parser.build(modFile);
|
||||
}
|
||||
|
||||
public static IModFileInfo modsTomlParser(final IModFile imodFile) {
|
||||
public static @Nullable IModFileInfo modsTomlParser(final IModFile imodFile) {
|
||||
ModFile modFile = (ModFile) imodFile;
|
||||
LOGGER.debug(LogMarkers.LOADING,"Considering mod file candidate {}", modFile.getFilePath());
|
||||
final Path modsjson = modFile.findResource("META-INF", "mods.toml");
|
||||
|
||||
@@ -15,6 +15,7 @@ import net.minecraftforge.forgespi.locating.ForgeFeature;
|
||||
import org.apache.maven.artifact.versioning.ArtifactVersion;
|
||||
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
|
||||
import org.apache.maven.artifact.versioning.VersionRange;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.net.URL;
|
||||
@@ -22,10 +23,11 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class ModInfo implements IModInfo, IConfigurable
|
||||
{
|
||||
@ApiStatus.Internal
|
||||
public class ModInfo implements IModInfo, IConfigurable {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static final DefaultArtifactVersion DEFAULT_VERSION = new DefaultArtifactVersion("1");
|
||||
private static final Pattern VALID_MODID = Pattern.compile("^[a-z][a-z0-9_]{1,63}$");
|
||||
@@ -80,7 +82,8 @@ public class ModInfo implements IModInfo, IConfigurable
|
||||
this.displayName = config.<String>getConfigElement("displayName")
|
||||
.orElse(this.modId);
|
||||
this.description = config.<String>getConfigElement("description")
|
||||
.orElse("MISSING DESCRIPTION");
|
||||
.orElse("MISSING DESCRIPTION")
|
||||
.replace("\r\n", "\n").stripIndent();
|
||||
this.logoFile = Optional.ofNullable(config.<String>getConfigElement("logoFile")
|
||||
.orElseGet(() -> ownFile.flatMap(mf -> mf.<String>getConfigElement("logoFile"))
|
||||
.orElse(null)));
|
||||
@@ -205,14 +208,29 @@ public class ModInfo implements IModInfo, IConfigurable
|
||||
private final boolean mandatory;
|
||||
private final Ordering ordering;
|
||||
private final DependencySide side;
|
||||
private Optional<URL> referralUrl;
|
||||
private final Optional<URL> referralUrl;
|
||||
|
||||
public ModVersion(final IModInfo owner, final IConfigurable config) {
|
||||
this.owner = owner;
|
||||
this.modId = config.<String>getConfigElement("modId")
|
||||
.orElseThrow(()->new InvalidModFileException("Missing required field modid in dependency", getOwningFile()));
|
||||
this.mandatory = config.<Boolean>getConfigElement("mandatory")
|
||||
.orElseThrow(()->new InvalidModFileException("Missing required field mandatory in dependency", getOwningFile()));
|
||||
if (ModFileInfo.DETECT_NON_FORGE_MODS_TOML && this.modId.equals("forge")) {
|
||||
var fileProps = owner.getOwningFile().getFileProperties();
|
||||
// Checking containsKey to avoid a possible exception if the property is not present (due to Collections.emptyMap())
|
||||
if (!fileProps.isEmpty() && fileProps.containsKey(ModFileInfo.NOT_A_FORGE_MOD_PROP)) {
|
||||
// if the mod has a dependency on Forge, but we thought it wasn't a Forge mod earlier, we were wrong
|
||||
// so remove the flag.
|
||||
fileProps.remove(ModFileInfo.NOT_A_FORGE_MOD_PROP);
|
||||
}
|
||||
}
|
||||
var mandatory = config.<Boolean>getConfigElement("mandatory");
|
||||
if (mandatory.isPresent()) {
|
||||
this.mandatory = mandatory.get();
|
||||
} else if (ModFileInfo.DETECT_NON_FORGE_MODS_TOML && owner.getOwningFile().getFileProperties().containsKey(ModFileInfo.NOT_A_FORGE_MOD_PROP)) {
|
||||
this.mandatory = true;
|
||||
} else {
|
||||
throw new InvalidModFileException("Missing required field mandatory in dependency", getOwningFile());
|
||||
}
|
||||
this.versionRange = config.<String>getConfigElement("versionRange")
|
||||
.map(MavenVersionAdapter::createFromVersionSpec)
|
||||
.orElse(UNBOUNDED);
|
||||
|
||||
+3
-34
@@ -6,48 +6,17 @@
|
||||
package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import cpw.mods.jarhandling.JarMetadata;
|
||||
import cpw.mods.jarhandling.SecureJar;
|
||||
import net.minecraftforge.forgespi.locating.IModFile;
|
||||
import net.minecraftforge.forgespi.locating.IModLocator;
|
||||
|
||||
import java.lang.module.ModuleDescriptor;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public final class ModJarMetadata implements JarMetadata {
|
||||
private IModFile modFile;
|
||||
private ModuleDescriptor descriptor;
|
||||
|
||||
// TODO: Remove helper functions to cleanup api
|
||||
@Deprecated(forRemoval = true, since="1.18")
|
||||
static Optional<IModFile> buildFile(IModLocator locator, Predicate<SecureJar> jarTest, BiPredicate<String, String> filter, Path... files) {
|
||||
return buildFile(j->new ModFile(j, locator, ModFileParser::modsTomlParser), jarTest, filter, files);
|
||||
}
|
||||
|
||||
// TODO: Remove helper functions to cleanup api
|
||||
@Deprecated(forRemoval = true, since="1.18")
|
||||
static IModFile buildFile(IModLocator locator, Path... files) {
|
||||
return buildFile(locator, j->true, (a,b) -> true, files).orElseThrow(()->new IllegalArgumentException("Failed to find valid JAR file"));
|
||||
}
|
||||
|
||||
// TODO: Remove helper functions to cleanup api
|
||||
@Deprecated(forRemoval = true, since="1.18")
|
||||
static Optional<IModFile> buildFile(Function<SecureJar, IModFile> mfConstructor, Predicate<SecureJar> jarTest, BiPredicate<String, String> filter, Path... files) {
|
||||
var mjm = new ModJarMetadata();
|
||||
var sj = SecureJar.from(()->ModFile.DEFAULTMANIFEST, j->mjm, filter, files);
|
||||
if (jarTest.test(sj)) {
|
||||
var mf = mfConstructor.apply(sj);
|
||||
mjm.setModFile(mf);
|
||||
return Optional.of(mf);
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
ModJarMetadata() {
|
||||
}
|
||||
|
||||
|
||||
-88
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraftforge.fml.loading.FMLPaths;
|
||||
import net.minecraftforge.fml.loading.FileUtils;
|
||||
import net.minecraftforge.fml.loading.LogMarkers;
|
||||
import net.minecraftforge.fml.loading.MavenCoordinateResolver;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ModListHandler {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
/**
|
||||
* Reads the modList paths specified, and searches each maven root for mods matching. Returns a list of mods
|
||||
* found.
|
||||
*
|
||||
* @param modListPaths Paths to search for mod file lists
|
||||
* @param mavenRootPaths Roots to look for mods listed
|
||||
* @return list of found mod coordinates
|
||||
*/
|
||||
public static List<String> processModLists(final List<String> modListPaths, final List<Path> mavenRootPaths) {
|
||||
final List<String> modCoordinates = modListPaths.stream().map(ModListHandler::transformPathToList).
|
||||
flatMap(Collection::stream).
|
||||
collect(Collectors.toList());
|
||||
|
||||
List<Pair<Path,String>> localModCoords = modCoordinates.stream().map(mc->Pair.of(MavenCoordinateResolver.get(mc), mc)).collect(Collectors.toList());
|
||||
final List<Pair<Path, String>> foundCoordinates = localModCoords.stream().
|
||||
map(mc -> mavenRootPaths.stream().
|
||||
map(root -> Pair.of(root.resolve(mc.getLeft()), mc.getRight())).
|
||||
filter(path -> Files.exists(path.getLeft())).
|
||||
findFirst().
|
||||
orElseGet(()->{
|
||||
LOGGER.warn(LogMarkers.CORE, "Failed to find coordinate {}", mc);
|
||||
return null;
|
||||
})).
|
||||
filter(Objects::nonNull).
|
||||
collect(Collectors.toList());
|
||||
|
||||
final List<String> found = foundCoordinates.stream().map(Pair::getRight).collect(Collectors.toList());
|
||||
LOGGER.debug(LogMarkers.CORE, "Found mod coordinates from lists: {}", found);
|
||||
return found;
|
||||
}
|
||||
|
||||
private static List<String> transformPathToList(final String path) {
|
||||
LOGGER.debug(LogMarkers.CORE, "Reading mod list {}", path);
|
||||
Path filePath = FMLPaths.GAMEDIR.get().resolve(path);
|
||||
if (!Files.exists(filePath)) {
|
||||
LOGGER.warn(LogMarkers.CORE, "Failed to find modlist file at {}", filePath);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
String extension = FileUtils.fileExtension(filePath);
|
||||
if (Objects.equals("list",extension)) {
|
||||
return readListFile(filePath).stream().filter(s -> !s.isEmpty()).collect(Collectors.toList());
|
||||
} else {
|
||||
LOGGER.warn(LogMarkers.CORE, "Failed to read unknown file list type {} for file {}", extension, filePath);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple list file, ending in ".list" with one mod coordinate per line
|
||||
* @param filePath path
|
||||
* @return list
|
||||
*/
|
||||
private static List<String> readListFile(final Path filePath) {
|
||||
try {
|
||||
return Files.readAllLines(filePath);
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn(LogMarkers.CORE, "Failed to read file list {}", filePath, e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import org.objectweb.asm.AnnotationVisitor;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.util.LinkedList;
|
||||
|
||||
public class ModMethodVisitor extends MethodVisitor {
|
||||
|
||||
private final LinkedList<ModAnnotation> annotations;
|
||||
private String methodName;
|
||||
private String methodDescriptor;
|
||||
|
||||
public ModMethodVisitor(String name, String desc, final LinkedList<ModAnnotation> annotations)
|
||||
{
|
||||
super(Opcodes.ASM9);
|
||||
this.methodName = name;
|
||||
this.methodDescriptor = desc;
|
||||
this.annotations = annotations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationVisitor visitAnnotation(String annotationName, boolean runtimeVisible)
|
||||
{
|
||||
ModAnnotation ann = new ModAnnotation(ElementType.METHOD, Type.getType(annotationName), methodName+methodDescriptor);
|
||||
annotations.addFirst(ann);
|
||||
return new ModAnnotationVisitor(annotations, ann);
|
||||
}
|
||||
|
||||
}
|
||||
+41
-12
@@ -6,12 +6,18 @@
|
||||
package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
|
||||
import cpw.mods.jarhandling.SecureJar;
|
||||
import cpw.mods.modlauncher.api.IModuleLayerManager;
|
||||
import cpw.mods.modlauncher.api.ITransformationService;
|
||||
import net.minecraftforge.fml.loading.*;
|
||||
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
|
||||
import net.minecraftforge.fml.loading.EarlyLoadingException;
|
||||
import net.minecraftforge.fml.loading.ImmediateWindowHandler;
|
||||
import net.minecraftforge.fml.loading.LoadingModList;
|
||||
import net.minecraftforge.fml.loading.LogMarkers;
|
||||
import net.minecraftforge.fml.loading.ModSorter;
|
||||
import net.minecraftforge.forgespi.language.IModFileInfo;
|
||||
import net.minecraftforge.forgespi.locating.IModFile;
|
||||
import net.minecraftforge.forgespi.locating.ModFileLoadingException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
@@ -19,25 +25,30 @@ import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ModValidator {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private final Map<IModFile.Type, List<ModFile>> modFiles;
|
||||
private final List<ModFile> candidatePlugins;
|
||||
private final List<ModFile> candidateMods;
|
||||
private final List<ModFile> gameLibraries;
|
||||
private LoadingModList loadingModList;
|
||||
private List<IModFile> brokenFiles;
|
||||
private final List<IModFile> brokenFiles = new ArrayList<>();
|
||||
private final List<EarlyLoadingException.ExceptionData> discoveryErrorData;
|
||||
private final List<ModFileLoadingException> modFileLoadingExceptions;
|
||||
|
||||
public ModValidator(final Map<IModFile.Type, List<ModFile>> modFiles, final List<IModFileInfo> brokenFiles, final List<EarlyLoadingException.ExceptionData> discoveryErrorData) {
|
||||
this.modFiles = modFiles;
|
||||
public ModValidator(Map<IModFile.Type, List<ModFile>> modFiles, List<IModFileInfo> brokenFiles, List<EarlyLoadingException.ExceptionData> discoveryErrorData, List<ModFileLoadingException> modFileLoadingExceptions) {
|
||||
this.candidateMods = lst(modFiles.get(IModFile.Type.MOD));
|
||||
this.candidateMods.addAll(lst(modFiles.get(IModFile.Type.GAMELIBRARY)));
|
||||
this.gameLibraries = lst(modFiles.get(IModFile.Type.GAMELIBRARY));
|
||||
this.candidateMods.addAll(this.gameLibraries);
|
||||
this.candidatePlugins = lst(modFiles.get(IModFile.Type.LANGPROVIDER));
|
||||
this.candidatePlugins.addAll(lst(modFiles.get(IModFile.Type.LIBRARY)));
|
||||
this.discoveryErrorData = discoveryErrorData;
|
||||
this.brokenFiles = brokenFiles.stream().map(IModFileInfo::getFile).collect(Collectors.toList()); // mutable list
|
||||
this.brokenFiles.addAll(brokenFiles.stream().map(IModFileInfo::getFile).toList());
|
||||
this.modFileLoadingExceptions = modFileLoadingExceptions;
|
||||
}
|
||||
|
||||
public ModValidator(Map<IModFile.Type, List<ModFile>> modFiles, List<IModFileInfo> brokenFiles, List<EarlyLoadingException.ExceptionData> discoveryErrorData) {
|
||||
this(modFiles, brokenFiles, discoveryErrorData, List.of());
|
||||
}
|
||||
|
||||
private static List<ModFile> lst(List<ModFile> files) {
|
||||
@@ -55,8 +66,7 @@ public class ModValidator {
|
||||
@NotNull
|
||||
private List<ModFile> validateFiles(final List<ModFile> mods) {
|
||||
final List<ModFile> brokenFiles = new ArrayList<>();
|
||||
for (Iterator<ModFile> iterator = mods.iterator(); iterator.hasNext(); )
|
||||
{
|
||||
for (Iterator<ModFile> iterator = mods.iterator(); iterator.hasNext();) {
|
||||
ModFile modFile = iterator.next();
|
||||
if (!modFile.getProvider().isValid(modFile) || !modFile.identifyMods()) {
|
||||
LOGGER.warn(LogMarkers.SCAN, "File {} has been ignored - it is invalid", modFile.getFilePath());
|
||||
@@ -72,7 +82,26 @@ public class ModValidator {
|
||||
}
|
||||
|
||||
public ITransformationService.Resource getModResources() {
|
||||
return new ITransformationService.Resource(IModuleLayerManager.Layer.GAME, this.candidateMods.stream().map(IModFile::getSecureJar).toList());
|
||||
var mods = new ArrayList<SecureJar>();
|
||||
// Add only the valid mods that we will be attempting to load.
|
||||
// If any detectable error happens during the sorting process {missing deps, duplicates,
|
||||
// This helps prevent coremods/mixins from screwing up us displaying the error screens that the sorting/validation is trying to display.
|
||||
// This won't fix them all as they are still loaded and will still apply but it might help
|
||||
for (var info : this.loadingModList.getModFiles())
|
||||
mods.add(info.getFile().getSecureJar());
|
||||
|
||||
// Add any game libraries, this *may* be duplicates, depending on the state of the sorting. But until I get around to re-writing
|
||||
// that clusterfuck of a system, have this simple de-duplication. Order of resource is important, but i'm unsure if duplication is
|
||||
// so might as well waste a few cycles on checking. Still faster then converting from a set to a list.
|
||||
// Ideally we would explicitly list out which mods to load {forge and MC being the only ones} but some coremods could require extra things.
|
||||
// So try game libraries as well
|
||||
for (var lib : this.gameLibraries) {
|
||||
var jar = lib.getSecureJar();
|
||||
if (!mods.contains(jar))
|
||||
mods.add(jar);
|
||||
}
|
||||
|
||||
return new ITransformationService.Resource(IModuleLayerManager.Layer.GAME, mods);
|
||||
}
|
||||
|
||||
private List<EarlyLoadingException.ExceptionData> validateLanguages() {
|
||||
|
||||
+21
-21
@@ -9,33 +9,28 @@ import com.mojang.logging.LogUtils;
|
||||
import net.minecraftforge.fml.loading.FMLPaths;
|
||||
import net.minecraftforge.fml.loading.LogMarkers;
|
||||
import net.minecraftforge.fml.loading.ModDirTransformerDiscoverer;
|
||||
import net.minecraftforge.fml.loading.StringUtils;
|
||||
import net.minecraftforge.forgespi.locating.IModLocator;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static cpw.mods.modlauncher.api.LamdbaExceptionUtils.uncheck;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Support loading mods located in JAR files in the mods folder
|
||||
*/
|
||||
public class ModsFolderLocator extends AbstractJarFileModLocator
|
||||
{
|
||||
public class ModsFolderLocator extends AbstractModProvider implements IModLocator {
|
||||
private static final String SUFFIX = ".jar";
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private final Path modFolder;
|
||||
private final String customName;
|
||||
|
||||
public ModsFolderLocator() {
|
||||
this(FMLPaths.MODSDIR.get());
|
||||
}
|
||||
|
||||
ModsFolderLocator(Path modFolder) {
|
||||
this(modFolder, "mods folder");
|
||||
this(FMLPaths.MODSDIR.get(), "mods folder");
|
||||
}
|
||||
|
||||
ModsFolderLocator(Path modFolder, String name) {
|
||||
@@ -44,13 +39,22 @@ public class ModsFolderLocator extends AbstractJarFileModLocator
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Path> scanCandidates() {
|
||||
LOGGER.debug(LogMarkers.SCAN,"Scanning mods dir {} for mods", this.modFolder);
|
||||
public List<IModLocator.ModFileOrException> scanMods() {
|
||||
LOGGER.debug(LogMarkers.SCAN, "Scanning mods dir {} for mods", this.modFolder);
|
||||
var excluded = ModDirTransformerDiscoverer.allExcluded();
|
||||
try (var stream = Files.list(this.modFolder)) {
|
||||
return stream.filter(path -> !excluded.contains(path) && path.getFileName().toString().toLowerCase(Locale.ROOT).endsWith(SUFFIX))
|
||||
.sorted(Comparator.comparing(path -> path.getFileName().toString().toLowerCase(Locale.ROOT)))
|
||||
.map(this::createMod)
|
||||
.toList();
|
||||
} catch (IOException e) {
|
||||
return sneak(e);
|
||||
}
|
||||
}
|
||||
|
||||
return uncheck(()-> Files.list(this.modFolder))
|
||||
.filter(p-> !excluded.contains(p) && StringUtils.toLowerCase(p.getFileName().toString()).endsWith(SUFFIX))
|
||||
.sorted(Comparator.comparing(path-> StringUtils.toLowerCase(path.getFileName().toString())));
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <E extends Throwable, R> R sneak(Throwable e) throws E {
|
||||
throw (E)e;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -62,8 +66,4 @@ public class ModsFolderLocator extends AbstractJarFileModLocator
|
||||
public String toString() {
|
||||
return "{"+customName+" locator at "+this.modFolder+"}";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initArguments(final Map<String, ?> arguments) {
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -15,9 +15,12 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
public class NightConfigWrapper implements IConfigurable {
|
||||
@ApiStatus.Internal
|
||||
class NightConfigWrapper implements IConfigurable {
|
||||
private final UnmodifiableConfig config;
|
||||
private IModFileInfo file;
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ import com.mojang.logging.LogUtils;
|
||||
import net.minecraftforge.fml.loading.LogMarkers;
|
||||
import net.minecraftforge.forgespi.language.IModLanguageProvider;
|
||||
import net.minecraftforge.forgespi.language.ModFileScanData;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
@@ -18,7 +20,8 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
public class Scanner {
|
||||
@ApiStatus.Internal
|
||||
class Scanner {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private final ModFile fileToScan;
|
||||
|
||||
@@ -45,7 +48,7 @@ public class Scanner {
|
||||
try (InputStream in = Files.newInputStream(path)){
|
||||
ModClassVisitor mcv = new ModClassVisitor();
|
||||
ClassReader cr = new ClassReader(in);
|
||||
cr.accept(mcv, 0);
|
||||
cr.accept(mcv, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
|
||||
mcv.buildData(result.getClasses(), result.getAnnotations());
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
// mark path bad
|
||||
|
||||
@@ -10,7 +10,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
public final class ProgressMeter {
|
||||
private final String name;
|
||||
private final int steps;
|
||||
private AtomicInteger current;
|
||||
private final AtomicInteger current;
|
||||
private Message label;
|
||||
|
||||
public ProgressMeter(String name, int steps, int current, Message label) {
|
||||
|
||||
Loaded 100 of 2388 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user