mirror of
https://github.com/MinecraftForge/MinecraftForge.git
synced 2026-11-14 01:04:24 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
664a2c06ad | ||
|
|
e85295a837 | ||
|
|
21582ef641 | ||
|
|
020ec3d51b | ||
|
|
774b79e609 | ||
|
|
ee6022b006 | ||
|
|
37efd2b8ae | ||
|
|
cf3d42ef38 | ||
|
|
03a505a3f2 | ||
|
|
bdc856edf5 | ||
|
|
6eebea0896 | ||
|
|
9218e08955 | ||
|
|
7006fe4f29 | ||
|
|
301e7252b0 | ||
|
|
2b5852072c | ||
|
|
0b38373a45 | ||
|
|
07e538f3dd | ||
|
|
b39e300c1b | ||
|
|
50db51a366 | ||
|
|
df80acc49d |
No files matched your search
@@ -21,6 +21,3 @@ src/generated/**/*.json text eol=lf
|
||||
src/generated_test/**/.cache/cache text eol=lf
|
||||
src/generated_test/**/*.json text eol=lf
|
||||
|
||||
# We force eclipse to use unix line endings
|
||||
ide/eclipse/template/.settings/*.prefs text eol=lf
|
||||
|
||||
+7
-9
@@ -13,9 +13,6 @@
|
||||
/classes
|
||||
*.iml
|
||||
|
||||
# We however WANT the ide folder as it has tempaltes/configs for developing Forge.
|
||||
!ide/**
|
||||
|
||||
#gradle
|
||||
/build
|
||||
/.gradle
|
||||
@@ -26,6 +23,7 @@
|
||||
/projects/mcp/
|
||||
/projects/clean/
|
||||
/projects/forge/
|
||||
/projects/fmlonly/
|
||||
#/patches/*
|
||||
|
||||
#occupational hazards
|
||||
@@ -34,14 +32,14 @@
|
||||
/projects/**/run/
|
||||
/projects/**/*.launch
|
||||
/repo/
|
||||
src/generated/resources/**/.cache/
|
||||
src/generated_test/resources/**/.cache/
|
||||
src/generated_mdk/resources/**/.cache/
|
||||
src/generated/resources/.cache/
|
||||
src/generated_test/resources/.cache/
|
||||
src/generated_mdk/resources/.cache/
|
||||
|
||||
#Patch rejects
|
||||
#/patches-/
|
||||
#*.patch.rej
|
||||
#/projects/*/rejects/
|
||||
/patches-/
|
||||
*.patch.rej
|
||||
/projects/*/rejects/
|
||||
|
||||
# FML changelog
|
||||
changelog.txt
|
||||
|
||||
Vendored
+15
@@ -47,6 +47,8 @@ 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)
|
||||
@@ -74,6 +76,19 @@ 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({
|
||||
|
||||
Vendored
+125
@@ -0,0 +1,125 @@
|
||||
@Library('forge-shared-library')_
|
||||
|
||||
pipeline {
|
||||
options {
|
||||
disableConcurrentBuilds()
|
||||
}
|
||||
agent {
|
||||
docker {
|
||||
image 'gradle:7-jdk16'
|
||||
//args '-v forgegc:/home/gradle/.gradle_cache/'
|
||||
}
|
||||
}
|
||||
environment {
|
||||
GRADLE_ARGS = '--no-daemon --console=plain' // No daemon for now as FG3 kinda derps. //'-Dorg.gradle.daemon.idletimeout=5000'
|
||||
DISCORD_WEBHOOK = credentials('forge-discord-jenkins-webhook')
|
||||
DISCORD_PREFIX = "Job: Forge Branch: ${BRANCH_NAME} Build: #${BUILD_NUMBER}"
|
||||
JENKINS_HEAD = 'https://wiki.jenkins-ci.org/download/attachments/2916393/headshot.png'
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('notify_start') {
|
||||
when {
|
||||
not {
|
||||
changeRequest()
|
||||
}
|
||||
}
|
||||
steps {
|
||||
discordSend(
|
||||
title: "${DISCORD_PREFIX} Started",
|
||||
successful: true,
|
||||
result: 'ABORTED', //White border
|
||||
thumbnail: JENKINS_HEAD,
|
||||
webhookURL: DISCORD_WEBHOOK
|
||||
)
|
||||
}
|
||||
}
|
||||
/*
|
||||
stage('pull_cache') {
|
||||
steps {
|
||||
sh 'cp -r /home/gradle/.gradle_cache/jdks/ /home/gradle/.gradle/jdks/'
|
||||
sh 'cp -r /home/gradle/.gradle_cache/wrapper/ /home/gradle/.gradle/wrapper/'
|
||||
sh 'cp -r /home/gradle/.gradle_cache/caches/ /home/gradle/.gradle/caches/'
|
||||
}
|
||||
}
|
||||
*/
|
||||
stage('setup') {
|
||||
steps {
|
||||
withGradle {
|
||||
sh './gradlew ${GRADLE_ARGS} --refresh-dependencies --continue setup'
|
||||
}
|
||||
script {
|
||||
env.MYVERSION = sh(returnStdout: true, script: './gradlew :forge:properties -q | grep "^version:" | cut -d" " -f2').trim()
|
||||
env.FMLONLY_VERSION = sh(returnStdout: true, script: './gradlew :fmlonly:properties -q | grep "^version:" | cut -d" " -f2').trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('changelog') {
|
||||
when {
|
||||
not {
|
||||
changeRequest()
|
||||
}
|
||||
}
|
||||
steps {
|
||||
writeChangelog(currentBuild, 'build/changelog.txt')
|
||||
}
|
||||
}
|
||||
stage('publish') {
|
||||
when {
|
||||
not {
|
||||
changeRequest()
|
||||
}
|
||||
}
|
||||
environment {
|
||||
CROWDIN = credentials('forge-crowdin')
|
||||
KEYSTORE = credentials('forge-jenkins-keystore-old')
|
||||
KEYSTORE_KEYPASS = credentials('forge-jenkins-keystore-old-keypass')
|
||||
KEYSTORE_STOREPASS = credentials('forge-jenkins-keystore-old-keypass')
|
||||
}
|
||||
steps {
|
||||
withCredentials([usernamePassword(credentialsId: 'maven-forge-user', usernameVariable: 'MAVEN_USER', passwordVariable: 'MAVEN_PASSWORD')]) {
|
||||
withGradle {
|
||||
sh './gradlew ${GRADLE_ARGS} publish -PkeystoreKeyPass=${KEYSTORE_KEYPASS} -PkeystoreStorePass=${KEYSTORE_STOREPASS} -Pkeystore=${KEYSTORE} -PcrowdinKey=${CROWDIN}'
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
success {
|
||||
build job: 'filegenerator', parameters: [string(name: 'COMMAND', value: "promote net.minecraftforge:forge ${env.MYVERSION} latest")], propagate: false, wait: false
|
||||
build job: 'filegenerator', parameters: [string(name: 'COMMAND', value: "promote net.minecraftforge:fmlonly ${env.FMLONLY_VERSION} latest")], propagate: false, wait: false
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('test_publish_pr') { //Publish to local repo to test full process, but don't include credentials so it can't sign/publish to maven
|
||||
when {
|
||||
changeRequest()
|
||||
}
|
||||
environment {
|
||||
CROWDIN = credentials('forge-crowdin')
|
||||
}
|
||||
steps {
|
||||
withGradle {
|
||||
sh './gradlew ${GRADLE_ARGS} publish -PcrowdinKey=${CROWDIN}'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
script {
|
||||
archiveArtifacts artifacts: 'projects/forge/build/libs/**/*.*', fingerprint: true, onlyIfSuccessful: true, allowEmptyArchive: true
|
||||
|
||||
if (env.CHANGE_ID == null) { // This is unset for non-PRs
|
||||
discordSend(
|
||||
title: "${DISCORD_PREFIX} Finished ${currentBuild.currentResult}",
|
||||
description: '```\n' + getChanges(currentBuild) + '\n```',
|
||||
successful: currentBuild.resultIsBetterOrEqualTo("SUCCESS"),
|
||||
result: currentBuild.currentResult,
|
||||
thumbnail: JENKINS_HEAD,
|
||||
webhookURL: DISCORD_WEBHOOK
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,9 +57,6 @@ This software contains a partial repackaging of javaxdelta, a BSD licensed progr
|
||||
binary differences and applying them, sourced from the subversion at http://sourceforge.net/projects/javaxdelta/
|
||||
authored by genman, heikok, pivot.
|
||||
The only changes are to replace some Trove collection types with standard Java collections, and repackaged.
|
||||
|
||||
This software includes the Monocraft font from https://github.com/IdreesInc/Monocraft/ for use in the early loading
|
||||
display.
|
||||
=========================================================================
|
||||
|
||||
|
||||
|
||||
+803
-330
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 'net.minecraftforge:srgutils:0.5.+'
|
||||
implementation 'commons-io:commons-io:2.12.0'
|
||||
implementation 'com.google.code.gson:gson:2.10.1'
|
||||
implementation 'commons-io:commons-io:2.8.0'
|
||||
implementation 'com.google.code.gson:gson:2.10'
|
||||
implementation 'org.eclipse.jgit:org.eclipse.jgit:6.5.0.202303070854-r'
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import groovy.json.JsonBuilder
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.tasks.InputFile
|
||||
import org.gradle.api.tasks.Internal
|
||||
import org.gradle.api.tasks.OutputFile
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.objectweb.asm.tree.ClassNode
|
||||
import org.objectweb.asm.tree.FieldNode
|
||||
@@ -12,9 +12,7 @@ import org.objectweb.asm.tree.MethodNode
|
||||
|
||||
abstract class BytecodeFinder extends DefaultTask {
|
||||
@InputFile abstract RegularFileProperty getJar()
|
||||
// It should be fine to mark the output as internal as we want to control when we run it anyways.
|
||||
// This also shuts Gradle 8 up about implicit task dependencies.
|
||||
@Internal abstract RegularFileProperty getOutput()
|
||||
@OutputFile abstract RegularFileProperty getOutput()
|
||||
|
||||
BytecodeFinder() {
|
||||
output.convention(project.layout.buildDirectory.dir(name).map { it.file("output.json") })
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks;
|
||||
|
||||
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.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.Optional
|
||||
import org.gradle.api.tasks.OutputFile
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
abstract class Crowdin extends DefaultTask {
|
||||
@Input abstract Property<String> getId()
|
||||
@Input @Optional abstract Property<String> getKey()
|
||||
@Input boolean json = true
|
||||
@OutputFile abstract RegularFileProperty getOutput()
|
||||
@OutputFile abstract RegularFileProperty getExport()
|
||||
|
||||
Crowdin() {
|
||||
outputs.upToDateWhen{ false }
|
||||
id.convention('minecraft-forge')
|
||||
output.convention(project.layout.buildDirectory.dir(name).map {it.file("output.zip") })
|
||||
export.convention(project.layout.buildDirectory.dir(name).map {it.file("export.json") })
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
def run() {
|
||||
File outputFile = output.get().asFile
|
||||
File exportFile = export.get().asFile
|
||||
if (outputFile.exists())
|
||||
outputFile.delete()
|
||||
|
||||
if (!key.isPresent())
|
||||
return
|
||||
String key = this.key.get()
|
||||
String id = this.id.get()
|
||||
|
||||
// Force an export
|
||||
new URL("https://api.crowdin.com/api/project/${id}/export?key=${key}").withInputStream { i ->
|
||||
exportFile.withOutputStream { it << i }
|
||||
}
|
||||
|
||||
if (!exportFile.text.contains('success')) {
|
||||
throw new RuntimeException("Crowdin export failed, see ${exportFile} for more info")
|
||||
}
|
||||
|
||||
new URL("https://api.crowdin.com/api/project/${id}/download/all.zip?key=${key}").withInputStream { i ->
|
||||
new ZipInputStream(i).withCloseable { zin ->
|
||||
outputFile.withOutputStream { out ->
|
||||
new ZipOutputStream(out).withCloseable { zout ->
|
||||
ZipEntry zein
|
||||
while ((zein = zin.nextEntry) != null) {
|
||||
if (zein.isDirectory()) {
|
||||
zout.putNextEntry(new ZipEntry(zein.name))
|
||||
} else {
|
||||
// 1.13+ uses json
|
||||
if (zein.name.endsWith('.json') == json) {
|
||||
ZipEntry zeout = new ZipEntry(json ? zein.name.toLowerCase() : zein.name)
|
||||
zeout.time = 1
|
||||
zout.putNextEntry(zeout)
|
||||
zout << zin
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package net.minecraftforge.forge.tasks
|
||||
|
||||
import org.gradle.api.tasks.*
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import groovy.json.JsonSlurper
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipFile
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
abstract class DevInstallerJar extends DefaultTask {
|
||||
@OutputFile abstract RegularFileProperty getOutput()
|
||||
@InputFile abstract RegularFileProperty getInput()
|
||||
|
||||
DevInstallerJar() {
|
||||
output.convention(project.layout.buildDirectory.file('libs/' + project.name + '-' + project.version + '-installer-dev.jar'))
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
protected void exec() {
|
||||
try (def zos = new ZipOutputStream(output.get().asFile.newDataOutputStream())) {
|
||||
def seen = [] as Set
|
||||
def deps = [:] as java.util.TreeMap
|
||||
try (def base = new ZipFile(input.get().asFile)) {
|
||||
base.entries().each { entry ->
|
||||
zos.putNextEntry(entry)
|
||||
def data = base.getInputStream(entry).bytes
|
||||
zos.write(data)
|
||||
|
||||
if (entry.name == 'install_profile.json' || entry.name == 'version.json') {
|
||||
def json = new JsonSlurper().parse(data)
|
||||
json.libraries.each { lib ->
|
||||
deps.put(lib.name, lib.downloads.artifact)
|
||||
}
|
||||
}
|
||||
|
||||
if (entry.name.startsWith('maven/'))
|
||||
seen.add(entry.name.substring(6))
|
||||
}
|
||||
}
|
||||
|
||||
def resolved = project.configurations.installer.resolvedConfiguration.resolvedArtifacts
|
||||
|
||||
deps.each { name, info ->
|
||||
if (seen.contains(info.path))
|
||||
return
|
||||
|
||||
def art = resolved.find { "${it.moduleVersion.id.group}:${it.moduleVersion.id.name}:${it.moduleVersion.id.version}" == name }
|
||||
|
||||
def file = null
|
||||
if (art != null) {
|
||||
if (!Util.checkExists(info.url))
|
||||
file = art.file
|
||||
} else {
|
||||
// TODO download missing things
|
||||
}
|
||||
|
||||
if (file != null) {
|
||||
//println('maven/' + info.path)
|
||||
zos.putNextEntry(new ZipEntry('maven/' + info.path))
|
||||
zos.write(file.bytes)
|
||||
seen.add(info.path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ class TeamcityRequests {
|
||||
@Nullable
|
||||
static Map<String, Build> buildsByCommit() throws IOException {
|
||||
final Map<String, Build> builds = [:]
|
||||
jsonRequest(new TypeToken<Builds>() {}, 'https://teamcity.minecraftforge.net/guestAuth/app/rest/builds?fields=build:(revisions,number)&locator=buildType:(id:MinecraftForge_MinecraftForge_MinecraftForge_MinecraftForge__Build),defaultFilter:false,count:300,status:SUCCESS')
|
||||
jsonRequest(new TypeToken<Builds>() {}, "https://teamcity.minecraftforge.net/guestAuth/app/rest/builds?fields=build:(revisions,number)&locator=buildType:(id:MinecraftForge_MinecraftForge_MinecraftForge_MinecraftForge__Build),count:300,status:SUCCESS")
|
||||
?.build?.forEach {
|
||||
it.revisions.revision.each { rev -> builds[rev.version] = it }
|
||||
}
|
||||
|
||||
@@ -81,6 +81,14 @@ public class Util {
|
||||
if (!checkExists(url)) {
|
||||
url = "https://maven.minecraftforge.net/${path}"
|
||||
}
|
||||
//TODO remove when Mojang launcher is updated
|
||||
if (!classifiers && art.classifier != null) {
|
||||
//Mojang launcher doesn't currently support classifiers, so... move it to part of the version, and force the extension to 'jar'
|
||||
// However, keep the path normal so that our mirror system works.
|
||||
art.version = "${art.version}-${art.classifier}"
|
||||
art.classifier = null
|
||||
art.extension = 'jar'
|
||||
}
|
||||
ret[key] = [
|
||||
name: "${art.group}:${art.name}:${art.version}" + (art.classifier == null ? '' : ":${art.classifier}") + (art.extension == 'jar' ? '' : "@${art.extension}"),
|
||||
downloads: [
|
||||
@@ -127,7 +135,7 @@ public class Util {
|
||||
return getArtifacts(project, cfg, true)
|
||||
}
|
||||
|
||||
static boolean checkExists(url) {
|
||||
private static boolean checkExists(url) {
|
||||
try {
|
||||
def code = new URL(url).openConnection().with {
|
||||
requestMethod = 'HEAD'
|
||||
|
||||
@@ -171,7 +171,7 @@ abstract class CheckATs extends CheckTask {
|
||||
}
|
||||
|
||||
private static List<String> joinBack(TreeMap<String, ATParser.Entry> lines, Map<String, InheritanceData> inheritance, IMappingFile mappings) {
|
||||
final data = [] as List<String>
|
||||
final data = []
|
||||
final remapComment = { ATParser.Entry entry ->
|
||||
if (!mappings || !entry || !entry.desc) return null
|
||||
final comment = entry.comment?.substring(1)?.trim()
|
||||
|
||||
@@ -36,7 +36,7 @@ abstract class CheckTask extends DefaultTask implements VerificationTask {
|
||||
final doFix = getMode().get() === CheckMode.FIX
|
||||
final Reporter reporter = new Reporter(doFix)
|
||||
check(reporter, doFix)
|
||||
|
||||
|
||||
if (reporter.messages) {
|
||||
if (getMode().get() === CheckMode.CHECK) {
|
||||
logger.error("Check task '{}' found errors:\n{}", name, reporter.messages.join('\n'))
|
||||
@@ -91,20 +91,18 @@ abstract class CheckTask extends DefaultTask implements VerificationTask {
|
||||
@DelegatesTo(genericTypeIndex = 0, target = 'type') Closure configuration) {
|
||||
taskName = taskName.capitalize()
|
||||
tasks.register("check$taskName", clazz) { CheckTask task ->
|
||||
def castedTask = task as T
|
||||
configuration.setDelegate(castedTask)
|
||||
configuration.call(castedTask)
|
||||
castedTask.mode.set(CheckMode.CHECK)
|
||||
castedTask.group = 'checks'
|
||||
configuration.setDelegate((T) task)
|
||||
configuration.call(task)
|
||||
task.mode.set(CheckMode.CHECK)
|
||||
task.group = 'checks'
|
||||
}
|
||||
tasks.named('checkAll').configure { it.dependsOn("check$taskName") }
|
||||
|
||||
tasks.register("checkAndFix$taskName", clazz) { CheckTask task ->
|
||||
def castedTask = task as T
|
||||
configuration.setDelegate(castedTask)
|
||||
configuration.call(castedTask)
|
||||
castedTask.mode.set(CheckMode.FIX)
|
||||
castedTask.group = 'checks'
|
||||
configuration.setDelegate((T) task)
|
||||
configuration.call(task)
|
||||
task.mode.set(CheckMode.FIX)
|
||||
task.group = 'checks'
|
||||
}
|
||||
tasks.named('checkAllAndFix').configure { it.dependsOn("checkAndFix$taskName") }
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
plugins {
|
||||
id 'java-library'
|
||||
id 'eclipse'
|
||||
id 'net.minecraftforge.gradle.patcher' version '[6.0.16,6.2)'
|
||||
}
|
||||
|
||||
evaluationDependsOn(':mcp')
|
||||
|
||||
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')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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' }
|
||||
src.entryAttributes['ignore_optional_problems'] = 'true'
|
||||
}
|
||||
+4
-3
@@ -2,8 +2,8 @@
|
||||
|
||||
MinecraftForge
|
||||
=============
|
||||
[][Download]
|
||||
[][Download]
|
||||
[][Download] [][Discord] [][Patreon]
|
||||
|
||||
Forge is a free, open-source modding API all of your favourite mods use!
|
||||
@@ -11,7 +11,8 @@ Forge is a free, open-source modding API all of your favourite mods use!
|
||||
| Version | Support |
|
||||
|---------| ------------- |
|
||||
| 1.20.x | Active |
|
||||
| 1.19.x | Legacy |
|
||||
| 1.19.4 | Legacy |
|
||||
| 1.19.2 | Legacy |
|
||||
|
||||
* [Download]
|
||||
* [Forum]
|
||||
|
||||
@@ -2,19 +2,20 @@ plugins {
|
||||
id 'com.github.ben-manes.versions'
|
||||
id 'org.javamodularity.moduleplugin' version '1.8.3' apply false
|
||||
id 'org.cadixdev.licenser'
|
||||
id 'java-library'
|
||||
id 'jacoco'
|
||||
}
|
||||
apply plugin: 'java-library'
|
||||
apply plugin: 'jacoco'
|
||||
apply plugin: 'org.javamodularity.moduleplugin'
|
||||
|
||||
dependencyUpdates.rejectVersionIf { isNonStable(it.candidate.version) }
|
||||
|
||||
dependencies {
|
||||
compileOnly(libs.nulls)
|
||||
compileOnly("org.jetbrains:annotations:${JETBRAINS_ANNOTATIONS_VERSION}")
|
||||
|
||||
api(libs.eventbus)
|
||||
api("net.minecraftforge:eventbus:${EVENTBUS_VERSION}")
|
||||
|
||||
implementation(project(':fmlloader'))
|
||||
implementation(libs.commons.io)
|
||||
implementation("commons-io:commons-io:${COMMONS_IO_VERSION}")
|
||||
}
|
||||
|
||||
task sourcesJar(type: Jar) {
|
||||
@@ -43,7 +44,6 @@ ext {
|
||||
}
|
||||
|
||||
jar.doFirst {
|
||||
manifest.attributes['Automatic-Module-Name'] = project.name
|
||||
MANIFESTS.each { pkg, values ->
|
||||
if (pkg == '')
|
||||
manifest.attributes(values)
|
||||
|
||||
@@ -66,7 +66,7 @@ public class DeferredWorkQueue
|
||||
timer,
|
||||
aggregate
|
||||
);
|
||||
throw aggregate;
|
||||
//TODO 1.20: throw aggregate exception
|
||||
} else {
|
||||
LOGGER.debug(LOADING, "Synchronous work queue completed in {}", timer);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ import java.util.function.Supplier;
|
||||
* @param <T> the type of the record which is held by the extension point
|
||||
*/
|
||||
@SuppressWarnings("unused") // Type parameter T
|
||||
// TODO: Complete re-write, this hasnt actually be used in a generic way by mods and we could make it a lot simpler.
|
||||
public interface IExtensionPoint<T extends Record>
|
||||
{
|
||||
/**
|
||||
@@ -41,7 +40,7 @@ public interface IExtensionPoint<T extends Record>
|
||||
* where {@code true} means it is from the server and {@code false} means it is from the world save. The return
|
||||
* value of the predicate determines whether the remote version is "compatible" for the purposes of the display test.</p>
|
||||
*
|
||||
* <p>The local compatibility version may be of the value {@link DisplayTest#IGNORESERVERONLY},
|
||||
* <p>The local compatibility version may be of the value {@link net.minecraftforge.network.NetworkConstants#IGNORESERVERONLY},
|
||||
* in which case clients will ignore the mod's presence if it is present on the server but not on the client.
|
||||
* However, the remote version test predicate must still accept this value as a remote version in order to display
|
||||
* as compatible if the mod is present on the client.</p>
|
||||
@@ -95,6 +94,6 @@ public interface IExtensionPoint<T extends Record>
|
||||
*/
|
||||
@SuppressWarnings("JavadocReference") // reference to NetworkConstants, ForgeHooksClient
|
||||
record DisplayTest(Supplier<String> suppliedVersion, BiPredicate<String, Boolean> remoteVersionTest) implements IExtensionPoint<DisplayTest> {
|
||||
public static final String IGNORESERVERONLY = "SERVER_ONLY";
|
||||
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";
|
||||
}
|
||||
}
|
||||
@@ -7,14 +7,12 @@ package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.fml.loading.progress.ProgressMeter;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.ToIntFunction;
|
||||
|
||||
/**
|
||||
* A mod loading state. During mod loading, the mod loader transitions between states in a defined sorted list of states,
|
||||
@@ -46,33 +44,25 @@ public interface IModLoadingState {
|
||||
*/
|
||||
Function<ModList, String> message();
|
||||
|
||||
/**
|
||||
* @return a function that computes the size of this transition based on the size of the modlist.
|
||||
* Used to compute progress.
|
||||
*/
|
||||
ToIntFunction<ModList> size();
|
||||
|
||||
/**
|
||||
* {@return an optional runnable, which runs before starting the transition from this state to the next}
|
||||
* @see #buildTransition(Executor, Executor, ProgressMeter, Function, Function)
|
||||
* @see #buildTransition(Executor, Executor, Function, Function)
|
||||
*/
|
||||
Optional<Consumer<ModList>> inlineRunnable();
|
||||
|
||||
/**
|
||||
* Builds the transition task for this state with a blank pre-sync and post-sync task.
|
||||
*
|
||||
* @param <T> a type of event fired on the mod-specific event bus
|
||||
* @param syncExecutor a synchronous executor
|
||||
* @param syncExecutor a synchronous executor
|
||||
* @param parallelExecutor a parallel executor
|
||||
* @param progressBar a progress meter for tracking progress
|
||||
* @param <T> a type of event fired on the mod-specific event bus
|
||||
* @return a transition task for this state
|
||||
* @see #buildTransition(Executor, Executor, ProgressMeter, Function, Function)
|
||||
* @see #buildTransition(Executor, Executor, Function, Function)
|
||||
*/
|
||||
default <T extends Event & IModBusEvent>
|
||||
Optional<CompletableFuture<Void>> buildTransition(final Executor syncExecutor,
|
||||
final Executor parallelExecutor,
|
||||
final ProgressMeter progressBar) {
|
||||
return buildTransition(syncExecutor, parallelExecutor, progressBar,
|
||||
final Executor parallelExecutor) {
|
||||
return buildTransition(syncExecutor, parallelExecutor,
|
||||
e -> CompletableFuture.runAsync(() -> {}, e),
|
||||
e -> CompletableFuture.runAsync(() -> {}, e));
|
||||
}
|
||||
@@ -81,18 +71,16 @@ public interface IModLoadingState {
|
||||
* Builds the transition task for this state. The pre-sync and post-sync task functions allow the transition builder
|
||||
* to run these tasks on the same executor as the actual event dispatch and pre/post hooks.
|
||||
*
|
||||
* @param <T> a type of event fired on the mod-specific event bus
|
||||
* @param syncExecutor a synchronous executor
|
||||
* @param syncExecutor a synchronous executor
|
||||
* @param parallelExecutor a parallel executor
|
||||
* @param progressBar a progress meter for tracking progress
|
||||
* @param preSyncTask a function which returns a task to run before event pre-dispatch hook
|
||||
* @param postSyncTask a function which returns a task to run after event post-dispatch hook
|
||||
* @param preSyncTask a function which returns a task to run before event pre-dispatch hook
|
||||
* @param postSyncTask a function which returns a task to run after event post-dispatch hook
|
||||
* @param <T> a type of event fired on the mod-specific event bus
|
||||
* @return a transition task for this state
|
||||
*/
|
||||
<T extends Event & IModBusEvent>
|
||||
Optional<CompletableFuture<Void>> buildTransition(final Executor syncExecutor,
|
||||
final Executor parallelExecutor,
|
||||
final ProgressMeter progressBar,
|
||||
final Function<Executor, CompletableFuture<Void>> preSyncTask,
|
||||
final Function<Executor, CompletableFuture<Void>> postSyncTask);
|
||||
}
|
||||
@@ -7,7 +7,6 @@ package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.fml.loading.progress.ProgressMeter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -25,26 +24,20 @@ public interface IModStateTransition {
|
||||
}
|
||||
|
||||
default <T extends Event & IModBusEvent>
|
||||
CompletableFuture<Void> build(final String name,
|
||||
final Executor syncExecutor,
|
||||
CompletableFuture<Void> build(final Executor syncExecutor,
|
||||
final Executor parallelExecutor,
|
||||
final ProgressMeter progressBar,
|
||||
final Function<Executor, CompletableFuture<Void>> preSyncTask,
|
||||
final Function<Executor, CompletableFuture<Void>> postSyncTask) {
|
||||
List<CompletableFuture<Void>> futures = new ArrayList<>();
|
||||
|
||||
this.eventFunctionStream().get()
|
||||
.map(f -> (EventGenerator<T>) f)
|
||||
.reduce((head, tail) -> addCompletableFutureTaskForModDispatch(syncExecutor, parallelExecutor, futures, progressBar, head, ModLoadingStage::currentState, tail))
|
||||
.ifPresent(last -> addCompletableFutureTaskForModDispatch(syncExecutor, parallelExecutor, futures, progressBar, last, nextModLoadingStage(), null));
|
||||
.reduce((head, tail) -> addCompletableFutureTaskForModDispatch(syncExecutor, parallelExecutor, futures, head, ModLoadingStage::currentState, tail))
|
||||
.ifPresent(last -> addCompletableFutureTaskForModDispatch(syncExecutor, parallelExecutor, futures, last, nextModLoadingStage(), null));
|
||||
|
||||
final CompletableFuture<Void> preSyncTaskCF = preSyncTask.apply(syncExecutor);
|
||||
final CompletableFuture<Void> eventDispatchCF = ModList.gather(futures).thenCompose(ModList::completableFutureFromExceptionList);
|
||||
final CompletableFuture<Void> postEventDispatchCF = preSyncTaskCF
|
||||
.thenApplyAsync(n -> {
|
||||
progressBar.label(progressBar.name() + ": dispatching "+name);
|
||||
return null;
|
||||
}, parallelExecutor)
|
||||
.thenComposeAsync(n -> eventDispatchCF, parallelExecutor)
|
||||
.thenApply(r -> {
|
||||
postSyncTask.apply(syncExecutor);
|
||||
@@ -61,7 +54,6 @@ public interface IModStateTransition {
|
||||
EventGenerator<T> addCompletableFutureTaskForModDispatch(final Executor syncExecutor,
|
||||
final Executor parallelExecutor,
|
||||
final List<CompletableFuture<Void>> completableFutures,
|
||||
final ProgressMeter progressBar,
|
||||
final EventGenerator<T> eventGenerator,
|
||||
final BiFunction<ModLoadingStage, Throwable, ModLoadingStage> nextState,
|
||||
final EventGenerator<T> nextGenerator) {
|
||||
@@ -70,7 +62,7 @@ public interface IModStateTransition {
|
||||
var preDispatchHook = (BiFunction<Executor, EventGenerator<T>, CompletableFuture<Void>>) preDispatchHook();
|
||||
completableFutures.add(preDispatchHook.apply(selectedExecutor, eventGenerator));
|
||||
|
||||
completableFutures.add(ModList.get().futureVisitor(eventGenerator, progressBar, nextState).apply(threadSelector().apply(syncExecutor, parallelExecutor)));
|
||||
completableFutures.add(ModList.get().futureVisitor(eventGenerator, nextState).apply(threadSelector().apply(syncExecutor, parallelExecutor)));
|
||||
|
||||
var postDispatchHook = (BiFunction<Executor, EventGenerator<T>, CompletableFuture<Void>>) postDispatchHook();
|
||||
completableFutures.add(postDispatchHook.apply(selectedExecutor, eventGenerator));
|
||||
|
||||
@@ -9,7 +9,6 @@ import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.config.IConfigEvent;
|
||||
import net.minecraftforge.fml.config.ModConfig;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.fml.loading.progress.ProgressMeter;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
|
||||
import java.util.EnumMap;
|
||||
@@ -114,7 +113,6 @@ public abstract class ModContainer
|
||||
public static <T extends Event & IModBusEvent> CompletableFuture<Void> buildTransitionHandler(
|
||||
final ModContainer target,
|
||||
final IModStateTransition.EventGenerator<T> eventGenerator,
|
||||
final ProgressMeter progressBar,
|
||||
final BiFunction<ModLoadingStage, Throwable, ModLoadingStage> stateChangeHandler,
|
||||
final Executor executor) {
|
||||
return CompletableFuture
|
||||
@@ -125,7 +123,6 @@ public abstract class ModContainer
|
||||
}, executor)
|
||||
.whenComplete((mc, exception) -> {
|
||||
target.modLoadingStage = stateChangeHandler.apply(target.modLoadingStage, exception);
|
||||
progressBar.increment();
|
||||
ModLoadingContext.get().setActiveContainer(null);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.fml.loading.progress.ProgressMeter;
|
||||
import net.minecraftforge.forgespi.language.IModFileInfo;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import net.minecraftforge.forgespi.language.ModFileScanData;
|
||||
@@ -115,11 +114,10 @@ public class ModList
|
||||
|
||||
<T extends Event & IModBusEvent> Function<Executor, CompletableFuture<Void>> futureVisitor(
|
||||
final IModStateTransition.EventGenerator<T> eventGenerator,
|
||||
final ProgressMeter progressBar,
|
||||
final BiFunction<ModLoadingStage, Throwable, ModLoadingStage> stateChange) {
|
||||
return executor -> gather(
|
||||
this.mods.stream()
|
||||
.map(mod -> ModContainer.buildTransitionHandler(mod, eventGenerator, progressBar, stateChange, executor))
|
||||
.map(mod -> ModContainer.buildTransitionHandler(mod, eventGenerator, stateChange, executor))
|
||||
.collect(Collectors.toList()))
|
||||
.thenComposeAsync(ModList::completableFutureFromExceptionList, executor);
|
||||
}
|
||||
|
||||
@@ -10,13 +10,11 @@ import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.fml.loading.FMLEnvironment;
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
import net.minecraftforge.fml.loading.ImmediateWindowHandler;
|
||||
import net.minecraftforge.fml.loading.LoadingModList;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.InvalidModIdentifier;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
|
||||
import net.minecraftforge.fml.loading.progress.ProgressMeter;
|
||||
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
|
||||
import net.minecraftforge.fml.loading.progress.StartupMessageManager;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import net.minecraftforge.forgespi.language.IModLanguageProvider;
|
||||
import net.minecraftforge.forgespi.locating.ForgeFeature;
|
||||
@@ -83,7 +81,7 @@ public class ModLoader
|
||||
private final ModStateManager stateManager;
|
||||
private boolean loadingStateValid;
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
private final Optional<Consumer<String>> statusConsumer = StartupNotificationManager.modLoaderConsumer();
|
||||
private final Optional<Consumer<String>> statusConsumer = StartupMessageManager.modLoaderConsumer();
|
||||
private final Set<IModLoadingState> completedStates = new HashSet<>();
|
||||
private ModList modList;
|
||||
|
||||
@@ -135,19 +133,20 @@ public class ModLoader
|
||||
* @param periodicTask Optional periodic task to perform on the main thread while other activities run
|
||||
*/
|
||||
public void gatherAndInitializeMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
|
||||
ForgeFeature.registerFeature("javaVersion", ForgeFeature.VersionFeatureTest.forVersionString(IModInfo.DependencySide.BOTH, System.getProperty("java.version")));
|
||||
ForgeFeature.registerFeature("openGLVersion", ForgeFeature.VersionFeatureTest.forVersionString(IModInfo.DependencySide.CLIENT, ImmediateWindowHandler.getGLVersion()));
|
||||
ForgeFeature.registerFeature("java_version", ForgeFeature.VersionFeatureTest.forVersionString(IModInfo.DependencySide.SERVER, System.getProperty("java.version")));
|
||||
loadingStateValid = true;
|
||||
statusConsumer.ifPresent(c->c.accept("Waiting for scan to complete"));
|
||||
FMLLoader.backgroundScanHandler.waitForScanToComplete(periodicTask);
|
||||
statusConsumer.ifPresent(c->c.accept("Loading mods"));
|
||||
final ModList modList = ModList.of(loadingModList.getModFiles().stream().map(ModFileInfo::getFile).toList(),
|
||||
loadingModList.getMods());
|
||||
if (!this.loadingExceptions.isEmpty()) {
|
||||
LOGGER.fatal(CORE, "Error during pre-loading phase", loadingExceptions.get(0));
|
||||
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
|
||||
modList.setLoadedMods(Collections.emptyList());
|
||||
loadingStateValid = false;
|
||||
throw new LoadingFailedException(loadingExceptions);
|
||||
}
|
||||
statusConsumer.ifPresent(c->c.accept("Validating features"));
|
||||
List<? extends ForgeFeature.Bound> failedBounds = loadingModList.getMods().stream()
|
||||
.map(ModInfo::getForgeFeatures)
|
||||
.flatMap(Collection::stream)
|
||||
@@ -155,15 +154,15 @@ public class ModLoader
|
||||
.toList();
|
||||
|
||||
if (!failedBounds.isEmpty()) {
|
||||
LOGGER.fatal(CORE, "Failed to validate feature bounds for mods: {}", failedBounds);
|
||||
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
|
||||
LOGGER.fatal(CORE, "Failed to validate feature bounds for mods");
|
||||
modList.setLoadedMods(Collections.emptyList());
|
||||
loadingStateValid = false;
|
||||
throw new LoadingFailedException(failedBounds.stream()
|
||||
.map(fb -> new ModLoadingException(fb.modInfo(), ModLoadingStage.CONSTRUCT, "fml.modloading.feature.missing", null, fb, ForgeFeature.featureValue(fb)))
|
||||
.map(fb -> new ModLoadingException(fb.modInfo(), ModLoadingStage.CONSTRUCT, "fml.modloading.feature.missing", null, fb.featureName(), fb.featureBound(), ForgeFeature.featureValue(fb)))
|
||||
.toList());
|
||||
}
|
||||
|
||||
statusConsumer.ifPresent(c->c.accept("Building Mod List"));
|
||||
final List<ModContainer> modContainers = loadingModList.getModFiles().stream()
|
||||
.map(ModFileInfo::getFile)
|
||||
.map(this::buildMods)
|
||||
@@ -171,54 +170,51 @@ public class ModLoader
|
||||
.toList();
|
||||
if (!loadingExceptions.isEmpty()) {
|
||||
LOGGER.fatal(CORE, "Failed to initialize mod containers", loadingExceptions.get(0));
|
||||
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
|
||||
modList.setLoadedMods(Collections.emptyList());
|
||||
loadingStateValid = false;
|
||||
throw new LoadingFailedException(loadingExceptions);
|
||||
}
|
||||
modList.setLoadedMods(modContainers);
|
||||
this.modList = modList;
|
||||
var stateList = stateManager.getStates(ModLoadingPhase.GATHER);
|
||||
var progress = StartupMessageManager.addProgressBar("Mod Gather", stateList.stream().mapToInt(mls -> mls.size().applyAsInt(modList)).sum());
|
||||
stateList.forEach(mls->dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress));
|
||||
progress.complete();
|
||||
statusConsumer.ifPresent(c->c.accept("Dispatching gathering events"));
|
||||
stateManager.getStates(ModLoadingPhase.GATHER).forEach(mls->dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask));
|
||||
statusConsumer.ifPresent(c->c.accept("Gathering phase complete"));
|
||||
}
|
||||
|
||||
public void loadMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
|
||||
var stateList = stateManager.getStates(ModLoadingPhase.LOAD);
|
||||
var progress = StartupMessageManager.addProgressBar("Mod Loading", stateList.stream().mapToInt(mls -> mls.size().applyAsInt(modList)).sum());
|
||||
stateList.forEach(mls->dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress));
|
||||
progress.complete();
|
||||
statusConsumer.ifPresent(c->c.accept("Performing load phase"));
|
||||
stateManager.getStates(ModLoadingPhase.LOAD).forEach(mls->dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask));
|
||||
statusConsumer.ifPresent(c->c.accept("Mod setup complete"));
|
||||
}
|
||||
|
||||
public void finishMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
|
||||
var stateList = stateManager.getStates(ModLoadingPhase.COMPLETE);
|
||||
var progress = StartupMessageManager.addProgressBar("Mod Complete", stateList.stream().mapToInt(mls -> mls.size().applyAsInt(modList)).sum());
|
||||
stateList.forEach(mls->dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress));
|
||||
statusConsumer.ifPresent(c->c.accept("Performing completion phase"));
|
||||
stateManager.getStates(ModLoadingPhase.COMPLETE).forEach(mls->dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask));
|
||||
statusConsumer.ifPresent(c->c.accept(String.format("Mod loading complete - %d mods loaded", this.modList.size())));
|
||||
progress.complete();
|
||||
}
|
||||
|
||||
private void dispatchAndHandleError(IModLoadingState state, ModWorkManager.DrivenExecutor syncExecutor, Executor parallelExecutor, final Runnable ticker, final ProgressMeter progressBar) {
|
||||
private void dispatchAndHandleError(IModLoadingState state, ModWorkManager.DrivenExecutor syncExecutor, Executor parallelExecutor, final Runnable ticker) {
|
||||
if (!isLoadingStateValid()) {
|
||||
LOGGER.error("Cowardly refusing to process mod state change request from {}", state);
|
||||
return;
|
||||
}
|
||||
progressBar.label(progressBar.name()+ " working");
|
||||
syncExecutor.drive(ticker);
|
||||
state.inlineRunnable().ifPresent(a->this.handleInlineTransition(a, state, syncExecutor, ticker));
|
||||
state.buildTransition(syncExecutor, parallelExecutor, progressBar).ifPresent(t->waitForTransition(state, syncExecutor, ticker, t));
|
||||
statusConsumer.ifPresent(c->c.accept(state.message().apply(this.modList)));
|
||||
state.inlineRunnable().ifPresent(a->a.accept(this.modList));
|
||||
state.buildTransition(syncExecutor, parallelExecutor).ifPresent(t->waitForTransition(state, syncExecutor, ticker, t));
|
||||
completedStates.add(state);
|
||||
}
|
||||
|
||||
private void handleInlineTransition(final Consumer<ModList> transition, final IModLoadingState state, final ModWorkManager.DrivenExecutor syncExecutor, final Runnable ticker) {
|
||||
var pb = StartupMessageManager.addProgressBar("State transition " +state.name()+" running", 0);
|
||||
syncExecutor.drive(ticker);
|
||||
transition.accept(this.modList);
|
||||
syncExecutor.drive(ticker);
|
||||
pb.complete();
|
||||
syncExecutor.drive(ticker);
|
||||
private void dispatchAndHandleError(IModLoadingState state, ModWorkManager.DrivenExecutor syncExecutor, Executor parallelExecutor, final Runnable ticker, Function<Executor, CompletableFuture<Void>> preSyncTask, Function<Executor, CompletableFuture<Void>> postSyncTask) {
|
||||
if (!isLoadingStateValid()) {
|
||||
LOGGER.error("Cowardly refusing to process mod state change request from {}", state);
|
||||
return;
|
||||
}
|
||||
statusConsumer.ifPresent(c->c.accept(state.message().apply(this.modList)));
|
||||
state.inlineRunnable().ifPresent(a->a.accept(this.modList));
|
||||
state.buildTransition(syncExecutor, parallelExecutor, preSyncTask, postSyncTask).ifPresent(t->waitForTransition(state, syncExecutor, ticker, t));
|
||||
completedStates.add(state);
|
||||
}
|
||||
|
||||
private void waitForTransition(final IModLoadingState state, final ModWorkManager.DrivenExecutor syncExecutor, final Runnable ticker, final CompletableFuture<Void> transition) {
|
||||
while (!transition.isDone()) {
|
||||
syncExecutor.drive(ticker);
|
||||
@@ -233,7 +229,6 @@ public class ModLoader
|
||||
.collect(Collectors.toList());
|
||||
if (!notModLoading.isEmpty()) {
|
||||
LOGGER.fatal("Encountered non-modloading exceptions!", e);
|
||||
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
|
||||
throw e;
|
||||
}
|
||||
|
||||
@@ -242,7 +237,6 @@ public class ModLoader
|
||||
.map(ModLoadingException.class::cast)
|
||||
.collect(Collectors.toList());
|
||||
LOGGER.fatal(LOADING,"Failed to complete lifecycle event {}, {} errors found", state.name(), modLoadingExceptions.size());
|
||||
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
|
||||
throw new LoadingFailedException(modLoadingExceptions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,14 +7,12 @@ package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import net.minecraftforge.fml.loading.progress.ProgressMeter;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.ToIntFunction;
|
||||
|
||||
/**
|
||||
* Implementation of the {@link IModLoadingState} interface.
|
||||
@@ -28,7 +26,6 @@ import java.util.function.ToIntFunction;
|
||||
*/
|
||||
public record ModLoadingState(String name, String previous,
|
||||
Function<ModList, String> message,
|
||||
ToIntFunction<ModList> size,
|
||||
ModLoadingPhase phase,
|
||||
Optional<Consumer<ModList>> inlineRunnable,
|
||||
Optional<IModStateTransition> transition) implements IModLoadingState {
|
||||
@@ -36,10 +33,9 @@ public record ModLoadingState(String name, String previous,
|
||||
public <T extends Event & IModBusEvent>
|
||||
Optional<CompletableFuture<Void>> buildTransition(final Executor syncExecutor,
|
||||
final Executor parallelExecutor,
|
||||
final ProgressMeter progressBar,
|
||||
final Function<Executor, CompletableFuture<Void>> preSyncTask,
|
||||
final Function<Executor, CompletableFuture<Void>> postSyncTask) {
|
||||
return transition.map(t -> t.build(name, syncExecutor, parallelExecutor, progressBar, preSyncTask, postSyncTask));
|
||||
return transition.map(t -> t.build(syncExecutor, parallelExecutor, preSyncTask, postSyncTask));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,7 +47,7 @@ public record ModLoadingState(String name, String previous,
|
||||
* @param phase the mod loading phase the state belongs to
|
||||
*/
|
||||
public static ModLoadingState empty(final String name, final String previous, final ModLoadingPhase phase) {
|
||||
return new ModLoadingState(name, previous, ml -> "", f->0, phase, Optional.empty(), Optional.empty());
|
||||
return new ModLoadingState(name, previous, ml -> "", phase, Optional.empty(), Optional.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,7 +62,7 @@ public record ModLoadingState(String name, String previous,
|
||||
*/
|
||||
public static ModLoadingState withTransition(final String name, final String previous, final ModLoadingPhase phase,
|
||||
final IModStateTransition transition) {
|
||||
return new ModLoadingState(name, previous, ml -> "Processing transition " + name, ModList::size, phase, Optional.empty(), Optional.of(transition));
|
||||
return new ModLoadingState(name, previous, ml -> "Processing transition " + name, phase, Optional.empty(), Optional.of(transition));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,7 +78,7 @@ public record ModLoadingState(String name, String previous,
|
||||
public static ModLoadingState withTransition(final String name, final String previous,
|
||||
final Function<ModList, String> message, final ModLoadingPhase phase,
|
||||
final IModStateTransition transition) {
|
||||
return new ModLoadingState(name, previous, message, ModList::size, phase, Optional.empty(), Optional.of(transition));
|
||||
return new ModLoadingState(name, previous, message, phase, Optional.empty(), Optional.of(transition));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,6 +93,6 @@ public record ModLoadingState(String name, String previous,
|
||||
*/
|
||||
public static ModLoadingState withInline(final String name, final String previous, final ModLoadingPhase phase,
|
||||
final Consumer<ModList> inline) {
|
||||
return new ModLoadingState(name, previous, ml -> "Processing work " + name, ml->0, phase, Optional.of(inline), Optional.empty());
|
||||
return new ModLoadingState(name, previous, ml -> "Processing work " + name, phase, Optional.of(inline), Optional.empty());
|
||||
}
|
||||
}
|
||||
@@ -23,9 +23,8 @@ public class ModWorkManager {
|
||||
|
||||
default void drive(Runnable ticker) {
|
||||
if (!selfDriven()) {
|
||||
ticker.run();
|
||||
while (true) {
|
||||
if (!driveOne()) break;
|
||||
while (driveOne()) {
|
||||
ticker.run();
|
||||
}
|
||||
} else {
|
||||
// park for a bit so other threads can schedule
|
||||
@@ -94,7 +93,7 @@ public class ModWorkManager {
|
||||
private static ForkJoinPool parallelThreadPool;
|
||||
public static Executor parallelExecutor() {
|
||||
if (parallelThreadPool == null) {
|
||||
final int loadingThreadCount = FMLConfig.getIntConfigValue(FMLConfig.ConfigValue.MAX_THREADS);
|
||||
final int loadingThreadCount = FMLConfig.loadingThreadCount();
|
||||
LOGGER.debug(LOADING, "Using {} threads for parallel mod-loading", loadingThreadCount);
|
||||
parallelThreadPool = new ForkJoinPool(loadingThreadCount, ModWorkManager::newForkJoinWorkerThread, null, false);
|
||||
}
|
||||
|
||||
@@ -5,30 +5,19 @@
|
||||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import net.minecraftforge.fml.loading.progress.ProgressMeter;
|
||||
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class StartupMessageManager {
|
||||
public static void addModMessage(final String message) {
|
||||
StartupNotificationManager.addModMessage(message);
|
||||
net.minecraftforge.fml.loading.progress.StartupMessageManager.addModMessage(message);
|
||||
}
|
||||
|
||||
public static Optional<Consumer<String>> modLoaderConsumer() {
|
||||
return StartupNotificationManager.modLoaderConsumer();
|
||||
return net.minecraftforge.fml.loading.progress.StartupMessageManager.modLoaderConsumer();
|
||||
}
|
||||
|
||||
public static Optional<Consumer<String>> mcLoaderConsumer() {
|
||||
return StartupNotificationManager.mcLoaderConsumer();
|
||||
}
|
||||
|
||||
public static ProgressMeter addProgressBar(final String barName, final int count) {
|
||||
return StartupNotificationManager.addProgressBar(barName, count);
|
||||
}
|
||||
|
||||
public static ProgressMeter prependProgressBar(final String barName, final int count) {
|
||||
return StartupNotificationManager.prependProgressBar(barName, count);
|
||||
return net.minecraftforge.fml.loading.progress.StartupMessageManager.mcLoaderConsumer();
|
||||
}
|
||||
}
|
||||
@@ -104,7 +104,7 @@ public class VersionChecker
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
if (!FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.VERSION_CHECK))
|
||||
if (!FMLConfig.runVersionCheck())
|
||||
{
|
||||
LOGGER.info("Global Forge version check system disabled, no further processing.");
|
||||
return;
|
||||
|
||||
@@ -26,7 +26,7 @@ import static net.minecraftforge.fml.config.ConfigTracker.CONFIG;
|
||||
public class ConfigFileTypeHandler {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
static ConfigFileTypeHandler TOML = new ConfigFileTypeHandler();
|
||||
private static final Path defaultConfigPath = FMLPaths.GAMEDIR.get().resolve(FMLConfig.getConfigValue(FMLConfig.ConfigValue.DEFAULT_CONFIG_PATH));
|
||||
private static final Path defaultConfigPath = FMLPaths.GAMEDIR.get().resolve(FMLConfig.defaultConfigPath());
|
||||
|
||||
public Function<ModConfig, CommentedFileConfig> reader(Path configBasePath) {
|
||||
return (c) -> {
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
plugins {
|
||||
id 'org.javamodularity.moduleplugin' version '1.8.7' apply false
|
||||
id 'org.cadixdev.licenser'
|
||||
id 'java-library'
|
||||
id 'jacoco'
|
||||
}
|
||||
|
||||
import org.gradle.internal.os.OperatingSystem
|
||||
switch (OperatingSystem.current()) {
|
||||
case OperatingSystem.LINUX:
|
||||
project.ext.lwjglNatives = "natives-linux"
|
||||
break
|
||||
case OperatingSystem.MAC_OS:
|
||||
project.ext.lwjglNatives = "natives-macos"
|
||||
break
|
||||
case OperatingSystem.WINDOWS:
|
||||
project.ext.lwjglNatives = "natives-windows"
|
||||
break
|
||||
}
|
||||
|
||||
java.withSourcesJar()
|
||||
|
||||
dependencies {
|
||||
compileOnly(libs.nulls)
|
||||
implementation(project(':fmlloader'))
|
||||
implementation(project(':fmlcore'))
|
||||
implementation(libs.bundles.lwjgl)
|
||||
implementation('org.slf4j:slf4j-api:1.8.0-beta4')
|
||||
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')
|
||||
testRuntimeOnly('org.slf4j:slf4j-jdk14:1.8.0-beta4')
|
||||
testRuntimeOnly("org.lwjgl:lwjgl::$lwjglNatives")
|
||||
testRuntimeOnly("org.lwjgl:lwjgl-glfw::$lwjglNatives")
|
||||
testRuntimeOnly("org.lwjgl:lwjgl-opengl::$lwjglNatives")
|
||||
testRuntimeOnly("org.lwjgl:lwjgl-stb::$lwjglNatives")
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes([
|
||||
'Specification-Title': 'FMLEarlyDisplay',
|
||||
'Specification-Vendor': 'Forge Development LLC',
|
||||
'Specification-Version': '1',
|
||||
'Implementation-Title': 'FML Early Display',
|
||||
'Implementation-Version': '1.0',
|
||||
'Implementation-Vendor': 'Forge'
|
||||
] as LinkedHashMap, 'net/minecraftforge/fml/earlydisplay/')
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
options.compilerArgs << '-Xlint:unchecked'
|
||||
}
|
||||
|
||||
license {
|
||||
header = rootProject.file('LICENSE-header.txt')
|
||||
include 'net/minecraftforge/'
|
||||
}
|
||||
|
||||
publishing.publications.mavenJava(MavenPublication) {
|
||||
from components.java
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.earlydisplay;
|
||||
|
||||
public enum ColourScheme {
|
||||
RED(new Colour(239, 50, 61), new Colour(255, 255, 255)),
|
||||
BLACK(new Colour(0, 0, 0), new Colour(255, 255, 255));
|
||||
|
||||
private final Colour background;
|
||||
private final Colour foreground;
|
||||
|
||||
ColourScheme(final Colour background, final Colour foreground) {
|
||||
this.background = background;
|
||||
this.foreground = foreground;
|
||||
}
|
||||
|
||||
public Colour background() {
|
||||
return background;
|
||||
}
|
||||
|
||||
public Colour foreground() {
|
||||
return foreground;
|
||||
}
|
||||
|
||||
public record Colour(int red, int green, int blue) {
|
||||
public float redf() {
|
||||
return ((float)red)/255f;
|
||||
}
|
||||
public float greenf() {
|
||||
return ((float)green)/255f;
|
||||
}
|
||||
public float bluef() {
|
||||
return ((float)blue)/255f;
|
||||
}
|
||||
|
||||
public int packedint(int a) {
|
||||
return ((a & 0xff) << 24) | ((blue & 0xff) << 16) | ((green & 0xff) << 8) | (red & 0xff);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,629 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.earlydisplay;
|
||||
|
||||
import joptsimple.OptionParser;
|
||||
import net.minecraftforge.fml.loading.FMLConfig;
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
import net.minecraftforge.fml.loading.FMLPaths;
|
||||
import net.minecraftforge.fml.loading.ImmediateWindowHandler;
|
||||
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;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.awt.Desktop;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.net.URI;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
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;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.lwjgl.glfw.GLFW.*;
|
||||
import static org.lwjgl.opengl.GL.createCapabilities;
|
||||
import static org.lwjgl.opengl.GL32C.*;
|
||||
|
||||
/**
|
||||
* The Loading Window that is opened Immediately after Forge starts.
|
||||
* It is called from the ModDirTransformerDiscoverer, the soonest method that ModLauncher calls into Forge code.
|
||||
* In this way, we can be sure that this will not run before any transformer or injection.
|
||||
*
|
||||
* The window itself is spun off into a secondary thread, and is handed off to the main game by Forge.
|
||||
*
|
||||
* Because it is created so early, this thread will "absorb" the context from OpenGL.
|
||||
* Therefore, it is of utmost importance that the Context is made Current for the main thread before handoff,
|
||||
* otherwise OS X will crash out.
|
||||
*
|
||||
* Based on the prior ClientVisualization, with some personal touches.
|
||||
*/
|
||||
public class DisplayWindow implements ImmediateWindowProvider {
|
||||
private static final int[][] GL_VERSIONS = new int[][] {{4,6}, {4,5}, {4,4}, {4,3}, {4,2}, {4,1}, {4,0}, {3,3}, {3,2}};
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger("EARLYDISPLAY");
|
||||
private final AtomicBoolean animationTimerTrigger = new AtomicBoolean(true);
|
||||
|
||||
private ColourScheme colourScheme;
|
||||
private ElementShader elementShader;
|
||||
|
||||
private RenderElement.DisplayContext context;
|
||||
private List<RenderElement> elements;
|
||||
private int framecount;
|
||||
private EarlyFramebuffer framebuffer;
|
||||
private ScheduledFuture<?> windowTick;
|
||||
private ScheduledFuture<?> initializationFuture;
|
||||
|
||||
private PerformanceInfo performanceInfo;
|
||||
private ScheduledFuture<?> performanceTick;
|
||||
// The GL ID of the window. Used for all operations
|
||||
private long window;
|
||||
// The thread that contains and ticks the window while Forge is loading mods
|
||||
private ScheduledExecutorService renderScheduler;
|
||||
private int fbWidth;
|
||||
private int fbHeight;
|
||||
private int fbScale;
|
||||
private int winWidth;
|
||||
private int winHeight;
|
||||
private int winX;
|
||||
private int winY;
|
||||
|
||||
private final Semaphore renderLock = new Semaphore(1);
|
||||
private boolean maximized;
|
||||
private String glVersion;
|
||||
private SimpleFont font;
|
||||
private Runnable repaintTick = ()->{};
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "fmlearlywindow";
|
||||
}
|
||||
@Override
|
||||
public Runnable initialize(String[] arguments) {
|
||||
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));
|
||||
var heightopt = parser.accepts("height")
|
||||
.withRequiredArg().ofType(Integer.class)
|
||||
.defaultsTo(FMLConfig.getIntConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_HEIGHT));
|
||||
var maximizedopt = parser.accepts("earlywindow.maximized");
|
||||
parser.allowsUnrecognizedOptions();
|
||||
var parsed = parser.parse(arguments);
|
||||
winWidth = parsed.valueOf(widthopt);
|
||||
winHeight = parsed.valueOf(heightopt);
|
||||
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) {
|
||||
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
|
||||
}
|
||||
}
|
||||
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));
|
||||
performanceInfo = new PerformanceInfo();
|
||||
return start(parsed.valueOf(mcversionopt), forgeVersion);
|
||||
}
|
||||
|
||||
private static final long MINFRAMETIME = TimeUnit.MILLISECONDS.toNanos(10); // This is the FPS cap on the window - note animation is capped at 20FPS via the tickTimer
|
||||
private long nextFrameTime = 0;
|
||||
/**
|
||||
* The main render loop.
|
||||
* renderThread executes this.
|
||||
*
|
||||
* Performs initialization and then ticks the screen at 20 fps.
|
||||
* When the thread is killed, context is destroyed.
|
||||
*/
|
||||
private void renderThreadFunc() {
|
||||
if (!renderLock.tryAcquire()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
long nt;
|
||||
if ((nt = System.nanoTime()) < nextFrameTime) {
|
||||
return;
|
||||
}
|
||||
nextFrameTime = nt + MINFRAMETIME;
|
||||
glfwMakeContextCurrent(window);
|
||||
framebuffer.activate();
|
||||
glViewport(0, 0, this.context.scaledWidth(), this.context.scaledHeight());
|
||||
this.context.elementShader().activate();
|
||||
this.context.elementShader().updateScreenSizeUniform(this.context.scaledWidth(), this.context.scaledHeight());
|
||||
glClearColor(colourScheme.background().redf(), colourScheme.background().greenf(), colourScheme.background().bluef(), 1f);
|
||||
paintFramebuffer();
|
||||
this.context.elementShader().clear();
|
||||
framebuffer.deactivate();
|
||||
glViewport(0, 0, fbWidth, fbHeight);
|
||||
framebuffer.draw(this.fbWidth, this.fbHeight);
|
||||
// Swap buffers; we're done
|
||||
glfwSwapBuffers(window);
|
||||
} catch (Throwable t) {
|
||||
LOGGER.error("BARF", t);
|
||||
} finally {
|
||||
if (this.windowTick != null) glfwMakeContextCurrent(0); // we release the gl context IF we're running off the main thread
|
||||
renderLock.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render initialization methods called by the Render Thread.
|
||||
* It compiles the fragment and vertex shaders for rendering text with STB, and sets up basic render framework.
|
||||
*
|
||||
* Nothing fancy, we just want to draw and render text.
|
||||
*/
|
||||
private void initRender(final @Nullable String mcVersion, final String forgeVersion) {
|
||||
// This thread owns the GL render context now. We should make a note of that.
|
||||
glfwMakeContextCurrent(window);
|
||||
// Wait for one frame to be complete before swapping; enable vsync in other words.
|
||||
glfwSwapInterval(1);
|
||||
createCapabilities();
|
||||
LOGGER.info("GL info: "+ glGetString(GL_RENDERER) + " GL version " + glGetString(GL_VERSION) + ", " + glGetString(GL_VENDOR));
|
||||
|
||||
elementShader = new ElementShader();
|
||||
try {
|
||||
elementShader.init();
|
||||
} catch (Throwable t) {
|
||||
LOGGER.error("Crash during shader initialization", t);
|
||||
crashElegantly("An error occurred initializing shaders.");
|
||||
}
|
||||
|
||||
// Set the clear color based on the colour scheme
|
||||
glClearColor(colourScheme.background().redf(), colourScheme.background().greenf(), colourScheme.background().bluef(), 1f);
|
||||
|
||||
// we always render to an 854x480 texture and then fit that to the screen - with a scale factor
|
||||
this.context = new RenderElement.DisplayContext(854, 480, fbScale, elementShader, colourScheme, performanceInfo);
|
||||
framebuffer = new EarlyFramebuffer(this.context);
|
||||
try {
|
||||
this.font = new SimpleFont("Monocraft.ttf", fbScale, 200000, 1 + RenderElement.INDEX_TEXTURE_OFFSET);
|
||||
} catch (Throwable t) {
|
||||
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)
|
||||
));
|
||||
|
||||
var date = Calendar.getInstance();
|
||||
if (FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_SQUIR) || (date.get(Calendar.MONTH) == Calendar.APRIL && date.get(Calendar.DAY_OF_MONTH) == 1))
|
||||
this.elements.add(0, RenderElement.squir());
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glfwMakeContextCurrent(0);
|
||||
this.windowTick = renderScheduler.scheduleAtFixedRate(this::renderThreadFunc, 50, 50, TimeUnit.MILLISECONDS);
|
||||
this.performanceTick = renderScheduler.scheduleAtFixedRate(performanceInfo::update, 0, 500, TimeUnit.MILLISECONDS);
|
||||
// schedule a 50 ms ticker to try and smooth out the rendering
|
||||
renderScheduler.scheduleAtFixedRate(()-> animationTimerTrigger.set(true), 1, 50, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called every frame by the Render Thread to draw to the screen.
|
||||
*/
|
||||
void paintFramebuffer() {
|
||||
// Clear the screen to our color
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
this.elements.removeIf(element -> !element.render(context, framecount));
|
||||
if (animationTimerTrigger.compareAndSet(true, false)) // we only increment the framecount on a periodic basis
|
||||
framecount++;
|
||||
}
|
||||
|
||||
|
||||
public void render(int alpha) {
|
||||
var currentVAO = glGetInteger(GL_VERTEX_ARRAY_BINDING);
|
||||
var currentFB = glGetInteger(GL_READ_FRAMEBUFFER_BINDING);
|
||||
glViewport(0, 0, this.context.scaledWidth(), this.context.scaledHeight());
|
||||
RenderElement.globalAlpha = alpha;
|
||||
framebuffer.activate();
|
||||
glClearColor(colourScheme.background().redf(), colourScheme.background().greenf(), colourScheme.background().bluef(), alpha / 255f);
|
||||
elementShader.activate();
|
||||
elementShader.updateScreenSizeUniform(this.context.scaledWidth(), this.context.scaledHeight());
|
||||
paintFramebuffer();
|
||||
elementShader.clear();
|
||||
framebuffer.deactivate();
|
||||
glBindVertexArray(currentVAO);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, currentFB);
|
||||
}
|
||||
/**
|
||||
* Start the window and Render Thread; we're ready to go.
|
||||
*/
|
||||
public Runnable start(@Nullable String mcVersion, final String forgeVersion) {
|
||||
renderScheduler = Executors.newSingleThreadScheduledExecutor(r -> {
|
||||
final var thread = Executors.defaultThreadFactory().newThread(r);
|
||||
thread.setDaemon(true);
|
||||
return thread;
|
||||
});
|
||||
initWindow(mcVersion);
|
||||
this.initializationFuture = renderScheduler.schedule(() -> initRender(mcVersion, forgeVersion), 1, TimeUnit.MILLISECONDS);
|
||||
return this::periodicTick;
|
||||
}
|
||||
|
||||
private static final String ERROR_URL = "https://links.minecraftforge.net/early-display-errors";
|
||||
@Override
|
||||
public String getGLVersion() {
|
||||
return this.glVersion;
|
||||
}
|
||||
|
||||
private void crashElegantly(String errorDetails) {
|
||||
String qrText;
|
||||
try (var is = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream("/glfailure.txt")))) {
|
||||
qrText = is.lines().collect(Collectors.joining("\n"));
|
||||
} catch (IOException ioe) {
|
||||
qrText = "";
|
||||
}
|
||||
|
||||
StringBuilder msgBuilder = new StringBuilder(2000);
|
||||
msgBuilder.append("Failed to initialize graphics window with current settings.\n");
|
||||
msgBuilder.append("\n\n");
|
||||
msgBuilder.append("Failure details:\n");
|
||||
msgBuilder.append(errorDetails);
|
||||
msgBuilder.append("\n\n");
|
||||
msgBuilder.append("If you click yes, we will try and open " + ERROR_URL + " in your default browser");
|
||||
LOGGER.error("ERROR DISPLAY\n"+msgBuilder);
|
||||
// we show the display on a new dedicated thread
|
||||
Executors.newSingleThreadExecutor().submit(()-> {
|
||||
var res = TinyFileDialogs.tinyfd_messageBox("Minecraft: Forge", msgBuilder.toString(), "yesno", "error", false);
|
||||
if (res) {
|
||||
try {
|
||||
Desktop.getDesktop().browse(URI.create(ERROR_URL));
|
||||
} catch (IOException ioe) {
|
||||
TinyFileDialogs.tinyfd_messageBox("Minecraft: Forge", "Sadly, we couldn't open your browser.\nVisit " + ERROR_URL, "ok", "error", false);
|
||||
}
|
||||
}
|
||||
System.exit(1);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Called to initialize the window when preparing for the Render Thread.
|
||||
*
|
||||
* The act of calling glfwInit here creates a concurrency issue; GL doesn't know whether we're gonna call any
|
||||
* GL functions from the secondary thread and the main thread at the same time.
|
||||
*
|
||||
* It's then our job to make sure this doesn't happen, only calling GL functions where the Context is Current.
|
||||
* As long as we can verify that, then GL (and things like OS X) have no complaints with doing this.
|
||||
*
|
||||
* @param mcVersion Minecraft Version
|
||||
* @return The selected GL profile as an integer pair
|
||||
*/
|
||||
public void initWindow(@Nullable String mcVersion) {
|
||||
// Initialize GLFW with a time guard, in case something goes wrong
|
||||
long glfwInitBegin = System.nanoTime();
|
||||
if (!glfwInit()) {
|
||||
crashElegantly("We are unable to initialize the graphics system.\nglfwInit failed.\n");
|
||||
throw new IllegalStateException("Unable to initialize GLFW");
|
||||
}
|
||||
long glfwInitEnd = System.nanoTime();
|
||||
|
||||
if (glfwInitEnd - glfwInitBegin > 1e9) {
|
||||
LOGGER.error("WARNING : glfwInit took {} seconds to start.", (glfwInitEnd - glfwInitBegin) / 1.0e9);
|
||||
}
|
||||
|
||||
// Clear the Last Exception (#7285 - Prevent Vanilla throwing an IllegalStateException due to invalid controller mappings)
|
||||
handleLastGLFWError((error, description) -> LOGGER.error(String.format("Suppressing Last GLFW error: [0x%X]%s", error, description)));
|
||||
|
||||
// Set window hints for the new window we're gonna create.
|
||||
glfwDefaultWindowHints();
|
||||
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API);
|
||||
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);
|
||||
}
|
||||
|
||||
long primaryMonitor = glfwGetPrimaryMonitor();
|
||||
if (primaryMonitor == 0) {
|
||||
LOGGER.error("Failed to find a primary monitor - this means LWJGL isn't working properly");
|
||||
crashElegantly("Failed to locate a primary monitor.\nglfwGetPrimaryMonitor failed.\n");
|
||||
throw new IllegalStateException("Can't find a primary monitor");
|
||||
}
|
||||
GLFWVidMode vidmode = glfwGetVideoMode(primaryMonitor);
|
||||
|
||||
if (vidmode == null) {
|
||||
LOGGER.error("Failed to get the current display video mode.");
|
||||
crashElegantly("Failed to get current display resolution.\nglfwGetVideoMode failed.\n");
|
||||
throw new IllegalStateException("Can't get a resolution");
|
||||
}
|
||||
long window = 0;
|
||||
var successfulWindow = new AtomicBoolean(false);
|
||||
var windowFailFuture = renderScheduler.schedule(()->{
|
||||
if (!successfulWindow.get()) crashElegantly("Timed out trying to setup the Game Window.");
|
||||
}, 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];
|
||||
do {
|
||||
final var glVersionToTry = GL_VERSIONS[versidx][0] + "." + GL_VERSIONS[versidx][1];
|
||||
if (skipVersions.contains(glVersionToTry)) {
|
||||
LOGGER.info("Skipping GL version "+ glVersionToTry+" because of configuration");
|
||||
versidx++;
|
||||
continue;
|
||||
}
|
||||
LOGGER.info("Trying GL version " + glVersionToTry);
|
||||
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);
|
||||
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) {
|
||||
LOGGER.trace(lastGLError[versidx]);
|
||||
}
|
||||
versidx++;
|
||||
} while (window == 0 && versidx < GL_VERSIONS.length);
|
||||
// LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(12));
|
||||
if (versidx== GL_VERSIONS.length && window == 0) {
|
||||
LOGGER.error("Failed to find any valid GLFW profile. "+lastGLError[0]);
|
||||
|
||||
crashElegantly("Failed to find a valid GLFW profile.\nWe tried "+
|
||||
Arrays.stream(GL_VERSIONS).map(p->p[0]+"."+p[1]).filter(o -> !skipVersions.contains(o))
|
||||
.collect(Collector.of(()->new StringJoiner(", ").setEmptyValue("no versions"), StringJoiner::add, StringJoiner::merge, StringJoiner::toString))+
|
||||
" but none of them worked.\n"+ Arrays.stream(lastGLError).filter(Objects::nonNull).collect(Collectors.joining("\n")));
|
||||
throw new IllegalStateException("Failed to create a GLFW window with any profile");
|
||||
}
|
||||
successfulWindow.set(true);
|
||||
if (!windowFailFuture.cancel(true)) throw new IllegalStateException("We died but didn't somehow?");
|
||||
var requestedVersion = GL_VERSIONS[versidx-1][0]+"."+GL_VERSIONS[versidx-1][1];
|
||||
var maj = glfwGetWindowAttrib(window, GLFW_CONTEXT_VERSION_MAJOR);
|
||||
var min = glfwGetWindowAttrib(window, GLFW_CONTEXT_VERSION_MINOR);
|
||||
var gotVersion = maj+"."+min;
|
||||
LOGGER.info("Requested GL version "+requestedVersion+" got version "+gotVersion);
|
||||
this.glVersion = gotVersion;
|
||||
this.window = window;
|
||||
|
||||
int[] x = new int[1];
|
||||
int[] y = new int[1];
|
||||
glfwGetMonitorPos(primaryMonitor, x, y);
|
||||
int monitorX = x[0];
|
||||
int monitorY = y[0];
|
||||
// glfwSetWindowSizeLimits(window, 854, 480, GLFW_DONT_CARE, GLFW_DONT_CARE);
|
||||
if (this.maximized) {
|
||||
glfwMaximizeWindow(window);
|
||||
}
|
||||
|
||||
glfwGetWindowSize(window, x, y);
|
||||
this.winWidth = x[0];
|
||||
this.winHeight = y[0];
|
||||
|
||||
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)));
|
||||
|
||||
glfwSetFramebufferSizeCallback(window, this::fbResize);
|
||||
glfwSetWindowPosCallback(window, this::winMove);
|
||||
glfwSetWindowSizeCallback(window, this::winResize);
|
||||
|
||||
// Show the window
|
||||
glfwShowWindow(window);
|
||||
glfwGetWindowPos(window, x, y);
|
||||
this.winX = x[0];
|
||||
this.winY = y[0];
|
||||
glfwGetFramebufferSize(window, x, y);
|
||||
this.fbWidth = x[0];
|
||||
this.fbHeight = y[0];
|
||||
glfwPollEvents();
|
||||
}
|
||||
|
||||
private void badWindowHandler(final int code, final long desc) {
|
||||
LOGGER.error("Got error from GLFW window init: "+code+ " "+MemoryUtil.memUTF8(desc));
|
||||
}
|
||||
|
||||
private void winResize(long window, int width, int height) {
|
||||
if (window == this.window && width != 0 && height != 0) {
|
||||
this.winWidth = width;
|
||||
this.winHeight = height;
|
||||
}
|
||||
}
|
||||
private void fbResize(long window, int width, int height) {
|
||||
if (window == this.window && width != 0 && height != 0) {
|
||||
this.fbWidth = width;
|
||||
this.fbHeight = height;
|
||||
}
|
||||
}
|
||||
|
||||
private void winMove(long window, int x, int y) {
|
||||
if (window == this.window) {
|
||||
this.winX = x;
|
||||
this.winY = y;
|
||||
}
|
||||
}
|
||||
private void handleLastGLFWError(BiConsumer<Integer, String> handler) {
|
||||
try (MemoryStack memorystack = MemoryStack.stackPush()) {
|
||||
PointerBuffer pointerbuffer = memorystack.mallocPointer(1);
|
||||
int error = glfwGetError(pointerbuffer);
|
||||
if (error != GLFW_NO_ERROR) {
|
||||
long pDescription = pointerbuffer.get();
|
||||
String description = pDescription == 0L ? "" : MemoryUtil.memUTF8(pDescription);
|
||||
handler.accept(error, description);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hand-off the window to the vanilla game.
|
||||
* Called on the main thread instead of the game's initialization.
|
||||
*
|
||||
* @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()) {
|
||||
this.windowTick.cancel(false);
|
||||
}
|
||||
var tries = 0;
|
||||
var renderlockticket = false;
|
||||
do {
|
||||
try {
|
||||
renderlockticket = renderLock.tryAcquire(100, TimeUnit.MILLISECONDS);
|
||||
if (++tries > 9) {
|
||||
Thread.dumpStack();
|
||||
crashElegantly("We seem to be having trouble handing off the window, tried for 1 second");
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.interrupted();
|
||||
}
|
||||
} while (!renderlockticket);
|
||||
// we don't want the lock, just making sure it's back on the main thread
|
||||
renderLock.release();
|
||||
|
||||
glfwMakeContextCurrent(window);
|
||||
// Set the title to what the game wants
|
||||
glfwSetWindowTitle(window, title.get());
|
||||
glfwSwapInterval(0);
|
||||
// Clean up our hooks
|
||||
glfwSetFramebufferSizeCallback(window, null).free();
|
||||
glfwSetWindowPosCallback(window, null).free();
|
||||
glfwSetWindowSizeCallback(window, null).free();
|
||||
this.repaintTick = this::renderThreadFunc; // the repaint will continue to be called until the overlay takes over
|
||||
this.windowTick = null; // this tells the render thread that the async ticker is done
|
||||
return window;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean positionWindow(final Optional<Object> monitor, final IntConsumer widthSetter, final IntConsumer heightSetter, final IntConsumer xSetter, final IntConsumer ySetter) {
|
||||
widthSetter.accept(this.winWidth);
|
||||
heightSetter.accept(this.winHeight);
|
||||
xSetter.accept(this.winX);
|
||||
ySetter.accept(this.winY);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFramebufferSize(final IntConsumer width, final IntConsumer height) {
|
||||
width.accept(this.fbWidth);
|
||||
height.accept(this.fbHeight);
|
||||
}
|
||||
|
||||
private Method loadingOverlay;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> Supplier<T> loadingOverlay(final Supplier<?> mc, final Supplier<?> ri, final Consumer<Optional<Throwable>> ex, final boolean fade) {
|
||||
try {
|
||||
return (Supplier<T>)loadingOverlay.invoke(null, mc, ri, ex, this);
|
||||
} catch (Throwable e) {
|
||||
throw new IllegalStateException("How did you get here?", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateModuleReads(final ModuleLayer layer) {
|
||||
var fm = layer.findModule("forge").orElseThrow();
|
||||
getClass().getModule().addReads(fm);
|
||||
var clz = FMLLoader.getGameLayer().findModule("forge").map(l->Class.forName(l, "net.minecraftforge.client.loading.ForgeLoadingOverlay")).orElseThrow();
|
||||
var methods = Arrays.stream(clz.getMethods()).filter(m-> Modifier.isStatic(m.getModifiers())).collect(Collectors.toMap(Method::getName, Function.identity()));
|
||||
loadingOverlay = methods.get("newInstance");
|
||||
}
|
||||
|
||||
public int getFramebufferTextureId() {
|
||||
return framebuffer.getTexture();
|
||||
}
|
||||
|
||||
public RenderElement.DisplayContext context() {
|
||||
return this.context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void periodicTick() {
|
||||
glfwPollEvents();
|
||||
repaintTick.run();
|
||||
}
|
||||
|
||||
public void addMojangTexture(final int textureId) {
|
||||
this.elements.add(0, RenderElement.mojang(textureId, framecount));
|
||||
// this.elements.get(0).retire(framecount + 1);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
// Close the Render Scheduler thread
|
||||
renderScheduler.shutdown();
|
||||
this.framebuffer.close();
|
||||
this.context.elementShader().close();
|
||||
SimpleBufferBuilder.destroy();
|
||||
}
|
||||
}
|
||||
-67
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.earlydisplay;
|
||||
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
import static net.minecraftforge.fml.earlydisplay.RenderElement.clamp;
|
||||
import static org.lwjgl.opengl.GL32C.*;
|
||||
|
||||
public class EarlyFramebuffer {
|
||||
private final int framebuffer;
|
||||
private final int texture;
|
||||
|
||||
private final RenderElement.DisplayContext context;
|
||||
|
||||
EarlyFramebuffer(final RenderElement.DisplayContext context) {
|
||||
this.context = context;
|
||||
this.framebuffer = glGenFramebuffers();
|
||||
this.texture = glGenTextures();
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, this.framebuffer);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, this.texture);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, context.width() * context.scale(), context.height() * context.scale(), 0, GL_RGBA, GL_UNSIGNED_BYTE, (IntBuffer)null);
|
||||
glTexParameterIi(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameterIi(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, this.texture, 0);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
void activate() {
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, this.framebuffer);
|
||||
}
|
||||
|
||||
void deactivate() {
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
void draw(int windowFBWidth, int windowFBHeight) {
|
||||
var wscale = ((float)windowFBWidth / this.context.width());
|
||||
var hscale = ((float)windowFBHeight / this.context.height());
|
||||
var scale = this.context.scale() * Math.min(wscale, hscale) / 2f;
|
||||
var wleft = (int)(windowFBWidth * 0.5f - scale * this.context.width());
|
||||
var wtop = (int)(windowFBHeight * 0.5f - scale * this.context.height());
|
||||
var wright = (int)(windowFBWidth * 0.5f + scale * this.context.width());
|
||||
var wbottom = (int)(windowFBHeight * 0.5f + scale * this.context.height());
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, this.framebuffer);
|
||||
final var colour = this.context.colourScheme().background();
|
||||
glClearColor(colour.redf(), colour.greenf(), colour.bluef(), 1f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
// src Y are flipped, since our FB is flipped
|
||||
glBlitFramebuffer(0, this.context.height() * this.context.scale(), this.context.width() * this.context.scale(), 0, clamp(wleft, 0, windowFBWidth), clamp(wtop, 0, windowFBHeight), clamp(wright, 0, windowFBWidth), clamp(wbottom, 0, windowFBHeight), GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
int getTexture() {
|
||||
return this.texture;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
glDeleteTextures(this.texture);
|
||||
glDeleteFramebuffers(this.framebuffer);
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.earlydisplay;
|
||||
|
||||
|
||||
import static org.lwjgl.opengl.GL32C.*;
|
||||
|
||||
public class ElementShader {
|
||||
private int program;
|
||||
private int textureUniform;
|
||||
private int screenSizeUniform;
|
||||
private int renderTypeUniform;
|
||||
|
||||
public void init() {
|
||||
int vertexShader = glCreateShader(GL_VERTEX_SHADER);
|
||||
int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
|
||||
|
||||
// Bind the source of our shaders to the ones created above
|
||||
glShaderSource(fragmentShader, """
|
||||
#version 150 core
|
||||
uniform sampler2D tex;
|
||||
uniform int rendertype;
|
||||
in vec2 fTex;
|
||||
in vec4 fColour;
|
||||
out vec4 fragColor;
|
||||
|
||||
void main() {
|
||||
if (rendertype == 0)
|
||||
fragColor = vec4(1,1,1,texture(tex, fTex).r) * fColour;
|
||||
if (rendertype == 1)
|
||||
fragColor = texture(tex, fTex) * fColour;
|
||||
if (rendertype == 2)
|
||||
fragColor = fColour;
|
||||
}
|
||||
""");
|
||||
glShaderSource(vertexShader, """
|
||||
#version 150 core
|
||||
in vec2 position;
|
||||
in vec2 tex;
|
||||
in vec4 colour;
|
||||
uniform vec2 screenSize;
|
||||
out vec2 fTex;
|
||||
out vec4 fColour;
|
||||
void main() {
|
||||
fTex = tex;
|
||||
fColour = colour;
|
||||
gl_Position = vec4((position/screenSize) * 2 - 1, 0.0, 1.0);
|
||||
}
|
||||
""");
|
||||
|
||||
// Compile the vertex and fragment elementShader so that we can use them
|
||||
glCompileShader(vertexShader);
|
||||
if (glGetShaderi(vertexShader, GL_COMPILE_STATUS) == GL_FALSE) {
|
||||
throw new IllegalStateException("VertexShader linkage failure. \n" + glGetShaderInfoLog(vertexShader));
|
||||
}
|
||||
glCompileShader(fragmentShader);
|
||||
if (glGetShaderi(fragmentShader, GL_COMPILE_STATUS) == GL_FALSE) {
|
||||
throw new IllegalStateException("FragmentShader linkage failure. \n" + glGetShaderInfoLog(fragmentShader));
|
||||
}
|
||||
|
||||
var program = glCreateProgram();
|
||||
glBindAttribLocation(program, 0, "position");
|
||||
glBindAttribLocation(program, 1, "tex");
|
||||
glBindAttribLocation(program, 2, "colour");
|
||||
glAttachShader(program, vertexShader);
|
||||
glAttachShader(program, fragmentShader);
|
||||
glLinkProgram(program);
|
||||
if (glGetProgrami(program, GL_LINK_STATUS) == GL_FALSE) {
|
||||
throw new RuntimeException("ShaderProgram linkage failure. \n" + glGetProgramInfoLog(program));
|
||||
}
|
||||
this.program = program;
|
||||
|
||||
glDetachShader(program, vertexShader);
|
||||
glDetachShader(program, fragmentShader);
|
||||
glDeleteShader(vertexShader);
|
||||
glDeleteShader(fragmentShader);
|
||||
textureUniform = glGetUniformLocation(program, "tex");
|
||||
screenSizeUniform = glGetUniformLocation(program, "screenSize");
|
||||
renderTypeUniform = glGetUniformLocation(program, "rendertype");
|
||||
|
||||
activate();
|
||||
}
|
||||
|
||||
public void activate() {
|
||||
glUseProgram(program);
|
||||
}
|
||||
public void updateTextureUniform(int textureNumber) {
|
||||
glUniform1i(textureUniform, textureNumber);
|
||||
}
|
||||
|
||||
public void updateScreenSizeUniform(int width, int height) {
|
||||
glUniform2f(screenSizeUniform, width, height);
|
||||
}
|
||||
|
||||
public void updateRenderTypeUniform(RenderType type) {
|
||||
glUniform1i(renderTypeUniform, type.ordinal());
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
glDeleteProgram(program);
|
||||
}
|
||||
|
||||
public enum RenderType {
|
||||
FONT, TEXTURE, BAR;
|
||||
}
|
||||
|
||||
public int program() {
|
||||
return program;
|
||||
}
|
||||
}
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.earlydisplay;
|
||||
|
||||
import com.sun.management.OperatingSystemMXBean;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.MemoryMXBean;
|
||||
import java.lang.management.MemoryUsage;
|
||||
|
||||
public class PerformanceInfo {
|
||||
|
||||
private final OperatingSystemMXBean osBean;
|
||||
private final MemoryMXBean memoryBean;
|
||||
float memory;
|
||||
private String text;
|
||||
|
||||
PerformanceInfo() {
|
||||
osBean = ManagementFactory.getPlatformMXBean(OperatingSystemMXBean.class);
|
||||
memoryBean = ManagementFactory.getMemoryMXBean();
|
||||
}
|
||||
|
||||
void update() {
|
||||
final MemoryUsage heapusage = memoryBean.getHeapMemoryUsage();
|
||||
memory = (float) heapusage.getUsed() / heapusage.getMax();
|
||||
var cpuLoad = osBean.getProcessCpuLoad();
|
||||
String cpuText;
|
||||
if (cpuLoad == -1) {
|
||||
cpuText = String.format("*CPU: %.1f%%", osBean.getCpuLoad() * 100f);
|
||||
} else {
|
||||
cpuText = String.format("CPU: %.1f%%", 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);
|
||||
}
|
||||
|
||||
String text() {
|
||||
return text;
|
||||
}
|
||||
|
||||
float memory() {
|
||||
return memory;
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.earlydisplay;
|
||||
|
||||
public class QuadHelper {
|
||||
public static void loadQuad(SimpleBufferBuilder bb, float x0, float x1, float y0, float y1, float u0, float u1, float v0, float v1, int colour) {
|
||||
bb.pos(x0, y0).tex(u0, v0).colour(colour).endVertex();
|
||||
bb.pos(x1, y0).tex(u1, v0).colour(colour).endVertex();
|
||||
bb.pos(x0, y1).tex(u0, v1).colour(colour).endVertex();
|
||||
bb.pos(x1, y1).tex(u1, v1).colour(colour).endVertex();
|
||||
}
|
||||
|
||||
public static void loadQuad(SimpleBufferBuilder bb, float x0, float x1, float y0, float y1, float u0, float u1, float v0, float v1) {
|
||||
bb.pos(x0, y0).tex(u0, v0).endVertex();
|
||||
bb.pos(x1, y0).tex(u1, v0).endVertex();
|
||||
bb.pos(x0, y1).tex(u0, v1).endVertex();
|
||||
bb.pos(x1, y1).tex(u1, v1).endVertex();
|
||||
}
|
||||
}
|
||||
@@ -1,344 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.earlydisplay;
|
||||
|
||||
import net.minecraftforge.fml.loading.progress.Message;
|
||||
import net.minecraftforge.fml.loading.progress.ProgressMeter;
|
||||
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.lwjgl.opengl.GL32C.*;
|
||||
|
||||
public class RenderElement {
|
||||
static final int INDEX_TEXTURE_OFFSET = 5;
|
||||
private final SimpleBufferBuilder bb;
|
||||
private final Renderer renderer;
|
||||
static int globalAlpha = 255;
|
||||
private int retireCount;
|
||||
|
||||
interface Renderer {
|
||||
|
||||
void accept(SimpleBufferBuilder bb, DisplayContext context, int frame);
|
||||
|
||||
default Renderer then(Renderer r) {
|
||||
if (r == null) return this;
|
||||
return (bb, ctx, frame) -> {
|
||||
r.accept(bb, ctx, frame);
|
||||
this.accept(bb, ctx, frame);
|
||||
};
|
||||
}
|
||||
}
|
||||
interface TextureRenderer {
|
||||
void accept(SimpleBufferBuilder bb, DisplayContext context, int[] size, int frame);
|
||||
}
|
||||
interface Initializer extends Supplier<Renderer> {}
|
||||
|
||||
interface TextGenerator {
|
||||
void accept(SimpleBufferBuilder bb, SimpleFont fh, DisplayContext ctx);
|
||||
}
|
||||
|
||||
public record DisplayContext(int width, int height, int scale, ElementShader elementShader, ColourScheme colourScheme, PerformanceInfo performance) {
|
||||
public int scaledWidth() {
|
||||
return scale() * width();
|
||||
}
|
||||
|
||||
public int scaledHeight() {
|
||||
return scale() * height();
|
||||
}
|
||||
}
|
||||
|
||||
public RenderElement(final Initializer rendererInitializer) {
|
||||
this.bb = new SimpleBufferBuilder(1);
|
||||
this.renderer = rendererInitializer.get();
|
||||
}
|
||||
|
||||
public boolean render(DisplayContext ctx, int count) {
|
||||
this.renderer.accept(bb, ctx, count);
|
||||
return this.retireCount == 0 || this.retireCount < count;
|
||||
}
|
||||
|
||||
public void retire(final int frame) {
|
||||
this.retireCount = frame;
|
||||
}
|
||||
|
||||
private static void startupLogMessages(SimpleBufferBuilder bb, SimpleFont font, DisplayContext context) {
|
||||
List<StartupNotificationManager.AgeMessage> messages = StartupNotificationManager.getMessages();
|
||||
List<SimpleFont.DisplayText> texts = new ArrayList<>();
|
||||
for (int i = messages.size() - 1; i >= 0; i--) {
|
||||
final StartupNotificationManager.AgeMessage pair = messages.get(i);
|
||||
final float fade = clamp((4000.0f - (float) pair.age() - ( i - 4 ) * 1000.0f) / 5000.0f, 0.0f, 1.0f);
|
||||
if (fade <0.01f) continue;
|
||||
Message msg = pair.message();
|
||||
int colour = Math.min((int)(fade * 255f), globalAlpha) << 24 | 0xFFFFFF;
|
||||
texts.add(new SimpleFont.DisplayText(msg.getText()+"\n", colour));
|
||||
}
|
||||
|
||||
font.generateVerticesForTexts(10, context.scaledHeight() - texts.size() * font.lineSpacing() + font.descent() - 10, bb, texts.toArray(SimpleFont.DisplayText[]::new));
|
||||
}
|
||||
public static RenderElement monag() {
|
||||
return new RenderElement(RenderElement.initializeTexture("monagstudios.png", 45000, 4, (bb, ctx, sz, frame) -> {
|
||||
var size = 256;
|
||||
var x0 = (ctx.width() - 2 * size) / 2;
|
||||
var y0 = 64;
|
||||
QuadHelper.loadQuad(bb, x0, x0+size, y0, y0+size/2f, 0f, 1f, 0f, 0.5f, 0xFFFFFFFF);
|
||||
QuadHelper.loadQuad(bb, x0+size, x0+2*size, y0, y0+size/2f, 0f, 1f, 0.5f, 1f, 0xFFFFFFFF);
|
||||
}));
|
||||
|
||||
}
|
||||
|
||||
public static RenderElement mojang(final int textureId, final int frameStart) {
|
||||
return new RenderElement(()->(bb, ctx, frame) -> {
|
||||
var size = 256 * ctx.scale();
|
||||
var x0 = (ctx.scaledWidth() - 2 * size) / 2;
|
||||
var y0 = 64 * ctx.scale() + 32;
|
||||
ctx.elementShader().updateTextureUniform(0);
|
||||
ctx.elementShader().updateRenderTypeUniform(ElementShader.RenderType.TEXTURE);
|
||||
var fade = Math.min((frame - frameStart) * 10, 255);
|
||||
glBindTexture(GL_TEXTURE_2D, textureId);
|
||||
bb.begin(SimpleBufferBuilder.Format.POS_TEX_COLOR, SimpleBufferBuilder.Mode.QUADS);
|
||||
QuadHelper.loadQuad(bb, x0, x0+size, y0, y0+size/2f, 0f, 1f, 0f, 0.5f, (fade << 24) | 0xFFFFFF);
|
||||
QuadHelper.loadQuad(bb, x0+size, x0+2*size, y0, y0+size/2f, 0f, 1f, 0.5f, 1f, (fade << 24) | 0xFFFFFF);
|
||||
bb.draw();
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
});
|
||||
}
|
||||
public static RenderElement logMessageOverlay(SimpleFont font) {
|
||||
return new RenderElement(RenderElement.initializeText(font, RenderElement::startupLogMessages));
|
||||
}
|
||||
|
||||
public static RenderElement forgeVersionOverlay(SimpleFont font, String version) {
|
||||
return new RenderElement(RenderElement.initializeText(font, (bb, fnt, ctx)->
|
||||
font.generateVerticesForTexts(ctx.scaledWidth() - font.stringWidth(version) - 10,
|
||||
ctx.scaledHeight() - font.lineSpacing() + font.descent() - 10, bb,
|
||||
new SimpleFont.DisplayText(version, ctx.colourScheme.foreground().packedint(RenderElement.globalAlpha)))));
|
||||
}
|
||||
public static RenderElement squir() {
|
||||
return new RenderElement(RenderElement.initializeTexture("squirrel.png", 45000, 3, (bb, context, size, frame) -> {
|
||||
var inset = 5f;
|
||||
var x0 = inset;
|
||||
var x1 = inset + size[0] * context.scale();
|
||||
var y0 = inset;
|
||||
var y1 = inset + size[1] * context.scale();
|
||||
int fade = (int) (Math.cos(frame * Math.PI / 16) * 16) + 16;
|
||||
// int fade = 0xff;
|
||||
var colour = (Math.min(fade, globalAlpha) & 0xff) << 24 | 0xffffff;
|
||||
QuadHelper.loadQuad(bb, x0, x1, y0, y1, 0f, 1f, 0f, 1f, colour);
|
||||
}));
|
||||
}
|
||||
|
||||
public static RenderElement anvil(SimpleFont font) {
|
||||
return new RenderElement(RenderElement.initializeTexture("forge_anvil.png", 20000, 2, (bb, context, size, frame) -> {
|
||||
var x0 = context.scaledWidth() - size[0] * context.scale();
|
||||
var x1 = context.scaledWidth();
|
||||
var y0 = context.scaledHeight() - size[0] * context.scale() - font.descent() - font.lineSpacing();
|
||||
var y1 = context.scaledHeight() - font.descent() - font.lineSpacing();
|
||||
int frameidx = frame % 32;
|
||||
float framepos = (frameidx * (float)size[0]) / size[1];
|
||||
float framesize = size[0] / (float)size[1];
|
||||
QuadHelper.loadQuad(bb, x0, x1, y0, y1, 0f, 1f, framepos, framepos+framesize, globalAlpha << 24 | 0xFFFFFF);
|
||||
}));
|
||||
}
|
||||
public static RenderElement progressBars(SimpleFont font) {
|
||||
return new RenderElement(() -> (bb, ctx, frame) -> RenderElement.startupProgressBars(font, bb, ctx, frame));
|
||||
}
|
||||
|
||||
public static RenderElement performanceBar(SimpleFont font) {
|
||||
return new RenderElement(() -> (bb, ctx, frame) -> RenderElement.memoryInfo(font, bb, ctx, frame));
|
||||
}
|
||||
|
||||
public static void startupProgressBars(SimpleFont font, final SimpleBufferBuilder buffer, final DisplayContext context, final int frameNumber) {
|
||||
Renderer acc = null;
|
||||
var barCount = 2;
|
||||
List<ProgressMeter> currentProgress = StartupNotificationManager.getCurrentProgress();
|
||||
var size = currentProgress.size();
|
||||
var alpha = 0xFF;
|
||||
for (int i = 0; i < barCount && i < size; i++) {
|
||||
final ProgressMeter pm = currentProgress.get(i);
|
||||
Renderer barRenderer = barRenderer(i, alpha, font, pm, context);
|
||||
acc = barRenderer.then(acc);
|
||||
alpha >>= 1;
|
||||
}
|
||||
if (acc != null)
|
||||
acc.accept(buffer, context, frameNumber);
|
||||
}
|
||||
private static final int BAR_HEIGHT = 20;
|
||||
private static final int BAR_WIDTH = 400;
|
||||
private static Renderer barRenderer(int cnt, int alpha, SimpleFont font, ProgressMeter pm, DisplayContext context) {
|
||||
var barSpacing = font.lineSpacing() - font.descent() + BAR_HEIGHT;
|
||||
var y = 250 * context.scale() + cnt * barSpacing;
|
||||
var colour = (alpha << 24) | 0xFFFFFF;
|
||||
Renderer bar;
|
||||
if (pm.steps() == 0) {
|
||||
bar = progressBar(ctx->new int[] {(ctx.scaledWidth() - BAR_WIDTH * ctx.scale()) / 2, y + font.lineSpacing() - font.descent(), BAR_WIDTH * ctx.scale()}, f->colour, frame -> indeterminateBar(frame, cnt == 0));
|
||||
} else {
|
||||
bar = progressBar(ctx -> new int[]{(ctx.scaledWidth() - BAR_WIDTH * ctx.scale()) / 2, y + font.lineSpacing() - font.descent(), BAR_WIDTH * ctx.scale()}, f -> colour, f -> new float[]{0f, pm.progress()});
|
||||
}
|
||||
Renderer label = (bb, ctx, frame) -> renderText(font, text((ctx.scaledWidth() - BAR_WIDTH * ctx.scale()) / 2, y, pm.label().getText(), colour), bb, ctx);
|
||||
return bar.then(label);
|
||||
}
|
||||
private static float[] indeterminateBar(int frame, boolean isActive) {
|
||||
if (RenderElement.globalAlpha != 0xFF || !isActive) {
|
||||
return new float[] {0f,1f};
|
||||
} else {
|
||||
var progress = frame % 100;
|
||||
return new float[]{clamp((progress - 2) / 100f, 0f, 1f), clamp((progress + 2) / 100f, 0f, 1f)};
|
||||
}
|
||||
}
|
||||
|
||||
private static void memoryInfo(SimpleFont font, final SimpleBufferBuilder buffer, final DisplayContext context, final int frameNumber) {
|
||||
var y = 10 * context.scale();
|
||||
PerformanceInfo pi = context.performance();
|
||||
final int colour = hsvToRGB((1.0f - (float)Math.pow(pi.memory(), 1.5f)) / 3f, 1.0f, 0.5f);
|
||||
var bar = progressBar(ctx -> new int[]{(ctx.scaledWidth() - BAR_WIDTH * ctx.scale()) / 2, y, BAR_WIDTH * ctx.scale()}, f -> colour, f -> new float[]{0f, pi.memory()});
|
||||
var width = font.stringWidth(pi.text());
|
||||
Renderer label = (bb, ctx, frame) -> renderText(font, text(ctx.scaledWidth() / 2 - width / 2, y + 18, pi.text(), context.colourScheme.foreground().packedint(globalAlpha)), bb, ctx);
|
||||
bar.then(label).accept(buffer, context, frameNumber);
|
||||
}
|
||||
|
||||
interface ColourFunction {
|
||||
int colour(int frame);
|
||||
}
|
||||
|
||||
interface ProgressDisplay {
|
||||
float[] progress(int frame);
|
||||
}
|
||||
|
||||
interface BarPosition {
|
||||
int[] location(DisplayContext context);
|
||||
}
|
||||
public static Renderer progressBar(BarPosition position, ColourFunction colourFunction, ProgressDisplay progressDisplay) {
|
||||
return (bb, context, frame) -> {
|
||||
var colour = colourFunction.colour(frame);
|
||||
var alpha = (colour & 0xFF000000) >> 24;
|
||||
context.elementShader().updateTextureUniform(0);
|
||||
context.elementShader().updateRenderTypeUniform(ElementShader.RenderType.BAR);
|
||||
var progress = progressDisplay.progress(frame);
|
||||
bb.begin(SimpleBufferBuilder.Format.POS_TEX_COLOR, SimpleBufferBuilder.Mode.QUADS);
|
||||
var inset = 2;
|
||||
var pos = position.location(context);
|
||||
var x0 = pos[0];
|
||||
var x1 = pos[0] + pos[2] + 4 * inset;
|
||||
var y0 = pos[1];
|
||||
var y1 = y0 + BAR_HEIGHT;
|
||||
QuadHelper.loadQuad(bb, x0, x1, y0, y1, 0f, 0f, 0f, 0f, context.colourScheme().foreground().packedint(alpha));
|
||||
|
||||
x0 += inset;
|
||||
x1 -= inset;
|
||||
y0 += inset;
|
||||
y1 -= inset;
|
||||
QuadHelper.loadQuad(bb, x0, x1, y0, y1, 0f, 0f, 0f, 0f, context.colourScheme().background().packedint(RenderElement.globalAlpha));
|
||||
|
||||
x1 = x0 + inset + (int)(progress[1] * pos[2]);
|
||||
x0 += inset + progress[0] * pos[2];
|
||||
y0 += inset;
|
||||
y1 -= inset;
|
||||
QuadHelper.loadQuad(bb, x0, x1, y0, y1, 0f, 0f, 0f, 0f, colour);
|
||||
bb.draw();
|
||||
};
|
||||
}
|
||||
|
||||
private static Initializer initializeText(SimpleFont font, TextGenerator textGenerator) {
|
||||
return () -> (bb, context, frame) -> renderText(font, textGenerator, bb, context);
|
||||
}
|
||||
|
||||
private static void renderText(final SimpleFont font, final TextGenerator textGenerator, final SimpleBufferBuilder bb, final DisplayContext context) {
|
||||
context.elementShader().updateTextureUniform(font.textureNumber());
|
||||
context.elementShader().updateRenderTypeUniform(ElementShader.RenderType.FONT);
|
||||
bb.begin(SimpleBufferBuilder.Format.POS_TEX_COLOR, SimpleBufferBuilder.Mode.QUADS);
|
||||
textGenerator.accept(bb, font, context);
|
||||
bb.draw();
|
||||
}
|
||||
|
||||
private static TextGenerator text(int x, int y, String text, int colour) {
|
||||
return (bb, font, context) -> font.generateVerticesForTexts(x, y, bb, new SimpleFont.DisplayText(text, colour));
|
||||
}
|
||||
private static Initializer initializeTexture(final String textureFileName, int size, int textureNumber, TextureRenderer positionAndColour) {
|
||||
return ()->{
|
||||
int[] imgSize = STBHelper.loadTextureFromClasspath(textureFileName, size, GL_TEXTURE0 + textureNumber + INDEX_TEXTURE_OFFSET);
|
||||
return (bb, ctx, frame) -> {
|
||||
ctx.elementShader().updateTextureUniform(textureNumber + INDEX_TEXTURE_OFFSET);
|
||||
ctx.elementShader().updateRenderTypeUniform(ElementShader.RenderType.TEXTURE);
|
||||
renderTexture(bb, ctx, frame, imgSize, positionAndColour);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
private static void renderTexture(SimpleBufferBuilder bb, DisplayContext context, int frame, int[] size, TextureRenderer positionAndColour) {
|
||||
bb.begin(SimpleBufferBuilder.Format.POS_TEX_COLOR, SimpleBufferBuilder.Mode.QUADS);
|
||||
positionAndColour.accept(bb, context, size, frame);
|
||||
bb.draw();
|
||||
}
|
||||
|
||||
|
||||
public static float clamp(float num, float min, float max) {
|
||||
if (num < min) {
|
||||
return min;
|
||||
} else {
|
||||
return num > max ? max : num;
|
||||
}
|
||||
}
|
||||
|
||||
public static int clamp(int num, int min, int max) {
|
||||
if (num < min) {
|
||||
return min;
|
||||
} else {
|
||||
return num > max ? max : num;
|
||||
}
|
||||
}
|
||||
|
||||
public static int hsvToRGB(float hue, float saturation, float value) {
|
||||
int i = (int)(hue * 6.0F) % 6;
|
||||
float f = hue * 6.0F - (float)i;
|
||||
float f1 = value * (1.0F - saturation);
|
||||
float f2 = value * (1.0F - f * saturation);
|
||||
float f3 = value * (1.0F - (1.0F - f) * saturation);
|
||||
float f4;
|
||||
float f5;
|
||||
float f6;
|
||||
switch(i) {
|
||||
case 0:
|
||||
f4 = value;
|
||||
f5 = f3;
|
||||
f6 = f1;
|
||||
break;
|
||||
case 1:
|
||||
f4 = f2;
|
||||
f5 = value;
|
||||
f6 = f1;
|
||||
break;
|
||||
case 2:
|
||||
f4 = f1;
|
||||
f5 = value;
|
||||
f6 = f3;
|
||||
break;
|
||||
case 3:
|
||||
f4 = f1;
|
||||
f5 = f2;
|
||||
f6 = value;
|
||||
break;
|
||||
case 4:
|
||||
f4 = f3;
|
||||
f5 = f1;
|
||||
f6 = value;
|
||||
break;
|
||||
case 5:
|
||||
f4 = value;
|
||||
f5 = f1;
|
||||
f6 = f2;
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Something went wrong when converting from HSV to RGB. Input was " + hue + ", " + saturation + ", " + value);
|
||||
}
|
||||
|
||||
int j = clamp((int)(f4 * 255.0F), 0, 255);
|
||||
int k = clamp((int)(f5 * 255.0F), 0, 255);
|
||||
int l = clamp((int)(f6 * 255.0F), 0, 255);
|
||||
return 0xFF << 24 | j << 16 | k << 8 | l;
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.earlydisplay;
|
||||
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.stb.STBImage;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.Channels;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.lwjgl.opengl.GL32C.*;
|
||||
|
||||
public class STBHelper {
|
||||
public static ByteBuffer readFromClasspath(final String name, int initialCapacity) {
|
||||
ByteBuffer buf;
|
||||
try (var channel = Channels.newChannel(
|
||||
Objects.requireNonNull(STBHelper.class.getClassLoader().getResourceAsStream(name), "The resource "+name+" cannot be found"))) {
|
||||
buf = BufferUtils.createByteBuffer(initialCapacity);
|
||||
while (true) {
|
||||
var readbytes = channel.read(buf);
|
||||
if (readbytes == -1) break;
|
||||
if (buf.remaining() == 0) { // extend the buffer by 50%
|
||||
var newBuf = BufferUtils.createByteBuffer(buf.capacity() * 3 / 2);
|
||||
buf.flip();
|
||||
newBuf.put(buf);
|
||||
buf = newBuf;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
buf.flip();
|
||||
return MemoryUtil.memSlice(buf); // we trim the final buffer to the size of the content
|
||||
}
|
||||
|
||||
public static int[] loadTextureFromClasspath(String file, int size, int textureNumber) {
|
||||
int[] lw = new int[1];
|
||||
int[] lh = new int[1];
|
||||
int[] lc = new int[1];
|
||||
var img = loadImageFromClasspath(file, size, lw, lh, lc);
|
||||
var texid = glGenTextures();
|
||||
glActiveTexture(textureNumber);
|
||||
glBindTexture(GL_TEXTURE_2D, texid);
|
||||
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, lw[0], lh[0], 0, GL_RGBA, GL_UNSIGNED_BYTE, img);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
MemoryUtil.memFree(img);
|
||||
return new int[] {lw[0], lh[0]};
|
||||
}
|
||||
|
||||
public static ByteBuffer loadImageFromClasspath(String file, int size, int[] width, int[] height, int[] channels) {
|
||||
ByteBuffer buf = STBHelper.readFromClasspath(file, size);
|
||||
return STBImage.stbi_load_from_memory(buf, width, height, channels, 4);
|
||||
}
|
||||
}
|
||||
-517
@@ -1,517 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.earlydisplay;
|
||||
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.lwjgl.opengl.GL32C.*;
|
||||
|
||||
/**
|
||||
* A very simple, Mojang inspired BufferBuilder.
|
||||
* <em>This has been customized for 2d rendering such as text and simple planar textures</em>
|
||||
* <p>
|
||||
* Not bound to any specific format, ideally should be held onto for re-use.
|
||||
* <p>
|
||||
* Can be used for 'immediate mode' style rendering using {@link #draw()}, or
|
||||
* upload to external vertex arrays for proper instancing using {@link #finishAndUpload()}.
|
||||
* <p>
|
||||
* This is a Triangles only buffer, all data uploaded is in Triangles.
|
||||
* Quads are converted to triangles using {@code 0, 1, 2, 0, 2, 3}.
|
||||
* <p>
|
||||
* Any given {@link Format} should have its individual {@link Element} components
|
||||
* buffered in the order specified by the {@link Format},
|
||||
* followed by an {@link #endVertex()} call to prepare for the next vertex.
|
||||
* <p>
|
||||
* It is illegal to buffer primitives in any format other than the one specified to
|
||||
* {@link #begin(Format, Mode)}.
|
||||
*
|
||||
* @author covers1624
|
||||
*/
|
||||
public class SimpleBufferBuilder implements Closeable {
|
||||
|
||||
private static final MemoryUtil.MemoryAllocator ALLOCATOR = MemoryUtil.getAllocator(false);
|
||||
|
||||
private static final int[] VERTEX_ARRAYS = new int[Format.values().length];
|
||||
private static final int[] VERTEX_BUFFERS = new int[Format.values().length];
|
||||
private static final int[] VERTEX_BUFFER_LENGTHS = new int[Format.values().length];
|
||||
private static int elementBuffer = 0;
|
||||
private static int elementBufferVertexLength = 0;
|
||||
|
||||
static {
|
||||
Arrays.fill(VERTEX_ARRAYS, 0);
|
||||
Arrays.fill(VERTEX_BUFFERS, 0);
|
||||
Arrays.fill(VERTEX_BUFFER_LENGTHS, 0);
|
||||
}
|
||||
|
||||
private long bufferAddr; // Pointer to the backing buffer.
|
||||
private ByteBuffer buffer; // ByteBuffer view of the backing buffer.
|
||||
|
||||
private Format format; // The current format we are buffering.
|
||||
private Mode mode; // The current mode we are buffering.
|
||||
private boolean building; // If we are building the buffer.
|
||||
private int elementIndex; // The current element index we are buffering. if elementIndex == format.types.length, we expect 'endVertex'
|
||||
private int index; // The current index into the buffer we are writing to.
|
||||
private int vertices; // The number of complete vertices we have buffered.
|
||||
|
||||
/**
|
||||
* Create a new SimpleBufferBuilder with an initial capacity.
|
||||
* <p>
|
||||
* The buffer will be doubled as required.
|
||||
* <p>
|
||||
* Generally picking a small number, around 128/256 should be a
|
||||
* safe bet. Provided you cache your buffers, it should not mean much overall.
|
||||
*
|
||||
* @param capacity The initial capacity in bytes.
|
||||
*/
|
||||
public SimpleBufferBuilder(int capacity) {
|
||||
bufferAddr = ALLOCATOR.malloc(capacity);
|
||||
buffer = MemoryUtil.memByteBuffer(bufferAddr, capacity);
|
||||
}
|
||||
|
||||
public static void destroy() {
|
||||
glDeleteBuffers(VERTEX_BUFFERS);
|
||||
glDeleteBuffers(elementBuffer);
|
||||
glDeleteVertexArrays(VERTEX_ARRAYS);
|
||||
}
|
||||
|
||||
private static void ensureElementBufferLength(int vertices) {
|
||||
if (elementBufferVertexLength >= vertices) {
|
||||
return;
|
||||
}
|
||||
|
||||
// treating it as immutable storage, even though it's not
|
||||
final var newElementBuffer = glGenBuffers();
|
||||
var newElementBufferVertexLength = Math.max(1024, elementBufferVertexLength);
|
||||
while (newElementBufferVertexLength < vertices) {
|
||||
newElementBufferVertexLength *= 2;
|
||||
}
|
||||
|
||||
final var oldIndexCount = elementBufferVertexLength + elementBufferVertexLength / 2;
|
||||
final var newIndexCount = newElementBufferVertexLength + newElementBufferVertexLength / 2;
|
||||
|
||||
// allocate new buffer
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, newElementBuffer);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, newIndexCount * 4L, GL_STATIC_DRAW);
|
||||
|
||||
// mapping avoids creating additional CPU copies of the data
|
||||
// unsynchronized is fine because this is a brand-new buffer, and the old contents will be copied in afterward
|
||||
// also can invalidate the whole buffer too, similarly because brand new, don't care what was there before
|
||||
final var mappingOffset = oldIndexCount * 4;
|
||||
final var mappingSize = (newIndexCount - oldIndexCount) * 4;
|
||||
final var mappedBuffer = glMapBufferRange(GL_ELEMENT_ARRAY_BUFFER, mappingOffset, mappingSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
|
||||
|
||||
if(mappedBuffer == null){
|
||||
throw new NullPointerException("OpenGL buffer mapping failed");
|
||||
}
|
||||
|
||||
final int quads = newElementBufferVertexLength / 4;
|
||||
final int oldQuads = elementBufferVertexLength / 4;
|
||||
// generate indices for the extension to the buffer
|
||||
for (int i = oldQuads; i < quads; i++) {
|
||||
// Quads are a bit different, we need to emit 2 triangles such that
|
||||
// when combined they make up a single quad.
|
||||
mappedBuffer.putInt(i * 4 + 0).putInt(i * 4 + 1).putInt(i * 4 + 2);
|
||||
mappedBuffer.putInt(i * 4 + 1).putInt(i * 4 + 3).putInt(i * 4 + 2);
|
||||
}
|
||||
|
||||
glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER);
|
||||
|
||||
if (elementBuffer != 0) {
|
||||
// copy old data from previous element buffer
|
||||
glBindBuffer(GL_COPY_READ_BUFFER, elementBuffer);
|
||||
glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_ELEMENT_ARRAY_BUFFER, 0, 0, mappingOffset);
|
||||
glBindBuffer(GL_COPY_READ_BUFFER, 0);
|
||||
}
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
|
||||
glDeleteBuffers(elementBuffer);
|
||||
elementBuffer = newElementBuffer;
|
||||
elementBufferVertexLength = newElementBufferVertexLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start building a new set of vertex data in the
|
||||
* given format and mode.
|
||||
*
|
||||
* @param format The format to start building in.
|
||||
* @param mode The mode to start building in.
|
||||
*/
|
||||
public SimpleBufferBuilder begin(Format format, Mode mode) {
|
||||
if (bufferAddr == MemoryUtil.NULL) {
|
||||
throw new IllegalStateException("Buffer has been freed."); // You already free'd the buffer
|
||||
}
|
||||
if (building) {
|
||||
throw new IllegalStateException("Already building."); // Your already building verticies.
|
||||
}
|
||||
this.format = format;
|
||||
this.mode = mode;
|
||||
building = true;
|
||||
elementIndex = 0;
|
||||
ensureSpace(format.stride);
|
||||
// Rewind ready for new data.
|
||||
buffer.rewind();
|
||||
buffer.limit(buffer.capacity());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Buffer a position element.
|
||||
*
|
||||
* @param x The x.
|
||||
* @param y The y.
|
||||
* @param z The z.
|
||||
* @return The same builder.
|
||||
*/
|
||||
public SimpleBufferBuilder pos(float x, float y) {
|
||||
if (!building) throw new IllegalStateException("Not building."); // You did not call begin.
|
||||
|
||||
if (elementIndex == format.types.length) throw new IllegalStateException("Expected endVertex"); // we have reached the end of elements to buffer for this vertex, we expected an endVertex call.
|
||||
if (format.types[elementIndex] != Element.POS) throw new IllegalArgumentException("Expected " + format.types[elementIndex]); // You called the wrong method for the format order.
|
||||
|
||||
// Assumes that our POS element specifies the FLOAT data type.
|
||||
buffer.putFloat(index + 0, x);
|
||||
buffer.putFloat(index + 4, y);
|
||||
|
||||
// Increment index for the number of bytes we wrote and increment the element index.
|
||||
index += format.types[elementIndex].width;
|
||||
elementIndex++;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Buffer a texture element.
|
||||
*
|
||||
* @param u The u.
|
||||
* @param v The v.
|
||||
* @return The same builder.
|
||||
*/
|
||||
public SimpleBufferBuilder tex(float u, float v) {
|
||||
if (!building) throw new IllegalStateException("Not building."); // You did not call begin.
|
||||
|
||||
if (elementIndex == format.types.length) throw new IllegalStateException("Expected endVertex"); // we have reached the end of elements to buffer for this vertex, we expected an endVertex call.
|
||||
if (format.types[elementIndex] != Element.TEX) throw new IllegalArgumentException("Expected " + format.types[elementIndex]); // You called the wrong method for the format order.
|
||||
|
||||
// Assumes our TEX element specifies the FLOAT data type.
|
||||
buffer.putFloat(index + 0, u);
|
||||
buffer.putFloat(index + 4, v);
|
||||
|
||||
// Increment index for the number of bytes we wrote and increment the element index.
|
||||
index += format.types[elementIndex].width;
|
||||
elementIndex++;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Buffer a color element.
|
||||
*
|
||||
* @param r The red component. (0-1)
|
||||
* @param g The green component. (0-1)
|
||||
* @param b The blue component. (0-1)
|
||||
* @param a The alpha component. (0-1)
|
||||
* @return The same buffer.
|
||||
*/
|
||||
public SimpleBufferBuilder colour(float r, float g, float b, float a) {
|
||||
// Expand floats to 0-255 and forward.
|
||||
return colour((byte) (r * 255F), (byte) (g * 255F), (byte) (b * 255F), (byte) (a * 255F));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ColourScheme.Colour#packedint(int)
|
||||
* @param packedColor an ABGR packed int
|
||||
* @return the same buffer.
|
||||
*/
|
||||
public SimpleBufferBuilder colour(int packedColor) {
|
||||
if (!building) throw new IllegalStateException("Not building."); // You did not call begin.
|
||||
|
||||
if (elementIndex == format.types.length) throw new IllegalStateException("Expected endVertex"); // we have reached the end of elements to buffer for this vertex, we expected an endVertex call.
|
||||
if (format.types[elementIndex] != Element.COLOR) throw new IllegalArgumentException("Expected " + format.types[elementIndex]); // You called the wrong method for the format order.
|
||||
|
||||
// Assumes our COLOR element specifies the UNSIGNED_BYTE data type.
|
||||
buffer.putInt(index + 0, packedColor);
|
||||
|
||||
// Increment index for the number of bytes we wrote and increment the element index.
|
||||
index += format.types[elementIndex].width;
|
||||
elementIndex++;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Buffer a color element.
|
||||
*
|
||||
* @param r The red component. (0-255)
|
||||
* @param g The green component. (0-255)
|
||||
* @param b The blue component. (0-255)
|
||||
* @param a The alpha component. (0-255)
|
||||
* @return The same buffer.
|
||||
*/
|
||||
public SimpleBufferBuilder colour(byte r, byte g, byte b, byte a) {
|
||||
if (!building) throw new IllegalStateException("Not building."); // You did not call begin.
|
||||
|
||||
if (elementIndex == format.types.length) throw new IllegalStateException("Expected endVertex"); // we have reached the end of elements to buffer for this vertex, we expected an endVertex call.
|
||||
if (format.types[elementIndex] != Element.COLOR) throw new IllegalArgumentException("Expected " + format.types[elementIndex]); // You called the wrong method for the format order.
|
||||
|
||||
// Assumes our COLOR element specifies the UNSIGNED_BYTE data type.
|
||||
buffer.put(index + 0, r);
|
||||
buffer.put(index + 1, g);
|
||||
buffer.put(index + 2, b);
|
||||
buffer.put(index + 3, a);
|
||||
|
||||
// Increment index for the number of bytes we wrote and increment the element index.
|
||||
index += format.types[elementIndex].width;
|
||||
elementIndex++;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* End building the current vertex and prepare for the next.
|
||||
*
|
||||
* @return The same builder.
|
||||
*/
|
||||
public SimpleBufferBuilder endVertex() {
|
||||
if (!building) throw new IllegalStateException("Not building."); // You did not call begin.
|
||||
|
||||
if (elementIndex != format.types.length) throw new IllegalStateException("Expected " + format.types[elementIndex]); // You did not finish building the vertex.
|
||||
|
||||
// Reset elementIndex
|
||||
elementIndex = 0;
|
||||
// Increment the number of vertices we have so far buffered.
|
||||
vertices++;
|
||||
// Make sure there is space for the next vertex.
|
||||
ensureSpace(format.stride);
|
||||
return this;
|
||||
}
|
||||
|
||||
// Checks there is enough space in the buffer for specified number of bytes.
|
||||
// If there is not enough space, the buffer is increased by 50%.
|
||||
private void ensureSpace(int newBytes) {
|
||||
int cap = buffer.capacity();
|
||||
if (index + newBytes > cap) {
|
||||
int newCap = Math.max(3 * cap / 2, 3 * newBytes / 2);
|
||||
bufferAddr = ALLOCATOR.realloc(bufferAddr, newCap);
|
||||
buffer = MemoryUtil.memByteBuffer(bufferAddr, newCap);
|
||||
buffer.rewind();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload the current buffer.
|
||||
* <p>
|
||||
* This will bind a {@link org.lwjgl.opengl.GL32C#GL_ARRAY_BUFFER} and {@link org.lwjgl.opengl.GL32C#GL_ELEMENT_ARRAY_BUFFER}
|
||||
* <p>
|
||||
* The vertex data and index data is uploaded to their respective buffers.
|
||||
* <p>
|
||||
* Uploading the buffers finishes drawing and resets for the next buffer operation.
|
||||
* <p>
|
||||
* This should not be called in conjunction with {@link #draw()}
|
||||
*
|
||||
* @return The number of indexes that were uploaded.
|
||||
*/
|
||||
public int finishAndUpload() {
|
||||
if (!building) throw new IllegalStateException("Not building.");
|
||||
|
||||
int indices;
|
||||
try {
|
||||
if (elementIndex == format.types.length) throw new IllegalStateException("Expected endVertex"); // You didn't finish building your vertex.
|
||||
if (elementIndex != 0) throw new IllegalStateException("Not finished building vertex, Expected: " + format.types[elementIndex]); // You didn't finish building your vertex data.
|
||||
if (vertices == 0) return 0; // No vertices buffered, lets not do anything.
|
||||
if (vertices % mode.vertices != 0) throw new IllegalStateException("Does not contain vertices aligned to " + mode); // You did not put in enough vertices to cleanly slice the data into TRIANGLES/QUADS
|
||||
|
||||
// Reset position to 0, limit the buffer to our index.
|
||||
buffer.position(0);
|
||||
buffer.limit(index);
|
||||
|
||||
// Upload the raw vertex data in dynamic mode.
|
||||
final int vbo = VERTEX_BUFFERS[format.ordinal()];
|
||||
final int vboSize = VERTEX_BUFFER_LENGTHS[format.ordinal()];
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo);
|
||||
if (vboSize < index) {
|
||||
// expand buffer, it's not big enough
|
||||
var newVBOSize = Math.max(1024, vboSize);
|
||||
while (newVBOSize < index){
|
||||
newVBOSize *= 2;
|
||||
}
|
||||
// because everything is overwritten anyway, we can do an in-place reallocation
|
||||
glBufferData(GL_ARRAY_BUFFER, newVBOSize, GL_DYNAMIC_DRAW);
|
||||
VERTEX_BUFFER_LENGTHS[format.ordinal()] = newVBOSize;
|
||||
}
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, buffer);
|
||||
|
||||
// The number of indices for triangles is equal to our vertex count, as that is
|
||||
// what we operate in. However, for Quads, we have exactly vertices + vertices / 2
|
||||
// vertices once we convert the quads to triangles.
|
||||
indices = mode == Mode.TRIANGLES ? vertices : vertices + vertices / 2;
|
||||
|
||||
if (mode == Mode.QUADS) {
|
||||
ensureElementBufferLength(vertices);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementBuffer);
|
||||
}
|
||||
|
||||
return indices;
|
||||
} finally {
|
||||
// Reset builder state for next begin call.
|
||||
building = false;
|
||||
vertices = 0;
|
||||
index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload and draw this buffer using one of a number of re-usable set of buffers.
|
||||
* <p>
|
||||
* This will immediately upload the buffer, resetting this builder for the next
|
||||
* buffer operation, and draw the uploaded data.
|
||||
* <p>
|
||||
* You will need to bind shaders, textures, etc, before calling this function.
|
||||
*/
|
||||
public void draw() {
|
||||
if (!building) throw new IllegalStateException("Not building.");
|
||||
|
||||
int vao = VERTEX_ARRAYS[format.ordinal()];
|
||||
int vbo = VERTEX_BUFFERS[format.ordinal()];
|
||||
|
||||
if (vao == 0) {
|
||||
// These 3 buffers are paired, you can't allocate one without the others.
|
||||
assert vbo == 0;
|
||||
|
||||
// Make new vertex array and buffers!
|
||||
vao = glGenVertexArrays();
|
||||
vbo = glGenBuffers();
|
||||
|
||||
// Cache the vertex array and buffers for future re-use.
|
||||
VERTEX_ARRAYS[format.ordinal()] = vao;
|
||||
VERTEX_BUFFERS[format.ordinal()] = vbo;
|
||||
|
||||
// Ask our Format to set up its data layout for the vertex array.
|
||||
// but only once, the VAO saves this state
|
||||
glBindVertexArray(vao);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo);
|
||||
format.bind();
|
||||
format.enable();
|
||||
}
|
||||
// Bind the vertex array and buffers!
|
||||
glBindVertexArray(vao);
|
||||
|
||||
// Upload the data.
|
||||
int indices = finishAndUpload();
|
||||
|
||||
if (mode == Mode.QUADS) {
|
||||
glDrawElements(GL_TRIANGLES, indices, GL_UNSIGNED_INT, 0);
|
||||
} else {
|
||||
glDrawArrays(GL_TRIANGLES, 0, indices);
|
||||
}
|
||||
|
||||
// Unbind the vertex array.
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear this builder's cached buffer.
|
||||
* <p>
|
||||
* If you are completely done, call {@link #destroy()}
|
||||
*/
|
||||
@Override
|
||||
public void close() {
|
||||
ALLOCATOR.free(bufferAddr);
|
||||
bufferAddr = MemoryUtil.NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a primitive mode that this builder is capable of buffering in.
|
||||
*/
|
||||
public enum Mode {
|
||||
TRIANGLES(3),
|
||||
QUADS(4),
|
||||
;
|
||||
|
||||
public final int vertices;
|
||||
|
||||
Mode(int vertices) {
|
||||
this.vertices = vertices;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies a vertex element with a specific data type, number of primitives and a size in bytes.
|
||||
*/
|
||||
public enum Element {
|
||||
POS(GL_FLOAT, 2, 2 * 4),
|
||||
TEX(GL_FLOAT, 2, 2 * 4),
|
||||
COLOR(GL_UNSIGNED_BYTE, 4, 4);
|
||||
|
||||
public final int glType;
|
||||
public final int count;
|
||||
public final int width;
|
||||
|
||||
Element(int glType, int count, int width) {
|
||||
this.glType = glType;
|
||||
this.count = count;
|
||||
this.width = width;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies a combination of vertex elements.
|
||||
*/
|
||||
public enum Format {
|
||||
POS(Element.POS),
|
||||
POS_TEX(Element.POS, Element.TEX),
|
||||
POS_COLOR(Element.POS, Element.COLOR),
|
||||
POS_TEX_COLOR(Element.POS, Element.TEX, Element.COLOR);
|
||||
|
||||
private final Element[] types;
|
||||
public final int stride;
|
||||
|
||||
Format(Element... types) {
|
||||
this.types = types;
|
||||
|
||||
// Stride is the width of each vertex in bytes.
|
||||
stride = Arrays.stream(types).mapToInt(e -> e.width).sum();
|
||||
}
|
||||
|
||||
/**
|
||||
* set up the attribute pointers for this format.
|
||||
* <p>
|
||||
* Assumes that an array buffer is already bound and ready to go.
|
||||
*/
|
||||
public void bind() {
|
||||
int offset = 0;
|
||||
|
||||
// Set up the pointers that tell GL where our interleaved
|
||||
// vertex data is in the buffers.
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
Element type = types[i];
|
||||
switch (type.glType) {
|
||||
case GL_FLOAT -> glVertexAttribPointer(i, type.count, GL_FLOAT, false, stride, offset);
|
||||
case GL_UNSIGNED_BYTE -> glVertexAttribPointer(i, type.count, GL_UNSIGNED_BYTE, true, stride, offset);
|
||||
default -> throw new IllegalStateException("Unknown glType, I don't know how to bind this vertex element: " + type);
|
||||
}
|
||||
// add to the offset for the next element.
|
||||
offset += type.width;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the vertex attributes this format contains.
|
||||
*/
|
||||
public void enable() {
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
glEnableVertexAttribArray(i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the vertex attributes this format contains.
|
||||
*/
|
||||
public void disable() {
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
glDisableVertexAttribArray(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.earlydisplay;
|
||||
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.stb.STBTTAlignedQuad;
|
||||
import org.lwjgl.stb.STBTTFontinfo;
|
||||
import org.lwjgl.stb.STBTTPackContext;
|
||||
import org.lwjgl.stb.STBTTPackRange;
|
||||
import org.lwjgl.stb.STBTTPackedchar;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static org.lwjgl.opengl.GL32C.*;
|
||||
import static org.lwjgl.stb.STBTruetype.*;
|
||||
import static org.lwjgl.system.MemoryUtil.NULL;
|
||||
|
||||
public class SimpleFont {
|
||||
private final int textureNumber;
|
||||
private final int lineSpacing;
|
||||
private final int descent;
|
||||
private final int GLYPH_COUNT = 127-32;
|
||||
private Glyph[] glyphs;
|
||||
|
||||
private record Glyph(char c, int charwidth, int[] pos, float[] uv) {
|
||||
Pos loadQuad(Pos pos, int colour, SimpleBufferBuilder bb) {
|
||||
final var x0 = pos.x() + pos()[0];
|
||||
final var y0 = pos.y() + pos()[1];
|
||||
final var x1 = pos.x() + pos()[2];
|
||||
final var y1 = pos.y() + pos()[3];
|
||||
bb.pos(x0, y0).tex(uv()[0], uv()[1]).colour(colour).endVertex();
|
||||
bb.pos(x1, y0).tex(uv()[2], uv()[1]).colour(colour).endVertex();
|
||||
bb.pos(x0, y1).tex(uv()[0], uv()[3]).colour(colour).endVertex();
|
||||
bb.pos(x1, y1).tex(uv()[2], uv()[3]).colour(colour).endVertex();
|
||||
return new Pos(pos.x()+charwidth(), pos.y(), pos.minx());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the font and store it in the textureNumber location
|
||||
*/
|
||||
public SimpleFont(String fontName, int scale, int bufferSize, int textureNumber) {
|
||||
ByteBuffer buf = STBHelper.readFromClasspath(fontName, bufferSize);
|
||||
var info = STBTTFontinfo.create();
|
||||
if (!stbtt_InitFont(info, buf)) {
|
||||
throw new IllegalStateException("Bad font");
|
||||
}
|
||||
|
||||
var ascent = new float[1];
|
||||
var descent = new float[1];
|
||||
var lineGap = new float[1];
|
||||
int fontSize = 24;
|
||||
stbtt_GetScaledFontVMetrics(buf, 0, fontSize, ascent, descent, lineGap);
|
||||
this.lineSpacing = (int)(ascent[0] - descent[0] + lineGap[0]);
|
||||
this.descent = (int)Math.floor(descent[0]);
|
||||
int fontTextureId = glGenTextures();
|
||||
glActiveTexture(GL_TEXTURE0+textureNumber);
|
||||
this.textureNumber = textureNumber;
|
||||
glBindTexture(GL_TEXTURE_2D, fontTextureId);
|
||||
try (var packedchars = STBTTPackedchar.malloc(GLYPH_COUNT)) {
|
||||
int texwidth = 256;
|
||||
int texheight = 128;
|
||||
try (STBTTPackRange.Buffer packRanges = STBTTPackRange.malloc(1)) {
|
||||
var bitmap = BufferUtils.createByteBuffer(texwidth * texheight);
|
||||
try (STBTTPackRange packRange = STBTTPackRange.malloc()) {
|
||||
packRanges.put(packRange.set(fontSize, 32, null, GLYPH_COUNT, packedchars, (byte) 1, (byte) 1));
|
||||
packRanges.flip();
|
||||
}
|
||||
|
||||
try (STBTTPackContext pc = STBTTPackContext.malloc()) {
|
||||
stbtt_PackBegin(pc, bitmap, texwidth, texheight, 0, 1, NULL);
|
||||
stbtt_PackSetOversampling(pc, 1, 1);
|
||||
stbtt_PackSetSkipMissingCodepoints(pc, true);
|
||||
stbtt_PackFontRanges(pc, buf, 0, packRanges);
|
||||
stbtt_PackEnd(pc);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, texwidth, texheight, 0, GL_RED, GL_UNSIGNED_BYTE, bitmap);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
}
|
||||
}
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
try (var q = STBTTAlignedQuad.malloc()) {
|
||||
float[] x = new float[1];
|
||||
float[] y = new float[1];
|
||||
glyphs = new Glyph[GLYPH_COUNT];
|
||||
|
||||
for (int i = 0; i < GLYPH_COUNT; i++) {
|
||||
x[0] = 0f;
|
||||
y[0] = fontSize;
|
||||
stbtt_GetPackedQuad(packedchars, texwidth, texheight, i, x, y, q, true);
|
||||
glyphs[i] = new Glyph((char) (i + 32), (int) (x[0] - 0f), new int[]{(int) q.x0(), (int) q.y0(), (int) q.x1(), (int) q.y1()}, new float[]{q.s0(), q.t0(), q.s1(), q.t1()});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int lineSpacing() {
|
||||
return lineSpacing;
|
||||
}
|
||||
|
||||
int textureNumber() {
|
||||
return textureNumber;
|
||||
}
|
||||
|
||||
int descent() {
|
||||
return descent;
|
||||
}
|
||||
|
||||
public int stringWidth(String text) {
|
||||
var bytes = text.getBytes(StandardCharsets.US_ASCII);
|
||||
int len = 0;
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
final byte c = bytes[i];
|
||||
len += switch (c) {
|
||||
case '\n', '\t' -> 0;
|
||||
case ' ' -> glyphs[0].charwidth();
|
||||
default -> {
|
||||
if (c - 32 < this.GLYPH_COUNT && c > 32) {
|
||||
yield this.glyphs[c - 32].charwidth();
|
||||
} else {
|
||||
yield 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
return len;
|
||||
}
|
||||
private record Pos(int x, int y, int minx) {}
|
||||
|
||||
/**
|
||||
* A piece of text to display
|
||||
*
|
||||
* @param string The text
|
||||
* @param colour The colour of the text as an RGBA packed int
|
||||
*/
|
||||
public record DisplayText(String string, int colour) {
|
||||
private byte[] asBytes() {
|
||||
return string.getBytes(StandardCharsets.US_ASCII);
|
||||
}
|
||||
|
||||
Pos generateStringArray(SimpleFont font, Pos pos, SimpleBufferBuilder bb) {
|
||||
for (int i = 0; i < asBytes().length; i++) {
|
||||
byte c = asBytes()[i];
|
||||
pos = switch (c) {
|
||||
case '\n' -> new Pos(pos.minx(), pos.y()+font.lineSpacing(), pos.minx());
|
||||
case '\t' -> new Pos(pos.x()+font.glyphs[0].charwidth() * 4, pos.y(), pos.minx());
|
||||
case ' ' -> new Pos(pos.x()+font.glyphs[0].charwidth(), pos.y(), pos.minx());
|
||||
default -> {
|
||||
if (c-32 < font.GLYPH_COUNT && c > 32) {
|
||||
pos = font.glyphs[c - 32].loadQuad(pos, colour(), bb);
|
||||
}
|
||||
yield pos;
|
||||
}
|
||||
};
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate vertices for a set of display texts
|
||||
* @param x The starting screen x coordinate
|
||||
* @param y The starting screen y coordinate
|
||||
* @param texts Some {@link DisplayText} to display
|
||||
* @return a {@link SimpleBufferBuilder} that can draw the texts
|
||||
*/
|
||||
public SimpleBufferBuilder generateVerticesForTexts(int x, int y, SimpleBufferBuilder textBB, DisplayText... texts) {
|
||||
var pos = new Pos(x, y, x);
|
||||
for (DisplayText text : texts) {
|
||||
pos = text.generateStringArray(this, pos, textBB);
|
||||
}
|
||||
return textBB;
|
||||
}
|
||||
|
||||
}
|
||||
-1
@@ -1 +0,0 @@
|
||||
net.minecraftforge.fml.earlydisplay.DisplayWindow
|
||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB |
@@ -1,13 +0,0 @@
|
||||
█▀▀▀▀▀█ ▀▄▄▄▀█ █ █▀▀▀▀▀█
|
||||
█ ███ █ ███ ▀███▀ █ ███ █
|
||||
█ ▀▀▀ █ ▀ ▄ ███▀█ █ ▀▀▀ █
|
||||
▀▀▀▀▀▀▀ ▀ █ ▀ ▀▄█ ▀▀▀▀▀▀▀
|
||||
█▀█▀▄▄▀▄▀ █▄▀▄ ▀▀▀ ▄▀▀▀▄▀
|
||||
▄█▄▄ ▀▀███▀██▄ █▀ ▀▄▄
|
||||
▀█▄▀ ▀▀▄▄▀▀ █▀█▄▄████ ▀▀█
|
||||
▄▀▀▄▀ ▀▄▀▄█ ▀ ▀▀▀▄█ ▀▀▄
|
||||
▀ ▀▀ ▄ ▄██▀ ▄█▀▀▀█▀█▀█
|
||||
█▀▀▀▀▀█ ▄█▄▄▀▀▄█ ▀ █ ▀▀▀
|
||||
█ ███ █ ▄▄ ▄ █ ▀██▀██▄██
|
||||
█ ▀▀▀ █ ██▄███▀█ █ █ █▀
|
||||
▀▀▀▀▀▀▀ ▀▀ ▀▀▀ ▀▀▀▀▀▀▀▀
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 43 KiB |
+36
-28
@@ -2,43 +2,46 @@ plugins {
|
||||
id 'com.github.ben-manes.versions'
|
||||
id 'org.javamodularity.moduleplugin' version '1.8.3' apply false
|
||||
id 'org.cadixdev.licenser'
|
||||
id 'java-library'
|
||||
id 'jacoco'
|
||||
}
|
||||
apply plugin: 'java-library'
|
||||
apply plugin: 'jacoco'
|
||||
apply plugin: 'org.javamodularity.moduleplugin'
|
||||
|
||||
dependencyUpdates.rejectVersionIf { isNonStable(it.candidate.version) }
|
||||
configurations.forEach{ it.transitive = false }
|
||||
|
||||
dependencies {
|
||||
compileOnly(libs.nulls)
|
||||
compileOnly("org.jetbrains:annotations:${JETBRAINS_ANNOTATIONS_VERSION}")
|
||||
|
||||
api(libs.bundles.asm) // Needed by all the black magic
|
||||
api(libs.forgespi)
|
||||
api(libs.mergetool.api)
|
||||
api(libs.log4j.api)
|
||||
api(libs.slf4j.api)
|
||||
api(libs.guava)
|
||||
api(libs.gson)
|
||||
api(libs.maven.artifact)
|
||||
api(libs.apache.commons)
|
||||
api(libs.bundles.night.config)
|
||||
api(libs.modlauncher)
|
||||
api(libs.coremods)
|
||||
api(libs.mojang.logging)
|
||||
api(libs.jarjar.selector)
|
||||
api(libs.jarjar.meta)
|
||||
api("org.ow2.asm:asm:${ASM_VERSION}")
|
||||
api("org.ow2.asm:asm-tree:${ASM_VERSION}")
|
||||
api("org.ow2.asm:asm-commons:${ASM_VERSION}")
|
||||
api("net.minecraftforge:forgespi:${SPI_VERSION}")
|
||||
api("org.apache.logging.log4j:log4j-api:${LOG4J_VERSION}")
|
||||
api("org.slf4j:slf4j-api:${SLF4J_API_VERSION}")
|
||||
api("com.google.guava:guava:${GUAVA_VERSION}")
|
||||
api("com.google.code.gson:gson:${GSON_VERSION}")
|
||||
api("org.apache.maven:maven-artifact:${APACHE_MAVEN_ARTIFACT_VERSION}")
|
||||
api("org.apache.commons:commons-lang3:${APACHE_COMMONS_LANG3_VERSION}")
|
||||
api("com.electronwill.night-config:core:${NIGHTCONFIG_VERSION}")
|
||||
api("com.electronwill.night-config:toml:${NIGHTCONFIG_VERSION}")
|
||||
api("cpw.mods:modlauncher:${MODLAUNCHER_VERSION}")
|
||||
api("net.minecraftforge:coremods:${COREMODS_VERSION}")
|
||||
api("com.mojang:logging:${MOJANG_LOGGING_VERSION}")
|
||||
api "net.minecraftforge:JarJarSelector:${JARJAR_VERSION}"
|
||||
api "net.minecraftforge:JarJarMetadata:${JARJAR_VERSION}"
|
||||
|
||||
implementation(libs.jopt.simple)
|
||||
implementation(libs.securemodules)
|
||||
implementation(libs.accesstransformers)
|
||||
implementation(libs.terminalconsoleappender)
|
||||
implementation(libs.jimfs)
|
||||
implementation("net.sf.jopt-simple:jopt-simple:${JOPT_SIMPLE_VERSION}")
|
||||
implementation("cpw.mods:securejarhandler:${SECUREJARHANDLER_VERSION}")
|
||||
implementation("net.minecraftforge:accesstransformers:${ACCESSTRANSFORMERS_VERSION}")
|
||||
implementation("net.minecrell:terminalconsoleappender:${TERMINALCONSOLEAPPENDER_VERSION}")
|
||||
// implementation("org.jline:jline:${JLINE_VERSION}")
|
||||
|
||||
implementation(libs.log4j.core)
|
||||
annotationProcessor(libs.log4j.core)
|
||||
implementation("org.apache.logging.log4j:log4j-core:${LOG4J_VERSION}")
|
||||
annotationProcessor("org.apache.logging.log4j:log4j-core:${LOG4J_VERSION}")
|
||||
|
||||
testCompileOnly(libs.nulls)
|
||||
testRuntimeOnly(libs.bootstrap)
|
||||
testCompileOnly("org.jetbrains:annotations:${JETBRAINS_ANNOTATIONS_VERSION}")
|
||||
testRuntimeOnly("cpw.mods:bootstraplauncher:${BOOTSTRAPLAUNCHER_VERSION}")
|
||||
}
|
||||
|
||||
task sourcesJar(type: Jar) {
|
||||
@@ -48,6 +51,12 @@ task sourcesJar(type: Jar) {
|
||||
|
||||
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',
|
||||
@@ -60,7 +69,6 @@ ext {
|
||||
}
|
||||
|
||||
jar.doFirst {
|
||||
manifest.attributes['Automatic-Module-Name'] = project.name
|
||||
MANIFESTS.each { pkg, values ->
|
||||
manifest.attributes(values, pkg)
|
||||
}
|
||||
|
||||
@@ -56,7 +56,6 @@ public class ObjectHolderDefinalize implements ILaunchPluginService {
|
||||
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();
|
||||
|
||||
@@ -20,6 +20,8 @@ import org.objectweb.asm.tree.MethodNode;
|
||||
|
||||
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.Marker;
|
||||
import org.slf4j.MarkerFactory;
|
||||
|
||||
/**
|
||||
* Modifies specified enums to allow runtime extension by making the $VALUES field non-final and
|
||||
|
||||
@@ -14,7 +14,6 @@ public class BackgroundWaiter {
|
||||
private static ExecutorService runner = Executors.newSingleThreadExecutor();
|
||||
|
||||
public static void runAndTick(Runnable r, Runnable tick) {
|
||||
ImmediateWindowHandler.updateProgress("Loading bootstrap resources");
|
||||
final Future<?> work = runner.submit(r);
|
||||
do {
|
||||
tick.run();
|
||||
|
||||
@@ -14,8 +14,6 @@ import java.util.List;
|
||||
* or server.
|
||||
*/
|
||||
public class EarlyLoadingException extends RuntimeException {
|
||||
private static final long serialVersionUID = 2302192216172508006L;
|
||||
|
||||
public static class ExceptionData {
|
||||
private final IModInfo modInfo;
|
||||
private final String i18message;
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import com.electronwill.nightconfig.core.CommentedConfig;
|
||||
import com.electronwill.nightconfig.core.ConfigSpec;
|
||||
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
|
||||
import com.electronwill.nightconfig.core.file.FileNotFoundAction;
|
||||
@@ -16,77 +15,21 @@ import org.slf4j.Logger;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
|
||||
|
||||
public class FMLConfig
|
||||
{
|
||||
public enum ConfigValue {
|
||||
EARLY_WINDOW_CONTROL("earlyWindowControl", Boolean.TRUE, "Should we control the window. Disabling this disables new GL features and can be bad for mods that rely on them."),
|
||||
MAX_THREADS("maxThreads", -1, "Max threads for early initialization parallelism, -1 is based on processor count", FMLConfig::maxThreads),
|
||||
VERSION_CHECK("versionCheck", Boolean.TRUE, "Enable forge global version checking"),
|
||||
DEFAULT_CONFIG_PATH("defaultConfigPath", "defaultconfigs", "Default config path for servers"),
|
||||
DISABLE_OPTIMIZED_DFU("disableOptimizedDFU", Boolean.TRUE, "Disables Optimized DFU client-side - already disabled on servers"),
|
||||
EARLY_WINDOW_PROVIDER("earlyWindowProvider", "fmlearlywindow", "Early window provider"),
|
||||
EARLY_WINDOW_WIDTH("earlyWindowWidth", 854, "Early window width"),
|
||||
EARLY_WINDOW_HEIGHT("earlyWindowHeight", 480, "Early window height"),
|
||||
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?")
|
||||
;
|
||||
|
||||
private final String entry;
|
||||
private final Object defaultValue;
|
||||
private final String comment;
|
||||
private final Function<Object, Object> entryFunction;
|
||||
|
||||
ConfigValue(final String entry, final Object defaultValue, final String comment) {
|
||||
this(entry, defaultValue, comment, Function.identity());
|
||||
}
|
||||
ConfigValue(final String entry, final Object defaultValue, final String comment, Function<Object, Object> entryFunction) {
|
||||
this.entry = entry;
|
||||
this.defaultValue = defaultValue;
|
||||
this.comment = comment;
|
||||
this.entryFunction = entryFunction;
|
||||
}
|
||||
|
||||
void buildConfigEntry(ConfigSpec spec, CommentedConfig commentedConfig) {
|
||||
if (this.defaultValue instanceof List<?> list) {
|
||||
spec.defineList(this.entry, list, e -> e instanceof String);
|
||||
} else {
|
||||
spec.define(this.entry, this.defaultValue);
|
||||
}
|
||||
commentedConfig.add(this.entry, this.defaultValue);
|
||||
commentedConfig.setComment(this.entry, this.comment);
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T getConfigValue(CommentedFileConfig config) {
|
||||
return (T) this.entryFunction.apply(config.get(this.entry));
|
||||
}
|
||||
|
||||
public <T> void updateValue(final CommentedFileConfig configData, final T value) {
|
||||
configData.set(this.entry, value);
|
||||
}
|
||||
}
|
||||
|
||||
private static Object maxThreads(final Object value) {
|
||||
int val = (Integer)value;
|
||||
if (val <= 0) return Runtime.getRuntime().availableProcessors();
|
||||
else return val;
|
||||
}
|
||||
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static final FMLConfig INSTANCE = new FMLConfig();
|
||||
private static final ConfigSpec configSpec = new ConfigSpec();
|
||||
private static final CommentedConfig configComments = CommentedConfig.inMemory();
|
||||
private static FMLConfig INSTANCE = new FMLConfig();
|
||||
private static ConfigSpec configSpec = new ConfigSpec();
|
||||
static {
|
||||
for (ConfigValue cv: ConfigValue.values()) {
|
||||
cv.buildConfigEntry(configSpec, configComments);
|
||||
}
|
||||
configSpec.define("splashscreen", Boolean.TRUE);
|
||||
configSpec.define("maxThreads", -1);
|
||||
configSpec.define("versionCheck", Boolean.TRUE);
|
||||
configSpec.define("defaultConfigPath", "defaultconfigs");
|
||||
configSpec.define("disableOptimizedDFU", Boolean.TRUE);
|
||||
}
|
||||
|
||||
private CommentedFileConfig configData;
|
||||
@@ -95,6 +38,7 @@ public class FMLConfig
|
||||
{
|
||||
configData = CommentedFileConfig.builder(configFile).sync()
|
||||
.onFileNotFound(FileNotFoundAction.copyData(Objects.requireNonNull(getClass().getResourceAsStream("/META-INF/defaultfmlconfig.toml"))))
|
||||
.autosave().autoreload()
|
||||
.writingMode(WritingMode.REPLACE)
|
||||
.build();
|
||||
try
|
||||
@@ -103,14 +47,13 @@ public class FMLConfig
|
||||
}
|
||||
catch (ParsingException e)
|
||||
{
|
||||
throw new RuntimeException("Failed to load FML config from " + configFile, e);
|
||||
throw new RuntimeException("Failed to load FML config from " + configFile.toString(), e);
|
||||
}
|
||||
if (!configSpec.isCorrect(configData)) {
|
||||
LOGGER.warn(CORE, "Configuration file {} is not correct. Correcting", configFile);
|
||||
configSpec.correct(configData, (action, path, incorrectValue, correctedValue) ->
|
||||
LOGGER.info(CORE, "Incorrect key {} was corrected from {} to {}", path, incorrectValue, correctedValue));
|
||||
LOGGER.warn(CORE, "Incorrect key {} was corrected from {} to {}", path, incorrectValue, correctedValue));
|
||||
}
|
||||
configData.putAllComments(configComments);
|
||||
configData.save();
|
||||
}
|
||||
|
||||
@@ -121,34 +64,33 @@ public class FMLConfig
|
||||
if (LOGGER.isTraceEnabled(CORE))
|
||||
{
|
||||
LOGGER.trace(CORE, "Loaded FML config from {}", FMLPaths.FMLCONFIG.get());
|
||||
for (ConfigValue cv: ConfigValue.values()) {
|
||||
LOGGER.trace(CORE, "FMLConfig {} is {}", cv.entry, cv.getConfigValue(INSTANCE.configData));
|
||||
}
|
||||
LOGGER.trace(CORE, "Splash screen is {}", FMLConfig.splashScreenEnabled());
|
||||
LOGGER.trace(CORE, "Max threads for mod loading computed at {}", FMLConfig.loadingThreadCount());
|
||||
LOGGER.trace(CORE, "Version check is {}", FMLConfig.runVersionCheck());
|
||||
LOGGER.trace(CORE, "Default config paths at {}", FMLConfig.defaultConfigPath());
|
||||
}
|
||||
FMLPaths.getOrCreateGameRelativePath(Paths.get(FMLConfig.getConfigValue(ConfigValue.DEFAULT_CONFIG_PATH)));
|
||||
FMLPaths.getOrCreateGameRelativePath(Paths.get(FMLConfig.defaultConfigPath()));
|
||||
}
|
||||
|
||||
public static String getConfigValue(ConfigValue v) {
|
||||
return v.getConfigValue(INSTANCE.configData);
|
||||
public static boolean splashScreenEnabled() {
|
||||
return INSTANCE.configData.<Boolean>getOptional("splashscreen").orElse(Boolean.FALSE);
|
||||
}
|
||||
|
||||
public static boolean getBoolConfigValue(ConfigValue v) {
|
||||
return v.getConfigValue(INSTANCE.configData);
|
||||
public static int loadingThreadCount() {
|
||||
int val = INSTANCE.configData.<Integer>getOptional("maxThreads").orElse(-1);
|
||||
if (val <= 0) return Runtime.getRuntime().availableProcessors();
|
||||
return val;
|
||||
}
|
||||
|
||||
public static int getIntConfigValue(ConfigValue v) {
|
||||
return v.getConfigValue(INSTANCE.configData);
|
||||
}
|
||||
|
||||
public static <A> List<A> getListConfigValue(ConfigValue v) {
|
||||
return v.getConfigValue(INSTANCE.configData);
|
||||
}
|
||||
public static <T> void updateConfig(ConfigValue v, T value) {
|
||||
v.updateValue(INSTANCE.configData, value);
|
||||
INSTANCE.configData.save();
|
||||
public static boolean runVersionCheck() {
|
||||
return INSTANCE.configData.<Boolean>getOptional("versionCheck").orElse(Boolean.TRUE);
|
||||
}
|
||||
|
||||
public static String defaultConfigPath() {
|
||||
return getConfigValue(ConfigValue.DEFAULT_CONFIG_PATH);
|
||||
return INSTANCE.configData.<String>getOptional("defaultConfigPath").orElse("defaultconfigs");
|
||||
}
|
||||
|
||||
public static boolean isOptimizedDFUDisabled() {
|
||||
return INSTANCE.configData.<Boolean>getOptional("disableOptimizedDFU").orElse(Boolean.TRUE);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@ 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.EarlyProgressVisualization;
|
||||
import net.minecraftforge.fml.loading.progress.StartupMessageManager;
|
||||
import net.minecraftforge.fml.loading.targets.CommonLaunchHandler;
|
||||
import net.minecraftforge.forgespi.Environment;
|
||||
import net.minecraftforge.forgespi.coremod.ICoreModProvider;
|
||||
@@ -136,7 +138,6 @@ public class FMLLoader
|
||||
throw new RuntimeException("Missing launch handler: " + launchTarget);
|
||||
}
|
||||
|
||||
// TODO: 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");
|
||||
@@ -151,6 +152,7 @@ public class FMLLoader
|
||||
|
||||
versionInfo = new VersionInfo(arguments);
|
||||
|
||||
StartupMessageManager.modLoaderConsumer().ifPresent(c->c.accept("Early Loading!"));
|
||||
accessTransformer.getExtension().accept(Pair.of(naming, "srg"));
|
||||
|
||||
LOGGER.debug(CORE,"Received command line version data : {}", versionInfo);
|
||||
@@ -167,6 +169,7 @@ public class FMLLoader
|
||||
}
|
||||
|
||||
public static List<ITransformationService.Resource> completeScan(IModuleLayerManager layerManager) {
|
||||
progressWindowTick = EarlyProgressVisualization.INSTANCE.accept(dist, commonLaunchHandler.isData(), versionInfo.mcVersion());
|
||||
moduleLayerManager = layerManager;
|
||||
languageLoadingProvider = new LanguageLoadingProvider();
|
||||
backgroundScanHandler = modValidator.stage2Validation();
|
||||
@@ -202,10 +205,9 @@ public class FMLLoader
|
||||
return dist;
|
||||
}
|
||||
|
||||
public static void beforeStart(ModuleLayer gameLayer)
|
||||
public static void beforeStart(ClassLoader launchClassLoader)
|
||||
{
|
||||
ImmediateWindowHandler.acceptGameLayer(gameLayer);
|
||||
ImmediateWindowHandler.updateProgress("Launching minecraft");
|
||||
StartupMessageManager.modLoaderConsumer().ifPresent(c->c.accept("Launching minecraft"));
|
||||
progressWindowTick.run();
|
||||
}
|
||||
|
||||
|
||||
@@ -74,6 +74,14 @@ public enum FMLPaths
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #getOrCreateGameRelativePath(Path)} instead.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "1.19.4")
|
||||
public static Path getOrCreateGameRelativePath(Path path, String name) {
|
||||
return getOrCreateGameRelativePath(path);
|
||||
}
|
||||
|
||||
public static Path getOrCreateGameRelativePath(Path path) {
|
||||
Path gameFolderPath = FMLPaths.GAMEDIR.get().resolve(path);
|
||||
|
||||
|
||||
@@ -126,7 +126,6 @@ public class FMLServiceProvider implements ITransformationService
|
||||
targetMcpMappings = option.value(mappingsOption);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public @NotNull List<ITransformer> transformers()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class FileUtils
|
||||
{
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
|
||||
/**
|
||||
* @deprecated Use normal Java NIO methods instead
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "1.19.4")
|
||||
public static Path getOrCreateDirectory(Path dirPath, String dirLabel) {
|
||||
if (!Files.isDirectory(dirPath))
|
||||
{
|
||||
try {
|
||||
Files.createDirectories(dirPath);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Problem creating directory", e);
|
||||
}
|
||||
}
|
||||
|
||||
return dirPath;
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import net.minecraftforge.fml.loading.progress.ProgressMeter;
|
||||
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.*;
|
||||
import java.util.function.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ImmediateWindowHandler {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
|
||||
private static ImmediateWindowProvider provider;
|
||||
|
||||
private static ProgressMeter earlyProgress;
|
||||
public static void load(final String launchTarget, final String[] arguments) {
|
||||
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)) {
|
||||
provider = new DummyProvider();
|
||||
LOGGER.info("ImmediateWindowProvider not loading because splash screen is disabled");
|
||||
} 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(() -> {
|
||||
LOGGER.info("Failed to find ImmediateWindowProvider {}, disabling", providername);
|
||||
return Optional.of(new DummyProvider());
|
||||
}).orElseThrow();
|
||||
}
|
||||
// Only update config if the provider isn't the dummy provider
|
||||
if (!Objects.equals(provider.name(), "dummyprovider"))
|
||||
FMLConfig.updateConfig(FMLConfig.ConfigValue.EARLY_WINDOW_PROVIDER, provider.name());
|
||||
FMLLoader.progressWindowTick = provider.initialize(arguments);
|
||||
earlyProgress = StartupNotificationManager.addProgressBar("EARLY", 0);
|
||||
earlyProgress.label("Bootstrapping Minecraft");
|
||||
}
|
||||
|
||||
public static long setupMinecraftWindow(final IntSupplier width, final IntSupplier height, final Supplier<String> title, final LongSupplier monitor) {
|
||||
return provider.setupMinecraftWindow(width, height, title, monitor);
|
||||
}
|
||||
|
||||
public static boolean positionWindow(Optional<Object> monitor,IntConsumer widthSetter, IntConsumer heightSetter, IntConsumer xSetter, IntConsumer ySetter) {
|
||||
return provider.positionWindow(monitor, widthSetter, heightSetter, xSetter, ySetter);
|
||||
}
|
||||
|
||||
public static void updateFBSize(IntConsumer width, IntConsumer height) {
|
||||
provider.updateFramebufferSize(width, height);
|
||||
}
|
||||
|
||||
public static <T> Supplier<T> loadingOverlay(Supplier<?> mc, Supplier<?> ri, Consumer<Optional<Throwable>> ex, boolean fade) {
|
||||
earlyProgress.complete();
|
||||
return provider.loadingOverlay(mc, ri, ex, fade);
|
||||
}
|
||||
|
||||
public static void acceptGameLayer(final ModuleLayer layer) {
|
||||
provider.updateModuleReads(layer);
|
||||
}
|
||||
|
||||
public static void renderTick() {
|
||||
provider.periodicTick();
|
||||
}
|
||||
|
||||
public static String getGLVersion() {
|
||||
return provider.getGLVersion();
|
||||
}
|
||||
public static void updateProgress(final String message) {
|
||||
earlyProgress.label(message);
|
||||
}
|
||||
|
||||
private record DummyProvider() implements ImmediateWindowProvider {
|
||||
private static Method NV_HANDOFF;
|
||||
private static Method NV_POSITION;
|
||||
private static Method NV_OVERLAY;
|
||||
private static Method NV_VERSION;
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return "dummyprovider";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Runnable initialize(String[] args) {
|
||||
return () -> {};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFramebufferSize(final IntConsumer width, final IntConsumer height) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public long setupMinecraftWindow(final IntSupplier width, final IntSupplier height, final Supplier<String> title, final LongSupplier monitor) {
|
||||
try {
|
||||
var longsupplier = (LongSupplier)NV_HANDOFF.invoke(null, width, height, title, monitor);
|
||||
return longsupplier.getAsLong();
|
||||
} catch (Throwable e) {
|
||||
throw new IllegalStateException("How did you get here?", e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean positionWindow(Optional<Object> monitor, IntConsumer widthSetter, IntConsumer heightSetter, IntConsumer xSetter, IntConsumer ySetter) {
|
||||
try {
|
||||
return (boolean)NV_POSITION.invoke(null, monitor, widthSetter, heightSetter, xSetter, ySetter);
|
||||
} catch (Throwable e) {
|
||||
throw new IllegalStateException("How did you get here?", e);
|
||||
}
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Supplier<T> loadingOverlay(Supplier<?> mc, Supplier<?> ri, Consumer<Optional<Throwable>> ex, boolean fade) {
|
||||
try {
|
||||
return (Supplier<T>) NV_OVERLAY.invoke(null, mc, ri, ex, fade);
|
||||
} catch (Throwable e) {
|
||||
throw new IllegalStateException("How did you get here?", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getGLVersion() {
|
||||
try {
|
||||
return (String) NV_VERSION.invoke(null);
|
||||
} catch (Throwable e) {
|
||||
return "3.2"; // Vanilla sets 3.2 in com.mojang.blaze3d.platform.Window
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateModuleReads(final ModuleLayer layer) {
|
||||
var fm = layer.findModule("forge");
|
||||
if (fm.isPresent()) {
|
||||
getClass().getModule().addReads(fm.get());
|
||||
var clz = fm.map(l -> Class.forName(l, "net.minecraftforge.client.loading.NoVizFallback")).orElseThrow();
|
||||
var methods = Arrays.stream(clz.getMethods()).filter(m -> Modifier.isStatic(m.getModifiers())).collect(Collectors.toMap(Method::getName, Function.identity()));
|
||||
NV_HANDOFF = methods.get("windowHandoff");
|
||||
NV_OVERLAY = methods.get("loadingOverlay");
|
||||
NV_POSITION = methods.get("windowPositioning");
|
||||
NV_VERSION = methods.get("glVersion");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void periodicTick() {
|
||||
// NOOP
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.IntConsumer;
|
||||
import java.util.function.IntSupplier;
|
||||
import java.util.function.LongSupplier;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* This is for allowing the plugging in of alternative early display implementations.
|
||||
*
|
||||
* They can be selected through the config value "earlyWindowProvider" which defaults to "fmlearlywindow" implemented by {@link net.minecraftforge.fml.earlydisplay.DisplayWindow}
|
||||
*
|
||||
* There are a few key things to keep in mind if following through on implementation. You cannot access the game state as it
|
||||
* literally DOES NOT EXIST at the time this object is constructed. You have to be very careful about managing the handoff
|
||||
* to mojang, be sure that if you're trying to tick your window in a background thread (a nice idea!) that you properly
|
||||
* transition to the main thread before handoff is complete. Do note that in general, you should construct your GL objects
|
||||
* on the MAIN thread before starting your ticker, to ensure MacOS compatibility.
|
||||
*
|
||||
* No doubt many more things can be said here.
|
||||
*/
|
||||
public interface ImmediateWindowProvider {
|
||||
/**
|
||||
* @return The name of this window provider. Do NOT use fmlearlywindow.
|
||||
*/
|
||||
String name();
|
||||
|
||||
/**
|
||||
* This is called very early on to initialize ourselves. Use this to initialize the window and other GL core resources.
|
||||
*
|
||||
* One thing we want to ensure is that we try and create the highest GL_PROFILE we can accomplish.
|
||||
* GLFW_CONTEXT_VERSION_MAJOR,GLFW_CONTEXT_VERSION_MINOR should be as high as possible on the created window,
|
||||
* and it should have all the typical profile settings.
|
||||
*
|
||||
* @param arguments The arguments provided to the Java process. This is the entire command line, so you can process
|
||||
* stuff from it.
|
||||
* @return A runnable that will be periodically ticked by FML during startup ON THE MAIN THREAD. This is usually
|
||||
* a good place to put glfwPollEvents() tests.
|
||||
*/
|
||||
Runnable initialize(String[] arguments);
|
||||
|
||||
/**
|
||||
* This will be called during the handoff to minecraft to update minecraft with the size of the framebuffer we have.
|
||||
* Generally won't be called because Minecraft figures it out for itself.
|
||||
* @param width Consumer of the framebuffer width
|
||||
* @param height Consumer of the framebuffer height
|
||||
*/
|
||||
void updateFramebufferSize(IntConsumer width, IntConsumer height);
|
||||
|
||||
/**
|
||||
* This is called to setup the minecraft window, as if Mojang had done it themselves in their Window class. This
|
||||
* handoff is difficult to get right - you have to make sure that any activities you're doing to the window are finished
|
||||
* prior to returning. You should try and setup the width and height as Mojang expects - the suppliers give you all that
|
||||
* information. Alternatively, you can force Mojang to update from the current position of the window in {@link #positionWindow(Optional, IntConsumer, IntConsumer, IntConsumer, IntConsumer)}
|
||||
* instead. This might give a more seamless experience.
|
||||
*
|
||||
* @param width This is the width of the window Mojang expects
|
||||
* @param height This is the height of the Window Mojang expects.
|
||||
* @param title This is the title for the window.
|
||||
* @param monitor This is the monitor it should appear on.
|
||||
* @return The window id
|
||||
*/
|
||||
long setupMinecraftWindow(final IntSupplier width, final IntSupplier height, final Supplier<String> title, final LongSupplier monitor);
|
||||
|
||||
/**
|
||||
* This is called after window handoff to allow us to tell Mojang about our window's position. This might give a
|
||||
* preferrable user experience to users, because we just tell Mojang our truth, rather than accept theirs.
|
||||
* @param monitor This is the monitor we're rendering on. Note that this is the Mojang monitor object. You might have trouble unwrapping it.
|
||||
* @param widthSetter This sets the width on the Mojang side
|
||||
* @param heightSetter This sets the height on the Mojang side
|
||||
* @param xSetter This sets the x coordinate on the Mojang side
|
||||
* @param ySetter This sets the y coordinate on the Mojang side
|
||||
* @return true if you've handled the window positioning - this skips the "forced fullscreen" code until a later stage
|
||||
*/
|
||||
boolean positionWindow(Optional<Object> monitor, IntConsumer widthSetter, IntConsumer heightSetter, IntConsumer xSetter, IntConsumer ySetter);
|
||||
|
||||
/**
|
||||
* Return a Supplier of an object extending the LoadingOverlay class from Mojang. This is what will be used once
|
||||
* the Mojang window code has taken over rendering of the window, to render the later stages of the loading process.
|
||||
*
|
||||
* @param mc This supplies the Minecraft object
|
||||
* @param ri This supplies the ReloadInstance object that tells us when the loading is finished
|
||||
* @param ex This Consumes the final state of the loading - if it's an error you pass it the Throwable, otherwise you
|
||||
* pass Optional.empty()
|
||||
* @param fade This is the fade flag passed to LoadingOverlay. You probably want to ignore it.
|
||||
* @param <T> This is the type LoadingOverlay to allow type binding on the Mojang side
|
||||
* @return A supplier of your later LoadingOverlay screen.
|
||||
*/
|
||||
<T> Supplier<T> loadingOverlay(Supplier<?> mc, Supplier<?> ri, Consumer<Optional<Throwable>> ex, boolean fade);
|
||||
|
||||
/**
|
||||
* This is called during the module loading process to allow us to find objects inside the GAME layer, such as a
|
||||
* later loading screen.
|
||||
* @param layer This is the GAME layer from ModLauncher
|
||||
*/
|
||||
void updateModuleReads(ModuleLayer layer);
|
||||
|
||||
/**
|
||||
* This is called periodically during the loading process to "tick" the window. It is typically the same as the Runnable
|
||||
* from {@link #initialize(String[])}
|
||||
*/
|
||||
void periodicTick();
|
||||
|
||||
/**
|
||||
* This is called to construct a {@link net.minecraftforge.forgespi.locating.ForgeFeature} for the GL_VERSION we
|
||||
* managed to create for the window. Should be a string of the format {MAJOR}.{MINOR}, such as 4.6, 4.5 or such.
|
||||
*
|
||||
* @return the GL profile we created
|
||||
*/
|
||||
String getGLVersion();
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import java.util.Optional;
|
||||
* Finds Version data from a package, with possible default values
|
||||
*/
|
||||
public class JarVersionLookupHandler {
|
||||
|
||||
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();
|
||||
|
||||
@@ -9,6 +9,7 @@ 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.StartupMessageManager;
|
||||
import net.minecraftforge.forgespi.language.IModLanguageProvider;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ExplodedDirectoryLocator;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
|
||||
@@ -82,7 +83,7 @@ public class LanguageLoadingProvider
|
||||
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 {
|
||||
@@ -97,7 +98,7 @@ public class LanguageLoadingProvider
|
||||
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);
|
||||
StartupMessageManager.modLoaderConsumer().ifPresent(c->c.accept("Loaded language provider "+lp.name()+ " " + impl));
|
||||
languageProviderMap.put(lp.name(), new ModLanguageWrapper(lp, new DefaultArtifactVersion(impl)));
|
||||
});
|
||||
}
|
||||
|
||||
-12
@@ -28,18 +28,6 @@ public class ModDirTransformerDiscoverer implements ITransformerDiscoveryService
|
||||
"net.minecraftforge.forgespi.locating.IDependencyLocator"
|
||||
);
|
||||
|
||||
@Override
|
||||
public List<NamedPath> candidates(final Path gameDirectory, final String launchTarget) {
|
||||
FMLPaths.loadAbsolutePaths(gameDirectory);
|
||||
FMLConfig.load();
|
||||
return candidates(gameDirectory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void earlyInitialization(final String launchTarget, final String[] arguments) {
|
||||
ImmediateWindowHandler.load(launchTarget, arguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NamedPath> candidates(final Path gameDirectory) {
|
||||
ModDirTransformerDiscoverer.scan(gameDirectory);
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLStreamHandler;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
|
||||
|
||||
public class ModJarURLHandler extends URLStreamHandler
|
||||
{
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
// modjar://modid/path/to/file
|
||||
@Override
|
||||
protected URLConnection openConnection(URL url) {
|
||||
return new ModJarURLConnection(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int hashCode(final URL u) {
|
||||
return Objects.hash(u.getHost(), u.getFile());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean equals(final URL u1, final URL u2) {
|
||||
return Objects.equals(u1.getProtocol(), u2.getProtocol())
|
||||
&& Objects.equals(u1.getHost(), u2.getHost())
|
||||
&& Objects.equals(u1.getFile(), u2.getFile());
|
||||
}
|
||||
|
||||
static class ModJarURLConnection extends URLConnection {
|
||||
private Path resource;
|
||||
private String modpath;
|
||||
private String modid;
|
||||
private Optional<Manifest> manifest;
|
||||
|
||||
public ModJarURLConnection(final URL url) {
|
||||
super(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect()
|
||||
{
|
||||
// if (resource == null) {
|
||||
// modid = url.getHost();
|
||||
// // trim first char
|
||||
// modpath = url.getPath().substring(1);
|
||||
// resource = FMLLoader.getLoadingModList().getModFileById(modid).getFile().findResource(modpath);
|
||||
// manifest = FMLLoader.getLoadingModList().getModFileById(modid).getManifest();
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException
|
||||
{
|
||||
connect();
|
||||
LOGGER.trace(CORE, "Loading modjar URL {} got resource {} {}", url, resource, resource != null ? Files.exists(resource) : "missing");
|
||||
return Files.newInputStream(resource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getContentLengthLong() {
|
||||
try {
|
||||
connect();
|
||||
return Files.size(resource);
|
||||
} catch (IOException e) {
|
||||
return -1L;
|
||||
}
|
||||
}
|
||||
|
||||
// Used to cache protectiondomains by "top level object" aka the modid
|
||||
@Override
|
||||
public URL getURL() {
|
||||
return LamdbaExceptionUtils.uncheck(()->new URL("modjar://"+modid));
|
||||
}
|
||||
|
||||
public Optional<Manifest> getManifest() {
|
||||
return manifest;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ 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;
|
||||
@@ -71,6 +72,7 @@ public class ModSorter
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
private void sort()
|
||||
{
|
||||
// lambdas are identity based, so sorting them is impossible unless you hold reference to them
|
||||
@@ -119,6 +121,7 @@ public class ModSorter
|
||||
.collect(toList());
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
private void addDependency(MutableGraph<ModFileInfo> topoGraph, IModInfo.ModVersion dep)
|
||||
{
|
||||
final ModFileInfo self = (ModFileInfo)dep.getOwner().getOwningFile();
|
||||
@@ -130,7 +133,6 @@ public class ModSorter
|
||||
switch (dep.getOrdering()) {
|
||||
case BEFORE -> topoGraph.putEdge(self, target);
|
||||
case AFTER -> topoGraph.putEdge(target, self);
|
||||
default -> {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+32
-12
@@ -27,7 +27,8 @@ import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
public abstract class AbstractModProvider implements IModProvider {
|
||||
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";
|
||||
@@ -35,24 +36,26 @@ public abstract class AbstractModProvider implements IModProvider {
|
||||
protected IModLocator.ModFileOrException createMod(Path... path) {
|
||||
var mjm = new ModJarMetadata();
|
||||
var sj = SecureJar.from(
|
||||
Manifest::new,
|
||||
jar -> jar.moduleDataProvider().findFile(MODS_TOML).isPresent() ? mjm : JarMetadata.from(jar, path),
|
||||
path
|
||||
Manifest::new,
|
||||
jar -> jar.moduleDataProvider().findFile(MODS_TOML).isPresent() ? mjm : JarMetadata.from(jar, path),
|
||||
(root, p) -> true,
|
||||
path
|
||||
);
|
||||
|
||||
IModFile mod;
|
||||
var type = sj.moduleDataProvider().getManifest().getMainAttributes().getValue(ModFile.TYPE);
|
||||
if (type == null)
|
||||
if (type == null) {
|
||||
type = getDefaultJarModType();
|
||||
|
||||
}
|
||||
if (sj.moduleDataProvider().findFile(MODS_TOML).isPresent()) {
|
||||
LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", MODS_TOML, type, path);
|
||||
mod = new ModFile(sj, this, ModFileParser::modsTomlParser);
|
||||
} else if (type != null) {
|
||||
LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", MANIFEST, type, path);
|
||||
mod = new ModFile(sj, this, this::manifestParser, type);
|
||||
} else
|
||||
return new IModLocator.ModFileOrException(null, new ModFileLoadingException("Invalid mod file found " + Arrays.toString(path)));
|
||||
} else {
|
||||
return new IModLocator.ModFileOrException(null, new ModFileLoadingException("Invalid mod file found "+ Arrays.toString(path)));
|
||||
}
|
||||
|
||||
mjm.setModFile(mod);
|
||||
return new IModLocator.ModFileOrException(mod, null);
|
||||
@@ -85,19 +88,36 @@ public abstract class AbstractModProvider implements IModProvider {
|
||||
}
|
||||
|
||||
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 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() + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
-2
@@ -6,7 +6,6 @@
|
||||
package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
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.forgespi.language.ModFileScanData;
|
||||
@@ -14,6 +13,7 @@ import org.slf4j.Logger;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
@@ -30,6 +30,9 @@ 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;
|
||||
@@ -41,6 +44,9 @@ public class BackgroundScanHandler
|
||||
thread.setDaemon(true);
|
||||
return thread;
|
||||
});
|
||||
scannedFiles = new ArrayList<>();
|
||||
pendingFiles = new ArrayList<>();
|
||||
allFiles = new ArrayList<>();
|
||||
status = ScanStatus.NOT_STARTED;
|
||||
}
|
||||
|
||||
@@ -54,7 +60,8 @@ public class BackgroundScanHandler
|
||||
throw new IllegalStateException("Scanner has shutdown");
|
||||
}
|
||||
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));
|
||||
@@ -66,6 +73,8 @@ 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)
|
||||
|
||||
+23
-1
@@ -12,9 +12,12 @@ 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;
|
||||
@@ -22,7 +25,8 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class ClasspathLocator extends AbstractJarFileModLocator {
|
||||
public class ClasspathLocator extends AbstractJarFileModLocator
|
||||
{
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private final List<Path> legacyClasspath = Arrays.stream(System.getProperty("legacyClassPath", "").split(File.pathSeparator)).map(Path::of).toList();
|
||||
private boolean enabled = false;
|
||||
@@ -69,4 +73,22 @@ public class ClasspathLocator extends AbstractJarFileModLocator {
|
||||
var launchTarget = (String) arguments.get("launchTarget");
|
||||
enabled = launchTarget != null && launchTarget.contains("dev");
|
||||
}
|
||||
|
||||
private Path findJarPathFor(final String resourceName, final String jarName, final URL resource) {
|
||||
try {
|
||||
Path path;
|
||||
final URI uri = resource.toURI();
|
||||
if (uri.getScheme().equals("jar") && uri.getRawSchemeSpecificPart().contains("!/")) {
|
||||
int lastExcl = uri.getRawSchemeSpecificPart().lastIndexOf("!/");
|
||||
path = Paths.get(new URI(uri.getRawSchemeSpecificPart().substring(0, lastExcl)));
|
||||
} else {
|
||||
path = Paths.get(new URI("file://"+uri.getRawSchemeSpecificPart().substring(0, uri.getRawSchemeSpecificPart().length()-resourceName.length())));
|
||||
}
|
||||
//LOGGER.debug(CORE, "Found JAR {} at path {}", jarName, path.toString());
|
||||
return path;
|
||||
} catch (NullPointerException | URISyntaxException e) {
|
||||
LOGGER.error(LogMarkers.SCAN, "Failed to find JAR for class {} - {}", resourceName, jarName);
|
||||
throw new RuntimeException("Unable to locate "+resourceName+" - "+jarName, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -34,7 +34,7 @@ public class ExplodedDirectoryLocator implements IModLocator {
|
||||
explodedMods.forEach(explodedMod ->
|
||||
ModJarMetadata.buildFile(this,
|
||||
jar->jar.moduleDataProvider().findFile("/META-INF/mods.toml").isPresent(),
|
||||
null,
|
||||
(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();
|
||||
|
||||
+4
-3
@@ -11,11 +11,12 @@ import net.minecraftforge.forgespi.locating.ModFileLoadingException;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
|
||||
public class InvalidModFileException extends ModFileLoadingException {
|
||||
private static final long serialVersionUID = 1230464325917450374L;
|
||||
public class InvalidModFileException extends ModFileLoadingException
|
||||
{
|
||||
private final IModFileInfo modFileInfo;
|
||||
|
||||
public InvalidModFileException(String message, IModFileInfo modFileInfo) {
|
||||
public InvalidModFileException(String message, IModFileInfo modFileInfo)
|
||||
{
|
||||
super(String.format(Locale.ROOT, "%s (%s)", message, Optional.ofNullable(modFileInfo).map(mf->mf.getFile().getFileName()).orElse("MISSING FILE NAME")));
|
||||
this.modFileInfo = modFileInfo;
|
||||
}
|
||||
|
||||
+3
-1
@@ -96,7 +96,9 @@ public class MinecraftLocator extends AbstractModProvider implements IModLocator
|
||||
|
||||
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());
|
||||
final ModFileInfo modFileInfo = new ModFileInfo(modFile, configWrapper, List.of());
|
||||
configWrapper.setFile(modFileInfo);
|
||||
return modFileInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+29
-15
@@ -10,58 +10,72 @@ import org.objectweb.asm.Opcodes;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
public class ModAnnotationVisitor extends AnnotationVisitor {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
public ModAnnotationVisitor(LinkedList<ModAnnotation> annotations, ModAnnotation annotation, boolean isSubAnnotation)
|
||||
{
|
||||
this(annotations, annotation);
|
||||
this.isSubAnnotation = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(String key, Object value) {
|
||||
public void visit(String key, Object value)
|
||||
{
|
||||
annotation.addProperty(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnum(String name, String desc, String value) {
|
||||
public void visitEnum(String name, String desc, String value)
|
||||
{
|
||||
annotation.addEnumProperty(name, desc, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationVisitor visitArray(String name) {
|
||||
public AnnotationVisitor visitArray(String name)
|
||||
{
|
||||
return new ModAnnotationVisitor(annotations, annotation, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationVisitor visitAnnotation(String name, String desc) {
|
||||
var ma = annotations.getFirst();
|
||||
var childAnnotation = ma.addChildAnnotation(name, desc);
|
||||
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);
|
||||
return new ModAnnotationVisitor(annotations, childAnnotation,true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
public void visitEnd()
|
||||
{
|
||||
if (array)
|
||||
{
|
||||
annotation.endArray();
|
||||
}
|
||||
|
||||
if (isSubAnnotation)
|
||||
annotations.addLast(annotations.removeFirst());
|
||||
{
|
||||
ModAnnotation child = annotations.removeFirst();
|
||||
annotations.addLast(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
-13
@@ -15,49 +15,58 @@ 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 {
|
||||
public class ModClassVisitor extends ClassVisitor
|
||||
{
|
||||
private Type asmType;
|
||||
private Type asmSuperType;
|
||||
private Set<Type> interfaces;
|
||||
private final LinkedList<ModAnnotation> annotations = new LinkedList<>();
|
||||
|
||||
public ModClassVisitor() {
|
||||
public 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.isEmpty() ? Type.getObjectType(superName) : null;
|
||||
this.asmSuperType = superName != null && superName.length() > 0 ? 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) {
|
||||
public AnnotationVisitor visitAnnotation(final String annotationName, final boolean runtimeVisible)
|
||||
{
|
||||
ModAnnotation 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) {
|
||||
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value)
|
||||
{
|
||||
return new ModFieldVisitor(name, annotations);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
|
||||
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions)
|
||||
{
|
||||
return new ModMethodVisitor(name, desc, annotations);
|
||||
}
|
||||
|
||||
public void buildData(final Set<ModFileScanData.ClassData> classes, final Set<ModFileScanData.AnnotationData> baked) {
|
||||
public void buildData(final Set<ModFileScanData.ClassData> classes, final Set<ModFileScanData.AnnotationData> annotations) {
|
||||
classes.add(new ModFileScanData.ClassData(this.asmType, this.asmSuperType, this.interfaces));
|
||||
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));
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
+4
-4
@@ -12,10 +12,9 @@ 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.ImmediateWindowHandler;
|
||||
import net.minecraftforge.fml.loading.LogMarkers;
|
||||
import net.minecraftforge.fml.loading.UniqueModListBuilder;
|
||||
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
|
||||
import net.minecraftforge.fml.loading.progress.StartupMessageManager;
|
||||
import net.minecraftforge.forgespi.Environment;
|
||||
import net.minecraftforge.forgespi.language.IModFileInfo;
|
||||
import net.minecraftforge.forgespi.locating.IDependencyLocator;
|
||||
@@ -39,7 +38,7 @@ public class ModDiscoverer {
|
||||
|
||||
public ModDiscoverer(Map<String, ?> arguments) {
|
||||
Launcher.INSTANCE.environment().computePropertyIfAbsent(Environment.Keys.MODDIRECTORYFACTORY.get(), v->ModsFolderLocator::new);
|
||||
Launcher.INSTANCE.environment().computePropertyIfAbsent(Environment.Keys.PROGRESSMESSAGE.get(), v-> StartupNotificationManager.locatorConsumer().orElseGet(()-> s->{}));
|
||||
Launcher.INSTANCE.environment().computePropertyIfAbsent(Environment.Keys.PROGRESSMESSAGE.get(), v-> StartupMessageManager.locatorConsumer().orElseGet(()-> s->{}));
|
||||
final var moduleLayerManager = Launcher.INSTANCE.environment().findModuleLayerManager().orElseThrow();
|
||||
modLocators = ServiceLoader.load(moduleLayerManager.getLayer(IModuleLayerManager.Layer.SERVICE).orElseThrow(), IModLocator.class);
|
||||
dependencyLocators = ServiceLoader.load(moduleLayerManager.getLayer(IModuleLayerManager.Layer.SERVICE).orElseThrow(), IDependencyLocator.class);
|
||||
@@ -67,7 +66,7 @@ public class ModDiscoverer {
|
||||
List<EarlyLoadingException.ExceptionData> discoveryErrorData = new ArrayList<>();
|
||||
boolean successfullyLoadedMods = true;
|
||||
List<IModFileInfo> brokenFiles = new ArrayList<>();
|
||||
ImmediateWindowHandler.updateProgress("Discovering mod files");
|
||||
|
||||
//Loop all mod locators to get the prime mods to load from.
|
||||
for (IModLocator locator : modLocatorList) {
|
||||
try {
|
||||
@@ -169,6 +168,7 @@ public class ModDiscoverer {
|
||||
var locatedModFiles = locatedFiles.stream().filter(ModFile.class::isInstance).map(ModFile.class::cast).toList();
|
||||
for (IModFile mf : locatedModFiles) {
|
||||
LOGGER.info(LogMarkers.SCAN, "Found mod file {} of type {} with provider {}", mf.getFileName(), mf.getType(), mf.getProvider());
|
||||
StartupMessageManager.modLoaderConsumer().ifPresent(c->c.accept("Found mod file "+mf.getFileName()+" of type "+mf.getType()));
|
||||
}
|
||||
loadedFiles.addAll(locatedModFiles);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ 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.StartupMessageManager;
|
||||
import net.minecraftforge.forgespi.language.IModFileInfo;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import net.minecraftforge.forgespi.language.IModLanguageProvider;
|
||||
@@ -156,6 +157,7 @@ public class ModFile implements IModFile {
|
||||
if (throwable != null) {
|
||||
this.scanError = throwable;
|
||||
}
|
||||
StartupMessageManager.modLoaderConsumer().ifPresent(c->c.accept("Completed deep scan of "+this.getFileName()));
|
||||
}
|
||||
|
||||
public void setFileProperties(Map<String, Object> fileProperties) {
|
||||
@@ -206,11 +208,8 @@ public class ModFile implements IModFile {
|
||||
this.securityStatus = status;
|
||||
}
|
||||
|
||||
public SecureJar.Status getSecurityStatus() {
|
||||
return this.securityStatus;
|
||||
}
|
||||
|
||||
public ArtifactVersion getJarVersion() {
|
||||
public ArtifactVersion getJarVersion()
|
||||
{
|
||||
return new DefaultArtifactVersion(this.jarVersion);
|
||||
}
|
||||
|
||||
|
||||
+11
-27
@@ -26,14 +26,7 @@ import java.security.SignatureException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -50,40 +43,31 @@ public class ModFileInfo implements IModFileInfo, IConfigurable
|
||||
private final String license;
|
||||
private final List<String> usesServices;
|
||||
|
||||
ModFileInfo(final ModFile modFile, final IConfigurable config, Consumer<IModFileInfo> configFileConsumer)
|
||||
ModFileInfo(final ModFile modFile, final IConfigurable config)
|
||||
{
|
||||
this.modFile = modFile;
|
||||
this.config = config;
|
||||
configFileConsumer.accept(this);
|
||||
// modloader is essential
|
||||
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)
|
||||
.orElseThrow(()->new InvalidModFileException("Missing ModLoader version in file", this));
|
||||
this.languageSpecs = new ArrayList<>(List.of(new LanguageSpec(modLoader, modLoaderVersion)));
|
||||
// the remaining properties are optional with sensible defaults
|
||||
this.license = config.<String>getConfigElement("license")
|
||||
.orElse("");
|
||||
this.showAsResourcePack = config.<Boolean>getConfigElement("showAsResourcePack")
|
||||
.orElse(false);
|
||||
this.usesServices = config.<List<String>>getConfigElement("services")
|
||||
.orElse(List.of());
|
||||
this.properties = config.<Map<String, Object>>getConfigElement("properties")
|
||||
.orElse(Collections.emptyMap());
|
||||
.orElse("");
|
||||
this.showAsResourcePack = config.<Boolean>getConfigElement("showAsResourcePack").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);
|
||||
this.issueURL = config.<String>getConfigElement("issueTrackerURL").map(StringUtils::toURL).orElse(null);
|
||||
final List<? extends IConfigurable> modConfigs = config.getConfigList("mods");
|
||||
if (modConfigs.isEmpty())
|
||||
{
|
||||
throw new InvalidModFileException("Missing mods list", this);
|
||||
}
|
||||
this.mods = modConfigs.stream()
|
||||
.map(mi-> (IModInfo)new ModInfo(this, mi))
|
||||
.toList();
|
||||
.map(mi-> new ModInfo(this, mi))
|
||||
.collect(Collectors.toList());
|
||||
if (LOGGER.isDebugEnabled(LogMarkers.LOADING))
|
||||
{
|
||||
LOGGER.debug(LogMarkers.LOADING, "Found valid mod file {} with {} mods - versions {}",
|
||||
@@ -93,8 +77,8 @@ public class ModFileInfo implements IModFileInfo, IConfigurable
|
||||
}
|
||||
}
|
||||
|
||||
public ModFileInfo(final ModFile file, final IConfigurable config, Consumer<IModFileInfo> configFileConsumer, final List<LanguageSpec> languageSpecs) {
|
||||
this(file, config, configFileConsumer);
|
||||
public ModFileInfo(final ModFile file, final IConfigurable config, final List<LanguageSpec> languageSpecs) {
|
||||
this(file, config);
|
||||
this.languageSpecs.addAll(languageSpecs);
|
||||
}
|
||||
|
||||
|
||||
+5
-2
@@ -22,6 +22,7 @@ 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();
|
||||
@@ -43,7 +44,9 @@ public class ModFileParser {
|
||||
fileConfig.load();
|
||||
fileConfig.close();
|
||||
final NightConfigWrapper configWrapper = new NightConfigWrapper(fileConfig);
|
||||
return new ModFileInfo(modFile, configWrapper, configWrapper::setFile);
|
||||
final ModFileInfo modFileInfo = new ModFileInfo(modFile, configWrapper);
|
||||
configWrapper.setFile(modFileInfo);
|
||||
return modFileInfo;
|
||||
}
|
||||
|
||||
protected static List<CoreModFile> getCoreMods(final ModFile modFile) {
|
||||
@@ -64,6 +67,6 @@ public class ModFileParser {
|
||||
return coreModPaths.entrySet().stream()
|
||||
.peek(e-> LOGGER.debug(LogMarkers.LOADING,"Found coremod {} with Javascript path {}", e.getKey(), e.getValue()))
|
||||
.map(e -> new CoreModFile(e.getKey(), modFile.findResource(e.getValue()),modFile))
|
||||
.toList();
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ModInfo implements IModInfo, IConfigurable
|
||||
{
|
||||
@@ -53,58 +54,55 @@ public class ModInfo implements IModInfo, IConfigurable
|
||||
Optional<ModFileInfo> ownFile = Optional.ofNullable(owningFile);
|
||||
this.owningFile = owningFile;
|
||||
this.config = config;
|
||||
// These are sourced from the mod specific [[mod]] entry
|
||||
this.modId = config.<String>getConfigElement("modId")
|
||||
.orElseThrow(() -> new InvalidModFileException("Missing modId", owningFile));
|
||||
// verify we have a valid modid
|
||||
if (!VALID_MODID.matcher(this.modId).matches()) {
|
||||
LOGGER.error(LogUtils.FATAL_MARKER, "Invalid modId found in file {} - {} does not match the standard: {}", this.owningFile.getFile().getFilePath(), this.modId, VALID_MODID.pattern());
|
||||
throw new InvalidModFileException("Invalid modId found : " + this.modId, owningFile);
|
||||
}
|
||||
this.namespace = config.<String>getConfigElement("namespace")
|
||||
.orElse(this.modId);
|
||||
// verify our namespace is valid
|
||||
this.namespace = config.<String>getConfigElement("namespace").orElse(this.modId);
|
||||
if (!VALID_NAMESPACE.matcher(this.namespace).matches()) {
|
||||
LOGGER.error(LogUtils.FATAL_MARKER, "Invalid override namespace found in file {} - {} does not match the standard: {}", this.owningFile.getFile().getFilePath(), this.namespace, VALID_NAMESPACE.pattern());
|
||||
throw new InvalidModFileException("Invalid override namespace found : " + this.namespace, owningFile);
|
||||
}
|
||||
this.version = config.<String>getConfigElement("version")
|
||||
.map(s -> StringSubstitutor.replace(s, ownFile.map(ModFileInfo::getFile).orElse(null)))
|
||||
.map(DefaultArtifactVersion::new)
|
||||
.orElse(DEFAULT_VERSION);
|
||||
// verify we have a valid mod version
|
||||
if (!VALID_VERSION.matcher(this.version.toString()).matches()) {
|
||||
throw new InvalidModFileException("Illegal version number specified "+this.version, this.getOwningFile());
|
||||
}
|
||||
// The remaining properties all default to sensible values and are not essential
|
||||
this.displayName = config.<String>getConfigElement("displayName")
|
||||
.orElse(this.modId);
|
||||
this.description = config.<String>getConfigElement("description")
|
||||
.orElse("MISSING DESCRIPTION")
|
||||
.replace("\r\n", "\n").stripIndent();
|
||||
.map(DefaultArtifactVersion::new).orElse(DEFAULT_VERSION);
|
||||
this.displayName = config.<String>getConfigElement("displayName").orElse(this.modId);
|
||||
this.description = config.<String>getConfigElement("description").orElse("MISSING DESCRIPTION");
|
||||
|
||||
this.logoFile = Optional.ofNullable(config.<String>getConfigElement("logoFile")
|
||||
.orElseGet(() -> ownFile.flatMap(mf -> mf.<String>getConfigElement("logoFile"))
|
||||
.orElse(null)));
|
||||
.orElseGet(() -> ownFile.flatMap(mf -> mf.<String>getConfigElement("logoFile")).orElse(null)));
|
||||
this.logoBlur = config.<Boolean>getConfigElement("logoBlur")
|
||||
.orElseGet(() -> ownFile.flatMap(f -> f.<Boolean>getConfigElement("logoBlur"))
|
||||
.orElse(true));
|
||||
|
||||
this.updateJSONURL = config.<String>getConfigElement("updateJSONURL")
|
||||
.map(StringUtils::toURL);
|
||||
this.modUrl = config.<String>getConfigElement("modUrl")
|
||||
.map(StringUtils::toURL);
|
||||
// These are sourced from the file rather than the mod-specific block, but with a modid tag
|
||||
this.dependencies = ownFile.map(mfi -> mfi.getConfigList("dependencies", this.modId))
|
||||
.orElse(Collections.emptyList())
|
||||
|
||||
this.dependencies = ownFile.map(mfi -> mfi.getConfigList("dependencies", this.modId)
|
||||
.stream()
|
||||
.map(dep -> new ModVersion(this, dep))
|
||||
.toList();
|
||||
this.features = ownFile.flatMap(mfi -> mfi.<Map<String, Object>>getConfigElement("features", this.modId))
|
||||
.collect(Collectors.toList()))
|
||||
.orElse(Collections.emptyList());
|
||||
|
||||
this.features = ownFile.map(mfi -> mfi.<Map<String, String>>getConfigElement("features", this.modId)
|
||||
.stream()
|
||||
.flatMap(m->m.entrySet().stream())
|
||||
.map(this::makeBound)
|
||||
.toList();
|
||||
this.properties = ownFile.flatMap(mfi -> mfi.<Map<String, Object>>getConfigElement("modproperties", this.modId))
|
||||
.map(e->new ForgeFeature.Bound(e.getKey(), e.getValue(), this))
|
||||
.collect(Collectors.toList())).orElse(Collections.emptyList());
|
||||
|
||||
this.properties = ownFile.map(mfi -> mfi.<Map<String, Object>>getConfigElement("modproperties", this.modId)
|
||||
.orElse(Collections.emptyMap()))
|
||||
.orElse(Collections.emptyMap());
|
||||
|
||||
this.modUrl = config.<String>getConfigElement("modUrl")
|
||||
.map(StringUtils::toURL);
|
||||
|
||||
// verify we have a valid mod version otherwise throw an exception
|
||||
if (!VALID_VERSION.matcher(this.version.toString()).matches()) {
|
||||
throw new InvalidModFileException("Illegal version number specified "+this.version, this.getOwningFile());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -191,14 +189,6 @@ public class ModInfo implements IModInfo, IConfigurable
|
||||
return modUrl;
|
||||
}
|
||||
|
||||
private ForgeFeature.Bound makeBound(Map.Entry<String, Object> e) {
|
||||
if (e.getValue() instanceof String val) {
|
||||
return new ForgeFeature.Bound(e.getKey(), val, this);
|
||||
} else {
|
||||
throw new InvalidModFileException("Invalid feature bound {" + e.getValue() + "} for key {" + e.getKey() + "} only strings are accepted", this.owningFile);
|
||||
}
|
||||
}
|
||||
|
||||
class ModVersion implements net.minecraftforge.forgespi.language.IModInfo.ModVersion {
|
||||
private IModInfo owner;
|
||||
private final String modId;
|
||||
@@ -206,7 +196,7 @@ public class ModInfo implements IModInfo, IConfigurable
|
||||
private final boolean mandatory;
|
||||
private final Ordering ordering;
|
||||
private final DependencySide side;
|
||||
private final Optional<URL> referralUrl;
|
||||
private Optional<URL> referralUrl;
|
||||
|
||||
public ModVersion(final IModInfo owner, final IConfigurable config) {
|
||||
this.owner = owner;
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ public final class ModJarMetadata implements JarMetadata {
|
||||
// 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, null, files).orElseThrow(()->new IllegalArgumentException("Failed to find valid JAR file"));
|
||||
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
|
||||
|
||||
+2
-7
@@ -7,6 +7,7 @@ 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;
|
||||
@@ -62,7 +63,7 @@ public class ModListHandler {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
String extension = fileExtension(filePath);
|
||||
String extension = FileUtils.fileExtension(filePath);
|
||||
if (Objects.equals("list",extension)) {
|
||||
return readListFile(filePath).stream().filter(s -> !s.isEmpty()).collect(Collectors.toList());
|
||||
} else {
|
||||
@@ -71,12 +72,6 @@ public class ModListHandler {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private static String fileExtension(final Path path) {
|
||||
String fileName = path.getFileName().toString();
|
||||
int idx = fileName.lastIndexOf('.');
|
||||
return idx > -1 ? fileName.substring(idx+1) : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple list file, ending in ".list" with one mod coordinate per line
|
||||
* @param filePath path
|
||||
|
||||
+8
-4
@@ -8,7 +8,11 @@ package net.minecraftforge.fml.loading.moddiscovery;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import cpw.mods.modlauncher.api.IModuleLayerManager;
|
||||
import cpw.mods.modlauncher.api.ITransformationService;
|
||||
import net.minecraftforge.fml.loading.*;
|
||||
import net.minecraftforge.fml.loading.EarlyLoadingException;
|
||||
import net.minecraftforge.fml.loading.LoadingModList;
|
||||
import net.minecraftforge.fml.loading.LogMarkers;
|
||||
import net.minecraftforge.fml.loading.ModSorter;
|
||||
import net.minecraftforge.fml.loading.progress.StartupMessageManager;
|
||||
import net.minecraftforge.forgespi.language.IModFileInfo;
|
||||
import net.minecraftforge.forgespi.locating.IModFile;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -22,7 +26,7 @@ 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 Map<IModFile.Type, List<ModFile>> modFiles;
|
||||
private final List<ModFile> candidatePlugins;
|
||||
private final List<ModFile> candidateMods;
|
||||
private LoadingModList loadingModList;
|
||||
@@ -30,7 +34,7 @@ public class ModValidator {
|
||||
private final List<EarlyLoadingException.ExceptionData> discoveryErrorData;
|
||||
|
||||
public ModValidator(final Map<IModFile.Type, List<ModFile>> modFiles, final List<IModFileInfo> brokenFiles, final List<EarlyLoadingException.ExceptionData> discoveryErrorData) {
|
||||
//this.modFiles = modFiles;
|
||||
this.modFiles = modFiles;
|
||||
this.candidateMods = lst(modFiles.get(IModFile.Type.MOD));
|
||||
this.candidateMods.addAll(lst(modFiles.get(IModFile.Type.GAMELIBRARY)));
|
||||
this.candidatePlugins = lst(modFiles.get(IModFile.Type.LANGPROVIDER));
|
||||
@@ -48,7 +52,7 @@ public class ModValidator {
|
||||
if (LOGGER.isDebugEnabled(LogMarkers.SCAN)) {
|
||||
LOGGER.debug(LogMarkers.SCAN, "Found {} mod files with {} mods", candidateMods.size(), candidateMods.stream().mapToInt(mf -> mf.getModInfos().size()).sum());
|
||||
}
|
||||
ImmediateWindowHandler.updateProgress("Found "+candidateMods.size()+" mod candidates");
|
||||
StartupMessageManager.modLoaderConsumer().ifPresent(c->c.accept("Found "+ candidateMods.size()+" modfiles to load"));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
+1
-4
@@ -33,12 +33,9 @@ public class NightConfigWrapper implements IConfigurable {
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Optional<T> getConfigElement(final String... key) {
|
||||
var path = asList(key);
|
||||
return this.config.getOptional(path).map(value -> {
|
||||
return this.config.getOptional(asList(key)).map(value -> {
|
||||
if (value instanceof UnmodifiableConfig) {
|
||||
return (T) ((UnmodifiableConfig) value).valueMap();
|
||||
} else if (value instanceof ArrayList<?> al && al.size() > 0 && al.get(0) instanceof UnmodifiableConfig) {
|
||||
throw new InvalidModFileException("The configuration path " + path + " is invalid. I wasn't expecting a multi-object list - remove one of the [[ ]]", file);
|
||||
}
|
||||
return (T) value;
|
||||
});
|
||||
|
||||
+358
@@ -0,0 +1,358 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
|
||||
package net.minecraftforge.fml.loading.progress;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.lwjgl.PointerBuffer;
|
||||
import org.lwjgl.glfw.*;
|
||||
import org.lwjgl.opengl.GL;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL14;
|
||||
import org.lwjgl.stb.STBEasyFont;
|
||||
import org.lwjgl.stb.STBImage;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.MemoryUsage;
|
||||
import java.nio.Buffer;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.IntConsumer;
|
||||
import java.util.function.IntSupplier;
|
||||
import java.util.function.LongSupplier;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.lwjgl.glfw.GLFW.*;
|
||||
import static org.lwjgl.glfw.GLFW.glfwCreateWindow;
|
||||
import static org.lwjgl.opengl.GL11.*;
|
||||
import static org.lwjgl.system.MemoryStack.stackPush;
|
||||
import static org.lwjgl.system.MemoryUtil.NULL;
|
||||
|
||||
|
||||
class ClientVisualization implements EarlyProgressVisualization.Visualization {
|
||||
private final int screenWidth = 854;
|
||||
private final int screenHeight = 480;
|
||||
private long window;
|
||||
private Thread renderThread = new Thread(this::renderThreadFunc);
|
||||
|
||||
private boolean running = true;
|
||||
private GLFWFramebufferSizeCallback framebufferSizeCallback;
|
||||
private int[] fbSize;
|
||||
|
||||
private void initWindow(@Nullable String mcVersion) {
|
||||
GLFWErrorCallback.createPrint(System.err).set();
|
||||
|
||||
long glfwInitBegin = System.nanoTime();
|
||||
if (!glfwInit()) {
|
||||
throw new IllegalStateException("Unable to initialize GLFW");
|
||||
}
|
||||
long glfwInitEnd = System.nanoTime();
|
||||
|
||||
if (glfwInitEnd - glfwInitBegin > 1e9) {
|
||||
LogManager.getLogger().fatal("WARNING : glfwInit took {} seconds to start.", (glfwInitEnd-glfwInitBegin) / 1.0e9);
|
||||
}
|
||||
|
||||
// Clear the Last Exception (#7285 - Prevent Vanilla throwing an IllegalStateException due to invalid controller mappings)
|
||||
handleLastGLFWError((error, description) -> LogManager.getLogger().error(String.format("Suppressing Last GLFW error: [0x%X]%s", error, description)));
|
||||
|
||||
glfwDefaultWindowHints();
|
||||
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API);
|
||||
glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_NATIVE_CONTEXT_API);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
||||
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);
|
||||
}
|
||||
|
||||
window = glfwCreateWindow(screenWidth, screenHeight, "FML early loading progress", NULL, NULL);
|
||||
if (window == NULL) {
|
||||
throw new RuntimeException("Failed to create the GLFW window"); // ignore it and make the GUI optional?
|
||||
}
|
||||
framebufferSizeCallback = GLFWFramebufferSizeCallback.create(this::fbResize);
|
||||
|
||||
try (MemoryStack stack = stackPush()) {
|
||||
IntBuffer pWidth = stack.mallocInt(1);
|
||||
IntBuffer pHeight = stack.mallocInt(1);
|
||||
IntBuffer monPosLeft = stack.mallocInt(1);
|
||||
IntBuffer monPosTop = stack.mallocInt(1);
|
||||
glfwGetWindowSize(window, pWidth, pHeight);
|
||||
|
||||
// try to center the window, this is a best-effort as there may not be
|
||||
// a primary monitor and we might not even be on the primary monitor...
|
||||
long primaryMonitor = glfwGetPrimaryMonitor();
|
||||
if (primaryMonitor != NULL)
|
||||
{
|
||||
GLFWVidMode vidmode = glfwGetVideoMode(primaryMonitor);
|
||||
glfwGetMonitorPos(primaryMonitor, monPosLeft, monPosTop);
|
||||
glfwSetWindowPos(
|
||||
window,
|
||||
(vidmode.width() - pWidth.get(0)) / 2 + monPosLeft.get(0),
|
||||
(vidmode.height() - pHeight.get(0)) / 2 + monPosTop.get(0)
|
||||
);
|
||||
}
|
||||
IntBuffer iconWidth = stack.mallocInt(1);
|
||||
IntBuffer iconHeight = stack.mallocInt(1);
|
||||
IntBuffer iconChannels = stack.mallocInt(1);
|
||||
final GLFWImage.Buffer glfwImages = GLFWImage.mallocStack(1, stack);
|
||||
byte[] icon;
|
||||
try {
|
||||
icon = ByteStreams.toByteArray(getClass().getClassLoader().getResourceAsStream("forge_icon.png"));
|
||||
final ByteBuffer iconBuf = stack.malloc(icon.length);
|
||||
iconBuf.put(icon);
|
||||
((Buffer)iconBuf).position(0);
|
||||
final ByteBuffer imgBuffer = STBImage.stbi_load_from_memory(iconBuf, iconWidth, iconHeight, iconChannels, 4);
|
||||
if (imgBuffer == null) {
|
||||
throw new NullPointerException("Failed to load window icon"); // fall down to catch block
|
||||
}
|
||||
glfwImages.position(0);
|
||||
glfwImages.width(iconWidth.get(0));
|
||||
glfwImages.height(iconHeight.get(0));
|
||||
((Buffer)imgBuffer).position(0);
|
||||
glfwImages.pixels(imgBuffer);
|
||||
glfwImages.position(0);
|
||||
glfwSetWindowIcon(window, glfwImages);
|
||||
STBImage.stbi_image_free(imgBuffer);
|
||||
} catch (NullPointerException | IOException e) {
|
||||
System.err.println("Failed to load forge logo");
|
||||
}
|
||||
}
|
||||
int[] w = new int[1];
|
||||
int[] h = new int[1];
|
||||
glfwGetFramebufferSize(window, w, h);
|
||||
fbSize = new int[] {w[0], h[0]};
|
||||
glfwSetFramebufferSizeCallback(window, framebufferSizeCallback);
|
||||
glfwShowWindow(window);
|
||||
glfwPollEvents();
|
||||
}
|
||||
|
||||
private void renderProgress() {
|
||||
// glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
// glMatrixMode(GL_PROJECTION);
|
||||
// glLoadIdentity();
|
||||
// glOrtho(0.0D, screenWidth, screenHeight, 0.0D, -1000.0D, 1000.0D);
|
||||
// glMatrixMode(GL_MODELVIEW);
|
||||
// glLoadIdentity();
|
||||
//
|
||||
// glEnableClientState(GL11.GL_VERTEX_ARRAY);
|
||||
// glEnable(GL_BLEND);
|
||||
// renderBackground();
|
||||
// renderMessages();
|
||||
// glfwSwapBuffers(window);
|
||||
}
|
||||
|
||||
private static float clamp(float num, float min, float max) {
|
||||
if (num < min) {
|
||||
return min;
|
||||
} else {
|
||||
return num > max ? max : num;
|
||||
}
|
||||
}
|
||||
|
||||
private static int clamp(int num, int min, int max) {
|
||||
if (num < min) {
|
||||
return min;
|
||||
} else {
|
||||
return num > max ? max : num;
|
||||
}
|
||||
}
|
||||
|
||||
private static int hsvToRGB(float hue, float saturation, float value) {
|
||||
int i = (int)(hue * 6.0F) % 6;
|
||||
float f = hue * 6.0F - (float)i;
|
||||
float f1 = value * (1.0F - saturation);
|
||||
float f2 = value * (1.0F - f * saturation);
|
||||
float f3 = value * (1.0F - (1.0F - f) * saturation);
|
||||
float f4;
|
||||
float f5;
|
||||
float f6;
|
||||
switch(i) {
|
||||
case 0:
|
||||
f4 = value;
|
||||
f5 = f3;
|
||||
f6 = f1;
|
||||
break;
|
||||
case 1:
|
||||
f4 = f2;
|
||||
f5 = value;
|
||||
f6 = f1;
|
||||
break;
|
||||
case 2:
|
||||
f4 = f1;
|
||||
f5 = value;
|
||||
f6 = f3;
|
||||
break;
|
||||
case 3:
|
||||
f4 = f1;
|
||||
f5 = f2;
|
||||
f6 = value;
|
||||
break;
|
||||
case 4:
|
||||
f4 = f3;
|
||||
f5 = f1;
|
||||
f6 = value;
|
||||
break;
|
||||
case 5:
|
||||
f4 = value;
|
||||
f5 = f1;
|
||||
f6 = f2;
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Something went wrong when converting from HSV to RGB. Input was " + hue + ", " + saturation + ", " + value);
|
||||
}
|
||||
|
||||
int j = clamp((int)(f4 * 255.0F), 0, 255);
|
||||
int k = clamp((int)(f5 * 255.0F), 0, 255);
|
||||
int l = clamp((int)(f6 * 255.0F), 0, 255);
|
||||
return j << 16 | k << 8 | l;
|
||||
}
|
||||
|
||||
private void renderBackground() {
|
||||
glBegin(GL_QUADS);
|
||||
glColor4f(239F / 255F, 50F / 255F, 61F / 255F, 255F / 255F); //Color from ResourceLoadProgressGui
|
||||
glVertex2f(0, 0);
|
||||
glVertex2f(0, screenHeight);
|
||||
glVertex2f(screenWidth, screenHeight);
|
||||
glVertex2f(screenWidth, 0);
|
||||
glEnd();
|
||||
}
|
||||
|
||||
private void fbResize(long window, int width, int height) {
|
||||
if (window == this.window && width != 0 && height != 0) {
|
||||
fbSize = new int[] {width, height};
|
||||
}
|
||||
}
|
||||
|
||||
private void handleLastGLFWError(BiConsumer<Integer, String> handler) {
|
||||
try (MemoryStack memorystack = MemoryStack.stackPush()) {
|
||||
PointerBuffer pointerbuffer = memorystack.mallocPointer(1);
|
||||
int error = GLFW.glfwGetError(pointerbuffer);
|
||||
if (error != GLFW_NO_ERROR) {
|
||||
long pDescription = pointerbuffer.get();
|
||||
String description = pDescription == 0L ? "" : MemoryUtil.memUTF8(pDescription);
|
||||
handler.accept(error, description);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void renderMessages() {
|
||||
List<Pair<Integer, StartupMessageManager.Message>> messages = StartupMessageManager.getMessages();
|
||||
for (int i = 0; i < messages.size(); i++) {
|
||||
final Pair<Integer, StartupMessageManager.Message> pair = messages.get(i);
|
||||
final float fade = clamp((4000.0f - (float) pair.getLeft() - ( i - 4 ) * 1000.0f) / 5000.0f, 0.0f, 1.0f);
|
||||
if (fade <0.01f) continue;
|
||||
StartupMessageManager.Message msg = pair.getRight();
|
||||
renderMessage(msg.getText(), msg.getTypeColour(), ((screenHeight - 15) / 20) - i, fade);
|
||||
}
|
||||
renderMemoryInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFBSize(final IntConsumer width, final IntConsumer height) {
|
||||
width.accept(this.fbSize[0]);
|
||||
height.accept(this.fbSize[1]);
|
||||
}
|
||||
|
||||
private static final float[] memorycolour = new float[] { 0.0f, 0.0f, 0.0f};
|
||||
|
||||
private void renderMemoryInfo() {
|
||||
final MemoryUsage heapusage = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
|
||||
final MemoryUsage offheapusage = ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage();
|
||||
final float pctmemory = (float) heapusage.getUsed() / heapusage.getMax();
|
||||
String memory = String.format("Memory Heap: %d / %d MB (%.1f%%) OffHeap: %d MB", heapusage.getUsed() >> 20, heapusage.getMax() >> 20, pctmemory * 100.0, offheapusage.getUsed() >> 20);
|
||||
|
||||
final int i = hsvToRGB((1.0f - (float)Math.pow(pctmemory, 1.5f)) / 3f, 1.0f, 0.5f);
|
||||
memorycolour[2] = ((i) & 0xFF) / 255.0f;
|
||||
memorycolour[1] = ((i >> 8 ) & 0xFF) / 255.0f;
|
||||
memorycolour[0] = ((i >> 16 ) & 0xFF) / 255.0f;
|
||||
renderMessage(memory, memorycolour, 1, 1.0f);
|
||||
}
|
||||
|
||||
private void renderMessage(final String message, final float[] colour, int row, float alpha) {
|
||||
ByteBuffer charBuffer = MemoryUtil.memAlloc(message.length() * 270);
|
||||
int quads = STBEasyFont.stb_easy_font_print(0, 0, message, null, charBuffer);
|
||||
|
||||
glVertexPointer(3, GL11.GL_FLOAT, 16, charBuffer);
|
||||
glEnable(GL_BLEND);
|
||||
GL14.glBlendColor(0,0,0, alpha);
|
||||
glBlendFunc(GL14.GL_CONSTANT_ALPHA, GL14.GL_ONE_MINUS_CONSTANT_ALPHA);
|
||||
glColor3f(colour[0], colour[1], colour[2]);
|
||||
glPushMatrix();
|
||||
glTranslatef(10, row * 20, 0);
|
||||
glScalef(2, 2, 1);
|
||||
glDrawArrays(GL11.GL_QUADS, 0, quads * 4);
|
||||
glPopMatrix();
|
||||
MemoryUtil.memFree(charBuffer);
|
||||
}
|
||||
|
||||
private void renderThreadFunc() {
|
||||
glfwMakeContextCurrent(window);
|
||||
glfwSwapInterval(1);
|
||||
GL.createCapabilities();
|
||||
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
while (running) {
|
||||
renderProgress();
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
} catch (InterruptedException ignored) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
glfwMakeContextCurrent(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Runnable start(@Nullable String mcVersion) {
|
||||
initWindow(mcVersion);
|
||||
renderThread.setDaemon(true); // Don't hang the game if it terminates before handoff (i.e. datagen)
|
||||
renderThread.start();
|
||||
return org.lwjgl.glfw.GLFW::glfwPollEvents;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long handOffWindow(final IntSupplier width, final IntSupplier height, final Supplier<String> title, final LongSupplier monitorSupplier) {
|
||||
running = false;
|
||||
try {
|
||||
renderThread.join();
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
glfwSetWindowTitle(window, title.get());
|
||||
glfwSetWindowSize(window, width.getAsInt(), height.getAsInt());
|
||||
if (monitorSupplier.getAsLong() != 0L)
|
||||
glfwSetWindowMonitor(window, monitorSupplier.getAsLong(), 0, 0, width.getAsInt(), height.getAsInt(), GLFW_DONT_CARE);
|
||||
glfwMakeContextCurrent(window);
|
||||
GL.createCapabilities();
|
||||
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
renderProgress();
|
||||
glfwSwapInterval(0);
|
||||
glfwSwapBuffers(window);
|
||||
glfwSwapInterval(1);
|
||||
final GLFWFramebufferSizeCallback previous = glfwSetFramebufferSizeCallback(window, null);
|
||||
previous.free();
|
||||
return window;
|
||||
}
|
||||
}
|
||||
*/
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading.progress;
|
||||
|
||||
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.function.IntConsumer;
|
||||
import java.util.function.IntSupplier;
|
||||
import java.util.function.LongSupplier;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public enum EarlyProgressVisualization {
|
||||
INSTANCE;
|
||||
|
||||
private Visualization visualization;
|
||||
|
||||
public Runnable accept(Dist dist, boolean isData)
|
||||
{
|
||||
return accept(dist, isData, null);
|
||||
}
|
||||
|
||||
public Runnable accept(final Dist dist, final boolean isData, @Nullable String mcVersion) {
|
||||
// visualization = !isData && dist.isClient() && Boolean.parseBoolean(System.getProperty("fml.earlyprogresswindow", "true")) ? new ClientVisualization() : new NoVisualization();
|
||||
visualization = new NoVisualization();
|
||||
return visualization.start(mcVersion);
|
||||
}
|
||||
|
||||
public long handOffWindow(final IntSupplier width, final IntSupplier height, final Supplier<String> title, final LongSupplier monitor) {
|
||||
return visualization.handOffWindow(width, height, title, monitor);
|
||||
}
|
||||
|
||||
public void updateFBSize(IntConsumer width, IntConsumer height) {
|
||||
visualization.updateFBSize(width, height);
|
||||
}
|
||||
|
||||
interface Visualization {
|
||||
Runnable start(@Nullable String mcVersion);
|
||||
|
||||
default long handOffWindow(final IntSupplier width, final IntSupplier height, final Supplier<String> title, LongSupplier monitorSupplier) {
|
||||
return FMLLoader.getGameLayer().findModule("forge")
|
||||
.map(l->Class.forName(l, "net.minecraftforge.client.loading.NoVizFallback"))
|
||||
.map(LamdbaExceptionUtils.rethrowFunction(c->c.getMethod("fallback", IntSupplier.class, IntSupplier.class, Supplier.class, LongSupplier.class)))
|
||||
.map(LamdbaExceptionUtils.rethrowFunction(m->(LongSupplier)m.invoke(null, width, height, title, monitorSupplier)))
|
||||
.map(LongSupplier::getAsLong)
|
||||
.orElseThrow(()->new IllegalStateException("Why are you here?"));
|
||||
}
|
||||
|
||||
default void updateFBSize(IntConsumer width, IntConsumer height) {
|
||||
}
|
||||
}
|
||||
|
||||
private static class NoVisualization implements Visualization {
|
||||
@Override
|
||||
public Runnable start(@Nullable String mcVersion) {
|
||||
return () -> {};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading.progress;
|
||||
|
||||
public class Message {
|
||||
private final String text;
|
||||
private final MessageType type;
|
||||
private final long timestamp;
|
||||
|
||||
public Message(final String text, final MessageType type) {
|
||||
this.text = text;
|
||||
this.type = type;
|
||||
this.timestamp = System.nanoTime();
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
MessageType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
long timestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public float[] getTypeColour() {
|
||||
return type.colour();
|
||||
}
|
||||
|
||||
enum MessageType {
|
||||
MC(1.0f, 1.0f, 1.0f),
|
||||
ML(0.0f, 0.0f, 0.5f),
|
||||
LOC(0.0f, 0.5f, 0.0f),
|
||||
MOD(0.5f, 0.0f, 0.0f);
|
||||
|
||||
private final float[] colour;
|
||||
|
||||
MessageType(final float r, final float g, final float b) {
|
||||
colour = new float[] {r,g,b};
|
||||
}
|
||||
|
||||
public float[] colour() {
|
||||
return colour;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading.progress;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public final class ProgressMeter {
|
||||
private final String name;
|
||||
private final int steps;
|
||||
private AtomicInteger current;
|
||||
private Message label;
|
||||
|
||||
public ProgressMeter(String name, int steps, int current, Message label) {
|
||||
this.name = name;
|
||||
this.steps = steps;
|
||||
this.current = new AtomicInteger(current);
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int steps() {
|
||||
return steps;
|
||||
}
|
||||
|
||||
public int current() {
|
||||
return current.get();
|
||||
}
|
||||
|
||||
public Message label() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public void increment() {
|
||||
this.current.incrementAndGet();
|
||||
}
|
||||
|
||||
public void complete() {
|
||||
StartupNotificationManager.popBar(this);
|
||||
}
|
||||
|
||||
public float progress() {
|
||||
return current.get()/(float)steps;
|
||||
}
|
||||
|
||||
public void setAbsolute(final int absolute) {
|
||||
this.current.set(absolute);
|
||||
}
|
||||
|
||||
public void label(final String message) {
|
||||
this.label = new Message(message, Message.MessageType.ML);
|
||||
}
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading.progress;
|
||||
|
||||
import com.google.common.base.Ascii;
|
||||
import com.google.common.base.CharMatcher;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class StartupMessageManager {
|
||||
|
||||
private static volatile EnumMap<MessageType, List<Message>> messages = new EnumMap<>(MessageType.class);
|
||||
|
||||
public static List<Pair<Integer,Message>> getMessages() {
|
||||
final long ts = System.nanoTime();
|
||||
return messages.values().stream().flatMap(Collection::stream).
|
||||
sorted(Comparator.comparingLong(Message::getTimestamp).thenComparing(Message::getText).reversed()).
|
||||
map(m -> Pair.of((int) ((ts - m.timestamp) / 1e6), m)).
|
||||
limit(5).
|
||||
collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static class Message {
|
||||
private final String text;
|
||||
private final MessageType type;
|
||||
private final long timestamp;
|
||||
|
||||
public Message(final String text, final MessageType type) {
|
||||
this.text = text;
|
||||
this.type = type;
|
||||
this.timestamp = System.nanoTime();
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
MessageType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public float[] getTypeColour() {
|
||||
return type.colour();
|
||||
}
|
||||
}
|
||||
|
||||
enum MessageType {
|
||||
MC(1.0f, 1.0f, 1.0f),
|
||||
ML(0.0f, 0.0f, 0.5f),
|
||||
LOC(0.0f, 0.5f, 0.0f),
|
||||
MOD(0.5f, 0.0f, 0.0f);
|
||||
|
||||
private final float[] colour;
|
||||
|
||||
MessageType(final float r, final float g, final float b) {
|
||||
colour = new float[] {r,g,b};
|
||||
}
|
||||
|
||||
public float[] colour() {
|
||||
return colour;
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized static void addMessage(MessageType type, String message, int maxSize)
|
||||
{
|
||||
EnumMap<MessageType, List<Message>> newMessages = new EnumMap<>(messages);
|
||||
newMessages.compute(type, (key, existingList) -> {
|
||||
List<Message> newList = new ArrayList<>();
|
||||
if (existingList != null)
|
||||
{
|
||||
if (maxSize < 0)
|
||||
{
|
||||
newList.addAll(existingList);
|
||||
}
|
||||
else
|
||||
{
|
||||
newList.addAll(existingList.subList(0, Math.min(existingList.size(), maxSize)));
|
||||
}
|
||||
}
|
||||
newList.add(new Message(message, type));
|
||||
return newList;
|
||||
});
|
||||
messages = newMessages;
|
||||
}
|
||||
|
||||
public static void addModMessage(final String message) {
|
||||
final String safeMessage = Ascii.truncate(CharMatcher.ascii().retainFrom(message),80,"~");
|
||||
addMessage(MessageType.MOD, safeMessage, 20);
|
||||
}
|
||||
|
||||
public static Optional<Consumer<String>> modLoaderConsumer() {
|
||||
return Optional.of(s-> addMessage(MessageType.ML, s, -1));
|
||||
}
|
||||
|
||||
public static Optional<Consumer<String>> locatorConsumer() {
|
||||
return Optional.of(s -> addMessage(MessageType.LOC, s, -1));
|
||||
}
|
||||
|
||||
public static Optional<Consumer<String>> mcLoaderConsumer() {
|
||||
return Optional.of(s-> addMessage(MessageType.MC, s, -1));
|
||||
}
|
||||
}
|
||||
-94
@@ -1,94 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading.progress;
|
||||
|
||||
import com.google.common.base.Ascii;
|
||||
import com.google.common.base.CharMatcher;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class StartupNotificationManager {
|
||||
private static volatile EnumMap<Message.MessageType, List<Message>> messages = new EnumMap<>(Message.MessageType.class);
|
||||
|
||||
private static final Deque<ProgressMeter> progressMeters = new ArrayDeque<>();
|
||||
|
||||
public static List<ProgressMeter> getCurrentProgress() {
|
||||
synchronized (progressMeters) {
|
||||
return progressMeters.stream().toList();
|
||||
}
|
||||
}
|
||||
|
||||
public static ProgressMeter prependProgressBar(final String barName, final int count) {
|
||||
var pm = new ProgressMeter(barName, count, 0, new Message(barName, Message.MessageType.ML));
|
||||
synchronized (progressMeters) {
|
||||
progressMeters.addLast(pm);
|
||||
}
|
||||
return pm;
|
||||
}
|
||||
public static ProgressMeter addProgressBar(final String barName, final int count) {
|
||||
var pm = new ProgressMeter(barName, count, 0, new Message(barName, Message.MessageType.ML));
|
||||
synchronized (progressMeters) {
|
||||
progressMeters.push(pm);
|
||||
}
|
||||
return pm;
|
||||
}
|
||||
|
||||
public static void popBar(final ProgressMeter progressMeter) {
|
||||
synchronized (progressMeters) {
|
||||
progressMeters.remove(progressMeter);
|
||||
}
|
||||
}
|
||||
|
||||
public record AgeMessage(int age, Message message) {}
|
||||
public static List<AgeMessage> getMessages() {
|
||||
final long ts = System.nanoTime();
|
||||
return messages.values().stream().flatMap(Collection::stream)
|
||||
.sorted(Comparator.comparingLong(Message::timestamp).thenComparing(Message::getText).reversed())
|
||||
.map(m -> new AgeMessage((int) ((ts - m.timestamp()) / 1000000), m))
|
||||
.limit(2)
|
||||
.toList();
|
||||
}
|
||||
|
||||
private synchronized static void addMessage(Message.MessageType type, String message, int maxSize)
|
||||
{
|
||||
EnumMap<Message.MessageType, List<Message>> newMessages = new EnumMap<>(messages);
|
||||
newMessages.compute(type, (key, existingList) -> {
|
||||
List<Message> newList = new ArrayList<>();
|
||||
if (existingList != null)
|
||||
{
|
||||
if (maxSize < 0)
|
||||
{
|
||||
newList.addAll(existingList);
|
||||
}
|
||||
else
|
||||
{
|
||||
newList.addAll(existingList.subList(0, Math.min(existingList.size(), maxSize)));
|
||||
}
|
||||
}
|
||||
newList.add(new Message(message, type));
|
||||
return newList;
|
||||
});
|
||||
messages = newMessages;
|
||||
}
|
||||
|
||||
public static void addModMessage(final String message) {
|
||||
final String safeMessage = Ascii.truncate(CharMatcher.ascii().retainFrom(message),80,"~");
|
||||
addMessage(Message.MessageType.MOD, safeMessage, 20);
|
||||
}
|
||||
|
||||
public static Optional<Consumer<String>> modLoaderConsumer() {
|
||||
return Optional.of(s-> addMessage(Message.MessageType.ML, s, -1));
|
||||
}
|
||||
|
||||
public static Optional<Consumer<String>> locatorConsumer() {
|
||||
return Optional.of(s -> addMessage(Message.MessageType.LOC, s, -1));
|
||||
}
|
||||
|
||||
public static Optional<Consumer<String>> mcLoaderConsumer() {
|
||||
return Optional.of(s-> addMessage(Message.MessageType.MC, s, -1));
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,6 @@
|
||||
package net.minecraftforge.fml.loading.targets;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -21,7 +19,6 @@ import java.util.function.Supplier;
|
||||
* A class that attempts to parse command line arguments into key value pairs to allow addition and editing.
|
||||
* Can not use JOptSimple as that doesn't parse out the values for keys unless the spec says it has a value.
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
class ArgumentList {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private List<Supplier<String[]>> entries = new ArrayList<>();
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading.targets;
|
||||
|
||||
import cpw.mods.modlauncher.api.ServiceRunner;
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
import net.minecraftforge.fml.loading.LibraryFinder;
|
||||
import net.minecraftforge.fml.loading.VersionInfo;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public abstract class CommonClientLaunchHandler extends CommonLaunchHandler {
|
||||
@Override public Dist getDist() { return Dist.CLIENT; }
|
||||
@Override public String getNaming() { return "srg"; }
|
||||
@Override public boolean isProduction() { return true; }
|
||||
|
||||
@Override
|
||||
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
|
||||
return () -> {
|
||||
Class.forName(layer.findModule("minecraft").orElseThrow(),"net.minecraft.client.main.Main").getMethod("main", String[].class).invoke(null, (Object)arguments);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocatedPaths getMinecraftPaths() {
|
||||
final var vers = FMLLoader.versionInfo();
|
||||
var mc = LibraryFinder.findPathForMaven("net.minecraft", "client", "", "srg", vers.mcAndMCPVersion());
|
||||
var mcextra = LibraryFinder.findPathForMaven("net.minecraft", "client", "", "extra", vers.mcAndMCPVersion());
|
||||
var mcstream = Stream.<Path>builder().add(mc).add(mcextra);
|
||||
var modstream = Stream.<List<Path>>builder();
|
||||
|
||||
processMCStream(vers, mcstream, modstream);
|
||||
|
||||
var fmlcore = LibraryFinder.findPathForMaven(vers.forgeGroup(), "fmlcore", "", "", vers.mcAndForgeVersion());
|
||||
var javafmllang = LibraryFinder.findPathForMaven(vers.forgeGroup(), "javafmllanguage", "", "", vers.mcAndForgeVersion());
|
||||
var lowcodelang = LibraryFinder.findPathForMaven(vers.forgeGroup(), "lowcodelanguage", "", "", vers.mcAndForgeVersion());
|
||||
var mclang = LibraryFinder.findPathForMaven(vers.forgeGroup(), "mclanguage", "", "", vers.mcAndForgeVersion());
|
||||
|
||||
return new LocatedPaths(mcstream.build().toList(), (a,b) -> true, modstream.build().toList(), List.of(fmlcore, javafmllang, lowcodelang, mclang));
|
||||
}
|
||||
|
||||
protected abstract void processMCStream(VersionInfo versionInfo, Stream.Builder<Path> mc, Stream.Builder<List<Path>> mods);
|
||||
}
|
||||
+50
-111
@@ -6,35 +6,47 @@
|
||||
package net.minecraftforge.fml.loading.targets;
|
||||
|
||||
import cpw.mods.jarhandling.SecureJar;
|
||||
import cpw.mods.modlauncher.api.ServiceRunner;
|
||||
import net.minecraftforge.fml.loading.LogMarkers;
|
||||
import net.minecraftforge.fml.loading.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
@ApiStatus.Internal
|
||||
abstract class CommonDevLaunchHandler extends CommonLaunchHandler {
|
||||
protected CommonDevLaunchHandler(LaunchType type, String prefix) {
|
||||
super(type, prefix);
|
||||
}
|
||||
|
||||
public abstract class CommonDevLaunchHandler extends CommonLaunchHandler {
|
||||
@Override public String getNaming() { return "mcp"; }
|
||||
@Override public boolean isProduction() { return false; }
|
||||
|
||||
@Override
|
||||
protected String[] preLaunch(String[] arguments, ModuleLayer layer) {
|
||||
super.preLaunch(arguments, layer);
|
||||
public LocatedPaths getMinecraftPaths() {
|
||||
// Minecraft is extra jar {resources} + our exploded directories in dev
|
||||
final var mcstream = Stream.<Path>builder();
|
||||
|
||||
// The extra jar is on the classpath, so try and pull it out of the legacy classpath
|
||||
var legacyCP = Objects.requireNonNull(System.getProperty("legacyClassPath"), "Missing legacyClassPath, cannot find client-extra").split(File.pathSeparator);
|
||||
var extra = findJarOnClasspath(legacyCP, "client-extra");
|
||||
|
||||
// The MC code/Patcher edits are in exploded directories
|
||||
final var modstream = Stream.<List<Path>>builder();
|
||||
final var mods = getModClasses();
|
||||
final var minecraft = mods.remove("minecraft");
|
||||
if (minecraft == null)
|
||||
throw new IllegalStateException("Could not find 'minecraft' mod paths.");
|
||||
minecraft.stream().distinct().forEach(mcstream::add);
|
||||
mods.values().forEach(modstream::add);
|
||||
|
||||
mcstream.add(extra);
|
||||
var mcFilter = getMcFilter(extra, minecraft, modstream);
|
||||
return new LocatedPaths(mcstream.build().toList(), mcFilter, modstream.build().toList(), getFmlStuff(legacyCP));
|
||||
}
|
||||
|
||||
protected String[] preLaunch(String[] arguments, ModuleLayer layer) {
|
||||
if (getDist().isDedicatedServer())
|
||||
return arguments;
|
||||
|
||||
@@ -63,34 +75,34 @@ abstract class CommonDevLaunchHandler extends CommonLaunchHandler {
|
||||
return args.getArguments();
|
||||
}
|
||||
|
||||
protected List<Path> getLibraries(String[] classpath) {
|
||||
// TODO: These should all be FMLModType: LIBRARY, find them that way?
|
||||
// Or at the very least include version info
|
||||
var fmlcore = findJarOnClasspath(classpath, "fmlcore");
|
||||
var javafmllang = findJarOnClasspath(classpath, "javafmllanguage");
|
||||
var lowcodelang = findJarOnClasspath(classpath, "lowcodelanguage");
|
||||
var mclang = findJarOnClasspath(classpath, "mclanguage");
|
||||
return List.of(fmlcore, javafmllang, lowcodelang, mclang);
|
||||
protected List<Path> getFmlStuff(String[] classpath) {
|
||||
// We also want the FML things, fmlcore, javafmllanguage, mclanguage, I don't like hard coding these, but hey whatever works for now.
|
||||
return Arrays.stream(classpath)
|
||||
.filter(e -> FileUtils.matchFileName(e, "fmlcore", "javafmllanguage", "lowcodelanguage", "mclanguage"))
|
||||
.map(Paths::get)
|
||||
.toList();
|
||||
}
|
||||
|
||||
protected BiPredicate<String, String> getMcFilter(Path extra, List<Path> minecraft) {
|
||||
var packages = getPackages(); // Pulled out so it is passed to the lambda as value
|
||||
var extraPath = extra.toString().replace('\\', '/');
|
||||
protected static Path findJarOnClasspath(String[] classpath, String match) {
|
||||
return Paths.get(Arrays.stream(classpath)
|
||||
.filter(e -> FileUtils.matchFileName(e, match))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalStateException("Could not find " + match + " in classpath")));
|
||||
}
|
||||
|
||||
protected BiPredicate<String, String> getMcFilter(Path extra, List<Path> minecraft, Stream.Builder<List<Path>> mods) {
|
||||
final var packages = getPackages();
|
||||
final var extraPath = extra.toString().replace('\\', '/');
|
||||
|
||||
// We serve everything, except for things in the forge packages.
|
||||
BiPredicate<String, String> mcFilter = (path, base) -> {
|
||||
if (base.equals(extraPath) ||
|
||||
path.endsWith("/")) return true;
|
||||
path.endsWith("/")) return true;
|
||||
for (var pkg : packages)
|
||||
if (path.startsWith(pkg)) return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
return mcFilter;
|
||||
}
|
||||
|
||||
protected Path getForgeMod(List<Path> minecraft) {
|
||||
var packages = getPackages(); // Pulled out so it is passed to the lambda as value
|
||||
// We need to separate out our resources/code so that we can show up as a different data pack.
|
||||
var modJar = SecureJar.from((path, base) -> {
|
||||
if (!path.endsWith(".class")) return true;
|
||||
@@ -98,91 +110,18 @@ abstract class CommonDevLaunchHandler extends CommonLaunchHandler {
|
||||
if (path.startsWith(pkg)) return true;
|
||||
return false;
|
||||
}, minecraft.stream().distinct().toArray(Path[]::new));
|
||||
|
||||
//modJar.getPackages().stream().sorted().forEach(System.out::println);
|
||||
return modJar.getRootPath();
|
||||
mods.add(List.of(modJar.getRootPath()));
|
||||
|
||||
return mcFilter;
|
||||
}
|
||||
|
||||
private static String[] getPackages() {
|
||||
return new String[] {
|
||||
"net/minecraftforge/",
|
||||
"META-INF/services/",
|
||||
"META-INF/coremods.json",
|
||||
"META-INF/mods.toml"
|
||||
};
|
||||
protected String[] getPackages() {
|
||||
return new String[]{ "net/minecraftforge/", "META-INF/services/", "META-INF/coremods.json", "META-INF/mods.toml" };
|
||||
}
|
||||
|
||||
private static String getRandomNumbers(int length) {
|
||||
// Generate a time-based random number, to mimic how n.m.client.Main works
|
||||
return Long.toString(System.nanoTime() % (int) Math.pow(10, length));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ServiceRunner makeService(final String[] arguments, final ModuleLayer gameLayer) {
|
||||
return super.makeService(preLaunch(arguments, gameLayer), gameLayer);
|
||||
}
|
||||
|
||||
// TODO: Get rid of this string based classpath crap when I re-write mod loader
|
||||
protected static Map<String, List<Path>> getModClasses() {
|
||||
var modClasses = Optional.ofNullable(System.getenv("MOD_CLASSES")).orElse("");
|
||||
LOGGER.debug(LogMarkers.CORE, "Got mod coordinates {} from env", modClasses);
|
||||
|
||||
/*
|
||||
* So MOD_CLASSES at the end of the day is a Map<ModId, List<Path>> but because we want to shove that into a single string.
|
||||
* And pass it in as an environment variable.. instead od using json, or a file path, or anything sane, cpw did hacky string stuff
|
||||
* Basically what we want to do is split the string by File.pathSeperator to get each key-value pair.
|
||||
* Then we find the magic string "%%" and split on that. Using the first half as the mod id and the second as the path.
|
||||
* BUT the modID is optional, and if not specified {why wouldn't it be?} it uses "defaultmodid"
|
||||
* Anyways, it was stream hell, but now it's simpler to debug/understand.
|
||||
*/
|
||||
Map<String, List<Path>> ret = new HashMap<>();
|
||||
var entries = modClasses.split(File.pathSeparator);
|
||||
for (var entry : entries) {
|
||||
var modid = "defaultmodid";
|
||||
var path = entry;
|
||||
|
||||
int idx = entry.indexOf("%%");
|
||||
if (idx != -1) {
|
||||
modid = entry.substring(0, idx);
|
||||
path = entry.substring(idx + 2);
|
||||
}
|
||||
|
||||
var p = Paths.get(path);
|
||||
var lst = ret.computeIfAbsent(modid, k -> new ArrayList<>());
|
||||
if (!lst.contains(p))
|
||||
lst.add(p);
|
||||
}
|
||||
|
||||
LOGGER.debug(LogMarkers.CORE, "Found supplied mod coordinates:");
|
||||
ret.forEach((k, v) -> {
|
||||
if (v.size() == 1)
|
||||
LOGGER.debug(LogMarkers.CORE, "\t{}: {}", k, v.get(0));
|
||||
else {
|
||||
LOGGER.debug(LogMarkers.CORE, "\t{}:", k);
|
||||
v.forEach(e -> LOGGER.debug("\t\t{}", e));
|
||||
}
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
protected static Path findJarOnClasspath(String[] classpath, String match) {
|
||||
String ret = null;
|
||||
|
||||
for (var entry : classpath) {
|
||||
int idx = entry.lastIndexOf(File.separatorChar);
|
||||
if (idx == -1) continue;
|
||||
|
||||
var name = entry.substring(idx + 1);
|
||||
if (name.startsWith(match)) {
|
||||
ret = entry;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ret == null)
|
||||
new IllegalStateException("Could not find " + match + " in classpath");
|
||||
|
||||
return Paths.get(ret);
|
||||
}
|
||||
}
|
||||
+34
-68
@@ -8,93 +8,59 @@ package net.minecraftforge.fml.loading.targets;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import cpw.mods.modlauncher.api.ILaunchHandlerService;
|
||||
import cpw.mods.modlauncher.api.ITransformingClassLoaderBuilder;
|
||||
import cpw.mods.modlauncher.api.ServiceRunner;
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
import net.minecraftforge.fml.loading.LogMarkers;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import org.apache.logging.log4j.core.LoggerContext;
|
||||
import org.apache.logging.log4j.core.config.ConfigurationFactory;
|
||||
import org.apache.logging.log4j.core.config.ConfigurationSource;
|
||||
import org.apache.logging.log4j.core.config.Configurator;
|
||||
import org.slf4j.Logger;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
/**
|
||||
* This is required by FMLLoader because ILaunchHandlerService doesn't have the context we need.
|
||||
* I need to clean this up to make proper api. But that involves editing ModLauncher itself which i'm not gunna do right now.
|
||||
*
|
||||
* So until that happens, guess this is public api.
|
||||
*/
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public abstract class CommonLaunchHandler implements ILaunchHandlerService {
|
||||
public record LocatedPaths(
|
||||
List<Path> minecraftPaths,
|
||||
BiPredicate<String, String> minecraftFilter,
|
||||
List<List<Path>> otherModPaths,
|
||||
List<Path> otherArtifacts
|
||||
) {}
|
||||
public record LocatedPaths(List<Path> minecraftPaths, BiPredicate<String, String> minecraftFilter, List<List<Path>> otherModPaths, List<Path> otherArtifacts) {}
|
||||
|
||||
protected static final Logger LOGGER = LogUtils.getLogger();
|
||||
|
||||
private final LaunchType type;
|
||||
private final String prefix;
|
||||
public abstract Dist getDist();
|
||||
|
||||
protected CommonLaunchHandler(LaunchType type, String prefix) {
|
||||
this.type = type;
|
||||
this.prefix = prefix;
|
||||
public abstract String getNaming();
|
||||
|
||||
public boolean isProduction() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public String name() { return this.prefix + this.type.name(); }
|
||||
public Dist getDist() { return this.type.dist(); }
|
||||
public boolean isData() { return this.type.data(); }
|
||||
public boolean isProduction() { return false; }
|
||||
public abstract String getNaming();
|
||||
public boolean isData() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public abstract LocatedPaths getMinecraftPaths();
|
||||
|
||||
@Override
|
||||
public void configureTransformationClassLoader(final ITransformingClassLoaderBuilder builder) {
|
||||
|
||||
}
|
||||
|
||||
protected String[] preLaunch(String[] arguments, ModuleLayer layer) {
|
||||
URI uri;
|
||||
try (var reader = layer.configuration().findModule("fmlloader").orElseThrow().reference().open()) {
|
||||
uri = reader.find("log4j2.xml").orElseThrow();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
protected final Map<String, List<Path>> getModClasses() {
|
||||
final String modClasses = Optional.ofNullable(System.getenv("MOD_CLASSES")).orElse("");
|
||||
LOGGER.debug(LogMarkers.CORE, "Got mod coordinates {} from env", modClasses);
|
||||
|
||||
// Force the log4j2 configuration to be loaded from fmlloader
|
||||
Configurator.reconfigure(ConfigurationFactory.getInstance().getConfiguration(LoggerContext.getContext(), ConfigurationSource.fromUri(uri)));
|
||||
record ExplodedModPath(String modid, Path path) {}
|
||||
// "a/b/;c/d/;" -> "modid%%c:\fish\pepper;modid%%c:\fish2\pepper2\;modid2%%c:\fishy\bums;modid2%%c:\hmm"
|
||||
final var modClassPaths = Arrays.stream(modClasses.split(File.pathSeparator))
|
||||
.map(inp -> inp.split("%%", 2))
|
||||
.map(splitString -> new ExplodedModPath(splitString.length == 1 ? "defaultmodid" : splitString[0], Paths.get(splitString[splitString.length - 1])))
|
||||
.collect(Collectors.groupingBy(ExplodedModPath::modid, Collectors.mapping(ExplodedModPath::path, Collectors.toList())));
|
||||
|
||||
return arguments;
|
||||
}
|
||||
LOGGER.debug(LogMarkers.CORE, "Found supplied mod coordinates [{}]", modClassPaths);
|
||||
|
||||
@Override
|
||||
public ServiceRunner launchService(final String[] arguments, final ModuleLayer gameLayer) {
|
||||
FMLLoader.beforeStart(gameLayer);
|
||||
return makeService(arguments, gameLayer);
|
||||
}
|
||||
|
||||
protected ServiceRunner makeService(final String[] arguments, final ModuleLayer gameLayer) {
|
||||
return () -> runTarget(this.type.module(), this.type.main(), arguments, gameLayer);
|
||||
}
|
||||
|
||||
protected record LaunchType(String name, String module, String main, Dist dist, boolean data) {};
|
||||
protected static final LaunchType CLIENT = new LaunchType("client", "minecraft", "net.minecraft.client.main.Main", Dist.CLIENT, false);
|
||||
protected static final LaunchType DATA = new LaunchType("data", "minecraft", "net.minecraft.data.Main", Dist.CLIENT, true);
|
||||
protected static final LaunchType SERVER = new LaunchType("server", "minecraft", "net.minecraft.server.Main", Dist.DEDICATED_SERVER, false);
|
||||
protected static final LaunchType SERVER_GAMETEST = new LaunchType("server_gametest", "forge", "net.minecraftforge.gametest.GameTestMain", Dist.DEDICATED_SERVER, false);
|
||||
|
||||
protected void runTarget(String module, String target, final String[] arguments, final ModuleLayer layer) throws Throwable {
|
||||
var mod = layer.findModule(module).orElseThrow();
|
||||
if (mod == null) throw new IllegalStateException("Could not find module " + module);
|
||||
var cls = Class.forName(mod, target);
|
||||
if (cls == null) throw new IllegalStateException("Could not find class " + target + " in module " + module);
|
||||
var mtd = cls.getMethod("main", String[].class);
|
||||
if (mtd == null) throw new IllegalStateException("Class " + target + " in module " + module + " does not have a main(String[]) method");
|
||||
mtd.invoke(null, (Object)arguments);
|
||||
//final var explodedTargets = ((Map<String, List<ExplodedDirectoryLocator.ExplodedMod>>)arguments).computeIfAbsent("explodedTargets", a -> new ArrayList<>());
|
||||
//modClassPaths.forEach((modlabel,paths) -> explodedTargets.add(new ExplodedDirectoryLocator.ExplodedMod(modlabel, paths)));
|
||||
return modClassPaths;
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading.targets;
|
||||
|
||||
import cpw.mods.jarhandling.SecureJar;
|
||||
import cpw.mods.modlauncher.api.ServiceRunner;
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
import net.minecraftforge.fml.loading.LibraryFinder;
|
||||
import net.minecraftforge.fml.loading.VersionInfo;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public abstract class CommonServerLaunchHandler extends CommonLaunchHandler {
|
||||
@Override public Dist getDist() { return Dist.DEDICATED_SERVER; }
|
||||
@Override public String getNaming() { return "srg"; }
|
||||
@Override public boolean isProduction() { return true; }
|
||||
|
||||
@Override
|
||||
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
|
||||
return () -> {
|
||||
Class.forName(layer.findModule("minecraft").orElseThrow(),"net.minecraft.server.Main").getMethod("main", String[].class).invoke(null, (Object)arguments);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocatedPaths getMinecraftPaths() {
|
||||
final var vers = FMLLoader.versionInfo();
|
||||
var mc = LibraryFinder.findPathForMaven("net.minecraft", "server", "", "srg", vers.mcAndMCPVersion());
|
||||
var mcextra = LibraryFinder.findPathForMaven("net.minecraft", "server", "", "extra", vers.mcAndMCPVersion());
|
||||
var mcextra_filtered = SecureJar.from( // We only want it for it's resources. So filter everything else out.
|
||||
(path, base) -> {
|
||||
return path.equals("META-INF/versions/") || // This is required because it bypasses our filter for the manifest, and it's a multi-release jar.
|
||||
(!path.endsWith(".class") &&
|
||||
!path.startsWith("META-INF/"));
|
||||
}, mcextra
|
||||
);
|
||||
BiPredicate<String, String> filter = (path, base) -> true;
|
||||
|
||||
var mcstream = Stream.<Path>builder().add(mc).add(mcextra_filtered.getRootPath());
|
||||
var modstream = Stream.<List<Path>>builder();
|
||||
|
||||
filter = processMCStream(vers, mcstream, filter, modstream);
|
||||
|
||||
var fmlcore = LibraryFinder.findPathForMaven(vers.forgeGroup(), "fmlcore", "", "", vers.mcAndForgeVersion());
|
||||
var javafmllang = LibraryFinder.findPathForMaven(vers.forgeGroup(), "javafmllanguage", "", "", vers.mcAndForgeVersion());
|
||||
var lowcodelang = LibraryFinder.findPathForMaven(vers.forgeGroup(), "lowcodelanguage", "", "", vers.mcAndForgeVersion());
|
||||
var mclang = LibraryFinder.findPathForMaven(vers.forgeGroup(), "mclanguage", "", "", vers.mcAndForgeVersion());
|
||||
|
||||
return new LocatedPaths(mcstream.build().toList(), filter, modstream.build().toList(), List.of(fmlcore, javafmllang, lowcodelang, mclang));
|
||||
}
|
||||
|
||||
protected abstract BiPredicate<String, String> processMCStream(VersionInfo versionInfo, Stream.Builder<Path> mc, BiPredicate<String, String> filter, Stream.Builder<List<Path>> mods);
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading.targets;
|
||||
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
import net.minecraftforge.fml.loading.VersionInfo;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public abstract class CommonUserdevLaunchHandler extends CommonDevLaunchHandler {
|
||||
@Override
|
||||
public LocatedPaths getMinecraftPaths() {
|
||||
final var vers = FMLLoader.versionInfo();
|
||||
|
||||
// Minecraft is extra jar {resources} + forge jar {patches}
|
||||
final var mcstream = Stream.<Path>builder();
|
||||
// Mod code is in exploded directories
|
||||
final var modstream = Stream.<List<Path>>builder();
|
||||
|
||||
// The MC extra and forge jars are on the classpath, so try and pull them out
|
||||
var legacyCP = Objects.requireNonNull(System.getProperty("legacyClassPath"), "Missing legacyClassPath, cannot find userdev jars").split(File.pathSeparator);
|
||||
var extra = findJarOnClasspath(legacyCP, "client-extra");
|
||||
|
||||
processStreams(legacyCP, vers, mcstream, modstream);
|
||||
getModClasses().forEach((modid, paths) -> modstream.add(paths));
|
||||
|
||||
var minecraft = mcstream.build().collect(Collectors.toList());
|
||||
var mcFilter = getMcFilter(extra, minecraft, modstream);
|
||||
minecraft.add(extra); // Add extra late so the filter is made correctly
|
||||
return new LocatedPaths(minecraft, mcFilter, modstream.build().toList(), getFmlStuff(legacyCP));
|
||||
}
|
||||
|
||||
protected abstract void processStreams(String[] classpath, VersionInfo versionInfo, Stream.Builder<Path> mc, Stream.Builder<List<Path>> mods);
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading.targets;
|
||||
|
||||
import cpw.mods.modlauncher.api.ServiceRunner;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
public class FMLClientDevLaunchHandler extends CommonDevLaunchHandler {
|
||||
@Override public String name() { return "fmlclientdev"; }
|
||||
@Override public Dist getDist() { return Dist.CLIENT; }
|
||||
|
||||
@Override
|
||||
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
|
||||
return () -> {
|
||||
var args = preLaunch(arguments, layer);
|
||||
|
||||
Class.forName(layer.findModule("minecraft").orElseThrow(), "net.minecraft.client.main.Main").getMethod("main", String[].class).invoke(null, (Object) args);
|
||||
};
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading.targets;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import net.minecraftforge.fml.loading.LibraryFinder;
|
||||
import net.minecraftforge.fml.loading.VersionInfo;
|
||||
|
||||
public class FMLClientLaunchHandler extends CommonClientLaunchHandler {
|
||||
@Override public String name() { return "fmlclient"; }
|
||||
|
||||
@Override
|
||||
protected void processMCStream(VersionInfo versionInfo, Stream.Builder<Path> mc, Stream.Builder<List<Path>> mods) {
|
||||
var fmlonly = LibraryFinder.findPathForMaven(versionInfo.forgeGroup(), "fmlonly", "", "universal", versionInfo.mcAndForgeVersion());
|
||||
mods.add(List.of(fmlonly));
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading.targets;
|
||||
|
||||
import cpw.mods.modlauncher.api.ServiceRunner;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
public class FMLClientUserdevLaunchHandler extends FMLUserdevLaunchHandler {
|
||||
@Override
|
||||
public String name() { return "fmlclientuserdev"; }
|
||||
|
||||
@Override
|
||||
public Dist getDist() { return Dist.CLIENT; }
|
||||
|
||||
@Override
|
||||
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
|
||||
return () -> {
|
||||
var args = preLaunch(arguments, layer);
|
||||
|
||||
Class.forName(layer.findModule("minecraft").orElseThrow(), "net.minecraft.client.main.Main").getMethod("main", String[].class).invoke(null, (Object) args);
|
||||
};
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading.targets;
|
||||
|
||||
import cpw.mods.modlauncher.api.ServiceRunner;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
public class FMLDataUserdevLaunchHandler extends FMLUserdevLaunchHandler {
|
||||
@Override
|
||||
public String name() { return "fmldatauserdev"; }
|
||||
|
||||
@Override
|
||||
public Dist getDist() { return Dist.CLIENT; }
|
||||
|
||||
@Override
|
||||
public boolean isData() { return true; }
|
||||
|
||||
@Override
|
||||
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
|
||||
return () -> {
|
||||
var args = preLaunch(arguments, layer);
|
||||
|
||||
Class.forName(layer.findModule("minecraft").orElseThrow(), "net.minecraft.data.Main").getMethod("main", String[].class).invoke(null, (Object) args);
|
||||
};
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading.targets;
|
||||
|
||||
import cpw.mods.modlauncher.api.ServiceRunner;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
public class FMLServerDevLaunchHandler extends CommonDevLaunchHandler {
|
||||
@Override public String name() { return "fmlserverdev"; }
|
||||
@Override public Dist getDist() { return Dist.DEDICATED_SERVER; }
|
||||
|
||||
@Override
|
||||
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
|
||||
return () -> {
|
||||
var args = preLaunch(arguments, layer);
|
||||
|
||||
Class.forName(layer.findModule("minecraft").orElseThrow(),"net.minecraft.server.Main").getMethod("main", String[].class).invoke(null, (Object)args);
|
||||
};
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading.targets;
|
||||
|
||||
import cpw.mods.modlauncher.api.ILaunchHandlerService;
|
||||
import net.minecraftforge.fml.loading.LibraryFinder;
|
||||
import net.minecraftforge.fml.loading.VersionInfo;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class FMLServerLaunchHandler extends CommonServerLaunchHandler implements ILaunchHandlerService {
|
||||
@Override public String name() { return "fmlserver"; }
|
||||
|
||||
@Override
|
||||
protected BiPredicate<String, String> processMCStream(VersionInfo versionInfo, Stream.Builder<Path> mc, BiPredicate<String, String> filter, Stream.Builder<List<Path>> mods) {
|
||||
var fmlonly = LibraryFinder.findPathForMaven(versionInfo.forgeGroup(), "fmlonly", "", "universal", versionInfo.mcAndForgeVersion());
|
||||
mods.add(List.of(fmlonly));
|
||||
return filter;
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.loading.targets;
|
||||
|
||||
import cpw.mods.modlauncher.api.ServiceRunner;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
public class FMLServerUserdevLaunchHandler extends FMLUserdevLaunchHandler {
|
||||
@Override public String name() { return "fmlserveruserdev"; }
|
||||
@Override public Dist getDist() { return Dist.DEDICATED_SERVER; }
|
||||
|
||||
@Override
|
||||
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
|
||||
return () -> {
|
||||
var args = preLaunch(arguments, layer);
|
||||
|
||||
Class.forName(layer.findModule("minecraft").orElseThrow(),"net.minecraft.server.Main").getMethod("main", String[].class).invoke(null, (Object)args);
|
||||
};
|
||||
}
|
||||
}
|
||||
Loaded 100 of 1928 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user