Compare commits

...
25 Commits
Author SHA1 Message Date
LexManos 2bc9649bd6 26.3 - Initial update 2026-09-19 07:45:38 -07:00
coolsim 41dfdc8f8b Fixed getToolModifiedState for AXE_SCRAPE (#10880) 2026-08-26 18:06:47 -07:00
RealMangoRage 4dae990880 Add TooltipContext and TooltipDisplay to ItemTooltipEvent 2026-08-18 12:34:10 -07:00
LexManos f0f144156b Fix mod packets being processed after vanilla packets when received in the same tick. 2026-08-10 21:56:47 -07:00
LexManos d17cfd0b4b 26.2 RB 1 2026-07-27 12:57:42 -07:00
LexManos d697be351f Bump Dependencies, Notably ASM for Java 27 support 2026-07-21 16:05:10 -07:00
LexManos 703195cd36 Fix ModList screen selection getting out of sync. Closes #10855 2026-07-21 15:55:37 -07:00
Meme Man 3f1e9a90c9 Update equality check for RegistryObject to include RegistryObject#key. (#10860) 2026-07-21 10:32:48 -07:00
zznty da353a52fe Handle glfw window pos errors gracefully for wayland (#10853) 2026-07-19 09:15:42 -07:00
Matt 3bac35089f Add DeltaTracker (partialTick) context to PassDefinitions (#10831) 2026-07-16 12:47:53 -07:00
Matt cb2e205867 Toml parsing errors are now visible in the latest log. (#10847) 2026-07-13 17:05:48 -07:00
LexManos c5d015e60a Add mechanic for ImmediateWindowProviders to react to Graphics backed settings
Disable early loading screen when using Vulkan. Fixes #10829
2026-06-30 15:57:35 -07:00
72be0c9ccd ForgeDev 7
Co-authored-by: Jonathing <[email protected]>
Co-authored-by: Paint_Ninja <[email protected]>
2026-06-30 12:43:08 -07:00
LexManos dabe919492 Remove IForgeShearable Closes #10826
Entities use vanilla's Shearable interface
 Blocks use loot table conditions or useItemOn if you need positional information
2026-06-25 08:58:58 -07:00
LexManos 3da1b644d0 26.2 2026-06-17 19:35:27 -07:00
Matt d3476f3f80 [26.1.2] Split hotbar rendering into components, allow replacing renders (#10824) 2026-06-09 11:21:51 -07:00
LexManos 6665cdcf4e Fix misaligned ClientLevel load patch 2026-05-04 09:26:36 -07:00
LexManos d9665f6447 Fix IForgeBlockStateModel#getModelData is never called Fixes #10817 2026-05-02 16:48:24 -07:00
LexManos ec73fd5240 Fix base bucket model not being flipped for gasses. Closes #10814 2026-04-27 10:45:07 -07:00
LexManos 3bbdbacb81 Fix being considered in water when not. Closes #10813 2026-04-23 14:36:51 -07:00
Paint_Ninja 478d8b3f18 Add c:bars/copper and c:bars/iron tags (#10810) 2026-04-15 22:20:21 +01:00
Paint_Ninja 03f928a3de Add c:foods/dough tag (#10811) 2026-04-15 22:19:57 +01:00
Paint_Ninja 4834f16aef Add c:drink_containing/bottle and c:drink_containing/bucket (#10812) 2026-04-15 22:19:44 +01:00
LexManos 7846d25606 Copy fluid and lava still textures to Item atlas for DynamicFluidContainerModel. Fixes #10809 2026-04-12 12:53:34 -07:00
LexManos 77cb0c5c4e 26.1.2 2026-04-09 10:16:29 -07:00
1096 changed files with 12400 additions and 18703 deletions

No files matched your search

+1 -1
View File
@@ -2,7 +2,7 @@ name: Publish
on:
push:
branches: [ '26.1.1' ]
branches: [ '26.3' ]
permissions:
contents: read
+14 -3
View File
@@ -1,3 +1,6 @@
#forgedev dev if the folder exists in here
forgedev
#eclipse
**/bin
**/.settings
@@ -22,10 +25,12 @@
/*/build
/*/.gradle
# Minecraft
/run
/*.launch
# Projects repo, either ignore it, or ignore patches.
/projects/mcp/
/projects/clean/
/projects/forge/
src/minecraft
#occupational hazards
/projects/**/build/
@@ -33,6 +38,7 @@
/projects/**/run/
/projects/**/*.launch
/repo/
/rejects-*/
src/*/generated/**/.cache/
# Generated by gradle every import
@@ -47,3 +53,8 @@ src/*/generated/**/.cache/
/*/.factorypath
/*/.apt_generated/
/fmlcore/logs/
/lib/
/_actual/
/runs/
/forge.ipr
/forge.iws
+1 -1
View File
@@ -3,7 +3,7 @@ parts herein are licensed under the terms of the LGPL 2.1 found
here http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt and
copied below.
Homepage: http://minecraftforge.net/
Homepage: https://minecraftforge.net/
https://github.com/MinecraftForge/MinecraftForge
+782 -37
View File
@@ -1,57 +1,802 @@
import net.minecraftforge.forge.tasks.*
import net.minecraftforge.gradleutils.PomUtils
import org.apache.tools.ant.filters.ReplaceTokens
plugins {
id 'net.minecraftforge.licenser' version '1.0.1'
id 'com.github.ben-manes.versions' version '0.46.0'
id 'net.minecraftforge.gradleutils' version '[2.6.4,2.7)'
id 'java-library'
id 'idea'
id 'eclipse'
id 'de.undercouch.download' version '5.4.0'
id 'net.minecraftforge.gradle.patcher' version '[6.0.51,6.2)' apply false
id 'net.minecraftforge.gradle.mcp' version '[6.0.51,6.2)' apply false
id 'net.minecraftforge.gradlejarsigner' version '1.0.4'
id 'org.barfuin.gradle.taskinfo' version '3.0.1'
id 'maven-publish'
alias libs.plugins.licenser
alias libs.plugins.versions
alias libs.plugins.gradleutils
alias libs.plugins.gitversion
alias libs.plugins.changelog
alias libs.plugins.jarsigner
id 'net.minecraftforge.forgedev'
id 'net.minecraftforge.forge.build.convention'
}
Util.init() //Init all our extension methods!
gradleutils.displayName = 'Forge'
group = 'net.minecraftforge'
description = 'Modifications to Minecraft to enable mod developers.'
ext {
VERSION = gitversion.getMCTagOffsetBranch(MC_VERSION)
FORGE_VERSION = VERSION.substring(MC_VERSION.length() + 1)
// NOTE: See settings.gradle for all the referenceable version variables.
// All subprojects (that apply 'net.minecraftforge.forge.build.convention') will have these available.
println "Version: $version"
java.toolchain.languageVersion = JavaLanguageVersion.of(javaVersion)
jarSigner.autoDetect('forge')
// We have to tell ForgeDev to use the exact versions of tools we ship in the installer
fdtools {
configure('mavenizer') {
version = '0.5.22'
}
configure('binpatcher') {
version = buildLibs.binarypatcher.get().version
}
configure('installertools') {
version = buildLibs.installertools.get().version
}
configure('jarcompatibilitychecker') {
javaLauncher = javaToolchains.launcherFor(java.toolchain)
}
}
changelog {
from '47.999'
/* This is used for debugging, comparing all published artifacts against actual production
forgedev.validatePublish {
task {
baseBranch = 'upstream/26.1.2'
versionPrefix = minecraftVersion
}
}
*/
final minecraftFiles = forgedev.minecraftFiles(minecraftVersion)
final mcpBase = forgedev.mcpBase {
mcpVersion = "${minecraftVersion}-${project.ext.mcpVersion}"
accessTransformers.from file('src/main/resources/META-INF/accesstransformer.cfg')
sideAnnotationStrippers.from file('src/main/resources/forge.sas')
}
forgedev.patches {
base = mcpBase
patches = file('patches/minecraft')
patched = file('src/minecraft/java')
sourceSets.main.java.srcDir patched
}
tasks.register('setup') {
dependsOn ':forge:extractMapped'
if (findProject(':clean'))
dependsOn ':clean:extractMapped'
dependsOn forgedev.patches.apply
}
tasks.register('doChecks') {
dependsOn ':forge:checkJarCompatibility'
dependsOn ':forge:publish'
}
project(':mcp') {
apply plugin: 'net.minecraftforge.gradle.mcp'
mcp {
config MC_VERSION + '-' + MCP_VERSION
pipeline = 'joined'
sourceSets {
named('main') {
resources.srcDir 'src/main/generated'
}
repositories {
mavenLocal()
named('test') {
resources.srcDir 'src/test/generated'
}
}
if (System.env.TEAMCITY_VERSION) {
//Only setup the CI environment if and only if the environment variables are set.
tasks.named('configureTeamCity').configure {
doLast {
println "##teamcity[buildNumber '${project(':forge').version}']"
println "##teamcity[setParameter name='env.PUBLISHED_JAVA_ARTIFACT_VERSION' value='${project(':forge').version}']"
license {
header = file('LICENSE-header.txt')
include 'net/minecraftforge/'
exclude 'net/minecraftforge/common/LenientUnboundedMapCodec.java'
tasks.tap {
register('main') {
files.from files('src/main/java')
}
register('test') {
files.from files('src/test/java')
}
}
}
changelog {
from changelogBase
publishAll = false
}
// TODO [ForgeDev] Support compileOnlyApi, runtimeOnly, and a consumable annotation processor configuration
configurations {
// Don't pull all libraries, if we're missing something, add it to the installer list so the installer knows to download it.
register('bootstrap') {
transitive = false
canBeDeclared = true
canBeResolved = true
}
register('installer') {
transitive = false
canBeDeclared = true
canBeResolved = true
extendsFrom(bootstrap)
}
named('api') {
extendsFrom(installer)
}
named('implementation') {
// Inherit vanilla Minecraft's dependencies
extendsFrom(mcpBase.dependencyConfiguration)
}
}
dependencies {
// These need to actually be on the classpath at the start. This is only used for the server shim jar.
// And this is only needed because custom file systems are REQUIRED to be on the boot classloader.
// This has ASM/BootStrap/Unsafe all because I haven't gotten around to moving UnionFileSystem out to its own project.
bootstrap libs.jarjar.fs // JarInJar file system
bootstrap libs.roimfs // JarInJar File System - FileSystems need to be on boot loader.
bootstrap libs.bundles.jimfs // In memory file system used for ForgeDev launches
bootstrap libs.securemodules // Has Union file system in it
bootstrap libs.unsafe // Needed by securemodules
bootstrap libs.bundles.asm // Needed by securemodules
implementation libs.jopt.simple
installer libs.bootstrap
installer libs.bootstrap.api // Needed by securemodules
installer libs.accesstransformers
installer libs.eventbus
installer libs.jspecify // Dep of EventBus
installer libs.forgespi
installer libs.coremods.api
installer libs.modlauncher
installer libs.mergetool.api
installer libs.bundles.night.config
installer libs.maven.artifact
installer libs.bundles.terminalconsoleappender
installer libs.mixin
installer libs.mixinextras.forge
installer libs.bundles.jarjar
installer libs.roimfs
installer projects.fmlcore
installer projects.fmlloader
installer projects.fmlearlydisplay
installer projects.javafmllanguage
installer projects.lowcodelanguage
installer projects.mclanguage
installer projects.forgeTransformers
runtimeOnly files(mcpBase.extra)
runtimeOnly libs.bootstrap
runtimeOnly libs.bootstrap.dev
annotationProcessor libs.eventbus.validator
}
final EXTRA_TXTS = [
file('LICENSE.txt'),
changelog.task.flatMap(task -> task.outputFile)
]
forgedev.runs {
configureEach {
options {
workingDir = layout.projectDirectory.dir("runs/$name")
mainClass = 'net.minecraftforge.bootstrap.ForgeBootstrap'
args '--gameDir', '.'
jvmArgs '-Djava.net.preferIPv6Addresses=system', '-XX:+UseCompactObjectHeaders', '-XX:StackShadowPages=32'
systemProperty 'bsl.debug', 'true'
systemProperty 'terminal.jline', 'true'
}
}
create('client') {
options {
systemProperty 'eventbus.api.strictRuntimeChecks', 'true'
systemProperty 'org.lwjgl.system.SharedLibraryExtractDirectory', 'lwjgl_dll'
args '--launchTarget', 'forge_dev_client',
'--username', 'Dev',
'--version', project.name,
'--accessToken', '0',
'--userType', 'mojang',
'--versionType', 'release',
'--assetsDir', '{assets_root}',
'--assetIndex', '{asset_index}'
with(sourceSets.test) {
systemProperty 'forge.enableGameTest', 'true'
systemProperty 'forgedev.enableTestMods', 'true'
}
}
}
create('server') {
options {
args '--launchTarget', 'forge_dev_server'
}
}
create('gameTestServer') {
options {
systemProperty 'forge.enableGameTest', 'true'
args '--launchTarget', 'forge_dev_server_gametest'
args '--uniqueWorld' // Unique world is used so that the world regenerates, as well as the world config isn't influenced by other runs
with(sourceSets.test) {
systemProperty 'forgedev.enableTestMods', 'true'
}
}
}
create('data') {
options {
args '--launchTarget', 'forge_dev_data',
'--all',
'--existing', sourceSets.main.resources.srcDirs[0],
'--assetsDir', '{assets_root}',
'--assetIndex', '{asset_index}'
with(sourceSets.main) {
args '--mod', 'forge',
'--output', rootProject.file('src/main/generated/')
}
with(sourceSets.test) {
systemProperty 'forge.enableGameTest', 'true'
systemProperty 'forgedev.enableTestMods', 'true'
args '--mod', '.+',
'--existing', sourceSets.test.resources.srcDirs[0],
'--output', rootProject.file('src/test/generated/')
}
}
}
create('clientData') {
options {
args '--launchTarget', 'forge_dev_client_data',
'--all',
'--existing', sourceSets.main.resources.srcDirs[0],
'--assetsDir', '{assets_root}',
'--assetIndex', '{asset_index}'
with(sourceSets.main) {
args '--mod', 'forge',
'--output', rootProject.file('src/main/generated/')
}
with(sourceSets.test) {
systemProperty 'forgedev.enableTestMods', 'true'
args '--mod', '.+',
'--existing', sourceSets.test.resources.srcDirs[0],
'--output', rootProject.file('src/test/generated/')
}
}
}
}
tasks.register('genAllData') {
dependsOn forgedev.runs.data.run, forgedev.runs.data.runTest, forgedev.runs.clientData.run, forgedev.runs.clientData.runTest
}
final serverShim = forgedev.shim {
config {
mainClass = 'net.minecraftforge.bootstrap.ForgeBootstrap'
args = ['--launchTarget', 'forge_server']
}
classpath {
libraries configurations.installer
library tasks.named('universalJar', Jar)
library forgedev.binaryPatches('server').apply, 'server'
serverBundle.fileProvider(minecraftFiles.server)
}
bootstrapClasspath(configurations.bootstrap)
jar {
jarSigner.sign(it)
}
}
final finalizeSpawn = forgedev.methodCallFinder('findFinalizeSpawnTargets') {
jar.fileProvider(mcpBase.classes)
output = rootProject.file('forge-transformers/src/main/resources/coremods/finalize_spawn_targets.json')
blacklist 'net/minecraft/world/level/BaseSpawner' // Ignore this class as we special case it.
invokeVirtual('finalizeSpawn', '(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/EntitySpawnReason;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;')
}
forgedev.validateDeprecations(tasks.named('jar', Jar)) {
mcVersion = minecraftVersion
}
forgedev.checks() {
base = mcpBase
ats {}
sas {}
execs {}
patches {
dependsOn forgedev.patches.make
patchDir = forgedev.patches.patches
patchesWithS2SArtifact = [
'minecraft/net/minecraft/client/renderer/ViewArea.java.patch',
'minecraft/net/minecraft/data/models/blockstates/Variant.java.patch',
]
}
fix {
dependsOn finalizeSpawn
}
forgedev.patches.make.configure {
finalizedBy forgedev.checks.patches.fix
}
}
final crowdin = forgedev.crowdin // Create the crowdin tasks
tasks.named('jar', Jar) {
from(EXTRA_TXTS)
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
exclude '.cache'
from(zipTree(crowdin.flatMap { it.output } )) {
include 'assets/forge/lang/*.json'
}
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.forge'
])
attributes([
'Specification-Title' : gradleutils.displayName.get(),
'Specification-Vendor' : gradleutils.vendor.get(),
'Specification-Version' : gitversion.info.tag,
'Implementation-Title' : project.group,
'Implementation-Vendor' : gradleutils.vendor.get(),
'Implementation-Version': forgeVersion
], 'net/minecraftforge/versions/forge/')
attributes([
'Specification-Title' : 'Minecraft',
'Specification-Vendor' : gradleutils.vendor.get(),
'Specification-Version' : minecraftVersion,
'Implementation-Title' : 'MCP',
'Implementation-Vendor' : gradleutils.vendor.get(),
'Implementation-Version': mcpVersion
], 'net/minecraftforge/versions/mcp/')
}
jarSigner.sign(it)
}
final universalJar = tasks.register('universalJar', Jar) {
from (zipTree(tasks.named('jar', Jar).flatMap { it.archiveFile } )) {
exclude(forgedev.filterVanilla(mcpBase.classes))
}
manifest = tasks.named('jar', Jar).get().manifest
archiveClassifier = 'universal'
jarSigner.sign(it)
}
final sourcesJarAll = tasks.register('sourcesJarAll', Jar) {
archiveClassifier = 'sources-all'
from(sourceSets.main.allJava.srcDirs)
}
// Our sources, plus the patches we do to Vanilla Minecraft
final sourcesJar = tasks.register('sourcesJar', Jar) {
archiveClassifier = 'sources'
from(sourceSets.main.allJava.srcDirs - forgedev.patches.patched.asFile.get())
mustRunAfter(forgedev.patches.make)
from(forgedev.patches.patches) {
into 'patches/'
}
}
forgedev.userDev {
base(mcpBase)
config {
universal universalJar
sources sourcesJar
addLibraries(configurations.installer)
// TODO [ForgeDev][UserDev] Split API/Runtime elements in userdev config
// so we don't gotta keep making exceptions for MixinExtras
addCompileDependency(libs.mixinextras.common)
addAnnotationProcessorDependency(libs.mixinextras.common)
// TODO [ForgeDev] Consolidate Patcher + UserDev Config runs in a sane way
runs {
configureEach {
parents = []
main = 'net.minecraftforge.bootstrap.ForgeBootstrap'
args '--gameDir', '.'
jvmArgs '-Djava.net.preferIPv6Addresses=system', '-XX:+UseCompactObjectHeaders', '-XX:StackShadowPages=32'
client = it.name.containsIgnoreCase('client')
environment 'MCP_MAPPINGS', '{mcp_mappings}'
}
register('clientData') {
args '--launchTarget', "forge_userdev_client_data"
args '--assetIndex', '{asset_index}'
args '--assetsDir', '{assets_root}'
}
register('client') {
property 'forge.enableGameTest', 'true'
args '--launchTarget', "forge_userdev_client"
args '--version', 'MOD_DEV'
args '--assetIndex', '{asset_index}'
args '--assetsDir', '{assets_root}'
}
register('server') {
property 'forge.enableGameTest', 'true'
args '--launchTarget', "forge_userdev_server"
}
register('gameTestServer') {
args '--launchTarget', "forge_userdev_server_gametest"
}
register('data') {
args '--launchTarget', "forge_userdev_data"
args '--assetIndex', '{asset_index}'
args '--assetsDir', '{assets_root}'
}
}
}
patches {
modified.setFrom(sourcesJarAll.flatMap { it.archiveFile })
}
binaryPatches {
mustRunAfter(forgedev.patches.make)
patches.setFrom(forgedev.patches.patches)
dirty.setFrom(tasks.named('jar', Jar).flatMap { it.archiveFile })
}
}
forgedev.userdevCompatibility(minecraftVersion) {
clean = mcpBase.classes
dirty = tasks.named('jar', Jar)
}
// region Installer tasks, Not needed when we simplify the installer/remove obf
final clientBinPatches = forgedev.binaryPatches('client') {
clean = minecraftFiles.client
// Workaround for 3rd party launcher issues, marker needed to locate MC, store needed to fix zlib-ng issues
apply {
store = true
marker = '.forge_patched_minecraft'
}
}
final serverBinPatches = forgedev.binaryPatches('server') {
clean = minecraftFiles.serverExtracted
apply { store = true }
}
// endregion
forgedev.installer {
dev = !forgedev.ci
offline = false
baseVersion = buildLibs.installer.get().version.toString()
// Add extra libraries
pack serverShim.jar // The Server executable jar IS needed to be packed, as the installer would need a spec bump in order to download it
pack universalJar // I don't think this is strictly needed to be packed anymore, will need to double check later
launcherLibrary universalJar
// We configure the launcher here to keep the order for easier diff, after publishing, move this to the LauncherJson configuration below
launcherJson {
// We create this during install, so it's not downloadable
generated(clientBinPatches.apply, 'client')
}
launcherLibraries configurations.installer
final launcherId = "${minecraftVersion}-${project.name}-${forgeVersion}"
final installertools = tool(buildLibs.installertools)
final binarypatcher = tool(buildLibs.binarypatcher)
jar {
from(EXTRA_TXTS)
from(rootProject.file('/forge_installer_logo.png')) {
rename { 'big_logo.png' }
}
from(clientBinPatches.create.flatMap { t -> t.output }) {
rename { 'data/client.lzma' }
}
from(serverBinPatches.create.flatMap { t -> t.output }) {
rename { 'data/server.lzma' }
}
final argsFile = rootProject.file('server_files/args.txt')
final tokens = [tokens: [
SHIM_JAR_FILE: serverShim.jar.get().archiveFileName.get(),
MAVEN_PATH: forgedev.mavenArtifact.directory
]]
from(argsFile) {
filter(tokens, ReplaceTokens)
into 'data'
rename { 'unix_args.txt' }
}
from(argsFile) {
filter(tokens, ReplaceTokens)
into 'data'
rename { 'win_args.txt' }
}
from(rootProject.file('server_files')) {
filter(tokens, ReplaceTokens)
into 'data'
exclude 'args.txt'
}
jarSigner.sign(it)
}
json {
icon = rootProject.file('icon.ico')
executable serverShim.jar
minecraft = minecraftVersion
profileVersion = launcherId
final projectArtifact = "${project.group}:${project.name}:${project.version}"
data 'MC_UNPACKED', "[net.minecraft:client:${minecraftVersion}]", "[net.minecraft:server:${minecraftVersion}:unpacked]"
data 'MC_UNPACKED_SHA', minecraftFiles.client, minecraftFiles.serverExtracted
data 'BINPATCH', '/data/client.lzma', '/data/server.lzma'
data 'PATCHED', "[$projectArtifact:client]", "[$projectArtifact:server]"
data 'PATCHED_SHA', clientBinPatches.apply.flatMap { it.output }, serverBinPatches.apply.flatMap { it.output }
steps = [
extract(installertools) {
side = 'server'
archive '{INSTALLER}'
extract 'data/README.txt', '{ROOT}/README.txt'
extract 'data/run.sh', '{ROOT}/run.sh'
executable '{ROOT}/run.sh'
extract 'data/run.bat', '{ROOT}/run.bat'
optional 'data/user_jvm_args.txt', '{ROOT}/user_jvm_args.txt'
extract 'data/unix_args.txt', "{ROOT}/libraries/${forgedev.mavenArtifact.directory}/unix_args.txt"
extract 'data/win_args.txt', "{ROOT}/libraries/${forgedev.mavenArtifact.directory}/win_args.txt"
},
extractBundle(installertools) {
side = 'server'
libraries()
},
extractBundle(installertools) {
side = 'server'
jar '{MINECRAFT_JAR}', '{MC_UNPACKED}'
cache '{MC_UNPACKED}', '{MC_UNPACKED_SHA}'
},
step(binarypatcher) {
side = 'server'
args = [
'--clean', '{MC_UNPACKED}',
'--output', '{PATCHED}',
'--apply', '{BINPATCH}',
'--data', '--unpatched',
'--store'
]
cache '{PATCHED}', '{PATCHED_SHA}'
},
step(binarypatcher) {
side = 'client'
args = [
'--clean', '{MINECRAFT_JAR}',
'--output', '{PATCHED}',
'--apply', '{BINPATCH}',
'--data', '--unpatched',
'--store',
'--marker', '.forge_patched_minecraft'
]
cache '{PATCHED}', '{PATCHED_SHA}'
}
]
}
launcherJson {
// These are flags to preserve glitchy old behavior to make diffing easier, removing when migrating to production is recommended
sortLibraries = false // Don't sort, makes things non-deterministic, but easier to diff old versions
duplicateLibraries = true // Our 'bootstrap' layer go added twice
librariesLast = false // Makes libraries be added before args
id = launcherId
inheritsFrom = minecraftVersion
mainClass = 'net.minecraftforge.bootstrap.ForgeBootstrap'
gameArgs = [ '--launchTarget', 'forge_client' ]
jvmArgs = [ '-Djava.net.preferIPv6Addresses=system', '-XX:+UseCompactObjectHeaders', '-XX:StackShadowPages=32' ]
}
}
final mdkGradleWrapper = tasks.register('mdkGradleWrapper', Wrapper) {
gradleVersion = '9.7.1'
scriptFile = layout.buildDirectory.dir("wrapper/" + gradleVersion + "/gradlew")
jarFile = layout.buildDirectory.dir("wrapper/" + gradleVersion + "/gradle/wrapper/gradle-wrapper.jar")
}
final mdkZip = tasks.register('mdkZip', Zip) {
dependsOn mdkGradleWrapper
archiveBaseName = project.name
archiveClassifier = 'mdk'
archiveVersion = project.version
destinationDirectory = file('build/libs')
from(EXTRA_TXTS)
from mdkGradleWrapper.map(Wrapper.&getScriptFile)
from mdkGradleWrapper.map(Wrapper.&getBatchScript)
into('gradle/wrapper/') {
from mdkGradleWrapper.map(Wrapper.&getJarFile)
from mdkGradleWrapper.map(Wrapper.&getPropertiesFile)
}
from(rootProject.file('mdk/')){
rootProject.file('mdk/gitignore.txt').eachLine {
if (!it.trim().isEmpty() && !it.trim().startsWith('#'))
exclude it
}
filter(ReplaceTokens, tokens: [
FORGE_VERSION: forgeVersion,
FORGE_GROUP: project.group,
FORGE_NAME: project.name,
MC_VERSION: minecraftVersion,
FORGE_SPEC_VERSION: gitversion.info.tag.split("\\.")[0],
MC_NEXT_VERSION: minecraftNextVersion,
EVENTBUS_VERSION: libs.versions.eventbus.get()
])
rename 'gitignore\\.txt', '.gitignore'
rename 'gitattributes\\.txt', '.gitattributes'
}
from(rootProject.file('src/test/java/com/example/examplemod/')) {
into('src/main/java/com/example/examplemod/')
}
from(rootProject.file('src/test/generated/mdk_datagen/')) {
into('src/main/resources/')
exclude '**/.cache/'
}
}
/*
tasks.named('javadoc', Javadoc) {
description = 'Generates the combined javadocs for the FML projects and the main Forge project'
var includedProjects = [ ':fmlcore', ':fmlloader', ':javafmllanguage', ':mclanguage', ':forge-transformers' ]
source includedProjects.collect { project(it).sourceSets.main.allJava }
classpath = classpath + files(includedProjects.collect { project(it).sourceSets.main.compileClasspath })
var docsDir = rootProject.file('src/docs/')
inputs.dir(docsDir)
.withPropertyName('docs resources directory')
.withPathSensitivity(PathSensitivity.RELATIVE)
.optional()
failOnError = false
// Exclude the Minecraft classes if not enabled
if (!project.hasProperty('generateAllDocumentation')) {
exclude 'net/minecraft/**'
exclude 'com/mojang/**'
}
exclude 'mcp/**'
options {
stylesheetFile = new File(docsDir, 'stylesheet.css')
tags = [
'apiNote:a:<em>API Note:</em>',
'implSpec:a:<em>Implementation Requirements:</em>',
'implNote:a:<em>Implementation Note:</em>'
]
groups = [
'Forge Mod Loader': [
'net.minecraftforge.fml.common.asm*',
'net.minecraftforge.fml.loading*',
'net.minecraftforge.fml.server*'
],
'FML Core': [
'net.minecraftforge.fml',
'net.minecraftforge.fml.config*',
'net.minecraftforge.fml.event*',
'net.minecraftforge.fml.util*'
],
'FML Common': [
'net.minecraftforge.fml.core',
'net.minecraftforge.fml.event.config',
'net.minecraftforge.fml.event.lifecycle'
],
'FML Java/MC Language Providers': [
'net.minecraftforge.fml.common',
'net.minecraftforge.fml.javafmlmod',
'net.minecraftforge.fml.mclanguageprovider'
],
'Minecraft Forge API': [
'net.minecraftforge*'
]
]
author = false
noSince = true
noHelp = true
bottom = "Minecraft Forge is an open source modding API for Minecraft: Java Edition, licensed under the Lesser GNU General Public License, version 2.1."
windowTitle = "Minecraft Forge API ${version}"
docTitle = "Minecraft Forge API - ${forgeVersion} for Minecraft ${minecraftVersion}"
header = "<div style=\"margin-top: 9px;padding: 5px 6px;\"><strong>${forgeVersion} for Minecraft ${minecraftVersion}</strong></div>"
}
doLast {
project.copy {
from docsDir
exclude '/stylesheet.css'
into destinationDir
}
}
}
*/
publishing {
repositories {
maven gradleutils.publishingForgeMaven
}
publications.register('mavenJava', MavenPublication) {
changelog.publish(it)
gradleutils.promote(it)
artifact universalJar
artifact forgedev.installer.jar
artifact mdkZip
artifact forgedev.userDev.jar
artifact sourcesJar
artifact serverShim.jar
pom {
description = project.description
gradleutils.pom.addRemoteDetails(pom)
licenses {
license gradleutils.pom.licenses.LGPLv2_1
}
}
}
}
/*
// These are debugging tasks that are only used when developing ForgeDevPlugin, to bulk test task's config/caching
// region ForgeDev Tests
def all = tasks.register('all') {}
afterEvaluate {
if (gradle.startParameter.taskNames.contains('all')) {
tasks.forEach { task -> // This forces everything to be realized, which is why we have the startParameter check
if (!gradle.startParameter.isTaskGraph() && task.name.startsWith('run')) return
if (task.name in [all.name, 'dependencyInsight', 'dependencies', 'dependencyUpdates', 'init', 'buildEnvironment', 'idea',
'eclipse', 'javaToolchains', 'outgoingVariants', 'projects', 'properties', 'resolvableConfigurations', 'tasks', 'updateDaemonJvm',
'generateActionsWorkflow'
]) return
if (task.name.startsWith('clean')) return
if (task.name == 'checkJarCompatibility') return // This doesn't work in FG6 dev, I think it's cuz we check vs srg names. Will need to look at later
all.configure { dependsOn(task) }
}
}
}
*/
// endregion
// We dont have any unit tests, so disable this error
tasks.withType(AbstractTestTask).configureEach {
failOnNoDiscoveredTests = false
}
// This shouldn't be needed according to the Gradle devs, as we never run these commands together.
// But in another case of Gradle being gradle, here we are
[ 'makePatches', 'compileJava', 'checkLicenseMain', 'updateLicenseMain', 'sourcesJar', 'sourcesJarAll' ]
.each{ tasks.named(it).configure{ mustRunAfter forgedev.patches.apply }}
forgedev.runs.configureEach {
run.configure { dependsOn finalizeSpawn}
runTest.configure { dependsOn finalizeSpawn}
}
// run gradlew dependencyUpdates --no-parallel
//noinspection UnnecessaryQualifiedReference
tasks.named('dependencyUpdates', com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask) {
resolutionStrategy {
mcpBase.dependencies.get().forEach {dep -> force(dep) } // Force all vanilla dependencies to their correct versions
force(earlyDisplayLibs.slf4j.jdk14) // Force slf4j implementation, its tied to the api from the manifest
}
}
-3
View File
@@ -1,3 +0,0 @@
/.gradle/
/build/
/out/
-14
View File
@@ -1,14 +0,0 @@
repositories {
maven { url = 'https://maven.minecraftforge.net/' }
mavenCentral()
}
dependencies {
implementation 'org.ow2.asm:asm:9.9.1'
implementation 'org.ow2.asm:asm-tree:9.9.1'
implementation 'net.minecraftforge:srgutils:0.6.2'
implementation 'net.minecraftforge:JarJarMetadata:0.3.27'
implementation 'commons-io:commons-io:2.13.0'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'org.eclipse.jgit:org.eclipse.jgit:6.7.0.202309050840-r'
}
@@ -1,67 +0,0 @@
package net.minecraftforge.forge.tasks
import org.gradle.api.tasks.*
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.bundling.AbstractArchiveTask
import java.util.zip.ZipFile
abstract class BundleList extends DefaultTask {
@InputFiles abstract ConfigurableFileCollection getConfig()
@InputFile abstract RegularFileProperty getServerBundle()
@OutputFile abstract RegularFileProperty getOutput()
BundleList() {
config.setFrom(project.configurations.installer)
output.convention(project.layout.buildDirectory.file("$name/output.list"))
configure {
dependsOn(project.tasks.universalJar)
inputs.file(project.tasks.universalJar.archiveFile)
dependsOn(project.tasks.applyServerBinPatches)
inputs.file(project.tasks.applyServerBinPatches.output)
}
}
@TaskAction
void run() {
def entries = [:] as TreeMap
def resolved = project.configurations.installer.resolvedConfiguration.resolvedArtifacts
for (def dep : resolved) {
def info = Util.getMavenInfoFromDep(dep)
//println("$dep.file.sha1\t$info.name\t$info.path")
entries.put("$info.art.group:$info.art.name", "$dep.file.sha256\t$info.name\t$info.path")
}
var packed = (AbstractArchiveTask) project.tasks.universalJar
var info = Util.getMavenInfoFromTask(packed)
def file = packed.archiveFile.get().asFile
entries.put("$info.art.group:$info.art.name:$info.art.classifier", "$file.sha256\t$info.name\t$info.path")
var classifier = 'server'
var genned = project.tasks.applyServerBinPatches
info = Util.getMavenInfoFromTask(genned, classifier)
file = genned.output.get().asFile
entries.put("$info.art.group:$info.art.name:$info.art.classifier", "$file.sha256\t$info.name\t$info.path")
try (def zip = new ZipFile(serverBundle.get().asFile)) {
def entry = zip.getEntry('META-INF/libraries.list')
def data = zip.getInputStream(entry).text.split('\n')
for (def line : data) {
def (sha, artifact, path) = line.split('\t')
def (group, name, other) = artifact.split(':', 3)
//println("Group: $group Name: $name")
def key = "$group:$name"
if (other.indexOf(':') != -1){
key += ':' + other.split(':', 2)[1]
//println("\tKey: $key")
}
if (!entries.containsKey(key))
entries.put(key, line)
}
}
output.get().asFile.text = entries.values().join('\n')
}
}
@@ -1,52 +0,0 @@
package net.minecraftforge.forge.tasks
import groovy.json.JsonBuilder
import groovy.transform.CompileStatic
import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.TaskAction
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.FieldNode
import org.objectweb.asm.tree.MethodNode
@CompileStatic
abstract class BytecodeFinder extends DefaultTask {
@InputFile abstract RegularFileProperty getJar()
// It should be fine to mark the output as internal as we want to control when we run it anyways.
// This also shuts Gradle 8 up about implicit task dependencies.
@Internal abstract RegularFileProperty getOutput()
BytecodeFinder() {
output.convention(project.layout.buildDirectory.dir(name).map { it.file("output.json") })
}
@TaskAction
protected void exec() {
Util.init()
var outputFile = output.get().asFile
if (outputFile.exists())
outputFile.delete()
pre()
Util.processClassNodes(jar.get().asFile, this.&process)
post()
outputFile.text = new JsonBuilder(getData()).toPrettyString()
}
protected process(ClassNode node) {
if (node.fields !== null) node.fields.each { process(node, it) }
if (node.methods !== null) node.methods.each { process(node, it) }
}
protected pre() {}
protected process(ClassNode parent, FieldNode node) {}
protected process(ClassNode parent, MethodNode node) {}
protected post() {}
protected abstract Object getData()
}
@@ -1,48 +0,0 @@
package net.minecraftforge.forge.tasks
import groovy.transform.CompileStatic
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Internal
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.MethodNode
@CompileStatic
abstract class BytecodePredicateFinder extends BytecodeFinder {
@Internal
abstract Property<Closure<Boolean>> getPredicate()
private final Map<String, List<String>> matches = new TreeMap<>(Comparator.naturalOrder())
@Override
protected process(ClassNode parent, MethodNode node) {
for (final current : node.instructions) {
if (predicate.get().call(parent, node, current)) {
var methods = matches.compute(parent.name, { k, v -> v ?: new ArrayList<>() })
// only add non-synthetic methods
if ((node.access & Opcodes.ACC_SYNTHETIC) == 0) {
methods.add(node.name + node.desc)
}
return
}
}
}
@Internal
@Override
protected Object getData() {
var array = new ArrayList<Object>()
matches.forEach { c, m ->
{
array.add(
'class': c,
'methods': m
)
}
}
return array ?: { throw new RuntimeException('Failed to find any targets, please ensure that method names and descriptors are correct.') }()
}
}
@@ -1,97 +0,0 @@
package net.minecraftforge.forge.tasks;
import groovy.transform.CompileDynamic;
import groovy.transform.CompileStatic;
import java.util.Properties;
import java.util.Enumeration;
import java.util.Map;
import java.util.TreeSet;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.Reader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Set;
/**
* Eclipse config files are literally just java properties, with the header cleaned up.
* https://github.com/eclipse/buildship/blob/5b2c7fca7fa86cd74d71b3c099c7b1559eba038e/org.eclipse.buildship.core/src/main/java/org/eclipse/buildship/core/internal/configuration/PreferenceStore.java#L238
*
* This does the same thing, as well as sorting alphabetically.
* It also ignores all comments. We can add them latter if someone cares.
*/
@CompileStatic
public class CleanProperties extends Properties {
private static final long serialVersionUID = 1L;
private static final String LINE_SEP = System.getProperty("line.separator");
private static final String UNIX_LINE_SEP = "\n";
private static final Charset ENCODING = StandardCharsets.UTF_8;
public CleanProperties load(File input) throws IOException {
if (input.exists()) {
try (Reader is = new InputStreamReader(new FileInputStream(input), ENCODING)) {
super.load(is);
}
}
return this;
}
public void store(File out) throws IOException {
if (!out.getParentFile().exists())
out.getParentFile().mkdirs();
try (OutputStream os = new FileOutputStream(out)) {
store(os, null);
}
}
@Override
public synchronized Enumeration<Object> keys() {
Set<Object> ret = new TreeSet<>();
for (Enumeration<?> e = super.keys(); e.hasMoreElements();)
ret.add(e.nextElement());
return Collections.enumeration(ret);
}
@CompileDynamic
@Override
public Set<Map.Entry<Object, Object>> entrySet() {
Set<Map.Entry<Object, Object>> ret = new TreeSet<>((l, r) -> ((String)l.getKey()).compareTo((String)r.getKey()));
ret.addAll(super.entrySet());
return ret;
}
@Override
public void store(OutputStream out, String comments) throws IOException {
out.write(clean().getBytes(ENCODING));
out.flush();
}
@Override
public void store(Writer out, String comments) throws IOException {
out.write(clean());
out.flush();
}
private String clean() throws IOException {
ByteArrayOutputStream tmp = new ByteArrayOutputStream();
try {
super.store(tmp, null);
} finally {
tmp.close();
}
String ret = tmp.toString(ENCODING).replace(LINE_SEP, UNIX_LINE_SEP);
ret = ret.substring(ret.indexOf(UNIX_LINE_SEP) + 1);
return ret;
}
}
@@ -1,33 +0,0 @@
package net.minecraftforge.forge.tasks
import groovy.transform.stc.FirstParam
import java.util.function.BiConsumer
public class ClosureHelper {
BiConsumer<String, Closure> callback
public ClosureHelper(Closure cl, BiConsumer<String, Closure> callback) {
this.callback = callback
apply(this, cl)
}
def methodMissing(String name, Object args) {
if (!args.class.isArray()) return
Object[] aargs = (Object[])args
if (aargs.length == 1 && aargs[0] instanceof Closure) {
this.callback.accept(name, (Closure)aargs[0])
} else {
throw new IllegalArgumentException('Unknown method: "' + name + '" with arguments ' + args + ' for ' + this)
}
}
static <T> T apply(T obj, @DelegatesTo(value = FirstParam, strategy = Closure.DELEGATE_FIRST) Closure cl) {
cl.delegate = obj
cl.resolveStrategy = Closure.DELEGATE_FIRST
cl()
return obj
}
}
@@ -1,54 +0,0 @@
package net.minecraftforge.forge.tasks
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.OutputFiles
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.OutputDirectory
import java.nio.file.Files
abstract class DownloadLibraries extends DefaultTask {
@InputFile abstract RegularFileProperty getInput()
@OutputDirectory abstract DirectoryProperty getOutput()
@OutputFile abstract RegularFileProperty getLibrariesOutput()
DownloadLibraries() {
output.convention(project.layout.buildDirectory.dir(name))
librariesOutput.convention(project.layout.buildDirectory.dir(name).map(d -> d.file('libraries.txt')))
}
@TaskAction
def run() {
Util.init()
File outputDir = output.get().asFile
var libraries = new ArrayList<String>()
def json = input.get().asFile.json().libraries.each { lib ->
//TODO: Thread?
def artifacts = [lib.downloads.artifact] + lib.downloads.get('classifiers', [:]).values()
artifacts.each{ art ->
def target = new File(outputDir, art.path)
libraries.add(target.absolutePath)
if (!target.exists() || art.sha1 != target.sha1()) {
project.logger.lifecycle("Downloading ${art.url}")
if (!target.parentFile.exists()) {
target.parentFile.mkdirs()
}
new URL(art.url).withInputStream { i ->
target.withOutputStream { it << i }
}
if (art.sha1 != target.sha1()) {
throw new IllegalStateException("Failed to download ${art.url} to ${target.canonicalPath} SHA Mismatch")
}
}
}
}
Files.write(librariesOutput.get().asFile.toPath(), libraries)
}
}
@@ -1,28 +0,0 @@
package net.minecraftforge.forge.tasks
import groovy.transform.CompileStatic
import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
import java.util.zip.ZipFile
@CompileStatic
abstract class ExtractFile extends DefaultTask {
@InputFile abstract RegularFileProperty getInput()
@Input abstract Property<String> getTarget()
@OutputFile abstract RegularFileProperty getOutput()
@TaskAction
protected void exec() {
var zip = new ZipFile(input.get().asFile);
var entry = zip.getEntry(target.get())
if (entry == null)
throw new IllegalStateException(input.get().asFile.absolutePath + " does not contain " + target.get())
var stream = zip.getInputStream(entry)
output.get().asFile.withOutputStream { out ->
stream.transferTo(out)
}
}
}
@@ -1,101 +0,0 @@
package net.minecraftforge.forge.tasks
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import org.gradle.api.tasks.*
import org.objectweb.asm.tree.AbstractInsnNode
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.MethodNode
import static org.objectweb.asm.Opcodes.*
abstract class FieldCompareFinder extends BytecodeFinder {
@Nested
Map<String, Search> fields = [:] as HashMap
@Internal
Map<Search, String> fieldsReverse = [:] as HashMap
@Internal
Map<String, Set<ObjectTarget>> targets = [:] as TreeMap
@Override
protected pre() {
//fields.each{ k,v -> logger.lifecycle("Fields: " + k + ' ' + v) }
}
@Override
protected process(ClassNode parent, MethodNode node) {
AbstractInsnNode last = null
def parentInstance = new ObjectTarget(owner: parent.name, name: '', desc: '')
for (int x = 0; x < node.instructions.size(); x++) {
def current = node.instructions.get(x)
if (current.opcode === IF_ACMPEQ || current.opcode === IF_ACMPNE) {
if (last !== null && (last.opcode === GETSTATIC || last.opcode === GETFIELD)) {
def target = new Search(cls: last.owner, name: last.name)
def wanted = fieldsReverse.get(target)
def original = fields.get(wanted)
def instance = new ObjectTarget(owner: parent.name, name: node.name, desc: node.desc)
if (wanted !== null && (original.blacklist === null || (!original.blacklist.contains(instance) && !original.blacklist.contains(parentInstance)))) {
targets.computeIfAbsent(wanted, { k -> new TreeSet() }).add(instance)
}
}
}
last = current
}
}
@Internal
@Override
protected Object getData() {
def ret = [:] as HashMap
targets.forEach{ k, v ->
def e = fields.get(k)
ret[k] = [
cls: e.cls,
name: e.name,
replacement: e.replacement,
targets: v
]
}
return ret
}
@CompileStatic
@EqualsAndHashCode(excludes = ['replacement', 'blacklist'])
static class Search {
@Input
String cls
@Input
String name
@Input
String replacement
@Nested
@Optional
Set<ObjectTarget> blacklist
@Override
String toString() {
return cls + '.' + name
}
def blacklist(String owner, String name, String desc) {
if (blacklist === null)
blacklist = new HashSet<>()
blacklist.add(new ObjectTarget(owner: owner, name: name, desc: desc))
}
def blacklist(String owner) {
blacklist(owner, '', '')
}
}
void fields(Closure cl) {
new ClosureHelper(cl, {name, ccl ->
def search = ClosureHelper.apply(new Search(), ccl)
this.fields.put(name, search)
this.fieldsReverse.put(search, name)
})
}
}
@@ -1,49 +0,0 @@
package net.minecraftforge.forge.tasks
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import groovy.transform.CompileStatic
import groovy.transform.TupleConstructor
@CompileStatic
@TupleConstructor
final class InheritanceData implements Annotatable {
String name
int access
String superName
List<String> interfaces = []
Map<String, Method> methods = [:]
Map<String, Field> fields = [:]
List<Annotation> annotations = []
@TupleConstructor
static final class Method implements Annotatable {
int access
String override
List<Annotation> annotations = []
}
@TupleConstructor
static final class Field implements Annotatable {
int access
String desc
List<Annotation> annotations = []
}
@TupleConstructor
static final class Annotation {
String desc
}
private static final Gson GSON = new Gson()
static Map<String, InheritanceData> parse(File file) {
try (final reader = file.newReader()) {
return GSON.fromJson(reader, new TypeToken<Map<String, InheritanceData>>() {})
}
}
}
@CompileStatic
interface Annotatable {
List<InheritanceData.Annotation> getAnnotations()
}
@@ -1,158 +0,0 @@
package net.minecraftforge.forge.tasks
import org.gradle.api.file.DuplicatesStrategy
import org.gradle.api.tasks.bundling.AbstractArchiveTask
import org.gradle.api.tasks.bundling.Zip
import org.gradle.api.tasks.*
import org.gradle.api.DefaultTask
import org.gradle.api.provider.Property
import groovy.json.JsonSlurper
abstract class InstallerJar extends Zip {
@Input @Optional abstract Property<Boolean> getFat()
@Input @Optional abstract Property<Boolean> getOffline()
InstallerJar() {
archiveClassifier.set('installer')
archiveExtension.set('jar') // Needs to be Zip task to not override Manifest, so set extension
destinationDirectory.set(project.layout.buildDirectory.dir('libs'))
fat.convention(false)
offline.convention(false)
def installerJson = project.tasks.installerJson
def launcherJson = project.tasks.launcherJson
def downloadInstaller = project.tasks.downloadInstaller
dependsOn(installerJson, launcherJson, downloadInstaller, project.configurations.installer)
from(installerJson, launcherJson)
from(project.rootProject.file('/src/main/resources/url.png'))
project.afterEvaluate {
from(project.zipTree(downloadInstaller.output)) {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
if (fat.get() || offline.get()) {
def cfg = project.tasks.register(name + "Config", Configure)
cfg.get().configure {
parent = this
dependsOn(project.tasks.installerJson, project.tasks.launcherJson)
}
dependsOn(cfg)
} else {
// Things we ALWAYS bundle, this just the server shim jar, because the installer spec only says to extract the file.
// I should make it allow downloads but thats a spec break, and this is just a ~14KB jar
[
project.tasks.serverShimJar // Server bootstrap executable jar
].forEach { AbstractArchiveTask packed ->
def path = Util.getMavenInfoFromTask(packed).path
from(packed) {
rename { "maven/$path" }
}
}
}
}
}
static abstract class Configure extends DefaultTask {
public Zip parent
private int count = 0;
@TaskAction
protected void exec() {
def deps = [:] as java.util.TreeMap
// Gather all things that need downloading
[
project.tasks.installerJson,
project.tasks.launcherJson
].each { task ->
def json = task.output.get().asFile.json
json.libraries.each { lib ->
if (lib.downloads?.artifact?.url !== null && !lib.downloads.artifact.url.isEmpty())
deps.put(lib.name, lib.downloads.artifact)
}
}
// First find things we build in this project.
[
project.tasks.universalJar, // Forge runtime code
project.tasks.serverShimJar // Shim jar for dedicated server
].forEach { AbstractArchiveTask packed ->
def name = Util.getMavenInfoFromTask(packed).name
def info = deps.remove(name)
if (info !== null) {
println("Adding: $packed.path $name")
parent.from(packed) {
rename { "maven/$info.path" }
}
}
}
// Find any artifacts from the 'installer' config
// This config specifies the runtime files we intend for the interaller to have.
// And are typically what we would be developing and testing alongside Forge.
// So we may have local modified versions
def cfg = project.configurations.installer
while (cfg != null) {
//println('')
def resolved = cfg.resolvedConfiguration.resolvedArtifacts
int found = 0
for (def dep : resolved) {
def name = Util.getMavenInfoFromDep(dep).name
def info = deps.remove(name)
if (info == null) {
//println("Skipping: $name")
continue
}
//println("-$name")
found++
addFile(dep.file, info)
}
if (deps.isEmpty()) {
cfg = null
continue
}
// Prevent infinite loops if something fucky happens
if (found == 0)
throw new IllegalStateException("Failed to find any installer dependencies")
def seen = [] as Set
cfg = project.configurations.detachedConfiguration()
cfg.transitive = false
for (def key : deps.keySet()) {
def (group, artifact, other) = key.split(':', 3)
// Only resolve unique group:artifact so we don't get version overrides
if (seen.add(group + ':' + artifact)) {
//println("+$key")
cfg.dependencies.add(project.dependencies.create(key))
}
}
}
}
void addFile(file, info) {
boolean pack = parent.offline.get() || info.url.isEmpty()
// If it's a offline jar just always pack
if (!pack) {
try {
var remote = new URL("${info.url}.sha1").getText('UTF-8')
pack = info.sha1 != remote
} catch (FileNotFoundException e) {
pack = !info.url.startsWith('https://libraries.minecraft.net/')
// Oh noes its not there!
}
}
if (pack) {
println("Adding: $file.absolutePath")
parent.from(file) {
rename { "maven/$info.path" }
}
}
}
}
}
@@ -1,73 +0,0 @@
package net.minecraftforge.forge.tasks
import groovy.json.JsonBuilder
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.SetProperty
import org.gradle.api.tasks.*
import org.gradle.api.tasks.bundling.AbstractArchiveTask
import java.nio.file.Files
abstract class InstallerJson extends DefaultTask {
@OutputFile abstract RegularFileProperty getOutput()
@InputFiles abstract ConfigurableFileCollection getInput()
@Input @Optional final Map<String, Object> libraries = new LinkedHashMap<>()
@Input Map<String, Object> json = new LinkedHashMap<>()
@InputFile abstract RegularFileProperty getIcon()
@Input abstract Property<String> getLauncherJsonName()
@Input abstract Property<String> getLogo()
@Input abstract Property<String> getMirrors()
@Input abstract Property<String> getWelcome()
InstallerJson() {
launcherJsonName.convention('/version.json')
logo.convention('/big_logo.png')
mirrors.convention('https://files.minecraftforge.net/mirrors-2.0.json')
welcome.convention("Welcome to the ${project.name.capitalize()} installer.")
output.convention(project.layout.buildDirectory.file('libs/install_profile.json'))
project.afterEvaluate {
[
project.tasks.universalJar,
project.tasks.serverShimJar
].forEach { packed ->
dependsOn(packed)
input.from packed.archiveFile
}
}
}
@TaskAction
protected void exec() {
def libs = libraries
[
project.tasks.universalJar,
project.tasks.serverShimJar
].forEach { AbstractArchiveTask packed ->
def info = Util.getMavenInfoFromTask(packed)
libs.put(info.name, [
name: info.name,
downloads: [
artifact: [
path: info.path,
url: "https://maven.minecraftforge.net/$info.path",
sha1: packed.archiveFile.get().asFile.sha1(),
size: packed.archiveFile.get().asFile.length()
]
]
])
}
json.libraries = libs.values().sort{a,b -> a.name.compareTo(b.name)}
json.icon = "data:image/png;base64," + new String(Base64.getEncoder().encode(Files.readAllBytes(icon.get().asFile.toPath())))
json.json = launcherJsonName.get()
json.logo = logo.get()
if (!mirrors.get().isEmpty())
json.mirrorList = mirrors.get()
json.welcome = welcome.get()
Files.writeString(output.get().getAsFile().toPath(), new JsonBuilder(json).toPrettyString())
}
}
@@ -1,415 +0,0 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.forge.tasks;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import net.minecraftforge.jarjar.metadata.ContainedJarIdentifier;
import net.minecraftforge.jarjar.metadata.ContainedJarMetadata;
import net.minecraftforge.jarjar.metadata.ContainedVersion;
import net.minecraftforge.jarjar.metadata.Metadata;
import net.minecraftforge.jarjar.metadata.MetadataIOHandler;
import net.minecraftforge.jarjar.metadata.json.ArtifactVersionSerializer;
import net.minecraftforge.jarjar.metadata.json.ContainedJarIdentifierSerializer;
import net.minecraftforge.jarjar.metadata.json.ContainedJarMetadataSerializer;
import net.minecraftforge.jarjar.metadata.json.ContainedVersionSerializer;
import net.minecraftforge.jarjar.metadata.json.MetadataSerializer;
import net.minecraftforge.jarjar.metadata.json.VersionRangeSerializer;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.artifact.versioning.VersionRange;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.gradle.api.Action;
import org.gradle.api.DefaultTask;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.ExternalModuleDependency;
import org.gradle.api.artifacts.FileCollectionDependency;
import org.gradle.api.artifacts.MinimalExternalModuleDependency;
import org.gradle.api.artifacts.ModuleDependency;
import org.gradle.api.artifacts.ModuleIdentifier;
import org.gradle.api.artifacts.ModuleVersionIdentifier;
import org.gradle.api.artifacts.ResolvedArtifact;
import org.gradle.api.file.ProjectLayout;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Provider;
import org.gradle.api.provider.SetProperty;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;
import javax.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.io.Serial;
import java.io.Serializable;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.zip.ZipFile;
// TODO SUPER SUPER SUPER UGLY, CLEAN UP IN FORGEDEV 7
@Deprecated(forRemoval = true) // Will be moved to JarJar plugin in ForgeDev 7
public abstract class JarJarMetadataOptions extends DefaultTask {
private static final Gson GSON = new GsonBuilder()
.registerTypeAdapter(VersionRange.class, new VersionRangeSerializer())
.registerTypeAdapter(ArtifactVersion.class, new ArtifactVersionSerializer())
.registerTypeAdapter(DefaultArtifactVersion.class, new ArtifactVersionSerializer())
.registerTypeAdapter(ContainedJarIdentifier.class, new ContainedJarIdentifierSerializer())
.registerTypeAdapter(ContainedJarMetadata.class, new ContainedJarMetadataSerializer())
.registerTypeAdapter(ContainedVersion.class, new ContainedVersionSerializer())
.registerTypeAdapter(Metadata.class, new MetadataSerializer())
.setPrettyPrinting()
.create();
protected abstract @Input SetProperty<ResolvedDependencyInfoImpl> getResolvedDependencies();
protected abstract @OutputFile RegularFileProperty getMetadataFile();
protected abstract @Inject ProjectLayout getLayout();
// NOTE: I'm not adding a non-provider version. please just use the version catalog entries for now.
public void add(Provider<MinimalExternalModuleDependency> dependency, Action<? super ResolvedDependencyInfo> action) {
this.getResolvedDependencies().add(dependency.map(d -> {
var ret = ResolvedDependencyInfoImpl.from(this.getProject().getConfigurations(), d);
action.execute(ret);
return ret;
}));
}
@Inject
public JarJarMetadataOptions() {
this.getMetadataFile().convention(this.getLayout().getBuildDirectory().file(this.getName() + "/options.json"));
}
@TaskAction
protected void exec() {
record ForgeLocaterOptions(String resource, String layer, String id, List<ContainedJarMetadata> deps, ContainedJarMetadata meta, boolean nested) { }
var resolved = this.getResolvedDependencies().get();
var jars = new ArrayList<ForgeLocaterOptions>(resolved.size());
for (var dependency : resolved) {
var deps = new ArrayList<ContainedJarMetadata>();
try (var zip = new ZipFile(dependency.artifact)) {
var entry = zip.getEntry("META-INF/jarjar/metadata.json");
if (entry != null) {
try (var stream = zip.getInputStream(entry)) {
var meta = MetadataIOHandler.fromStream(stream).orElse(null);
if (meta == null)
throw new IllegalStateException("Corrupt metadata.json in " + dependency.artifact.getAbsolutePath());
for (var dep : meta.jars())
deps.add(new ContainedJarMetadata(dep.identifier(), dep.version(), "", dep.isObfuscated()));
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
jars.add(new ForgeLocaterOptions(
dependency.resource,
dependency.layer,
dependency.identifier,
deps,
new ContainedJarMetadata(
new ContainedJarIdentifier(validateGroup(dependency), dependency.module.getName()),
new ContainedVersion(null, parseVersion(dependency)),
Objects.requireNonNull(dependency.path, "Dependency path is unspecified: " + dependency.asString),
false
),
dependency.nested
));
}
try {
record Meta(List<ForgeLocaterOptions> options){}
Files.writeString(
this.getMetadataFile().getAsFile().get().toPath(),
GSON.toJson(new Meta(jars), Meta.class)
);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private String validateGroup(ResolvedDependencyInfoImpl dependency) {
try {
return Objects.requireNonNull(dependency.module.getGroup());
} catch (NullPointerException e) {
throw new IllegalArgumentException("Module dependency has no group: " + dependency.asString, e);
}
}
private ArtifactVersion parseVersion(ResolvedDependencyInfoImpl resolved) {
try {
return VersionRange.createFromVersionSpec(Objects.requireNonNull(resolved.version)).getRecommendedVersion();
} catch (InvalidVersionSpecificationException e) {
throw new IllegalArgumentException("Version is invalid for: " + resolved.asString, e);
} catch (NullPointerException e) {
throw new IllegalArgumentException("Version is unspecified for: " + resolved.asString, e);
}
}
private VersionRange parseVersionRange(ResolvedDependencyInfoImpl dependency) {
if (dependency.hasManuallySpecifiedRange) {
try {
return VersionRange.createFromVersionSpec(dependency.versionRange);
} catch (InvalidVersionSpecificationException e) {
throw new IllegalArgumentException("Version is invalid for: " + dependency.asString, e);
}
} else {
try {
return VersionRange.createFromVersionSpec("[%s,)".formatted(Objects.requireNonNull(dependency.versionRange)));
} catch (InvalidVersionSpecificationException e) {
throw new IllegalArgumentException("Version range is invalid for: " + dependency.asString, e);
} catch (NullPointerException e) {
throw new IllegalArgumentException("Version is unspecified for: " + dependency.asString, e);
}
}
}
public interface ResolvedDependencyInfo {
void containedJarMetadata(Action<? super ContainedJarMetadataInfo> action);
void setResource(String resource);
void setLayer(String layer);
void setId(String identifier);
void setNested(boolean nested);
interface ContainedJarMetadataInfo {
void setGroup(String group);
void setName(String name);
void setPath(String version);
}
}
static final class ResolvedDependencyInfoImpl implements ResolvedDependencyInfo, ResolvedDependencyInfo.ContainedJarMetadataInfo, Serializable {
private static final @Serial long serialVersionUID = -7577318115877822993L;
final MinimalModuleVersionIdentifier module;
final String version;
String versionRange;
boolean hasManuallySpecifiedRange;
boolean nested;
final File artifact;
String path;
String resource;
String layer;
String identifier;
final String asString;
public ResolvedDependencyInfoImpl(MinimalModuleVersionIdentifier module, String version, File artifact, String asString) {
this.module = module;
this.version = version;
this.artifact = artifact;
this.asString = asString;
}
@Override
public void containedJarMetadata(Action<? super ContainedJarMetadataInfo> action) {
action.execute(this);
}
@Override
public void setGroup(String group) {
this.module.group = group;
}
@Override
public void setName(String name) {
this.module.name = name;
}
@Override
public void setPath(String path) {
this.path = path;
}
@Override
public void setResource(String resource) {
this.resource = resource;
}
@Override
public void setLayer(String layer) {
this.layer = layer;
}
@Override
public void setId(String identifier) {
this.identifier = identifier;
}
@Override
public void setNested(boolean nested) {
this.nested = nested;
}
static Set<File> getFiles(Set<ResolvedDependencyInfoImpl> resolvedDependencies) {
var ret = new HashSet<File>(resolvedDependencies.size());
for (var dependency : resolvedDependencies) {
ret.add(dependency.artifact);
}
return ret;
}
static ResolvedDependencyInfoImpl from(ConfigurationContainer configurations, Dependency dependency) {
var group = dependency.getGroup();
var name = dependency.getName();
var version = dependency.getVersion();
if (dependency instanceof FileCollectionDependency filesDependency) {
File artifact;
try {
artifact = filesDependency.getFiles().getSingleFile();
} catch (IllegalStateException e) {
// TODO fileCollectionDependencyIsNotSingleFile
throw e;
}
return new ResolvedDependencyInfoImpl(
new MinimalModuleVersionIdentifier(group, name, version),
version,
artifact,
filesDependency.toString()
);
} else if (dependency instanceof ModuleDependency moduleDependency) {
moduleDependency = moduleDependency.copy();
if (moduleDependency instanceof ExternalModuleDependency externalModuleDependency) {
externalModuleDependency.version(v -> v.strictly(version.toString()));
}
var detachedConfiguration = configurations.detachedConfiguration(moduleDependency);
detachedConfiguration.setTransitive(false);
ResolvedDependencyInfoImpl ret = null;
for (var artifact : detachedConfiguration.getResolvedConfiguration().getFirstLevelModuleDependencies().iterator().next().getModuleArtifacts()) {
var fileName = getFileName(artifact);
if (!fileName.endsWith(".jar"))
continue;
if (ret != null)
throw new IllegalArgumentException("Module dependency has too many Jar artifacts: " + moduleDependency);
ret = new ResolvedDependencyInfoImpl(
new MinimalModuleVersionIdentifier(group, name, artifact.getModuleVersion().getId().getVersion()),
version,
artifact.getFile(),
moduleDependency.toString()
);
}
if (ret == null)
throw new IllegalArgumentException("Module dependency has no Jar artifacts: " + moduleDependency);
return ret;
} else {
throw new IllegalArgumentException("Unsupported dependency type: " + dependency.getClass().getName() + " -- " + dependency);
}
}
private static String getFileName(ResolvedArtifact artifact) {
try {
return InvokerHelper.getProperty(artifact.getId(), "fileName").toString();
} catch (Throwable e) {
// NOTE: Why not just use this to begin with?
// ComponentArtifactIdentifier can have a getFileName() method, which doesn't necessarily resolve the file itself.
// This allows us to get the name of the file to be used without asking Gradle to download the file.
// So, if a file is not a JAR file, we can check the name without actually downloading it.
return artifact.getFile().getName();
}
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj == null || obj.getClass() != this.getClass()) return false;
var that = (ResolvedDependencyInfoImpl) obj;
return Objects.equals(this.module, that.module) &&
Objects.equals(this.version, that.version) &&
Objects.equals(this.versionRange, that.versionRange) &&
Objects.equals(this.artifact, that.artifact);
}
@Override
public int hashCode() {
return Objects.hash(module, version, versionRange, artifact);
}
@Override
public String toString() {
return "ResolvedDependencyInfo[" +
"module=" + module + ", " +
"fixedVersion=" + version + ", " +
"versionRange=" + versionRange + ", " +
"artifact=" + artifact + ']';
}
static final class MinimalModuleVersionIdentifier implements ModuleIdentifier, ModuleVersionIdentifier {
private static final @Serial long serialVersionUID = -955346236759069739L;
private String group;
private String name;
private final String version;
@Inject
public MinimalModuleVersionIdentifier(String group, String name, String version) {
this.group = group;
this.name = name;
this.version = version;
}
@Override
public ModuleIdentifier getModule() {
return this;
}
@Override
public String getGroup() {
return this.group;
}
@Override
public String getName() {
return this.name;
}
@Override
public String getVersion() {
return this.version;
}
@Override
public boolean equals(Object obj) {
return this == obj || obj instanceof MinimalModuleVersionIdentifier o
&& Objects.equals(this.group, o.group)
&& Objects.equals(this.name, o.name)
&& Objects.equals(this.version, o.version);
}
@Override
public int hashCode() {
return Objects.hash(group, name, version);
}
@Override
public String toString() {
return "MinimalModuleVersionIdentifier[" +
"group=" + group + ", " +
"name=" + name + ", " +
"version=" + version + ']';
}
}
}
}
@@ -1,94 +0,0 @@
package net.minecraftforge.forge.tasks
import groovy.json.JsonBuilder
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.SetProperty
import org.gradle.api.tasks.*
import org.gradle.api.tasks.bundling.AbstractArchiveTask
import java.nio.file.Files
import static net.minecraftforge.forge.tasks.Util.getArtifacts
import static net.minecraftforge.forge.tasks.Util.iso8601Now
abstract class LauncherJson extends DefaultTask {
@OutputFile abstract RegularFileProperty getOutput()
@InputFiles abstract ConfigurableFileCollection getInput()
@Input Map<String, Object> json = new LinkedHashMap<>()
LauncherJson() {
output.convention(project.layout.buildDirectory.file('libs/version.json'))
dependsOn(project.tasks.universalJar)
input.from(project.tasks.universalJar.archiveFile)
input.from(project.configurations.installer)
configure {
def mc = project.rootProject.ext.MC_VERSION
def forge = project.rootProject.ext.FORGE_VERSION
def timestamp = iso8601Now()
json.putAll([
_comment: [
"Please do not automate the download and installation of Forge.",
"Our efforts are supported by ads from the download page.",
"If you MUST automate this, please consider supporting the project through https://www.patreon.com/LexManos/"
],
id: "$mc-$project.name-$forge",
time: timestamp,
releaseTime: timestamp,
inheritsFrom: mc,
type: 'release',
logging: [:],
mainClass: '',
libraries: []
] as LinkedHashMap)
[
project.tasks.universalJar
].forEach { packed ->
dependsOn(packed)
input.from packed.archiveFile
}
def patched = project.tasks.applyClientBinPatches
dependsOn(patched)
input.from patched.output
}
}
@TaskAction
protected void exec() {
var packed = (AbstractArchiveTask) project.tasks.universalJar
def info = Util.getMavenInfoFromTask(packed)
json.libraries.add([
name: info.name,
downloads: [
artifact: [
path: info.path,
url: "https://maven.minecraftforge.net/$info.path",
sha1: packed.archiveFile.get().asFile.sha1(),
size: packed.archiveFile.get().asFile.length()
]
]
])
var classifier = 'client'
var genned = project.tasks.applyClientBinPatches
info = Util.getMavenInfoFromTask(genned, classifier)
json.libraries.add([
name: info.name,
downloads: [
artifact: [
path: info.path,
url: "",
sha1: genned.output.get().asFile.sha1(),
size: genned.output.get().asFile.length()
]
]
])
json.libraries.addAll(getArtifacts(project.configurations.installer).values())
Files.writeString(output.get().asFile.toPath(), new JsonBuilder(json).toPrettyString())
}
}
@@ -1,46 +0,0 @@
package net.minecraftforge.forge.tasks
import groovy.transform.CompileStatic
import org.apache.commons.io.IOUtils
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
@CompileStatic
abstract class MergeJars extends DefaultTask {
MergeJars() {
output.convention(project.layout.buildDirectory.dir(name).map { it.file('output.jar') })
}
@TaskAction
void run() {
def jars = inputJars.files
try (ZipOutputStream zout = new ZipOutputStream(new FileOutputStream(output.get().asFile))) {
for (def jar : jars) {
try (ZipInputStream zin = new ZipInputStream(new FileInputStream(jar))) {
ZipEntry entry
while ((entry = zin.getNextEntry()) !== null) {
ZipEntry _new = new ZipEntry(entry.getName())
_new.setTime(0) //SHOULD be the same time as the main entry, but NOOOO _new.setTime(entry.getTime()) throws DateTimeException, so you get 0, screw you!
zout.putNextEntry(_new)
IOUtils.copy(zin, zout)
}
}
}
}
}
@InputFiles
abstract ConfigurableFileCollection getInputJars()
@OutputFile
abstract RegularFileProperty getOutput()
}
@@ -1,32 +0,0 @@
package net.minecraftforge.forge.tasks
import groovy.transform.EqualsAndHashCode
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Nested
import org.gradle.api.tasks.Optional
@EqualsAndHashCode
public class ObjectTarget implements Comparable<ObjectTarget> {
@Input
String owner
@Input
String name
@Input
@Optional
String desc
@Override
String toString() {
if (desc == null)
return owner + '.' + name
return owner + '.' + name + desc
}
@Override
int compareTo(ObjectTarget o) {
return toString() <=> o.toString()
}
}
@@ -1,47 +0,0 @@
package net.minecraftforge.forge.tasks
import org.gradle.api.DefaultTask
import org.gradle.api.artifacts.Dependency
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import java.nio.file.Files
import java.nio.file.StandardCopyOption
abstract class SetupCheckJarCompatibility extends DefaultTask {
SetupCheckJarCompatibility() {
group = 'jar compatibility'
onlyIf {
inputVersion.getOrNull() !== null
}
outputs.upToDateWhen { false } // Never up to date, because this setup task should always run
baseBinPatchesOutput.convention(project.layout.buildDirectory.dir(name).map { it.file('joined.lzma') })
}
@TaskAction
void run() {
def inputVersion = inputVersion.get()
def baseForgeUserdev = project.layout.buildDirectory.dir(name).map { it.file("forge-${inputVersion}-userdev.jar") }.get().asFile
project.rootProject.extensions.download.run {
src "https://maven.minecraftforge.net/net/minecraftforge/forge/${inputVersion}/forge-${inputVersion}-userdev.jar"
dest baseForgeUserdev
}
def joinedLzma = project.zipTree(baseForgeUserdev).matching { it.include('joined.lzma') }.singleFile
Files.copy(joinedLzma.toPath(), baseBinPatchesOutput.get().asFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
}
@Input
@Optional
abstract Property<String> getInputVersion()
@OutputFile
abstract RegularFileProperty getBaseBinPatchesOutput()
}
@@ -1,74 +0,0 @@
package net.minecraftforge.forge.tasks
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import groovy.transform.CompileStatic
import org.eclipse.jgit.api.Git
import javax.annotation.Nullable
import java.util.stream.Collectors
@CompileStatic
class TeamcityRequests {
public static final Gson GSON = new Gson()
@Nullable
static <T> T jsonRequest(TypeToken<T> clazz, String url) throws Exception {
final HttpURLConnection conn = (HttpURLConnection) URI.create(url).toURL().openConnection()
conn.setRequestProperty('Accept', 'application/json')
conn.setReadTimeout(5 * 1000)
conn.setConnectTimeout(5 * 1000)
conn.connect()
if (conn.responseCode !== 200) {
return null
}
try (final InputStream is = conn.getInputStream()) {
GSON.fromJson(new InputStreamReader(is), clazz.getType())
}
}
@Nullable
static Map<String, Build> buildsByCommit() throws IOException {
final Map<String, Build> builds = [:]
jsonRequest(new TypeToken<Builds>() {}, 'https://teamcity.minecraftforge.net/guestAuth/app/rest/builds?fields=build:(revisions,number)&locator=buildType:(id:MinecraftForge_MinecraftForge_MinecraftForge_MinecraftForge__Build),defaultFilter:false,count:300,status:SUCCESS')
?.build?.forEach {
it.revisions.revision.each { rev -> builds[rev.version] = it }
}
return builds
}
@Nullable
static String attemptFindBase(File gitPath) {
try (final git = Git.open(gitPath)) {
final Map<String, Build> buildByCommit = buildsByCommit()
for (final commit in git.log().setMaxCount(100).call()) {
// Find the first commit which was built on CI
final build = buildByCommit[commit.id.name]
if (build) {
return build.number
}
}
} catch (Exception ignored) {}
return null
}
static final class Builds {
public List<Build> build
}
static final class Build {
public String number
public Revisions revisions
}
static final class Revisions {
public int count
public List<Revision> revision
}
static final class Revision {
public String version
}
}
@@ -1,230 +0,0 @@
package net.minecraftforge.forge.tasks
import groovy.json.JsonBuilder
import groovy.json.JsonSlurper
import groovy.transform.CompileStatic
import groovy.transform.stc.ClosureParams
import groovy.transform.stc.SimpleType
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ResolvedArtifact
import org.gradle.api.tasks.bundling.AbstractArchiveTask
import org.objectweb.asm.ClassReader
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.ClassNode
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.security.MessageDigest
import java.text.SimpleDateFormat
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Semaphore
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
final class Util {
public static final int ASM_LEVEL = Opcodes.ASM9
private static final HttpClient HTTP = HttpClient.newBuilder().build()
static void init() {
File.metaClass.sha1 = { ->
MessageDigest md = MessageDigest.getInstance('SHA-1')
delegate.eachByte(4096) { byte[] bytes, int size ->
md.update(bytes, 0, size)
}
return md.digest().collect(this.&toHex).join('')
}
File.metaClass.getSha1 = { !delegate.exists() ? null : delegate.sha1() }
File.metaClass.sha256 = { ->
MessageDigest md = MessageDigest.getInstance('SHA-256')
delegate.eachByte(4096) { byte[] bytes, int size ->
md.update(bytes, 0, size)
}
return md.digest().collect(this.&toHex).join('')
}
File.metaClass.getSha256 = { !delegate.exists() ? null : delegate.sha256() }
File.metaClass.json = { -> new JsonSlurper().parseText(delegate.text) }
File.metaClass.getJson = { return delegate.exists() ? new JsonSlurper().parse(delegate) : [:] }
File.metaClass.setJson = { json -> delegate.text = new JsonBuilder(json).toPrettyString() }
Date.metaClass.iso8601 = { ->
var format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
var result = format.format(delegate)
return result[0..21] + ':' + result[22..-1]
}
String.metaClass.rsplit = { String del, int limit = -1 ->
var lst = new ArrayList<String>()
int x = 0
int idx
String tmp = delegate
while ((idx = tmp.lastIndexOf(del)) != -1 && (limit === -1 || x++ < limit)) {
lst.add(0, tmp.substring(idx + del.length(), tmp.length()))
tmp = tmp.substring(0, idx)
}
lst.add(0, tmp)
return lst
}
}
static String[] getClasspath(Project project, Map libs, String artifact) {
def ret = []
artifactTree(project, artifact).each { key, lib ->
libs[lib.name] = lib
if (lib.name != artifact)
ret.add(lib.name)
}
return ret
}
@CompileStatic
static Map getArtifacts(Configuration config) {
var ret = [:]
var semaphore = new Semaphore(1, true)
config.resolvedConfiguration.resolvedArtifacts.parallelStream().forEachOrdered(dep -> {
var info = getMavenInfoFromDep(dep)
var domain = 'libraries.minecraft.net'
var url = "https://$domain/$info.path"
if (!checkExists(url))
url.values[0] = 'maven.minecraftforge.net'
var sha1 = sha1(dep.file)
semaphore.acquire()
ret[info.key] = [
name: info.name,
downloads: [
artifact: [
path: info.path,
url: url.toString(),
sha1: sha1,
size: dep.file.length()
]
]
]
semaphore.release()
})
return ret
}
@CompileStatic
static Map getMavenInfoFromDep(ResolvedArtifact dep) {
return getMavenInfoFromMap([
group: dep.moduleVersion.id.group,
name: dep.moduleVersion.id.name,
version: dep.moduleVersion.id.version,
classifier: dep.classifier,
extension: dep.extension
])
}
@CompileStatic
static Map getMavenInfoFromTask(AbstractArchiveTask task) {
return getMavenInfoFromMap([
group: task.project.group.toString(),
name: task.project.name,
version: task.project.version.toString(),
classifier: task.archiveClassifier.get(),
extension: task.archiveExtension.get()
])
}
@CompileStatic
static Map getMavenInfoFromTask(Task task, String classifier) {
return getMavenInfoFromMap([
group: task.project.group.toString(),
name: task.project.name,
version: task.project.version.toString(),
classifier: classifier,
extension: 'jar'
])
}
@CompileStatic
private static Map getMavenInfoFromMap(Map<String, String> art) {
var key = "$art.group:$art.name"
var name = "$art.group:$art.name:$art.version"
var path = "${art.group.replace('.', '/')}/$art.name/$art.version/$art.name-$art.version"
if (art.classifier !== null) {
name += ":$art.classifier"
path += "-$art.classifier"
}
if ('jar' != art.extension) {
name += "@$art.extension"
path += ".$art.extension"
} else {
path += ".jar"
}
return [
key: key.toString(),
name: name.toString(),
path: path.toString(),
art: art
]
}
static String iso8601Now() { new Date().iso8601() }
@CompileStatic
static String sha1(File file) {
MessageDigest md = MessageDigest.getInstance('SHA-1')
file.eachByte(4096) { byte[] bytes, int size ->
md.update(bytes, 0, size)
}
return md.digest().collect(this.&toHex).join('')
}
@CompileStatic
private static String toHex(byte bite) {
return String.format('%02x', bite)
}
private static Map artifactTree(Project project, String artifact, boolean transitive = true) {
if (!project.ext.has('tree_resolver'))
project.ext.tree_resolver = 1
def cfg = project.configurations.create('tree_resolver_' + project.ext.tree_resolver++)
cfg.transitive = transitive
def dep = project.dependencies.create(artifact)
cfg.dependencies.add(dep)
def files = cfg.resolve()
return getArtifacts(cfg)
}
@CompileStatic
static boolean checkExists(String url) {
try {
return HTTP.send(HttpRequest.newBuilder(new URI(url))
.method('HEAD', HttpRequest.BodyPublishers.noBody()).build(), HttpResponse.BodyHandlers.discarding()
).statusCode() === 200
} catch (Exception e) {
if (e.toString().contains('unable to find valid certification path to requested target'))
throw new RuntimeException("Failed to connect to $url: Missing certificate root authority, try updating Java")
throw e
}
}
static String getLatestForgeVersion(String mcVersion) {
final json = new JsonSlurper().parseText(new URL('https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json').getText('UTF-8'))
final ver = json.promos["$mcVersion-latest"]
ver === null ? null : (mcVersion + '-' + ver)
}
@CompileStatic
static void processClassNodes(File file, @ClosureParams(value = SimpleType, options = 'org.objectweb.asm.tree.ClassNode') Closure process) {
file.withInputStream { i ->
new ZipInputStream(i).withCloseable { zin ->
ZipEntry zein
while ((zein = zin.nextEntry) !== null) {
if (zein.name.endsWith('.class')) {
var node = new ClassNode(ASM_LEVEL)
new ClassReader(zin).accept(node, 0)
process(node)
}
}
}
}
}
}
@@ -1,94 +0,0 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.forge.tasks
import groovy.transform.CompileStatic
import net.minecraftforge.srgutils.MinecraftVersion
import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.TaskAction
import org.objectweb.asm.tree.AnnotationNode
import org.objectweb.asm.tree.ClassNode
abstract class ValidateDeprecations extends DefaultTask {
@InputFile
abstract RegularFileProperty getInput()
@Input
abstract Property<String> getMcVersion()
ValidateDeprecations() {
this.onlyIf { !System.env.TEAMCITY_VERSION }
}
@TaskAction
protected void exec() {
var mcVer = MinecraftVersion.from(mcVersion.get())
List<String> errors = []
Util.processClassNodes(input.get().asFile) {
processNode(mcVer, errors, it)
}
if (!errors.isEmpty()) {
errors.forEach {
this.logger.error("Deprecated ${it[0]} is marked for removal in ${it[1]} but is not yet removed")
}
throw new IllegalStateException("Found deprecated members marked for removal but not yet removed in ${mcVer}; see log for details")
}
}
protected processNode(MinecraftVersion mcVer, List<String> errors, ClassNode node) {
node.visibleAnnotations?.each { annotation ->
ValidateDeprecations.processAnnotations(annotation, mcVer, errors) {
"class ${node.name}"
}
}
node.fields?.each { field ->
field.visibleAnnotations?.each { annotation ->
ValidateDeprecations.processAnnotations(annotation, mcVer, errors) {
"field ${node.name}#${field.name}"
}
}
}
node.methods?.each { method ->
method.visibleAnnotations?.each { annotation ->
ValidateDeprecations.processAnnotations(annotation, mcVer, errors) {
"method ${node.name}#${method.name}${method.desc}"
}
}
}
}
private static void processAnnotations(AnnotationNode annotation, MinecraftVersion mcVer, List<String> errors, Closure<String> context) {
def values = annotation.values
if (values === null)
return
int forRemoval = values.indexOf('forRemoval')
int since = values.indexOf('since')
if (annotation.desc == 'Ljava/lang/Deprecated;' && forRemoval !== -1 && since !== -1 && values.size() >= 4 && values[forRemoval + 1] === true) {
def oldVersion = MinecraftVersion.from(values[since + 1])
int[] split = ValidateDeprecations.splitDots(oldVersion.toString())
if (split.length < 2)
return
def removeVersion = MinecraftVersion.from("${split[0]}.${split[1] + 1}")
if (removeVersion <= mcVer)
errors.add([context(), removeVersion])
}
}
@CompileStatic
private static int[] splitDots(String version) {
String[] pts = version.split('\\.')
int[] values = new int[pts.length]
for (int x = 0; x < pts.length; x++)
values[x] = Integer.parseInt(pts[x])
return values
}
}
@@ -1,282 +0,0 @@
package net.minecraftforge.forge.tasks.checks
import groovy.transform.CompileStatic
import groovy.transform.TupleConstructor
import net.minecraftforge.forge.tasks.InheritanceData
import net.minecraftforge.srgutils.IMappingFile
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFile
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Optional
import org.objectweb.asm.Opcodes
@CompileStatic
abstract class CheckATs extends CheckTask {
@InputFile abstract RegularFileProperty getInheritance()
@InputFiles abstract ConfigurableFileCollection getAts()
@Override
void check(Reporter reporter, boolean fix) {
final inheritance = InheritanceData.parse(this.inheritance.get().asFile)
ats.each {
final lines = process(it, reporter, inheritance)
if (fix) {
it.text = joinBack(lines, inheritance).join('\n')
}
}
}
private static TreeMap<String, ATParser.Entry> process(File file, Reporter reporter, Map<String, InheritanceData> inheritance) {
final TreeMap<String, ATParser.Entry> lines = ATParser.parse(file.readLines(), reporter)
final Map<String, ATParser.Entry> constructorGroups = [:]
final itr = lines.entrySet().iterator()
final toRemove = []
while (itr.hasNext()) {
final next = itr.next()
String key = next.key
final entry = next.value
if (entry === null) continue
final binaryName = entry.cls.replaceAll('\\.', '/')
// Process Groups, this will remove any entries outside the group that is covered by the group
if (entry.group) {
final jcls = inheritance[binaryName]
if (jcls === null) {
itr.remove()
reporter.report("Invalid group: $key")
} else if ('*' == entry.desc) {
if (!jcls.fields) {
itr.remove()
reporter.report("Invalid group, class has no fields: $key")
} else {
jcls.fields.each { field, value ->
final fkey = entry.cls + ' ' + field
if (accessLevel(value.access) < accessStr(entry.modifier)) {
if (lines.containsKey(fkey)) {
toRemove.add(fkey)
} else if (!entry.existing.contains(fkey)) {
reporter.report("Missing group entry: $fkey")
}
entry.children.add(fkey)
} else if (lines.containsKey(fkey)) {
toRemove.add(fkey)
reporter.report("Found invalid group entry: $fkey")
}
}
entry.existing.findAll { it !in entry.children }.each { println('Removed: ' + it) }
}
} else if ('*()' == entry.desc) {
if (!jcls.methods) {
itr.remove()
reporter.report("Invalid group, class has no methods: $key")
} else {
jcls.methods.each { mtd, value ->
if (mtd.startsWith('<clinit>') || mtd.startsWith('lambda$')) return
key = entry.cls + ' ' + mtd.replace(' ', '')
if (accessLevel(value.access) < accessStr(entry.modifier)) {
if (lines.containsKey(key)) {
toRemove.add(key)
} else if (!entry.existing.contains(key)) {
reporter.report("Missing group entry: $key")
}
entry.children.add(key)
} else if (lines.containsKey(key)) {
toRemove.add(key)
reporter.report("Found invalid group entry: $key")
}
}
entry.existing.findAll { it !in entry.children }.each { println('Removed: ' + it) }
}
} else if ('<init>' == entry.desc) { //Make all public non-abstract subclasses
constructorGroups.put(binaryName, entry)
}
}
// Process normal lines, remove invalid and remove narrowing
else {
def jcls = inheritance.get(binaryName)
if (jcls === null) {
itr.remove()
reporter.report("Invalid: $key")
} else if (entry.desc == '') {
if (accessLevel(jcls.access) > accessStr(entry.modifier) && (entry.comment === null || !entry.comment.startsWith('#force '))) {
itr.remove()
reporter.report("Invalid Narrowing: $key")
}
} else if (!entry.desc.contains('(')) {
if (!jcls.fields || !jcls.fields.containsKey(entry.desc)) {
itr.remove()
reporter.report("Invalid: $key")
} else {
final value = jcls.fields[entry.desc]
if (accessLevel(value.access) > accessStr(entry.modifier) && (entry.comment === null || !entry.comment.startsWith('#force '))) {
itr.remove()
reporter.report("Invalid Narrowing: $key - ${entry.comment}")
}
}
} else {
final jdesc = entry.desc.replace('(', ' (')
if (!jcls.methods || !jcls.methods.containsKey(jdesc)) {
itr.remove()
reporter.report("Invalid: $key")
} else {
final value = jcls.methods[jdesc]
if (accessLevel(value.access) > accessStr(entry.modifier) && (entry.comment === null || !entry.comment.startsWith('#force '))) {
itr.remove()
reporter.report("Invalid Narrowing: $key")
}
}
}
}
}
inheritance.each { tcls, value ->
if (!value.methods || ((value.access & Opcodes.ACC_ABSTRACT) !== 0)) return
String parent = tcls
while (parent !== null) {
constructorGroups[parent]?.tap { entry ->
value.methods.each { mtd, v ->
if (mtd.startsWith('<init>')) {
final child = tcls.replaceAll('/', '\\.') + ' ' + mtd.replace(' ', '')
if (accessLevel(v.access) < 3) {
if (lines.containsKey(child)) {
toRemove.add(child)
} else if (child !in entry.existing) {
reporter.report("Missing group entry: $child")
}
entry.children.add(child)
} else if (lines.containsKey(child)) {
toRemove.add(child)
reporter.report("Found invalid group entry: $child")
}
}
}
}
parent = inheritance[parent]?.superName
}
}
constructorGroups.values().each { entry -> entry.existing.findAll { it !in entry.children }.each{ reporter.report("Found invalid group entry: $it") } }
toRemove.each(lines.&remove)
return lines
}
private static List<String> joinBack(TreeMap<String, ATParser.Entry> lines, Map<String, InheritanceData> inheritance) {
final data = [] as List<String>
lines.each { key, value ->
if (!value.group) {
def comment = null //value.comment
data.add(value.modifier + ' ' + key + (comment ? ' ' + comment : ''))
} else {
data.add(('#group ' + value.modifier + ' ' + key + ' ' + (value.comment ?: '')).trim())
value.children.each {
final line = value.modifier + ' ' + it
final entry = ATParser.parseEntry(line)
final comment = null //entry.comment
data.add(line + (comment ? ' ' + comment : ''))
}
data.add('#endgroup')
}
}
return data
}
static int accessStr(String access) {
if (access.endsWith('-f') || access.endsWith('+f')) return 4
switch (access.toLowerCase()) {
case 'public': return 3
case 'protected': return 2
case 'default': return 1
case 'private': return 0
default: return -1
}
}
static int accessLevel(int access) {
if ((access & Opcodes.ACC_PUBLIC) !== 0) return 3
if ((access & Opcodes.ACC_PROTECTED) !== 0) return 2
if ((access & Opcodes.ACC_PRIVATE) !== 0) return 0
return 1
}
}
@CompileStatic
class ATParser {
static TreeMap<String, Entry> parse(List<String> lines, CheckTask.Reporter reporter) {
TreeMap<String, Entry> outLines = new TreeMap<>()
Entry group = null
for (final line : lines) {
if (line.isEmpty()) continue
if (line.startsWith('#group ')) {
final entry = parseEntry(line.substring(7))
if (entry.desc != '*' && entry.desc != '*()' && entry.desc != '<init>') {
reporter.report("Invalid group: $line", false)
}
entry.group = true
entry.children = []
entry.existing = []
group = entry
if (outLines.containsKey(entry.key)) {
reporter.report("Duplicate group: $line", false)
}
outLines[entry.key] = group
} else if (group !== null) {
if (line.startsWith('#endgroup')) {
group = null
} else {
final key = parseEntry(line).key
group.existing.add(key)
}
} else if (line.startsWith('#endgroup')) {
reporter.report("Invalid group ending: $line", false)
} else if (line.startsWith('#')) {
//Nom
} else {
final entry = parseEntry(line)
if (outLines.containsKey(entry.key)) {
reporter.report("Found duplicate: $line")
continue
}
outLines[entry.key] = entry
}
}
return outLines
}
static Entry parseEntry(String line) {
final idx = line.indexOf('#')
final String comment = idx === -1 ? null : line.substring(idx)
if (idx !== -1) line = line.substring(0, idx - 1)
final data = (line.trim() + ' ').split(' ', -1)
data[1] = data[1].replaceAll('/', '.') // Convert to Source names, internal names are fine by spec, but not supported by old AST based AT implementations.
new Entry(data[0], data[1], data[2], comment)
}
@TupleConstructor
static final class Entry {
String modifier, cls, desc, comment
Set<String> existing
TreeSet<String> children
boolean group = false
@Lazy
String key = {cls + (desc.isEmpty() ? '' : ' ' + desc)}()
Object getAt(String key) {
return getProperty(key)
}
}
}
@@ -1,95 +0,0 @@
package net.minecraftforge.forge.tasks.checks
import net.minecraftforge.forge.tasks.Util
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Type
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
abstract class CheckExcs extends CheckTask {
@InputFile abstract RegularFileProperty getBinary()
@InputFiles abstract ConfigurableFileCollection getExcs()
@Override
void check(Reporter reporter, boolean fix) {
final Set<String> known = []
collectKnown(known)
excs.each { f ->
final lines = []
f.eachLine { line ->
int idx = line.indexOf('#')
if (idx === 0 || line.isEmpty()) {
return
}
if (idx !== -1) line = line.substring(0, idx - 1)
if (!line.contains('=')) {
reporter.report("Invalid: $line")
return
}
def (String key, String value) = line.split('=', 2)
if (!known.contains(key)) {
reporter.report("Unknown: $line")
return
}
String desc = key.split('\\.', 2)[1]
if (!desc.contains('(')) {
reporter.report("Invalid: $line")
return
}
desc = '(' + desc.split('\\(', 2)[1]
def (exceptions, String args) = value.contains('|') ? value.split('|', 2) : [value, '']
if (args.split(',').length !== Type.getArgumentTypes(desc).length) {
reporter.report("Invalid: $line")
return
}
lines.add(line)
return
}
if (fix) f.text = lines.sort().join('\n')
}
}
private void collectKnown(Collection<String> known) {
binary.get().asFile.withInputStream { i ->
new ZipInputStream(i).withCloseable { zin ->
final visitor = new ClassVisitor(Util.ASM_LEVEL) {
private String cls
@Override
void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
this.cls = name
}
@Override
MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
known.add(this.cls + '.' + name + descriptor)
super.visitMethod(access, name, descriptor, signature, exceptions)
}
}
ZipEntry zein
while ((zein = zin.nextEntry) !== null) {
if (zein.name.endsWith('.class')) {
ClassReader reader = new ClassReader(zin)
reader.accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES)
}
}
}
}
}
}
@@ -1,11 +0,0 @@
package net.minecraftforge.forge.tasks.checks
import groovy.transform.CompileStatic
@CompileStatic
enum CheckMode {
CHECK,
FIX
CheckMode() {}
}
@@ -1,182 +0,0 @@
package net.minecraftforge.forge.tasks.checks
import groovy.transform.CompileStatic
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.Optional
import java.nio.file.Files
import java.nio.file.Path
import java.util.regex.Pattern
@CompileStatic
abstract class CheckPatches extends CheckTask {
private static final Pattern HUNK_START_PATTERN = Pattern.compile('^@@ -[0-9,]* \\+[0-9,_]* @@$')
private static final Pattern WHITESPACE_PATTERN = Pattern.compile('^[+\\-]\\s*$')
private static final Pattern IMPORT_PATTERN = Pattern.compile('^[+\\-]\\s*import.*')
private static final Pattern FIELD_PATTERN = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final)?([^=;]*)(=.*)?;\\s*$')
private static final Pattern METHOD_PATTERN = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final)?([^(]*)[(]([^)]*)?[)]\\s*[{]\\s*$')
private static final Pattern CLASS_PATTERN = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final[\\s]*)?(class|interface)([^{]*)[{]\\s*$')
private static final Map<String, Integer> ACCESS_MAP = [private: 0, protected: 2, public: 3].tap { it.put(null, 1) }
@InputDirectory abstract DirectoryProperty getPatchDir()
@Input @Optional abstract ListProperty<String> getPatchesWithS2SArtifact()
@Override
void check(Reporter reporter, boolean fix) {
final patchDir = getPatchDir().get().asFile.toPath()
Files.walk(patchDir).withCloseable {
it.filter(Files.&isRegularFile).forEach { path ->
final String relativeName = patchDir.relativize(path).toString()
verifyPatch(path, reporter, fix, relativeName, patchesWithS2SArtifact.get().contains(relativeName.replace('\\', '/')))
}
}
}
static boolean accessChange(String previous, String current) {
//return ACCESS_MAP[previous] < ACCESS_MAP[current]
return previous != current
}
void verifyPatch(Path patch, Reporter reporter, boolean fix, String patchPath, boolean hasS2SArtifact) {
int oldFixedErrors = reporter.fixed.size()
final lines = Files.readAllLines(patch)
int hunksStart = 0
boolean onlyWhiteSpace = false
final List<String> newLines = []
// First two lines are file name ++/-- and we do not care
newLines.add(lines[0] + '\n')
newLines.add(lines[1] + '\n')
int i
for (i = 2; i < lines.size(); ++i) {
def line = lines[i]
newLines.add(line + '\n')
if (HUNK_START_PATTERN.matcher(line).find()) {
if (onlyWhiteSpace) {
if (!hasS2SArtifact)
reporter.report("Patch contains only white space hunk starting at line ${hunksStart + 1}, file: $patchPath")
int toRemove = i - hunksStart
while (toRemove-- > 0)
newLines.remove(newLines.size() - 1)
}
hunksStart = i
onlyWhiteSpace = true
continue
}
if (line.startsWithAny('+','-')) {
def prefixChange = false
def prevLine = lines[i - 1]
if (line.charAt(0) == (char)'+' && prevLine.charAt(0) == (char)'-') {
def prevTrim = prevLine.substring(1).replaceAll("\\s", "")
def currTrim = line.substring(1).replaceAll("\\s", "")
if (prevTrim == currTrim) {
prefixChange = true
}
def pMatcher = FIELD_PATTERN.matcher(prevLine)
def cMatcher = FIELD_PATTERN.matcher(line)
if (pMatcher.find() && cMatcher.find() &&
pMatcher.group(6) == cMatcher.group(6) && // = ...
pMatcher.group(5) == cMatcher.group(5) && // field name
pMatcher.group(3) == cMatcher.group(3) && // static
(accessChange(pMatcher.group(2), cMatcher.group(2)) || pMatcher.group(4) != cMatcher.group(4))) {
reporter.report("Patch contains access changes or final removal at line ${i + 1}, file: $patchPath", false)
}
pMatcher = METHOD_PATTERN.matcher(prevLine)
cMatcher = METHOD_PATTERN.matcher(line)
if (pMatcher.find() && cMatcher.find() &&
pMatcher.group(6) == cMatcher.group(6) && // params
pMatcher.group(5) == cMatcher.group(5) && // <T> void name
pMatcher.group(3) == cMatcher.group(3) && // static
(accessChange(pMatcher.group(2), cMatcher.group(2)) || pMatcher.group(4) != cMatcher.group(4))) {
reporter.report("Patch contains access changes or final removal at line ${i + 1}, file: $patchPath", false)
}
pMatcher = CLASS_PATTERN.matcher(prevLine)
cMatcher = CLASS_PATTERN.matcher(line)
if (pMatcher.find() && cMatcher.find() &&
pMatcher.group(6) == cMatcher.group(6) && // ClassName<> extends ...
pMatcher.group(5) == cMatcher.group(5) && // class | interface
pMatcher.group(3) == cMatcher.group(3) && // static
(accessChange(pMatcher.group(2), cMatcher.group(2)) || pMatcher.group(4) != cMatcher.group(4))) {
reporter.report("Patch contains access changes or final removal at line ${i + 1}, file: $patchPath", false)
}
}
if (line.charAt(0) == (char)'-' && i + 1 < lines.size()) {
final nextLine = lines[i + 1]
if (nextLine.charAt(0) == (char)'+') {
final nextTrim = nextLine.substring(1).replaceAll("\\s", "")
final currTrim = line.substring(1).replaceAll("\\s", "")
if (nextTrim == currTrim) {
prefixChange = true
}
}
}
final isWhiteSpaceChange = WHITESPACE_PATTERN.matcher(line).find()
if (!prefixChange && !isWhiteSpaceChange) {
onlyWhiteSpace = hasS2SArtifact && IMPORT_PATTERN.matcher(line).find()
} else if (isWhiteSpaceChange) {
final prevLineChange = prevLine.startsWithAny('+','-')
final nextLineChange = i + 1 < lines.size() && lines[i + 1].startsWithAny('+','-')
if (!prevLineChange && !nextLineChange) {
reporter.report("Patch contains white space change in valid hunk at line ${i + 1}, file: $patchPath \n$prevLine\n$line\n${lines[i+1]}", false)
}
}
if (line.contains('\t')) {
reporter.report("Patch contains tabs on line ${i + 1}, file: $patchPath")
line = line.replaceAll('\t', ' ')
newLines.remove(newLines.size() - 1)
newLines.add(line + '\n')
}
if (IMPORT_PATTERN.matcher(line).find() && !hasS2SArtifact) {
reporter.report("Patch contains import change on line ${i + 1}, file: $patchPath", false)
}
}
}
if (onlyWhiteSpace) {
if (!hasS2SArtifact)
reporter.report("Patch contains only white space hunk starting at line ${hunksStart + 1}, file: $patchPath")
def toRemove = i - hunksStart;
while (toRemove-- > 0)
newLines.remove(newLines.size() - 1)
}
if ((reporter.fixed.size() > oldFixedErrors && fix) || hasS2SArtifact) {
if (newLines.size() <= 2) {
logger.lifecycle("Patch is now empty removing, file: {}", patchPath)
Files.delete(patch)
}
else {
if (!hasS2SArtifact)
logger.lifecycle("*** Updating patch file. Please run setup then genPatches again. ***")
Files.newBufferedWriter(patch).withCloseable {
newLines.each { l -> it.write(l) }
}
}
}
}
}
@@ -1,110 +0,0 @@
package net.minecraftforge.forge.tasks.checks
import groovy.transform.CompileStatic
import net.minecraftforge.forge.tasks.Annotatable
import net.minecraftforge.forge.tasks.InheritanceData
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
@CompileStatic
abstract class CheckSAS extends CheckTask {
@InputFile abstract RegularFileProperty getInheritance()
@InputFiles abstract ConfigurableFileCollection getSass()
@Override
void check(Reporter reporter, boolean fix) {
final inheritance = InheritanceData.parse(this.inheritance.get().asFile)
sass.each { f ->
final lines = []
f.eachLine { line ->
if (line[0] == '\t') return // Skip any tabbed lines, those are ones we add
final idx = line.indexOf('#')
if (idx == 0 || line.isEmpty()) {
lines.add(line)
return
}
def comment = idx == -1 ? null : line.substring(idx)
if (idx != -1) line = line.substring(0, idx - 1)
final spl = (line.trim().replace('(', ' (') + ' ').split(' ', -1)
def (String cls, String name, String desc) = [spl[0], spl[1], spl[2]]
cls = cls.replaceAll('\\.', '/')
if (inheritance[cls] === null) {
reporter.report("Invalid: $line")
} else if (name.isEmpty()) { //Class SAS
final toAdd = []
final clsSided = isSided(inheritance[cls])
boolean sided = clsSided
/* TODO: MergeTool doesn't do fields
if (json[cls]['fields'] != null) {
for (entry in json[cls]['fields']) {
if (isSided(entry.value)) {
sided = true
toAdd.add('\t' + cls + ' ' + entry.key)
}
}
} */
final clsInh = inheritance[cls]
if (clsInh.methods) {
for (entry in clsInh.methods) {
if (isSided(entry.value)) {
sided = true
toAdd.add('\t' + cls + ' ' + entry.key.replaceAll(' ', ''))
findChildMethods(inheritance, cls, entry.key).each { lines.add('\t' + it) }
findChildMethods(inheritance, cls, entry.key).each { println(line + ' -- ' + it) }
} else if (clsSided) {
findChildMethods(inheritance, cls, entry.key).each { lines.add('\t' + it) }
findChildMethods(inheritance, cls, entry.key).each { println(line + ' -- ' + it) }
}
}
}
if (sided) {
lines.add(cls + (comment == null ? '' : ' ' + comment))
lines.addAll(toAdd.sort())
} else {
reporter.report("Invalid: $line")
}
} else if (desc.isEmpty()) { // Fields
/* TODO: MergeTool doesn't do fields
if (json[cls]['fields'] != null && isSided(json[cls]['fields'][name]))
lines.add(cls + ' ' + name + (comment == null ? '' : ' ' + comment))
else */
reporter.report("Invalid: $line")
} else { // Methods
final clsInh = inheritance[cls]
if (clsInh.methods === null || !isSided(clsInh.methods[name + ' ' + desc]))
reporter.report("Invalid: $line")
else {
lines.add(cls + ' ' + name + desc + (comment == null ? '' : ' ' + comment))
findChildMethods(inheritance, cls, name + ' ' + desc).each { println(line + ' -- ' + it) }
findChildMethods(inheritance, cls, name + ' ' + desc).each { lines.add('\t' + it) }
}
}
}
if (fix) f.text = lines.join('\n')
}
}
protected static boolean isSided(Annotatable annotatable) {
if (annotatable === null) return false
for (ann in annotatable.annotations) {
if ('Lnet/minecraftforge/api/distmarker/OnlyIn;' == ann.desc)
return true
}
return false
}
protected static findChildMethods(Map<String, InheritanceData> json, String cls, String desc) {
return json.values().findAll{ it.methods !== null && it.methods[desc] !== null && it.methods[desc].override == cls && isSided(it.methods[desc]) }
.collect { it.name + ' ' + desc.replace(' ', '') } as TreeSet
}
}
@@ -1,114 +0,0 @@
package net.minecraftforge.forge.tasks.checks
import groovy.transform.CompileStatic
import groovy.transform.stc.ClosureParams
import groovy.transform.stc.ThirdParam
import net.minecraftforge.forge.tasks.Util
import org.gradle.api.Action
import org.gradle.api.DefaultTask
import org.gradle.api.logging.LogLevel
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.TaskContainer
import org.gradle.api.tasks.VerificationTask
@CompileStatic
abstract class CheckTask extends DefaultTask implements VerificationTask {
@Input
abstract Property<CheckMode> getMode()
private boolean ignoreFailures = false
@Input
@Override
boolean getIgnoreFailures() {
return ignoreFailures
}
@Override
void setIgnoreFailures(boolean ignoreFailures) {
this.ignoreFailures = ignoreFailures
}
@TaskAction
void run() {
Util.init()
boolean doFix = getMode().get() === CheckMode.FIX
final Reporter reporter = new Reporter(doFix)
check(reporter, doFix)
if (reporter.messages) {
if (getMode().get() === CheckMode.CHECK) {
logger.error("Check task '{}' found errors:\n{}", name, reporter.messages.join('\n'))
if (!ignoreFailures) {
throw new IllegalArgumentException("${reporter.messages.size()} errors were found!")
}
} else {
if (logger.isEnabled(LogLevel.DEBUG)) {
logger.warn("Check task '{}' found {} errors and fixed {}:\n{}", name, reporter.messages.size(), reporter.fixed.size(), reporter.fixed.join('\n'))
} else {
logger.warn("Check task '{}' found {} errors and fixed {}.", name, reporter.messages.size(), reporter.fixed.size())
}
if (reporter.notFixed) {
logger.error('{} errors could not be fixed:\n{}', reporter.notFixed.size(), reporter.notFixed.join('\n'))
if (!ignoreFailures) {
throw new IllegalArgumentException("${reporter.notFixed.size()} errors which cannot be fixed were found!")
}
}
}
}
}
abstract void check(Reporter reporter, boolean fix)
@CompileStatic
static final class Reporter {
final boolean trackFixed
Reporter(boolean trackFixed) {
this.trackFixed = trackFixed
}
public final List<String> messages = []
public final List<String> fixed = []
public final List<String> notFixed = []
void report(String message, boolean canBeFixed = true) {
messages.add(message)
if (trackFixed) {
if (canBeFixed) {
fixed.add(message)
} else {
notFixed.add(message)
}
}
}
}
static <T extends CheckTask> void registerTask(TaskContainer tasks, String taskName, @DelegatesTo.Target('type') Class<T> clazz,
@DelegatesTo(genericTypeIndex = 0, target = 'type')
@ClosureParams(ThirdParam.FirstGenericType) Closure configuration) {
taskName = taskName.capitalize()
tasks.register("check$taskName", clazz) { CheckTask task ->
def castedTask = task as T
configuration.setDelegate(castedTask)
configuration.call(castedTask)
castedTask.mode.set(CheckMode.CHECK)
castedTask.group = 'checks'
}
tasks.named('checkAll').configure { it.dependsOn("check$taskName") }
tasks.register("checkAndFix$taskName", clazz) { CheckTask task ->
def castedTask = task as T
configuration.setDelegate(castedTask)
configuration.call(castedTask)
castedTask.mode.set(CheckMode.FIX)
castedTask.group = 'checks'
}
tasks.named('checkAllAndFix').configure { it.dependsOn("checkAndFix$taskName") }
}
}
-73
View File
@@ -1,73 +0,0 @@
plugins {
id 'java-library'
id 'net.minecraftforge.gradle.patcher'
}
evaluationDependsOn(':mcp')
repositories {
mavenCentral()
maven gradleutils.forgeMaven
maven gradleutils.minecraftLibsMaven
}
java.toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION)
dependencies {
implementation(libs.forgespi)
}
patcher {
parent = project(':mcp')
mcVersion = MC_VERSION
patchedSrc = file('src/main/java')
mappings channel: MAPPING_CHANNEL, version: MAPPING_VERSION
runs {
clean_client {
client true
taskName 'clean_client'
ideaModule "${rootProject.name}.${project.name}.main"
main 'net.minecraft.client.main.Main'
workingDirectory project.file('run/client')
args '--gameDir', '.'
args '--version', MC_VERSION
args '--assetsDir', downloadAssets.output
args '--assetIndex', '{asset_index}'
args '--accessToken', '0'
}
clean_server {
client false
taskName 'clean_server'
ideaModule "${rootProject.name}.${project.name}.main"
main 'net.minecraft.server.Main'
workingDirectory project.file('run/server')
}
}
}
tasks.withType(JavaCompile).configureEach {
options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation
options.warnings = false // Shutup deprecated for removal warnings
}
eclipse.classpath.file.whenMerged {
// Disable optional warnings on the minecraft decompiled source code. It just muddies the warning window and hides warnings in our own codebase
def src = entries.find { it.path == 'src/main/java' }
if (src == null) throw new IllegalStateException("You must run `setup` task before importing")
src.entryAttributes['ignore_optional_problems'] = 'true'
}
tasks.named('genPatches').configure {
onlyIf { false }
}
tasks.named('extractRangeMap').configure {
onlyIf { false }
tool = 'net.minecraftforge:Srg2Source:8.1.0:fatjar'
}
-1021
View File
File diff suppressed because it is too large. Load diff
-89
View File
@@ -1,89 +0,0 @@
import net.minecraftforge.forge.tasks.CleanProperties
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'net.minecraftforge.gradleutils'
group = 'net.minecraftforge'
version = VERSION
println("Version: $version")
repositories {
mavenCentral()
maven gradleutils.forgeMaven
maven gradleutils.minecraftLibsMaven
//mavenLocal()
}
tasks.withType(Javadoc).configureEach {
options.tags = [
'apiNote:a:<em>API Note:</em>',
'implSpec:a:<em>Implementation Requirements:</em>',
'implNote:a:<em>Implementation Note:</em>'
]
options.addStringOption('Xdoclint:all,-missing', '-public')
}
// We need to write the manifest to the binary file so we have properly versioned packaged at dev time.
tasks.register('writeManifest') {
doLast {
if (plugins.findPlugin('net.minecraftforge.gradle.patcher')) // Forge project
universalJar.manifest.writeTo(rootProject.file('src/main/resources/META-INF/MANIFEST.MF'))
else
jar.manifest.writeTo(project.file('src/main/resources/META-INF/MANIFEST.MF'))
}
}
tasks.register('generateResources') {
dependsOn('writeManifest')
}
tasks.named('processResources') {
dependsOn(generateResources)
}
// Make sure out manifests get written before compiling the code, IDEA calls this task if you tell it to use the gradle build.
tasks.withType(JavaCompile).configureEach {
dependsOn 'generateResources'
dependsOn 'processResources' // Needed because we merge the output of this with the output of the compile task. And gradle detects downstream tasks using the output without a hard dep
options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation
options.warnings = false // Shutup deprecated for removal warnings
options.forkOptions.jvmArgs += '-Xmx6G' // Needed to make compiling faster, and not run out of heap space in some cases.
}
// Merge the resources and classes into the same directory. We'll need to split them at runtime because
// Minecraft and Forge are in the same sourceSet as they are inter dependent.. for now..
sourceSets.each {
def dir = layout.buildDirectory.dir("classes/java/$it.name")
it.output.resourcesDir = dir
it.java.destinationDirectory = dir
}
tasks.register('copyEclipseSettings') {
doLast {
rootProject.fileTree('ide/eclipse/template/.settings/').matching { include '**/*.prefs' }.each { file ->
def target = project.file('.settings/' + file.name)
def temp = new CleanProperties().load(file)
def exst = new CleanProperties().load(target)
exst.put('eclipse.preferences.version', '1')
exst.putAll(temp)
exst.store(target)
}
}
}
// TODO: [Gradle][IntelliJ] Auto trigger these tasks on import.
eclipse {
// Run everytime eclipse builds the code
//autoBuildTasks writeManifest
// Run when importing the project
synchronizationTasks generateResources, copyEclipseSettings, eclipseClasspath, eclipseProject
}
idea {
module {
// IntelliJ IDEA does not do this by itself anymore...
downloadJavadoc = true
downloadSources = true
}
}
+2 -2
View File
@@ -2,8 +2,8 @@
MinecraftForge
=============
[![Stable Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Stable&prefix=1.21.11-&query=$.promos["1.21.11-recommended"]&color=brightgreen&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtc3RhciBmYS13LTE4IiBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1pY29uPSJzdGFyIiBkYXRhLXByZWZpeD0iZmFzIiBmb2N1c2FibGU9ImZhbHNlIiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCA1NzYgNTEyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNMjU5LjMgMTcuOEwxOTQgMTUwLjIgNDcuOSAxNzEuNWMtMjYuMiAzLjgtMzYuNyAzNi4xLTE3LjcgNTQuNmwxMDUuNyAxMDMtMjUgMTQ1LjVjLTQuNSAyNi4zIDIzLjIgNDYgNDYuNCAzMy43TDI4OCA0MzkuNmwxMzAuNyA2OC43YzIzLjIgMTIuMiA1MC45LTcuNCA0Ni40LTMzLjdsLTI1LTE0NS41IDEwNS43LTEwM2MxOS0xOC41IDguNS01MC44LTE3LjctNTQuNkwzODIgMTUwLjIgMzE2LjcgMTcuOGMtMTEuNy0yMy42LTQ1LjYtMjMuOS01Ny40IDB6IiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K)][Download]
[![Latest Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Latest&prefix=26.1-&query=$.promos["26.1.1-latest"]&color=blue&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtYnVnIGZhLXctMTYiIGFyaWEtaGlkZGVuPSJ0cnVlIiBkYXRhLWljb249ImJ1ZyIgZGF0YS1wcmVmaXg9ImZhcyIgZm9jdXNhYmxlPSJmYWxzZSIgcm9sZT0iaW1nIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTUxMS45ODggMjg4LjljLS40NzggMTcuNDMtMTUuMjE3IDMxLjEtMzIuNjUzIDMxLjFINDI0djE2YzAgMjEuODY0LTQuODgyIDQyLjU4NC0xMy42IDYxLjE0NWw2MC4yMjggNjAuMjI4YzEyLjQ5NiAxMi40OTcgMTIuNDk2IDMyLjc1OCAwIDQ1LjI1NS0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDBsLTU0LjczNi01NC43MzZDMzQ1Ljg4NiA0NjcuOTY1IDMxNC4zNTEgNDgwIDI4MCA0ODBWMjM2YzAtNi42MjctNS4zNzMtMTItMTItMTJoLTI0Yy02LjYyNyAwLTEyIDUuMzczLTEyIDEydjI0NGMtMzQuMzUxIDAtNjUuODg2LTEyLjAzNS05MC42MzYtMzIuMTA4bC01NC43MzYgNTQuNzM2Yy0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDAtMTIuNDk2LTEyLjQ5Ny0xMi40OTYtMzIuNzU4IDAtNDUuMjU1bDYwLjIyOC02MC4yMjhDOTIuODgyIDM3OC41ODQgODggMzU3Ljg2NCA4OCAzMzZ2LTE2SDMyLjY2NkMxNS4yMyAzMjAgLjQ5MSAzMDYuMzMuMDEzIDI4OC45LS40ODQgMjcwLjgxNiAxNC4wMjggMjU2IDMyIDI1Nmg1NnYtNTguNzQ1bC00Ni42MjgtNDYuNjI4Yy0xMi40OTYtMTIuNDk3LTEyLjQ5Ni0zMi43NTggMC00NS4yNTUgMTIuNDk4LTEyLjQ5NyAzMi43NTgtMTIuNDk3IDQ1LjI1NiAwTDE0MS4yNTUgMTYwaDIyOS40ODlsNTQuNjI3LTU0LjYyN2MxMi40OTgtMTIuNDk3IDMyLjc1OC0xMi40OTcgNDUuMjU2IDAgMTIuNDk2IDEyLjQ5NyAxMi40OTYgMzIuNzU4IDAgNDUuMjU1TDQyNCAxOTcuMjU1VjI1Nmg1NmMxNy45NzIgMCAzMi40ODQgMTQuODE2IDMxLjk4OCAzMi45ek0yNTcgMGMtNjEuODU2IDAtMTEyIDUwLjE0NC0xMTIgMTEyaDIyNEMzNjkgNTAuMTQ0IDMxOC44NTYgMCAyNTcgMHoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=
[![Stable Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Stable&prefix=26.2-&query=$.promos["26.2-recommended"]&color=brightgreen&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtc3RhciBmYS13LTE4IiBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1pY29uPSJzdGFyIiBkYXRhLXByZWZpeD0iZmFzIiBmb2N1c2FibGU9ImZhbHNlIiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCA1NzYgNTEyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNMjU5LjMgMTcuOEwxOTQgMTUwLjIgNDcuOSAxNzEuNWMtMjYuMiAzLjgtMzYuNyAzNi4xLTE3LjcgNTQuNmwxMDUuNyAxMDMtMjUgMTQ1LjVjLTQuNSAyNi4zIDIzLjIgNDYgNDYuNCAzMy43TDI4OCA0MzkuNmwxMzAuNyA2OC43YzIzLjIgMTIuMiA1MC45LTcuNCA0Ni40LTMzLjdsLTI1LTE0NS41IDEwNS43LTEwM2MxOS0xOC41IDguNS01MC44LTE3LjctNTQuNkwzODIgMTUwLjIgMzE2LjcgMTcuOGMtMTEuNy0yMy42LTQ1LjYtMjMuOS01Ny40IDB6IiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K)][Download]
[![Latest Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Latest&prefix=26.3-&query=$.promos["26.3-latest"]&color=blue&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtYnVnIGZhLXctMTYiIGFyaWEtaGlkZGVuPSJ0cnVlIiBkYXRhLWljb249ImJ1ZyIgZGF0YS1wcmVmaXg9ImZhcyIgZm9jdXNhYmxlPSJmYWxzZSIgcm9sZT0iaW1nIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTUxMS45ODggMjg4LjljLS40NzggMTcuNDMtMTUuMjE3IDMxLjEtMzIuNjUzIDMxLjFINDI0djE2YzAgMjEuODY0LTQuODgyIDQyLjU4NC0xMy42IDYxLjE0NWw2MC4yMjggNjAuMjI4YzEyLjQ5NiAxMi40OTcgMTIuNDk2IDMyLjc1OCAwIDQ1LjI1NS0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDBsLTU0LjczNi01NC43MzZDMzQ1Ljg4NiA0NjcuOTY1IDMxNC4zNTEgNDgwIDI4MCA0ODBWMjM2YzAtNi42MjctNS4zNzMtMTItMTItMTJoLTI0Yy02LjYyNyAwLTEyIDUuMzczLTEyIDEydjI0NGMtMzQuMzUxIDAtNjUuODg2LTEyLjAzNS05MC42MzYtMzIuMTA4bC01NC43MzYgNTQuNzM2Yy0xMi40OTggMTIuNDk3LTMyLjc1OSAxMi40OTYtNDUuMjU2IDAtMTIuNDk2LTEyLjQ5Ny0xMi40OTYtMzIuNzU4IDAtNDUuMjU1bDYwLjIyOC02MC4yMjhDOTIuODgyIDM3OC41ODQgODggMzU3Ljg2NCA4OCAzMzZ2LTE2SDMyLjY2NkMxNS4yMyAzMjAgLjQ5MSAzMDYuMzMuMDEzIDI4OC45LS40ODQgMjcwLjgxNiAxNC4wMjggMjU2IDMyIDI1Nmg1NnYtNTguNzQ1bC00Ni42MjgtNDYuNjI4Yy0xMi40OTYtMTIuNDk3LTEyLjQ5Ni0zMi43NTggMC00NS4yNTUgMTIuNDk4LTEyLjQ5NyAzMi43NTgtMTIuNDk3IDQ1LjI1NiAwTDE0MS4yNTUgMTYwaDIyOS40ODlsNTQuNjI3LTU0LjYyN2MxMi40OTgtMTIuNDk3IDMyLjc1OC0xMi40OTcgNDUuMjU2IDAgMTIuNDk2IDEyLjQ5NyAxMi40OTYgMzIuNzU4IDAgNDUuMjU1TDQyNCAxOTcuMjU1VjI1Nmg1NmMxNy45NzIgMCAzMi40ODQgMTQuODE2IDMxLjk4OCAzMi45ek0yNTcgMGMtNjEuODU2IDAtMTEyIDUwLjE0NC0xMTIgMTEyaDIyNEMzNjkgNTAuMTQ0IDMxOC44NTYgMCAyNTcgMHoiIGZpbGw9IndoaXRlIi8+Cjwvc3ZnPgo=
)][Download] [![Discord](https://img.shields.io/discord/313125603924639766.svg?color=%237289da&label=Discord&logo=discord&logoColor=%237289da)][Discord] [![Support](https://img.shields.io/badge/Patreon-Support-orange.svg?logo=Patreon)][Patreon]
Forge is a free, open-source modding API all of your favourite mods use!
+50 -40
View File
@@ -1,68 +1,78 @@
import net.minecraftforge.gradleutils.PomUtils
plugins {
id 'java-library'
id 'maven-publish'
id 'net.minecraftforge.licenser'
id 'net.minecraftforge.gradleutils'
alias libs.plugins.licenser
alias libs.plugins.gradleutils
alias libs.plugins.gitversion
alias libs.plugins.changelog
id 'net.minecraftforge.forge.build.convention'
}
apply from: rootProject.file('build_shared.gradle')
gradleutils.displayName = 'FML'
final vendor = 'Forge Development LLC'
description = 'Modifications to Minecraft to enable mod developers.'
dependencies {
compileOnly(libs.jetbrains.annotations)
compileOnly libs.jetbrains.annotations
api(libs.eventbus)
annotationProcessor(libs.eventbus.validator)
api libs.eventbus
implementation(project(':fmlloader'))
implementation(libs.commons.io)
implementation projects.fmlloader
implementation libs.commons.io
}
java {
toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION)
toolchain.languageVersion = JavaLanguageVersion.of(javaVersion)
withSourcesJar()
}
tasks.named('jar', Jar).configure {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.fmlcore',
'FMLModType': 'LIBRARY'
] as LinkedHashMap)
attributes([
'Specification-Title': 'FML',
'Specification-Vendor': 'Forge Development LLC',
'Specification-Version': '1',
'Implementation-Title': 'FML',
'Implementation-Version': '1.0',
'Implementation-Vendor': 'Forge Development LLC'
] as LinkedHashMap, 'net/minecraftforge/fml/')
}
license {
header = rootProject.file('LICENSE-header.txt')
}
changelog {
from changelogBase
}
tasks.withType(JavaCompile).configureEach {
options.compilerArgs << '-Xlint:-unchecked'
}
license {
header = rootProject.file('LICENSE-header.txt')
tasks.named('jar', Jar) {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.fmlcore',
'FMLModType' : 'LIBRARY'
])
attributes([
'Specification-Title' : gradleutils.displayName.get(),
'Specification-Vendor' : vendor,
'Specification-Version' : '1',
'Implementation-Title' : gradleutils.displayName.get(),
'Implementation-Version': '1.0',
'Implementation-Vendor' : vendor
], 'net/minecraftforge/fml/')
}
}
publishing {
publications.register('mavenJava', MavenPublication).configure {
from components.java
artifactId = 'fmlcore'
pom {
name = project.name
description = 'Modifactions to Minecraft to enable mod developers.'
url = 'https://github.com/MinecraftForge/MinecraftForge'
PomUtils.setGitHubDetails(pom, 'MinecraftForge')
license PomUtils.Licenses.LGPLv2_1
}
}
repositories {
maven gradleutils.publishingForgeMaven
}
publications.register('mavenJava', MavenPublication) {
changelog.publish(it)
gradleutils.promote(it)
from components.java
pom {
description = project.description
gradleutils.pom.addRemoteDetails(pom)
licenses {
license gradleutils.pom.licenses.LGPLv2_1
}
}
}
}
@@ -16,7 +16,7 @@ import java.util.function.Supplier;
/**
* Used to allow access to certain things from the game layer
*/
public class Bindings {
public final class Bindings {
private Bindings() {}
/**
@@ -27,7 +27,7 @@ public class Bindings {
.findFirst().orElseThrow(() -> new IllegalStateException("Could not find bindings provider"));
/**
* @return A supplier of net.minecraftforge.common.MinecraftForge#EVENT_BUS
* @return A supplier of {@link BusGroup#DEFAULT}
*/
public static Supplier<BusGroup> getForgeBus() {
return PROVIDER.getForgeBusSupplier();
@@ -14,16 +14,14 @@ import java.util.List;
import java.util.function.BooleanSupplier;
import java.util.function.Supplier;
public class CrashReportCallables
{
public class CrashReportCallables {
private static final Logger LOGGER = LogUtils.getLogger();
private static final List<ISystemReportExtender> crashCallables = Collections.synchronizedList(new ArrayList<>());
/**
* Register a custom {@link ISystemReportExtender}
*/
public static void registerCrashCallable(ISystemReportExtender callable)
{
public static void registerCrashCallable(ISystemReportExtender callable) {
crashCallables.add(callable);
}
@@ -33,19 +31,15 @@ public class CrashReportCallables
* @param headerName The name of the system report entry
* @param reportGenerator The report generator to be called when a crash report is built
*/
public static void registerCrashCallable(String headerName, Supplier<String> reportGenerator)
{
registerCrashCallable(new ISystemReportExtender()
{
public static void registerCrashCallable(String headerName, Supplier<String> reportGenerator) {
registerCrashCallable(new ISystemReportExtender() {
@Override
public String getLabel()
{
public String getLabel() {
return headerName;
}
@Override
public String get()
{
public String get() {
return reportGenerator.get();
}
});
@@ -58,31 +52,23 @@ public class CrashReportCallables
* @param reportGenerator The report generator to be called when a crash report is built
* @param active The supplier of the flag to be checked when a crash report is built
*/
public static void registerCrashCallable(String headerName, Supplier<String> reportGenerator, BooleanSupplier active)
{
registerCrashCallable(new ISystemReportExtender()
{
public static void registerCrashCallable(String headerName, Supplier<String> reportGenerator, BooleanSupplier active) {
registerCrashCallable(new ISystemReportExtender() {
@Override
public String getLabel()
{
public String getLabel() {
return headerName;
}
@Override
public String get()
{
public String get() {
return reportGenerator.get();
}
@Override
public boolean isActive()
{
try
{
public boolean isActive() {
try {
return active.getAsBoolean();
}
catch (Throwable t)
{
} catch (Throwable t) {
LOGGER.warn("CrashCallable '{}' threw an exception while checking the active flag, disabling", headerName, t);
return false;
}
@@ -90,8 +76,7 @@ public class CrashReportCallables
});
}
public static List<ISystemReportExtender> allCrashCallables()
{
public static List<ISystemReportExtender> allCrashCallables() {
return List.copyOf(crashCallables);
}
}
@@ -7,12 +7,10 @@ package net.minecraftforge.fml;
import java.util.function.Supplier;
public interface ISystemReportExtender extends Supplier<String>
{
public interface ISystemReportExtender extends Supplier<String> {
String getLabel();
default boolean isActive()
{
default boolean isActive() {
return true;
}
}
@@ -53,7 +53,7 @@ public abstract class ModContainer {
Supplier<IExtensionPoint.DisplayTest> displayTestSupplier = switch (displayTestString) {
case "MATCH_VERSION" -> // default displaytest checks for version string match
() -> new IExtensionPoint.DisplayTest(() -> this.modInfo.getVersion().toString(),
(incoming, isNetwork) -> Objects.equals(incoming, this.modInfo.getVersion().toString()));
(incoming, _) -> Objects.equals(incoming, this.modInfo.getVersion().toString()));
case "IGNORE_SERVER_VERSION" -> // Ignores any version information coming from the server - use for server only mods
IExtensionPoint.DisplayTest.IGNORE_SERVER_VERSION;
case "IGNORE_ALL_VERSION" -> // Ignores all information and provides no information
@@ -101,7 +101,7 @@ public final class ModList {
}
public static Optional<? extends ModContainer> getModContainerById(String modId) {
return Optional.ofNullable(indexedMods.get(modId));
return Optional.ofNullable(indexedMods == null ? null : indexedMods.get(modId));
}
public static Optional<? extends ModContainer> getModContainerByObject(Object obj) {
@@ -379,8 +379,8 @@ public final class ModLoader {
@SuppressWarnings("removal")
public static <T extends IModBusEvent> void postEventWrapContainerInModOrder(T event) {
postEventWithWrapInModOrder(event,
(mc, e) -> ModLoadingContext.get().setActiveContainer(mc),
(mc, e) -> ModLoadingContext.get().setActiveContainer(null)
(mc, _) -> ModLoadingContext.get().setActiveContainer(mc),
(_, _) -> ModLoadingContext.get().setActiveContainer(null)
);
}
@@ -9,7 +9,6 @@ import net.minecraftforge.fml.loading.progress.ProgressMeter;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Function;
@@ -99,7 +99,7 @@ final class ModStateTransitionHelper {
results[i] = raw.whenComplete((result, exception) -> list.set(i, new FutureResult<>(result, exception)));
}
return CompletableFuture.allOf(results).handle((r, th)->null).thenApply(res -> list);
return CompletableFuture.allOf(results).handle((_, _)->null).thenApply(_ -> list);
}
private static <T extends IModBusEvent> CompletableFuture<Void> addCompletableFutureTaskForModDispatch(
@@ -135,7 +135,7 @@ final class ModStateTransitionHelper {
handler.run();
mod.acceptEvent(eventGenerator.apply(mod));
}, executor)
.whenComplete((mc, exception) -> {
.whenComplete((_, exception) -> {
mod.modLoadingStage = nextState.apply(mod.modLoadingStage, exception);
progressBar.increment();
ModLoadingContext.get().setActiveContainer(null);
@@ -9,8 +9,8 @@ import java.util.concurrent.Executor;
import java.util.function.BinaryOperator;
public enum ThreadSelector implements BinaryOperator<Executor> {
SYNC((sync, parallel) -> sync),
PARALLEL((sync, parallel) -> parallel);
SYNC((sync, _) -> sync),
PARALLEL((_, parallel) -> parallel);
private final BinaryOperator<Executor> selector;
@@ -13,8 +13,6 @@ import net.minecraftforge.fml.loading.StringUtils;
import java.io.ByteArrayInputStream;
import java.nio.file.Path;
import java.util.Locale;
import java.util.concurrent.Callable;
public class ModConfig
{
+86 -61
View File
@@ -1,67 +1,74 @@
import net.minecraftforge.gradleutils.PomUtils
import org.gradle.api.plugins.jvm.JvmTestSuite
import org.gradle.internal.os.OperatingSystem
plugins {
id 'java-library'
id 'jvm-test-suite'
id 'maven-publish'
id 'net.minecraftforge.licenser'
id 'net.minecraftforge.gradleutils'
alias libs.plugins.licenser
alias libs.plugins.gradleutils
alias libs.plugins.gitversion
alias libs.plugins.changelog
id 'net.minecraftforge.forge.build.convention'
}
apply from: rootProject.file('build_shared.gradle')
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
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
}
gradleutils.displayName = 'FML Early Display'
final vendor = 'Forge Development LLC'
description = 'A pretty looking, but prone to erroring screen displayed during Forge loading.'
java {
toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION)
toolchain.languageVersion = JavaLanguageVersion.of(javaVersion)
withSourcesJar()
}
license {
header = rootProject.file('LICENSE-header.txt')
}
changelog {
from changelogBase
}
dependencies {
compileOnly(libs.jetbrains.annotations)
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")
compileOnly libs.jetbrains.annotations
implementation projects.fmlloader
implementation projects.fmlcore
implementation libs.bundles.lwjgl
implementation earlyDisplayLibs.slf4j.api
implementation libs.jopt.simple
}
tasks.named('test', Test).configure {
useJUnitPlatform()
// See gradle/gradle#35070 (https://github.com/gradle/gradle/issues/35070)
// We are making the list of providers first BEFORE mapping them to a provider of the resolved objects so that they can be finalized after configuration.
var lwjglTestLibs = [
dependencies.variantOf(earlyDisplayTestLibs.lwjgl) { classifier lwjglNatives },
dependencies.variantOf(earlyDisplayTestLibs.lwjgl.opengl) { classifier lwjglNatives },
dependencies.variantOf(earlyDisplayTestLibs.lwjgl.stb) { classifier lwjglNatives },
dependencies.variantOf(earlyDisplayTestLibs.lwjgl.sdl) { classifier lwjglNatives }
].with { list ->
provider { list.collect { it.get() } }
}
tasks.named('jar', Jar).configure {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.earlydisplay',
'Forge-Module-Layer': 'boot'
] as LinkedHashMap)
attributes([
'Specification-Title': 'FML Early Display',
'Specification-Vendor': 'Forge Development LLC',
'Specification-Version': '1',
'Implementation-Title': 'FML Early Display',
'Implementation-Vendor': 'Forge Development LLC',
'Implementation-Version': '1.0'
] as LinkedHashMap, 'net/minecraftforge/fml/earlydisplay/')
testing.suites.named('test', JvmTestSuite) {
useJUnitJupiter('5.8.2')
dependencies {
implementation earlyDisplayTestLibs.powermock.core
runtimeOnly earlyDisplayLibs.slf4j.jdk14
runtimeOnly.bundle lwjglTestLibs
}
}
@@ -69,24 +76,42 @@ tasks.withType(JavaCompile).configureEach {
options.compilerArgs << '-Xlint:unchecked'
}
license {
header = rootProject.file('LICENSE-header.txt')
tasks.named('jar', Jar) {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.earlydisplay',
'Forge-Module-Layer' : 'boot'
])
attributes([
'Specification-Title' : gradleutils.displayName.get(),
'Specification-Vendor' : vendor,
'Specification-Version' : '1',
'Implementation-Title' : gradleutils.displayName.get(),
'Implementation-Vendor' : vendor,
'Implementation-Version': '1.0'
], 'net/minecraftforge/fml/earlydisplay/')
}
}
publishing {
publications.register('mavenJava', MavenPublication).configure {
from components.java
artifactId = 'fmlearlydisplay'
pom {
name = project.name
description = 'A pretty looking, but prone to erroring screen displayed during Forge loading.'
url = 'https://github.com/MinecraftForge/MinecraftForge'
PomUtils.setGitHubDetails(pom, 'MinecraftForge')
license PomUtils.Licenses.LGPLv2_1
}
}
repositories {
maven gradleutils.publishingForgeMaven
}
}
publications.register('mavenJava', MavenPublication) {
changelog.publish(it)
gradleutils.promote(it)
from components.java
pom {
description = project.description
gradleutils.pom.addRemoteDetails(pom)
licenses {
license gradleutils.pom.licenses.LGPLv2_1
}
}
}
}
@@ -13,10 +13,12 @@ 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.GLFWVidMode;
import org.lwjgl.opengl.GL;
import org.lwjgl.sdl.SDLError;
import org.lwjgl.sdl.SDLVideo;
import org.lwjgl.sdl.SDL_Event;
import org.lwjgl.sdl.SDL_Rect;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import org.lwjgl.util.tinyfd.TinyFileDialogs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -29,12 +31,10 @@ import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
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;
@@ -43,17 +43,18 @@ 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.IntConsumer;
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.*;
import static org.lwjgl.sdl.SDLError.*;
import static org.lwjgl.sdl.SDLEvents.*;
import static org.lwjgl.sdl.SDLHints.*;
import static org.lwjgl.sdl.SDLInit.*;
import static org.lwjgl.sdl.SDLVideo.*;
/**
* 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.
@@ -83,10 +84,10 @@ public class DisplayWindow implements ImmediateWindowProvider {
private ScheduledFuture<?> initializationFuture;
private PerformanceInfo performanceInfo;
@SuppressWarnings("unused")
private ScheduledFuture<?> performanceTick;
// The GL ID of the window. Used for all operations
private long window;
// The SDL Context we use for switching between threads
private long sdlContext;
// The thread that contains and ticks the window while Forge is loading mods
private static final ScheduledExecutorService renderScheduler = Executors.newSingleThreadScheduledExecutor(r -> {
final var thread = Executors.defaultThreadFactory().newThread(r);
@@ -113,6 +114,14 @@ public class DisplayWindow implements ImmediateWindowProvider {
return "fmlearlywindow";
}
@Override
public ImmediateWindowProvider selectBackend(String backend) {
// We only support opengl
//if ("default".equals(backend) || "opengl".equals(backend))
// return this;
return ImmediateWindowProvider.getFallbackHandler();
}
@Override
public Runnable initialize(String[] arguments) {
String mcVersion = FMLLoader.versionInfo().mcVersion();
@@ -137,7 +146,7 @@ public class DisplayWindow implements ImmediateWindowProvider {
this.colourScheme = ColourScheme.BLACK;
} else {
try {
// check the options file for the colour scheme
// check the options file for the color scheme
var optionLines = Files.readAllLines(FMLPaths.GAMEDIR.get().resolve(Path.of("options.txt")));
var keyName = "darkMojangStudiosBackground:";
for (String line : optionLines) {
@@ -176,7 +185,7 @@ public class DisplayWindow implements ImmediateWindowProvider {
return;
}
nextFrameTime = nt + MINFRAMETIME;
glfwMakeContextCurrent(window);
SDL_GL_MakeCurrent(window, sdlContext);
framebuffer.activate();
glViewport(0, 0, this.context.scaledWidth(), this.context.scaledHeight());
this.context.elementShader().activate();
@@ -188,11 +197,12 @@ public class DisplayWindow implements ImmediateWindowProvider {
glViewport(0, 0, fbWidth, fbHeight);
framebuffer.draw(this.fbWidth, this.fbHeight);
// Swap buffers; we're done
glfwSwapBuffers(window);
SDL_GL_SwapWindow(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
if (this.windowTick != null)
SDL_GL_MakeCurrent(window, 0); // we release the gl context IF we're running off the main thread
renderLock.release();
}
}
@@ -204,12 +214,18 @@ public class DisplayWindow implements ImmediateWindowProvider {
* Nothing fancy, we just want to draw and render text.
*/
private void initRender(final @Nullable String mcVersion, final String forgeVersion) {
this.sdlContext = SDL_GL_CreateContext(window);
if (sdlContext == 0)
throw new IllegalStateException("Failed to create OpenGL context: " + SDL_GetError());
// This thread owns the GL render context now. We should make a note of that.
glfwMakeContextCurrent(window);
bindGlContext();
GL.createCapabilities();
// 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));
SDL_GL_SetSwapInterval(1);
LOGGER.info("GL info: " + glGetString(GL_RENDERER) + " GL version " + glGetString(GL_VERSION) + ", " + glGetString(GL_VENDOR));
elementShader = new ElementShader();
try {
@@ -219,7 +235,7 @@ public class DisplayWindow implements ImmediateWindowProvider {
crashElegantly("An error occurred initializing shaders.");
}
// Set the clear color based on the colour scheme
// Set the clear color based on the color 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
@@ -245,11 +261,15 @@ public class DisplayWindow implements ImmediateWindowProvider {
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glfwMakeContextCurrent(0);
releaseGlContext();
this.windowTick = renderScheduler.scheduleAtFixedRate(this::renderThreadFunc, 50, 50, TimeUnit.MILLISECONDS);
this.performanceTick = renderScheduler.scheduleAtFixedRate(performanceInfo::update, 0, 500, TimeUnit.MILLISECONDS);
// Update the performance information every 1/2 second
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);
//renderScheduler.scheduleAtFixedRate(() -> animationTimerTrigger.set(true), 1, 50, TimeUnit.MILLISECONDS);
}
/**
@@ -286,6 +306,7 @@ public class DisplayWindow implements ImmediateWindowProvider {
glBindVertexArray(currentVAO);
glBindFramebuffer(GL_FRAMEBUFFER, currentFB);
}
/**
* Start the window and Render Thread; we're ready to go.
*/
@@ -334,71 +355,65 @@ public class DisplayWindow implements ImmediateWindowProvider {
* 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");
// Initialize a time guard, in case something goes wrong
long initBegin = System.nanoTime();
if (!SDL_Init(SDL_INIT_VIDEO)) {
crashElegantly("We are unable to initialize the graphics system.\nSDL_Init failed.\n");
throw new IllegalStateException("Unable to initialize SDL");
}
long glfwInitEnd = System.nanoTime();
long initEnd = 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)));
if (initEnd - initBegin > 1e9)
LOGGER.error("WARNING : Window init took {} seconds to start.", (initEnd - initBegin) / 1.0e9);
// 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);
SDL_ResetHints();
String vanillaWindowTitle = "Minecraft* ";
if (mcVersion != null) vanillaWindowTitle += mcVersion;
if (mcVersion != null)
vanillaWindowTitle += mcVersion;
// this emulates what we would get without early progress window
// as vanilla never sets these, so GLFW uses the first window title
// set them explicitly to avoid it using "FML early loading progress" as the class
glfwWindowHintString(GLFW_X11_CLASS_NAME, vanillaWindowTitle);
glfwWindowHintString(GLFW_X11_INSTANCE_NAME, vanillaWindowTitle);
long primaryMonitor = glfwGetPrimaryMonitor();
var primaryMonitor = SDL_GetPrimaryDisplay();
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");
crashElegantly("Failed to locate a primary monitor.\nSDL_GetPrimaryDisplay failed.\n");
throw new IllegalStateException("Can't find a primary monitor");
}
GLFWVidMode vidmode = glfwGetVideoMode(primaryMonitor);
var vidmode = SDL_GetDesktopDisplayMode(primaryMonitor);
if (vidmode == null) {
LOGGER.error("Failed to get the current display video mode.");
crashElegantly("Failed to get current display resolution.\nglfwGetVideoMode failed.\n");
crashElegantly("Failed to get current display resolution.\nSDL_GetDesktopDisplayMode 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.");
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);
boolean showHelpLog = FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_LOG_HELP_MSG);
final String[] lastGLError = new String[GL_VERSIONS.length];
do {
final var glVersionToTry = GL_VERSIONS[versidx][0] + "." + GL_VERSIONS[versidx][1];
if (skipVersions.contains(glVersionToTry)) {
LOGGER.info("Skipping GL version "+ glVersionToTry+" because of configuration");
versidx++;
record Attempt(String vesion, String error) {}
final List<Attempt> attempts = new ArrayList<>();
String requestedVersion = null;
for (int x = 0; x < GL_VERSIONS.length; x++) {
var version = GL_VERSIONS[x];
requestedVersion = version[0] + "." + version[1];
if (skipVersions.contains(requestedVersion)) {
LOGGER.info("Skipping GL version " + requestedVersion + " because of configuration");
continue;
}
LOGGER.info("Trying GL version " + glVersionToTry);
if (showHelpLog && versidx == 0) {
LOGGER.info("Trying GL version " + requestedVersion);
if (showHelpLog && x == 0) {
LOGGER.info("""
If this message is the only thing at the bottom of your log before a crash, you probably have a driver issue.
@@ -410,125 +425,116 @@ public class DisplayWindow implements ImmediateWindowProvider {
You can safely ignore this message if the game starts up successfully.""");
}
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, GL_VERSIONS[versidx][0]); // we try our versions one at a time
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, GL_VERSIONS[versidx][1]);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
window = glfwCreateWindow(winWidth, winHeight, vanillaWindowTitle, 0L, 0L);
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")));
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, version[0]);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, version[1]);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
SDL_GL_SetAttribute(SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, GL_TRUE);
window = SDL_CreateWindow(vanillaWindowTitle, winWidth, winHeight,
SDL_WINDOW_OPENGL
| SDL_WINDOW_RESIZABLE
| SDL_WINDOW_HIGH_PIXEL_DENSITY
);
if (window == 0) {
var error = Objects.requireNonNullElse(SDLError.SDL_GetError(), "<no error>");
attempts.add(new Attempt(requestedVersion, error));
LOGGER.trace("Failed to create window using %d.%d: %s", version[0], version[1], error);
} else {
SDLVideo.SDL_SetWindowMinimumSize(window, 320, 240);
LOGGER.info("Created window using SDL video driver: {}", SDLVideo.SDL_GetCurrentVideoDriver());
break; // We got a valid window!
}
}
if (window == 0) {
LOGGER.error("Failed to find any valid GLFW profile.");
var message = new StringBuilder()
.append("Failed to find a valid GLFW profile.\n");
if (attempts.isEmpty()) {
message.append("All GL Versions were skipped");
} else {
message.append("We tried:");
for (var attempt : attempts)
message.append("\n\t").append(attempt.vesion).append(':').append(attempt.error);
}
crashElegantly(message.toString());
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;
if (!windowFailFuture.cancel(true))
throw new IllegalStateException("We died but didn't somehow?");
try (var stack = org.lwjgl.system.MemoryStack.stackPush()) {
var majBuf = stack.mallocInt(1);
var minBuf = stack.mallocInt(1);
SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, majBuf);
SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, minBuf);
this.glVersion = majBuf.get(0) + "." + minBuf.get(0);
}
LOGGER.info("Requested GL version " + requestedVersion + " got version " + this.glVersion);
this.window = window;
if (showHelpLog)
FMLConfig.updateConfig(FMLConfig.ConfigValue.EARLY_WINDOW_LOG_HELP_MSG, false);
int[] x = new int[1];
int[] y = new int[1];
glfwGetMonitorPos(primaryMonitor, x, y);
int monitorX = x[0];
int monitorY = y[0];
// glfwSetWindowSizeLimits(window, 854, 480, GLFW_DONT_CARE, GLFW_DONT_CARE);
if (this.maximized) {
glfwMaximizeWindow(window);
}
if (this.maximized)
SDL_MaximizeWindow(window);
glfwGetWindowSize(window, x, y);
this.winWidth = x[0];
this.winHeight = y[0];
var size = getWindowSize(window);
this.onWindowResize(size.x, size.y);
glfwSetWindowPos(window, (vidmode.width() - this.winWidth) / 2 + monitorX, (vidmode.height() - this.winHeight) / 2 + monitorY);
var monitor = getMonitorSize(primaryMonitor);
if (monitor != null)
SDL_SetWindowPosition(window, (monitor.width - size.x) / 2 + monitor.x, (monitor.height - size.y) / 2 + monitor.y);
// 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)));
pollEvents();
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();
SDL_ShowWindow(window);
}
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 record Point(int x, int y) {}
private record Rect(int x, int y, int width, int height) {}
private void winMove(long window, int x, int y) {
if (window == this.window) {
this.winX = x;
this.winY = y;
}
}
private static 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);
private static @Nullable Rect getMonitorSize(int monitor) {
try (var bounds = SDL_Rect.malloc()) {
// Fetch the display bounds for the current index
if (SDL_GetDisplayBounds(monitor, bounds)) {
int x = bounds.x();
int y = bounds.y();
int width = bounds.w();
int height = bounds.h();
LOGGER.info("Display {} Bounds -> X: {}, Y: {}, Width: {}, Height: {}", monitor, x, y, width, height);
return new Rect(x, y, width, height);
} else {
LOGGER.warn("Could not get bounds for display: " + SDL_GetError());
return null;
}
}
}
private static Point getWindowSize(long window) {
try (var stack = MemoryStack.stackPush()) {
var width = stack.mallocInt(1);
var height = stack.mallocInt(1);
SDL_GetWindowSize(window, width, height);
return new Point(width.get(0), height.get(0));
}
}
/**
* 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 int width, final int height, final String title, final long monitorSupplier, final Supplier<Object> backend) {
@Override
public long setupMinecraftWindow(final int width, final int height, final String title, final Supplier<Object> backend) {
// wait for the window to actually be initialized
try {
this.initializationFuture.get(30, TimeUnit.SECONDS);
@@ -560,14 +566,10 @@ public class DisplayWindow implements ImmediateWindowProvider {
// we don't want the lock, just making sure it's back on the main thread
renderLock.release();
glfwMakeContextCurrent(window);
SDL_GL_MakeCurrent(window, sdlContext);
// Set the title to what the game wants
glfwSetWindowTitle(window, title);
glfwSwapInterval(0);
// Clean up our hooks
glfwSetFramebufferSizeCallback(window, null).free();
glfwSetWindowPosCallback(window, null).free();
glfwSetWindowSizeCallback(window, null).free();
SDL_SetWindowTitle(window, title);
SDL_GL_SetSwapInterval(0); // Disable vsync, let Minecraft control it
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;
@@ -633,7 +635,7 @@ public class DisplayWindow implements ImmediateWindowProvider {
@Override
public void periodicTick() {
glfwPollEvents();
pollEvents();
repaintTick.run();
}
@@ -648,4 +650,67 @@ public class DisplayWindow implements ImmediateWindowProvider {
this.context.elementShader().close();
SimpleBufferBuilder.destroy();
}
}
private void bindGlContext() {
SDL_GL_MakeCurrent(window, sdlContext);
//if (!SDL_GL_MakeCurrent(window, sdlContext))
// throw new IllegalStateException("Failed to make OpenGL context current: " + SDL_GetError());
}
private void releaseGlContext() {
SDL_GL_MakeCurrent(window, 0);
}
private void pollEvents() {
try (var event = SDL_Event.malloc()) {
while (SDL_PollEvent(event)) {
switch (event.type()) {
case SDL_EVENT_WINDOW_MOVED:
this.onWindowMove(event.window().data1(), event.window().data2());
break;
case SDL_EVENT_WINDOW_RESIZED:
this.onWindowResize(event.window().data1(), event.window().data2());
break;
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
this.onFrameBufferResize(event.window().data1(), event.window().data2());
break;
/*
We were told to quit for some reason, find a way to abort?
case SDL_EVENT_QUIT:
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
case SDL_EVENT_TERMINATING:
Window was minimized or restored, maybe stop rendering?
case SDL_EVENT_WINDOW_MINIMIZED:
case SDL_EVENT_WINDOW_MAXIMIZED:
case SDL_EVENT_WINDOW_RESTORED:
*/
}
}
}
}
private void onWindowResize(int width, int height) {
if (width == 0 || height == 0)
return;
this.winWidth = width;
this.winHeight = height;
}
private void onFrameBufferResize(int width, int height) {
if (width == 0 || height == 0)
return;
this.fbWidth = width;
this.fbHeight = height;
}
private void onWindowMove(int x, int y) {
this.winX = x;
this.winY = y;
}
}
@@ -11,8 +11,6 @@ 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 {
@@ -39,11 +37,6 @@ public class RenderElement {
void accept(SimpleBufferBuilder bb, DisplayContext context, int[] size, int frame);
}
/** @deprecated Use {@link Renderer} directly. This was always eagerly resolved, making it an unnecessary wrapper. */
@Deprecated(since = "1.21.5", forRemoval = true)
@FunctionalInterface
interface Initializer extends Supplier<Renderer> {}
@FunctionalInterface
interface TextGenerator {
void accept(SimpleBufferBuilder bb, SimpleFont fh, DisplayContext ctx);
@@ -59,13 +52,6 @@ public class RenderElement {
}
}
/** @deprecated Use {@link RenderElement#RenderElement(Renderer)} instead */
@Deprecated(since = "1.21.5", forRemoval = true)
public RenderElement(final Initializer rendererInitializer) {
this.bb = new SimpleBufferBuilder(1);
this.renderer = rendererInitializer.get();
}
public RenderElement(Renderer renderer) {
this.bb = new SimpleBufferBuilder(1);
this.renderer = renderer;
@@ -116,7 +102,7 @@ public class RenderElement {
}
public static RenderElement forgeVersionOverlay(SimpleFont font, String version) {
return new RenderElement(RenderElement.initializeText(font, (bb, fnt, ctx)->
return new RenderElement(RenderElement.initializeText(font, (bb, _, 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)))));
@@ -178,11 +164,11 @@ public class RenderElement {
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));
bar = progressBar(ctx->new int[] {(ctx.scaledWidth() - BAR_WIDTH * ctx.scale()) / 2, y + font.lineSpacing() - font.descent(), BAR_WIDTH * ctx.scale()}, _->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()});
bar = progressBar(ctx -> new int[]{(ctx.scaledWidth() - BAR_WIDTH * ctx.scale()) / 2, y + font.lineSpacing() - font.descent(), BAR_WIDTH * ctx.scale()}, _ -> colour, _ -> 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);
Renderer label = (bb, ctx, _) -> 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) {
@@ -198,9 +184,9 @@ public class RenderElement {
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 bar = progressBar(ctx -> new int[]{(ctx.scaledWidth() - BAR_WIDTH * ctx.scale()) / 2, y, BAR_WIDTH * ctx.scale()}, _ -> colour, _ -> 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);
Renderer label = (bb, ctx, _) -> 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);
}
@@ -250,7 +236,7 @@ public class RenderElement {
}
private static Renderer initializeText(SimpleFont font, TextGenerator textGenerator) {
return (bb, context, frame) -> renderText(font, textGenerator, bb, context);
return (bb, context, _) -> renderText(font, textGenerator, bb, context);
}
private static void renderText(final SimpleFont font, final TextGenerator textGenerator, final SimpleBufferBuilder bb, final DisplayContext context) {
@@ -262,7 +248,7 @@ public class RenderElement {
}
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));
return (bb, font, _) -> font.generateVerticesForTexts(x, y, bb, new SimpleFont.DisplayText(text, colour));
}
private static Renderer initializeTexture(final String textureFileName, int size, int textureNumber, TextureRenderer positionAndColour) {
+144 -100
View File
@@ -1,69 +1,75 @@
import net.minecraftforge.gradleutils.PomUtils
import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import net.minecraftforge.forgedev.legacy.tasks.JarJarMetadataOptions
import org.gradle.api.plugins.jvm.JvmTestSuite
import javax.inject.Inject
plugins {
id 'java-library'
id 'jvm-test-suite'
id 'maven-publish'
id 'net.minecraftforge.licenser'
id 'net.minecraftforge.gradleutils'
alias(libs.plugins.apt)
alias libs.plugins.licenser
alias libs.plugins.gradleutils
alias libs.plugins.gitversion
alias libs.plugins.changelog
alias libs.plugins.apt
id 'net.minecraftforge.forge.build.convention'
}
apply from: rootProject.file('build_shared.gradle')
configurations.forEach{ it.transitive = false }
dependencies {
compileOnly(libs.jetbrains.annotations)
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.mojang.logging)
api(libs.jarjar.selector)
api(libs.jarjar.meta)
implementation(libs.jopt.simple)
implementation(libs.securemodules)
implementation(libs.accesstransformers)
implementation(libs.terminalconsoleappender)
implementation(libs.jimfs)
implementation(libs.roimfs)
// Needed because we have a custom log4j plugin, and they removed package scanning and require a data file to be generated
implementation(libs.log4j.core)
annotationProcessor(libs.bundles.log4j)
testCompileOnly(libs.jetbrains.annotations)
testRuntimeOnly(libs.bootstrap)
}
gradleutils.displayName = 'FMLLoader'
final vendor = 'Forge Development LLC'
description = 'Modifications to Minecraft to enable mod developers.'
java {
toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION)
toolchain.languageVersion = JavaLanguageVersion.of(javaVersion)
withSourcesJar()
}
tasks.named('jar', Jar).configure {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.fmlloader',
'Forge-Module-Layer': 'boot'
] as LinkedHashMap)
attributes([
'Specification-Title': 'FMLLoader',
'Specification-Vendor': 'Forge Development LLC',
'Specification-Version': '1',
'Implementation-Title': 'FMLLoader',
'Implementation-Vendor': 'Forge Development LLC',
'Implementation-Version': FORGE_VERSION
] as LinkedHashMap, 'net/minecraftforge/fml/loading/')
license {
header = rootProject.file('LICENSE-header.txt')
}
changelog {
from changelogBase
}
dependencies {
compileOnly libs.jetbrains.annotations
// TODO [FMLLoader] Figure out a better way to lock transitive dependencies to the explicit versions shipped by the isntaller
api(libs.bundles.asm ){ transitive = false } // Needed by all the black magic
api(libs.forgespi ){ transitive = false }
api(libs.mergetool.api ){ transitive = false }
api(libs.log4j.api ){ transitive = false }
api(libs.slf4j.api ){ transitive = false }
api(libs.guava ){ transitive = false }
api(libs.gson ){ transitive = false }
api(libs.maven.artifact ){ transitive = false }
api(libs.apache.commons ){ transitive = false }
api(libs.bundles.night.config){ transitive = false }
api(libs.modlauncher ){ transitive = false }
api(libs.mojang.logging ){ transitive = false }
api(libs.jarjar.selector ){ transitive = false }
api(libs.jarjar.meta ){ transitive = false }
implementation(libs.jopt.simple ){ transitive = false }
implementation(libs.securemodules ){ transitive = false }
implementation(libs.accesstransformers ){ transitive = false }
implementation(libs.terminalconsoleappender){ transitive = false }
implementation(libs.jimfs ){ transitive = false }
implementation(libs.roimfs ){ transitive = false }
// Needed because we have a custom log4j plugin, and they removed package scanning and require a data file to be generated
implementation(libs.log4j.core ){ transitive = false }
annotationProcessor(libs.bundles.log4j){ transitive = false }
}
testing.suites.named('test', JvmTestSuite) {
dependencies {
compileOnly libs.jetbrains.annotations
runtimeOnly libs.bootstrap
}
}
@@ -71,39 +77,81 @@ tasks.withType(JavaCompile).configureEach {
options.compilerArgs << '-Xlint:unchecked'
}
license {
header = rootProject.file('LICENSE-header.txt')
tasks.named('jar', Jar) {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.fmlloader',
'Forge-Module-Layer' : 'boot'
])
attributes([
'Specification-Title' : gradleutils.displayName.get(),
'Specification-Vendor' : vendor,
'Specification-Version' : '1',
'Implementation-Title' : gradleutils.displayName.get(),
'Implementation-Vendor' : vendor,
'Implementation-Version': forgeVersion
], 'net/minecraftforge/fml/loading/')
}
}
publishing {
publications.register('mavenJava', MavenPublication).configure {
from components.java
artifactId = 'fmlloader'
pom {
name = project.name
description = 'Modifactions to Minecraft to enable mod developers.'
url = 'https://github.com/MinecraftForge/MinecraftForge'
PomUtils.setGitHubDetails(pom, 'MinecraftForge')
license PomUtils.Licenses.LGPLv2_1
}
}
repositories {
maven gradleutils.publishingForgeMaven
}
}
tasks.register('writeForgeVersionJson') {
doLast {
file('src/main/resources/forge_version.json').json = [
forge: FORGE_VERSION,
mc: MC_VERSION,
mcp: MCP_VERSION
]
publications.register('mavenJava', MavenPublication) {
changelog.publish(it)
gradleutils.promote(it)
from components.java
pom {
description = project.description
gradleutils.pom.addRemoteDetails(pom)
licenses {
license gradleutils.pom.licenses.LGPLv2_1
}
}
}
}
tasks.register('jarJarOptionsJson', net.minecraftforge.forge.tasks.JarJarMetadataOptions) {
// A simple task to write JSON to a file. Using Task#doLast to output files is no longer supported.
// This code may seem verbose, but it's better this way. If more projects need this, we can move it to buildSrc.
@CompileStatic
@PackageScope abstract class WriteForgeVersionJson extends DefaultTask {
abstract @Input Property<String> getForgeVersion()
abstract @Input Property<String> getMinecraftVersion()
abstract @Input Property<String> getMcpVersion()
abstract @OutputFile RegularFileProperty getOutputFile()
@Inject
WriteForgeVersionJson() {}
@TaskAction
@CompileDynamic
void exec() {
var json = new groovy.json.JsonBuilder()
json {
forge this.forgeVersion.get()
mc this.minecraftVersion.get()
mcp this.mcpVersion.get()
}
this.outputFile.asFile.get().text = json.toPrettyString()
}
}
tasks.register('writeForgeVersionJson', WriteForgeVersionJson) {
forgeVersion = project.forgeVersion
minecraftVersion = project.minecraftVersion
mcpVersion = project.mcpVersion
outputFile = file('src/main/resources/forge_version.json')
}
tasks.register('jarJarOptionsJson', JarJarMetadataOptions) {
metadataFile = project.file('src/main/resources/jarjar_options.json')
// This is resolved too early by cpw.mods.modlauncher.TransformationServicesHandler.discoverServices(DiscoveryData) But eventually...
//add(libs.mixin, 'org/spongepowered/asm/mixin/Mixin.class')
@@ -118,29 +166,25 @@ tasks.register('jarJarOptionsJson', net.minecraftforge.forge.tasks.JarJarMetadat
}
}
tasks.named('generateResources').configure {
dependsOn('eclipseJdt')
dependsOn('eclipseJdtApt')
dependsOn('eclipseFactorypath')
dependsOn('writeForgeVersionJson')
dependsOn('jarJarOptionsJson')
tasks.named('generateResources') {
dependsOn(
tasks.named('eclipseJdt'),
tasks.named('eclipseJdtApt'),
tasks.named('eclipseFactorypath'),
tasks.named('writeForgeVersionJson', WriteForgeVersionJson),
tasks.named('jarJarOptionsJson')
)
}
tasks.named('sourcesJar') {
dependsOn('jarJarOptionsJson')
}
eclipse {
classpath {
// We need to set the default output directory for the log4j annotation processor
// Ideally we'd just set the value, but the eclipse plugin deduplicates the output directories
// So we have to do a hacky whenMerged
//defaultOutputDir = file('bin/main')
file.whenMerged {
entries.each { entry ->
if (entry.kind == 'output' && entry.hasProperty('path'))
entry.path = 'bin/main'
}
}
eclipse.classpath {
// We need to set the default output directory for the log4j annotation processor
// Ideally we'd just set the value, but the eclipse plugin deduplicates the output directories
// So we have to do a hacky whenMerged
//defaultOutputDir = file('bin/main')
file.whenMerged {
entries.each { entry ->
if (entry.kind == 'output' && entry.hasProperty('path'))
entry.path = 'bin/main'
}
}
}
@@ -136,7 +136,7 @@ public class RuntimeEnumExtender implements ILaunchPluginService {
classNode.methods.stream().filter(m -> m.name.equals("<init>")).forEach(m -> sb.append(" : ").append(m.desc).append("\n"));
LOGGER.error(LogUtils.FATAL_MARKER, sb.toString());
}
throw new IllegalStateException("Enum has create method with no matching constructor: " + desc);
throw new IllegalStateException("Enum has create method with no matching constructor: " + classType.getClassName() + " " + desc);
}
if (values == null)
@@ -109,7 +109,7 @@ public class FMLConfig
}
if (!configSpec.isCorrect(configData)) {
LOGGER.warn(CORE, "Configuration file {} is not correct. Correcting", configFile);
configSpec.correct(configData, (action, path, incorrectValue, correctedValue) ->
configSpec.correct(configData, (_, path, incorrectValue, correctedValue) ->
LOGGER.info(CORE, "Incorrect key {} was corrected from {} to {}", path, incorrectValue, correctedValue));
}
configData.putAllComments(configComments);
@@ -20,8 +20,8 @@ public class FMLEnvironment
public static final boolean secureJarsEnabled = FMLLoader.isSecureJarEnabled();
static void setupInteropEnvironment(IEnvironment environment) {
environment.computePropertyIfAbsent(IEnvironment.Keys.NAMING.get(), v->naming);
environment.computePropertyIfAbsent(Environment.Keys.DIST.get(), v->dist);
environment.computePropertyIfAbsent(IEnvironment.Keys.NAMING.get(), _->naming);
environment.computePropertyIfAbsent(Environment.Keys.DIST.get(), _->dist);
}
public static class Keys {
@@ -12,7 +12,6 @@ import net.minecraftforge.forgespi.Environment;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
@@ -42,7 +41,7 @@ public class FMLServiceProvider implements ITransformationService {
LOGGER.debug(CORE, "Loading configuration");
FMLConfig.load();
LOGGER.debug(CORE, "Preparing ModFile");
environment.computePropertyIfAbsent(Environment.Keys.MODFILEFACTORY.get(), k->ModFile::new);
environment.computePropertyIfAbsent(Environment.Keys.MODFILEFACTORY.get(), _->ModFile::new);
LOGGER.debug(CORE, "Preparing launch handler");
FMLLoader.setupLaunchHandler(environment, arguments);
FMLEnvironment.setupInteropEnvironment(environment);
@@ -10,8 +10,13 @@ import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import joptsimple.OptionParser;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.function.*;
import java.util.stream.Collectors;
@@ -41,18 +46,64 @@ public class ImmediateWindowHandler {
if (provider == null) {
LOGGER.info("Failed to find ImmediateWindowProvider {}, disabling", providername);
provider = new DummyProvider();
} else {
var backend = findBackend(arguments);
var newProvider = provider.selectBackend(backend);
if (provider != newProvider) {
if (newProvider == null)
newProvider = new DummyProvider();
LOGGER.info("ImmediateWindowProvider {} does not support {}, switching to {}", provider.name(), backend, newProvider.name());
provider = newProvider;
}
}
}
// 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 int width, final int height, final String title, final long monitor, final Supplier<Object> backend) {
return provider.setupMinecraftWindow(width, height, title, monitor, backend);
private static String findBackend(String[] arguments) {
// Try and parse from the command line arguments
var parser = new OptionParser();
var backendOption = parser.accepts("graphicsBackend").withRequiredArg();
parser.allowsUnrecognizedOptions();
var parsed = parser.parse(arguments);
if (parsed.has(backendOption))
return parsed.valueOf(backendOption).toLowerCase(Locale.ENGLISH);
// Read the options.txt if it exists.
var optionsFile = FMLPaths.GAMEDIR.get().resolve(Path.of("options.txt"));
if (!Files.exists(optionsFile)) // Default is OpenGL first
return "default";
List<String> lines = null;
try {
lines = Files.readAllLines(optionsFile);
} catch (IOException e) {
return "default"; // We failed to read for some reason, assume we're using the default.
}
final String key = "preferredGraphicsBackend:";
for (var line : lines) {
if (line.startsWith(key)) {
var backend = line.substring(key.length() + 1, line.length() - 1);
return backend.toLowerCase(Locale.ENGLISH);
}
}
return "default";
}
public static long setupMinecraftWindow(final int width, final int height, final String title, final Supplier<Object> backend) {
return provider.setupMinecraftWindow(width, height, title, backend);
}
public static boolean positionWindow(Optional<Object> monitor,IntConsumer widthSetter, IntConsumer heightSetter, IntConsumer xSetter, IntConsumer ySetter) {
@@ -83,7 +134,7 @@ public class ImmediateWindowHandler {
earlyProgress.label(message);
}
private record DummyProvider() implements ImmediateWindowProvider {
record DummyProvider() implements ImmediateWindowProvider {
private static Method NV_HANDOFF;
private static Method NV_POSITION;
private static Method NV_OVERLAY;
@@ -104,9 +155,9 @@ public class ImmediateWindowHandler {
}
@Override
public long setupMinecraftWindow(final int width, final int height, final String title, final long monitor, final Supplier<Object> backend) {
public long setupMinecraftWindow(final int width, final int height, final String title, final Supplier<Object> backend) {
try {
var longsupplier = (LongSupplier)NV_HANDOFF.invoke(null, width, height, title, monitor, backend);
var longsupplier = (LongSupplier)NV_HANDOFF.invoke(null, width, height, title, backend);
return longsupplier.getAsLong();
} catch (Throwable e) {
throw new IllegalStateException("How did you get here?", e);
@@ -24,11 +24,36 @@ import java.util.function.Supplier;
* No doubt many more things can be said here.
*/
public interface ImmediateWindowProvider {
/**
* Returns a new instance of ImmediateWindowProvider which just bounces to the vanilla code.
* This can be useful when you want to disable your provider and defer to vanilla behavior for some reason.
*/
public static ImmediateWindowProvider getFallbackHandler() {
return new ImmediateWindowHandler.DummyProvider();
}
/**
* @return The name of this window provider. Do NOT use fmlearlywindow.
*/
String name();
/**
* This is called before initialize, but after reading the preferred graphics backend config value from the user's
* options.txt or command line.
*
* If you do not support the requested backend, you can return a new ImmediateWindowProvider that does.
* {@link #getFallbackHandler()} can be used to get an instance that falls back to Vanilla's code effectively
* disabling the early loading screen.
*
* @param backend - The backend the user has selected, known values: "default", "opengl", and "vulkan". However this
* is read from the config file, or command line arguments so could be anything.
* Default and OpenGL are treated the same, attempting to load OpenGL first, then Vulkan.
* Vulkan attempts to load Vulkan first then OpenGL
*/
default ImmediateWindowProvider selectBackend(String backend) {
return this;
}
/**
* This is called very early on to initialize ourselves. Use this to initialize the window and other GL core resources.
*
@@ -61,10 +86,9 @@ public interface ImmediateWindowProvider {
* @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 int width, final int height, final String title, final long monitor, final Supplier<Object> backend);
long setupMinecraftWindow(final int width, final int height, final String title, final Supplier<Object> backend);
/**
* This is called after window handoff to allow us to tell Mojang about our window's position. This might give a
@@ -7,7 +7,6 @@ package net.minecraftforge.fml.loading;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.api.INameMappingService;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import java.io.BufferedReader;
@@ -6,7 +6,6 @@
package net.minecraftforge.fml.loading;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* Convert a maven coordinate into a Path.
@@ -26,6 +26,7 @@ public class StringUtils {
return java.util.stream.Stream.of(endings).anyMatch(lowerSearch::endsWith);
}
@SuppressWarnings("deprecation")
public static URL toURL(final String string) {
if (string == null || string.trim().isEmpty() || string.contains("myurl.me") || string.contains("example.invalid"))
return null;
@@ -13,7 +13,6 @@ import org.jetbrains.annotations.ApiStatus;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.BiPredicate;
@ApiStatus.Internal // since 1.21.1, will be made non-public in a later MC version
public class VersionSupportMatrix {
@@ -7,7 +7,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;
import org.slf4j.Logger;
@@ -56,7 +55,7 @@ public final class BackgroundScanHandler {
ImmediateWindowHandler.updateProgress("Scanning mod candidates");
CompletableFuture<ModFileScanData> future = CompletableFuture.supplyAsync(file::compileContent, modContentScanner)
.whenComplete(file::setScanResult);
if (DEBUG) future = future.whenComplete((r, t) -> addCompletedFile(file, t));
if (DEBUG) future = future.whenComplete((_, t) -> addCompletedFile(file, t));
file.setFutureScanResult(future);
}
@@ -24,7 +24,7 @@ public enum InvalidModIdentifier {
LITELOADER(filePresent("litemod.json")),
OPTIFINE(filePresent("optifine/Installer.class")),
BUKKIT(filePresent("plugin.yml")),
INVALIDZIP((f,zf) -> zf.isEmpty()); // note: only this one INVALIDZIP check is ran until the todo on this class is fixed
INVALIDZIP((_,zf) -> zf.isEmpty()); // note: only this one INVALIDZIP check is ran until the todo on this class is fixed
private final BiPredicate<Path, Optional<ZipFile>> ident;
@@ -51,7 +51,7 @@ public enum InvalidModIdentifier {
private static BiPredicate<Path, Optional<ZipFile>> filePresent(String filename)
{
return (f, zfo) -> zfo.map(zf -> zf.getEntry(filename) != null).orElse(false);
return (_, zfo) -> zfo.map(zf -> zf.getEntry(filename) != null).orElse(false);
}
private static <T> Optional<T> optionalFromException(Supplier_WithExceptions<T, ? extends Exception> supp)
@@ -145,7 +145,7 @@ public class JarInJarDependencyLocator extends AbstractModProvider implements ID
var ids = new TreeMap<String, List<Entry>>();
for (var entry : selector.entries.values()) {
if (entry != FAILED && entry.coord != null)
ids.computeIfAbsent(entry.coord, id -> new ArrayList<>()).add(entry);
ids.computeIfAbsent(entry.coord, _ -> new ArrayList<>()).add(entry);
}
for (var entry : ids.entrySet()) {
@@ -27,7 +27,7 @@ public final class MinecraftLocator extends AbstractModProvider implements IModL
// Minecraft itself.
var meta = new ModJarMetadata();
var mcjar = SecureJar.from(jar -> meta, paths);
var mcjar = SecureJar.from(_ -> meta, paths);
var mc = ModFileFactory.FACTORY.build(mcjar, this, MinecraftLocator::buildMinecraftTOML);
meta.setModFile(mc);
@@ -8,7 +8,6 @@ package net.minecraftforge.fml.loading.moddiscovery;
import com.google.common.collect.ImmutableMap;
import com.mojang.logging.LogUtils;
import cpw.mods.jarhandling.SecureJar;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.fml.loading.LanguageLoadingProvider;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.forgespi.language.IModFileInfo;
@@ -26,7 +25,6 @@ import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -6,11 +6,13 @@
package net.minecraftforge.fml.loading.moddiscovery;
import com.electronwill.nightconfig.core.file.FileConfig;
import com.electronwill.nightconfig.core.io.ParsingException;
import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.forgespi.language.IModFileInfo;
import net.minecraftforge.forgespi.locating.IModFile;
import net.minecraftforge.forgespi.locating.ModFileFactory;
import net.minecraftforge.forgespi.locating.ModFileLoadingException;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
@@ -24,6 +26,7 @@ public class ModFileParser {
return parser.build(modFile);
}
// Note: Although @Nullable, several other places in FML assume ModFileInfo is not null. Keep this in mind
public static @Nullable IModFileInfo modsTomlParser(final IModFile imodFile) {
ModFile modFile = (ModFile) imodFile;
LOGGER.debug(LogMarkers.LOADING,"Considering mod file candidate {}", modFile.getFilePath());
@@ -32,11 +35,19 @@ public class ModFileParser {
LOGGER.warn(LogMarkers.LOADING, "Mod file {} is missing mods.toml file", modFile.getFilePath());
return null;
}
try {
final FileConfig fileConfig = FileConfig.builder(modsjson).build();
fileConfig.load();
fileConfig.close();
final NightConfigWrapper configWrapper = new NightConfigWrapper(fileConfig);
return new ModFileInfo(modFile, configWrapper, configWrapper::setFile);
} catch (ParsingException e) { // Handle landmine toml errors, e.g. incorrectly ported mods.
LOGGER.error("Mod candidate {} contains a corrupt or misconfigured toml.", modFile.getFileName());
throw new ModFileLoadingException("Mod candidate " + modFile.getFileName() + " contains a corrupt or misconfigured toml.");
} catch (Exception other) { // Otherwise this is just someone who (probably) forgot a comma or something.
LOGGER.error("Mod candidate {}'s toml .", modFile.getFileName());
throw new ModFileLoadingException("Mod candidate " + modFile.getFileName() + " contains broken toml, likely due to a typo.");
}
final FileConfig fileConfig = FileConfig.builder(modsjson).build();
fileConfig.load();
fileConfig.close();
final NightConfigWrapper configWrapper = new NightConfigWrapper(fileConfig);
return new ModFileInfo(modFile, configWrapper, configWrapper::setFile);
}
}
@@ -56,7 +56,7 @@ public class StartupNotificationManager {
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) -> {
newMessages.compute(type, (_, existingList) -> {
List<Message> newList = new ArrayList<>();
if (existingList != null)
{
@@ -141,7 +141,7 @@ abstract class CommonDevLaunchHandler extends CommonLaunchHandler {
protected static Path getForgeOnly(Path forge) {
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) -> {
var modJar = SecureJar.from((path, _) -> {
if (!path.endsWith(".class")) return true;
for (var pkg : packages)
if (path.startsWith(pkg)) return true;
@@ -112,7 +112,7 @@ public final class ForgeDevLocator extends AbstractModProvider implements IModLo
// We want just the class files from the root of the input paths. So make a new union with a filter.
var classes = UnionHelper.newFileSystem(
(name, base) -> {
(name, _) -> {
if (name.endsWith("/")) {
if (name.startsWith("/"))
name = name.substring(1);
@@ -164,7 +164,7 @@ public final class ForgeDevLocator extends AbstractModProvider implements IModLo
if ("value".equals(key)) {
int idx = clsName.lastIndexOf('/');
var pkg = clsName.substring(0, idx);
mods.computeIfAbsent(pkg, k -> new HashSet<>()).add((String)value);
mods.computeIfAbsent(pkg, _ -> new HashSet<>()).add((String)value);
idx = pkg.lastIndexOf('/');
while (idx != -1) {
@@ -11,8 +11,8 @@
"artifact": "MixinExtras"
},
"version": {
"range": "[0.5.3,)",
"artifactVersion": "0.5.3"
"range": "[0.5.4,)",
"artifactVersion": "0.5.4"
},
"path": "",
"isObfuscated": false
@@ -24,7 +24,7 @@
"artifact": "mixinextras-forge"
},
"version": {
"artifactVersion": "0.5.3"
"artifactVersion": "0.5.4"
},
"path": "",
"isObfuscated": false
+59 -54
View File
@@ -1,73 +1,78 @@
import net.minecraftforge.gradleutils.PomUtils
plugins {
id 'java-library'
id 'maven-publish'
id 'net.minecraftforge.licenser'
id 'net.minecraftforge.gradleutils'
alias(libs.plugins.apt)
alias libs.plugins.licenser
alias libs.plugins.gradleutils
alias libs.plugins.gitversion
alias libs.plugins.changelog
id 'net.minecraftforge.forge.build.convention'
}
apply from: rootProject.file('build_shared.gradle')
configurations.forEach{ it.transitive = false }
dependencies {
compileOnly(libs.jetbrains.annotations)
implementation(libs.bundles.asm) // Needed by all the black magic
implementation(libs.log4j.api)
implementation(libs.gson)
implementation(libs.modlauncher)
implementation(libs.coremods.api)
testCompileOnly(libs.jetbrains.annotations)
}
gradleutils.displayName = 'Forge Transformers'
final vendor = 'Forge Development LLC'
description = 'Forge-specific transformers unrelated to the FML project.'
java {
toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION)
toolchain.languageVersion = JavaLanguageVersion.of(javaVersion)
withSourcesJar()
}
tasks.named('jar', Jar) {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.forge.transformers',
'Forge-Module-Layer': 'boot'
] as LinkedHashMap)
attributes([
'Specification-Title': 'Forge Transformers',
'Specification-Vendor': 'Forge Development LLC',
'Specification-Version': '1',
'Implementation-Title': 'Forge Transformers',
'Implementation-Vendor': 'Forge Development LLC',
'Implementation-Version': FORGE_VERSION
] as LinkedHashMap, 'net/minecraftforge/forge/transformers/')
}
}
tasks.withType(JavaCompile).configureEach {
options.compilerArgs << '-Xlint:unchecked'
}
license {
header = rootProject.file('LICENSE-header.txt')
}
publishing {
publications.register('mavenJava', MavenPublication) {
from components.java
artifactId = 'forge-transformers'
pom {
name = project.name
description = 'Forge-specific transformers unrelated to the FML project.'
url = 'https://github.com/MinecraftForge/MinecraftForge'
PomUtils.setGitHubDetails(pom, 'MinecraftForge')
license PomUtils.Licenses.LGPLv2_1
}
}
changelog {
from changelogBase
}
dependencies {
compileOnly(libs.jetbrains.annotations)
implementation(libs.bundles.asm ){ transitive = false } // Needed by all the black magic
implementation(libs.log4j.api ){ transitive = false }
implementation(libs.gson ){ transitive = false }
implementation(libs.modlauncher ){ transitive = false }
implementation(libs.coremods.api){ transitive = false }
testCompileOnly(libs.jetbrains.annotations)
}
tasks.named('jar', Jar) {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.forge.transformers',
'Forge-Module-Layer' : 'boot'
])
attributes([
'Specification-Title' : gradleutils.displayName.get(),
'Specification-Vendor' : vendor,
'Specification-Version' : '1',
'Implementation-Title' : gradleutils.displayName.get(),
'Implementation-Vendor' : vendor,
'Implementation-Version': forgeVersion
], 'net/minecraftforge/forge/transformers/')
}
}
publishing {
repositories {
maven gradleutils.publishingForgeMaven
}
publications.register('mavenJava', MavenPublication) {
changelog.publish(it)
gradleutils.promote(it)
from components.java
pom { pom ->
description = project.description
gradleutils.pom.addRemoteDetails(pom)
licenses {
license gradleutils.pom.licenses.LGPLv2_1
}
}
}
}
@@ -32,12 +32,6 @@ record FieldToMethodTransformer(String className, Map<String, String> fields) im
// TODO [Forge][Transformer] Make this properly data driven or configurable.
// It was hard-coded like this before when using JS CoreMods, though.
static final Map<String, Map<String, String>> TARGETS = Map.of(
"net.minecraft.world.level.biome.Biome",
Map.of(
"climateSettings", "getModifiedClimateSettings",
"specialEffects", "getModifiedSpecialEffects"
),
"net.minecraft.world.effect.MobEffectInstance",
Map.of(
"effect", "getEffect"
@@ -126,7 +120,7 @@ record FieldToMethodTransformer(String className, Map<String, String> fields) im
throw new IllegalStateException("No field with name " + fieldName + " found");
if (!Modifier.isPrivate(foundField.access) || Modifier.isStatic(foundField.access))
throw new IllegalStateException("Field " + fieldName + " is not private and an instance field");
throw new IllegalStateException("Field " + classNode.name + '.' + fieldName + " is not private and an instance field");
var methodSignature = "()" + foundField.desc;
var foundMethod = findMethod(classNode, methodName, methodSignature);
@@ -41,7 +41,7 @@ record MethodRedirector() implements ITransformer<ClassNode> {
"finalizeSpawn",
"(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/EntitySpawnReason;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;",
GSON.fromJson(new InputStreamReader(sneak(() -> MethodRedirector.class.getModule().getResourceAsStream("coremods/finalize_spawn_targets.json"))), Target[].class),
insn -> new MethodInsnNode(
_ -> new MethodInsnNode(
Opcodes.INVOKESTATIC,
"net/minecraftforge/event/ForgeEventFactory",
"onFinalizeSpawn",
@@ -1,8 +1,8 @@
[
{
"class": "net/minecraft/gametest/framework/GameTestHelper",
"class": "net/minecraft/gametest/framework/GameTestEntityBuilder",
"methods": [
"spawn(Lnet/minecraft/world/entity/EntityType;Lnet/minecraft/world/phys/Vec3;Lnet/minecraft/world/entity/EntitySpawnReason;)Lnet/minecraft/world/entity/Entity;"
"spawn()Lnet/minecraft/world/entity/Entity;"
]
},
{
@@ -20,7 +20,7 @@
{
"class": "net/minecraft/world/entity/EntityType",
"methods": [
"create(Lnet/minecraft/server/level/ServerLevel;Ljava/util/function/Consumer;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/entity/EntitySpawnReason;ZZ)Lnet/minecraft/world/entity/Entity;"
"create(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/entity/PostSpawnProcessor;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/entity/EntitySpawnReason;ZZ)Lnet/minecraft/world/entity/Entity;"
]
},
{
@@ -42,12 +42,6 @@
"finalizeSpawn(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/EntitySpawnReason;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;"
]
},
{
"class": "net/minecraft/world/entity/animal/frog/Tadpole",
"methods": [
]
},
{
"class": "net/minecraft/world/entity/monster/Strider",
"methods": [
@@ -85,24 +79,12 @@
"finalizeSpawn(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/EntitySpawnReason;Lnet/minecraft/world/entity/SpawnGroupData;)Lnet/minecraft/world/entity/SpawnGroupData;"
]
},
{
"class": "net/minecraft/world/entity/monster/zombie/ZombieVillager",
"methods": [
]
},
{
"class": "net/minecraft/world/entity/npc/CatSpawner",
"methods": [
"spawnCat(Lnet/minecraft/core/BlockPos;Lnet/minecraft/server/level/ServerLevel;Z)V"
]
},
{
"class": "net/minecraft/world/entity/npc/villager/Villager",
"methods": [
]
},
{
"class": "net/minecraft/world/entity/raid/Raid",
"methods": [
@@ -113,7 +95,7 @@
"class": "net/minecraft/world/level/NaturalSpawner",
"methods": [
"spawnCategoryForPosition(Lnet/minecraft/world/entity/MobCategory;Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/level/chunk/ChunkAccess;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/NaturalSpawner$SpawnPredicate;Lnet/minecraft/world/level/NaturalSpawner$AfterSpawnCallback;)V",
"spawnMobsForChunkGeneration(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/core/Holder;Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/util/RandomSource;)V"
"spawnMobsForChunkGeneration(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/util/RandomSource;)V"
]
},
{
@@ -158,11 +140,5 @@
"methods": [
"handleDataMarker(Ljava/lang/String;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/util/RandomSource;Lnet/minecraft/world/level/levelgen/structure/BoundingBox;)V"
]
},
{
"class": "net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate",
"methods": [
]
}
]
+23 -11
View File
@@ -1,17 +1,29 @@
# TODO Re-evaluate
# Sets default memory used for gradle commands. Can be overridden by user or command line properties.
# This is required to provide enough memory for the Minecraft decompilation process.
org.gradle.jvmargs=-Xmx6G
org.gradle.daemon=false
org.gradle.warning.mode=all
org.gradle.caching=true
org.gradle.parallel=true
org.gradle.configureondemand=true
JAVA_VERSION=25
MC_VERSION=26.1.1
MC_NEXT_VERSION=26.2
MCP_VERSION=20260401.101545
MAPPING_CHANNEL=official
MAPPING_VERSION=26.1.1
# TODO [ForgeDev] Enable this once ForgeDev 7 tasks are fully migrated
org.gradle.configuration-cache=false
org.gradle.configuration-cache.parallel=false
#org.gradle.configuration-cache.problems=warn
#org.gradle.configuration-cache.integrity-check=true
# Set to true before the first build of a new MC version, so we don't do compatibility checks
CHECK_COMPATIBILITY=false
# Set to true to allow for fuzzy patching. Useful during updateing
UPDATING=false
net.minecraftforge.gradleutils.ide.automatic.sources=true
net.minecraftforge.gradleutils.compilation.defaults=true
net.minecraftforge.gradle.merge-source-sets=true
# Controls if the compatibility checks are auto-added to the 'check' task.
# The check tasks are still created and can be run, they just are not grouped with the normal 'check' task.
# Set to false for the first builds of a new Minecraft version.
net.minecraftforge.forge.build.check.compatibility=false
# Set to true to allow for fuzzy patching. Useful during updating.
net.minecraftforge.forge.build.updating=false
# Set to true to enable the 'validatePublish' task which is currently a work in progress
net.minecraftforge.forgedev.validate.publish=false
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+7 -8
View File
@@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -84,7 +86,7 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -112,7 +114,6 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
@@ -170,7 +171,6 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -203,15 +203,14 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
Vendored
+3 -2
View File
@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@@ -68,11 +70,10 @@ goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
+61 -48
View File
@@ -1,66 +1,79 @@
import net.minecraftforge.gradleutils.PomUtils
plugins {
id 'java-library'
id 'maven-publish'
id 'net.minecraftforge.licenser'
id 'net.minecraftforge.gradleutils'
alias libs.plugins.licenser
alias libs.plugins.gradleutils
alias libs.plugins.gitversion
alias libs.plugins.changelog
id 'net.minecraftforge.forge.build.convention'
}
apply from: rootProject.file('build_shared.gradle')
dependencies {
compileOnly(libs.jetbrains.annotations)
implementation(project(':fmlloader'))
implementation(project(':fmlcore'))
implementation(libs.unsafe)
implementation(libs.securemodules)
}
gradleutils.displayName = 'JavaFMLMod'
final vendor = 'Forge Development LLC'
description = 'Language provider for Minecraft Forge that provides basic Java mod functionality.'
java {
toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION)
toolchain.languageVersion = JavaLanguageVersion.of(javaVersion)
withSourcesJar()
}
tasks.named('jar', Jar).configure {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.javafmlmod',
'FMLModType': 'LANGPROVIDER'
] as LinkedHashMap)
attributes([
'Specification-Title': 'JavaFMLMod',
'Specification-Vendor': 'Forge Development LLC',
'Specification-Version': '1',
'Implementation-Title': 'JavaFMLMod',
'Implementation-Vendor': 'Forge Development LLC',
'Implementation-Version': FORGE_VERSION
] as LinkedHashMap, 'net/minecraftforge/fml/javafmlmod/')
}
}
tasks.withType(JavaCompile).configureEach {
options.compilerArgs << '-Xlint:unchecked'
}
license {
header = rootProject.file('LICENSE-header.txt')
}
publishing {
publications.register('mavenJava', MavenPublication).configure {
from components.java
artifactId = 'javafmllanguage'
pom {
name = project.name
description = 'Language provider for Minecraft Forge that provides basic java mod functionality'
url = 'https://github.com/MinecraftForge/MinecraftForge'
PomUtils.setGitHubDetails(pom, 'MinecraftForge')
license PomUtils.Licenses.LGPLv2_1
}
}
changelog {
from changelogBase
}
dependencies {
compileOnly libs.jetbrains.annotations
implementation projects.fmlloader
implementation projects.fmlcore
implementation libs.unsafe
implementation libs.securemodules
}
tasks.withType(JavaCompile).configureEach {
options.compilerArgs << '-Xlint:unchecked'
}
tasks.named('jar', Jar) {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.javafmlmod',
'FMLModType' : 'LANGPROVIDER'
])
attributes([
'Specification-Title' : gradleutils.displayName.get(),
'Specification-Vendor' : vendor,
'Specification-Version' : '1',
'Implementation-Title' : gradleutils.displayName.get(),
'Implementation-Vendor' : vendor,
'Implementation-Version': forgeVersion
], 'net/minecraftforge/fml/javafmlmod/')
}
}
publishing {
repositories {
maven gradleutils.publishingForgeMaven
}
}
publications.register('mavenJava', MavenPublication) {
changelog.publish(it)
gradleutils.promote(it)
from components.java
pom {
description = project.description
gradleutils.pom.addRemoteDetails(pom)
licenses {
license gradleutils.pom.licenses.LGPLv2_1
}
}
}
}
@@ -37,8 +37,7 @@ public @interface Mod {
/**
* Annotate a class which will be subscribed to an Event Bus at mod construction time.
* Defaults to subscribing the current modid to the {@code MinecraftForge#EVENT_BUS}
* on both sides.
* Defaults to subscribing the current modid to the {@link Bus#BOTH} on both sides.
*
* @see Bus
*/
@@ -45,7 +45,7 @@ import java.util.stream.Collectors;
/**
* Automatic eventbus subscriber - reads {@link EventBusSubscriber}
* annotations and passes the class instances to the {@link Bus}
* defined by the annotation. Defaults to {@code MinecraftForge#EVENT_BUS}
* defined by the annotation. Defaults to {@code Bus.BOTH} if not specified.
*/
public final class AutomaticEventSubscriber {
private static final Logger LOGGER = LogManager.getLogger();
@@ -81,7 +81,7 @@ public class FMLJavaModLanguageProvider implements IModLanguageProvider
.filter(ad -> ad.annotationType().equals(MODANNOTATION))
.peek(ad -> LOGGER.debug(SCAN, "Found @Mod class {} with id {}", ad.clazz().getClassName(), ad.annotationData().get("value")))
.map(ad -> new FMLModTarget(ad.clazz().getClassName(), (String)ad.annotationData().get("value")))
.collect(Collectors.toMap(FMLModTarget::modId, Function.identity(), (a,b)->a));
.collect(Collectors.toMap(FMLModTarget::modId, Function.identity(), (a, _) -> a));
scanResult.addLanguageLoader(modTargetMap);
};
}
+58 -46
View File
@@ -1,65 +1,77 @@
import net.minecraftforge.gradleutils.PomUtils
plugins {
id 'java-library'
id 'maven-publish'
id 'net.minecraftforge.licenser'
id 'net.minecraftforge.gradleutils'
alias libs.plugins.licenser
alias libs.plugins.gradleutils
alias libs.plugins.gitversion
alias libs.plugins.changelog
id 'net.minecraftforge.forge.build.convention'
}
apply from: rootProject.file('build_shared.gradle')
gradleutils.displayName = 'LowCodeMod'
final vendor = 'Forge Development LLC'
description = 'Language provider for Minecraft Forge that loads mods without a Java entrypoint.'
java {
toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION)
toolchain.languageVersion = JavaLanguageVersion.of(javaVersion)
withSourcesJar()
}
dependencies {
compileOnly(libs.jetbrains.annotations)
implementation(project(':fmlloader'))
implementation(project(':fmlcore'))
}
tasks.named('jar', Jar).configure {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.lowcodemod',
'FMLModType': 'LANGPROVIDER'
] as LinkedHashMap)
attributes([
'Specification-Title': 'LowCodeMod',
'Specification-Vendor': 'Forge Development LLC',
'Specification-Version': '1.0',
'Implementation-Title': project.name,
'Implementation-Vendor': 'Forge Development LLC',
'Implementation-Version': FORGE_VERSION.split('\\.')[0]
] as java.util.LinkedHashMap, 'net/minecraftforge/fml/lowcodemod/')
}
}
tasks.withType(JavaCompile).configureEach {
options.compilerArgs << '-Xlint:unchecked'
}
license {
header = rootProject.file('LICENSE-header.txt')
}
publishing {
publications.register('mavenJava', MavenPublication).configure {
from components.java
artifactId = 'lowcodelanguage'
pom {
name = project.name
description = 'Language provider for Minecraft Forge that loads resource packs as mods'
url = 'https://github.com/MinecraftForge/MinecraftForge'
PomUtils.setGitHubDetails(pom, 'MinecraftForge')
license PomUtils.Licenses.LGPLv2_1
}
changelog {
from changelogBase
}
dependencies {
compileOnly libs.jetbrains.annotations
implementation projects.fmlloader
implementation projects.fmlcore
}
tasks.withType(JavaCompile).configureEach {
options.compilerArgs << '-Xlint:unchecked'
}
tasks.named('jar', Jar) {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.lowcodemod',
'FMLModType' : 'LANGPROVIDER'
])
attributes([
'Specification-Title' : gradleutils.displayName.get(),
'Specification-Vendor' : vendor,
'Specification-Version' : '1.0',
'Implementation-Title' : project.name,
'Implementation-Vendor' : vendor,
'Implementation-Version': forgeVersion.split('\\.')[0]
], 'net/minecraftforge/fml/lowcodemod/')
}
}
publishing {
repositories {
maven gradleutils.publishingForgeMaven
}
publications.register('mavenJava', MavenPublication) {
changelog.publish(it)
gradleutils.promote(it)
from components.java
pom {
description = project.description
gradleutils.pom.addRemoteDetails(pom)
licenses {
license gradleutils.pom.licenses.LGPLv2_1
}
}
}
}
@@ -74,7 +74,7 @@ public class LowCodeModLanguageProvider implements IModLanguageProvider
.flatMap(fi->fi.getMods().stream())
.map(IModInfo::getModId)
.map(LowCodeModTarget::new)
.collect(Collectors.toMap(LowCodeModTarget::modId, Function.identity(), (a, b)->a));
.collect(Collectors.toMap(LowCodeModTarget::modId, Function.identity(), (a, _)->a));
scanResult.addLanguageLoader(modTargetMap);
};
}
+58 -45
View File
@@ -1,64 +1,77 @@
import net.minecraftforge.gradleutils.PomUtils
plugins {
id 'java-library'
id 'maven-publish'
id 'net.minecraftforge.licenser'
id 'net.minecraftforge.gradleutils'
alias libs.plugins.licenser
alias libs.plugins.gradleutils
alias libs.plugins.gitversion
alias libs.plugins.changelog
id 'net.minecraftforge.forge.build.convention'
}
apply from: rootProject.file('build_shared.gradle')
dependencies {
compileOnly(libs.jetbrains.annotations)
implementation(project(':fmlloader'))
implementation(project(':fmlcore'))
}
gradleutils.displayName = 'MCLanguage'
final vendor = 'Forge Development LLC'
description = 'Language provider for Minecraft Forge that provides Minecraft Itself.'
java {
toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION)
toolchain.languageVersion = JavaLanguageVersion.of(javaVersion)
withSourcesJar()
}
tasks.named('jar', Jar).configure {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.mclanguageprovider',
'FMLModType': 'LANGPROVIDER'
] as LinkedHashMap)
attributes([
'Specification-Title': 'MCLanguage',
'Specification-Vendor': 'Forge Development LLC',
'Specification-Version': '1',
'Implementation-Title': 'MCLanguage',
'Implementation-Vendor': 'Forge Development LLC',
'Implementation-Version': '1.0'
] as LinkedHashMap, 'net/minecraftforge/fml/mclanguageprovider/')
}
}
tasks.withType(JavaCompile).configureEach {
options.compilerArgs << '-Xlint:unchecked'
}
license {
header = rootProject.file('LICENSE-header.txt')
}
publishing {
publications.register('mavenJava', MavenPublication).configure {
from components.java
artifactId = 'mclanguage'
pom {
name = project.name
description = 'Language provider for Minecraft Forge that provides Minecraft Itself'
url = 'https://github.com/MinecraftForge/MinecraftForge'
PomUtils.setGitHubDetails(pom, 'MinecraftForge')
license PomUtils.Licenses.LGPLv2_1
}
changelog {
from changelogBase
}
dependencies {
compileOnly libs.jetbrains.annotations
implementation projects.fmlloader
implementation projects.fmlcore
}
tasks.withType(JavaCompile).configureEach {
options.compilerArgs << '-Xlint:unchecked'
}
tasks.named('jar', Jar) {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.mclanguageprovider',
'FMLModType' : 'LANGPROVIDER'
])
attributes([
'Specification-Title' : gradleutils.displayName.get(),
'Specification-Vendor' : vendor,
'Specification-Version' : '1',
'Implementation-Title' : gradleutils.displayName.get(),
'Implementation-Vendor' : vendor,
'Implementation-Version': '1.0'
], 'net/minecraftforge/fml/mclanguageprovider/')
}
}
publishing {
repositories {
maven gradleutils.publishingForgeMaven
}
publications.register('mavenJava', MavenPublication) {
changelog.publish(it)
gradleutils.promote(it)
from components.java
pom {
description = project.description
gradleutils.pom.addRemoteDetails(pom)
licenses {
license gradleutils.pom.licenses.LGPLv2_1
}
}
}
}
+6
View File
@@ -0,0 +1,6 @@
[versions]
java = "25"
minecraft = "26.3"
minecraft-next = "26.4"
mcp = "20260918.230105"
changelog-base = "66.0"
@@ -1,35 +0,0 @@
--- a/com/mojang/blaze3d/opengl/GlConst.java
+++ b/com/mojang/blaze3d/opengl/GlConst.java
@@ -249,6 +_,10 @@
}
public static int toGlInternalId(final TextureFormat textureFormat) {
+ return toGlInternalId(textureFormat, false);
+ }
+ public static int toGlInternalId(final TextureFormat textureFormat, final boolean stencil) {
+ if (stencil && textureFormat.hasDepthAspect()) return org.lwjgl.opengl.GL30.GL_DEPTH32F_STENCIL8;
return switch (textureFormat) {
case RGBA8 -> 32856;
case RED8 -> 33321;
@@ -258,6 +_,10 @@
}
public static int toGlExternalId(final TextureFormat textureFormat) {
+ return toGlExternalId(textureFormat, false);
+ }
+ public static int toGlExternalId(final TextureFormat textureFormat, final boolean stencil) {
+ if (stencil && textureFormat.hasDepthAspect()) return org.lwjgl.opengl.GL30.GL_DEPTH_STENCIL;
return switch (textureFormat) {
case RGBA8 -> 6408;
case RED8 -> 6403;
@@ -267,6 +_,10 @@
}
public static int toGlType(final TextureFormat textureFormat) {
+ return toGlType(textureFormat, false);
+ }
+ public static int toGlType(final TextureFormat textureFormat, boolean stencil) {
+ if (stencil && textureFormat.hasDepthAspect()) return org.lwjgl.opengl.GL30.GL_FLOAT_32_UNSIGNED_INT_24_8_REV;
return switch (textureFormat) {
case RGBA8 -> 5121;
case RED8 -> 5121;
@@ -1,62 +0,0 @@
--- a/com/mojang/blaze3d/opengl/GlDevice.java
+++ b/com/mojang/blaze3d/opengl/GlDevice.java
@@ -129,7 +_,12 @@
final int depthOrLayers,
final int mipLevels
) {
- return this.createTexture(this.debugLabels.exists() && label != null ? label.get() : null, usage, format, width, height, depthOrLayers, mipLevels);
+ return this.createTexture(label, usage, format, width, height, depthOrLayers, mipLevels, false);
+ }
+
+ @Override
+ public GpuTexture createTexture(final @Nullable Supplier<String> label, @GpuTexture.Usage final int usage, final TextureFormat format, final int width, final int height, final int depthOrLayers, final int mipLevels, final boolean stencil ) {
+ return this.createTexture(this.debugLabels.exists() && label != null ? label.get() : null, usage, format, width, height, depthOrLayers, mipLevels, stencil);
}
@Override
@@ -142,6 +_,11 @@
final int depthOrLayers,
final int mipLevels
) {
+ return this.createTexture(label, usage, format, width, height, depthOrLayers, mipLevels, false);
+ }
+
+ @Override
+ public GpuTexture createTexture(@Nullable String label, @GpuTexture.Usage final int usage, final TextureFormat format, final int width, final int height, final int depthOrLayers, final int mipLevels, boolean stencil) {
GlStateManager.clearGlErrors();
int id = GlStateManager._genTexture();
if (label == null) {
@@ -171,12 +_,12 @@
GlStateManager._texImage2D(
cubeTarget,
i,
- GlConst.toGlInternalId(format),
+ GlConst.toGlInternalId(format, stencil),
width >> i,
height >> i,
0,
- GlConst.toGlExternalId(format),
- GlConst.toGlType(format),
+ GlConst.toGlExternalId(format, stencil),
+ GlConst.toGlType(format, stencil),
null
);
}
@@ -184,7 +_,7 @@
} else {
for (int i = 0; i < mipLevels; i++) {
GlStateManager._texImage2D(
- target, i, GlConst.toGlInternalId(format), width >> i, height >> i, 0, GlConst.toGlExternalId(format), GlConst.toGlType(format), null
+ target, i, GlConst.toGlInternalId(format, stencil), width >> i, height >> i, 0, GlConst.toGlExternalId(format, stencil), GlConst.toGlType(format, stencil), null
);
}
}
@@ -195,7 +_,7 @@
} else if (error != 0) {
throw new IllegalStateException("OpenGL error " + error);
} else {
- GlTexture texture = new GlTexture(usage, label, format, width, height, depthOrLayers, mipLevels, id);
+ GlTexture texture = new GlTexture(usage, label, format, width, height, depthOrLayers, mipLevels, id, stencil);
this.debugLabels.applyLabel(texture);
return texture;
}
@@ -1,29 +0,0 @@
--- a/com/mojang/blaze3d/opengl/GlTexture.java
+++ b/com/mojang/blaze3d/opengl/GlTexture.java
@@ -28,8 +_,13 @@
final int mipLevels,
final int id
) {
+ this(usage, label, format, width, height, depthOrLayers, mipLevels, id, false);
+ }
+
+ protected GlTexture(int usage, String label, TextureFormat format, int width, int height, int depthOrLayers, int mipLevels, int id, boolean stencil) {
super(usage, label, format, width, height, depthOrLayers, mipLevels);
this.id = id;
+ this.stencilEnabled = stencil;
}
@Override
@@ -96,5 +_,12 @@
if (this.closed && this.views == 0) {
this.destroyImmediately();
}
+ }
+
+ private final boolean stencilEnabled;
+
+ @Override
+ public boolean isStencilEnabled() {
+ return this.stencilEnabled;
}
}
@@ -1,18 +1,18 @@
--- a/com/mojang/blaze3d/pipeline/RenderTarget.java
+++ b/com/mojang/blaze3d/pipeline/RenderTarget.java
@@ -80,7 +_,7 @@
@@ -96,7 +_,7 @@
this.width = width;
this.height = height;
if (this.useDepth) {
- this.depthTexture = device.createTexture(() -> this.label + " / Depth", 15, TextureFormat.DEPTH32, width, height, 1, 1);
+ this.depthTexture = device.createTexture(() -> this.label + " / Depth", 15, TextureFormat.DEPTH32, width, height, 1, 1, this.stencilEnabled);
if (this.depthFormat != null) {
- this.depthTexture = device.createTexture(() -> this.label + " / Depth", 15, this.depthFormat, width, height, 1, 1);
+ this.depthTexture = device.createTexture(() -> this.label + " / Depth", 15, this.depthFormat, width, height, 1, 1, this.stencilEnabled);
this.depthTextureView = device.createTextureView(this.depthTexture);
}
@@ -124,5 +_,26 @@
@@ -140,5 +_,26 @@
public @Nullable GpuTextureView getDepthTextureView() {
return this.depthTextureView;
public boolean hasDepth() {
return this.depthFormat != null;
+ }
+
+ private boolean stencilEnabled = false;
@@ -1,31 +1,33 @@
--- a/com/mojang/blaze3d/platform/Window.java
+++ b/com/mojang/blaze3d/platform/Window.java
@@ -97,8 +_,9 @@
Monitor initialMonitor = this.screenManager.getMonitor(GLFW.glfwGetPrimaryMonitor());
this.windowedWidth = this.width = allowedWindowMinSize(displayData.width());
this.windowedHeight = this.height = allowedWindowMinSize(displayData.height());
- this.handle = this.createWindow(backend, this.width, this.height, title, this.fullscreen && initialMonitor != null ? initialMonitor.getMonitor() : 0L);
+ this.handle = net.minecraftforge.fml.loading.ImmediateWindowHandler.setupMinecraftWindow(this.width, this.height, title, this.fullscreen && initialMonitor != null ? initialMonitor.getMonitor() : 0L, () -> backend);
this.backend = backend;
@@ -114,9 +_,10 @@
Monitor initialMonitor = monitorManager.getMonitor(SDLVideo.SDL_GetPrimaryDisplay());
this.windowedWidth = this.width = allowedWindowMinSize(displayData.width(), 320);
this.windowedHeight = this.height = allowedWindowMinSize(displayData.height(), 240);
- this.handle = this.createWindow(backend, this.width, this.height, title);
+ this.handle = net.minecraftforge.fml.loading.ImmediateWindowHandler.setupMinecraftWindow(this.width, this.height, title, () -> backend);
this.setWindowMaxSize(maximumSize, maximumSize);
MacosUtil.disableCloseWindowMenuItem();
+ if (!net.minecraftforge.fml.loading.ImmediateWindowHandler.positionWindow(Optional.ofNullable(initialMonitor), w->this.width = this.windowedWidth = w, h->this.height = this.windowedHeight = h, x->this.x = this.windowedX = x, y->this.y = this.windowedY = y)) {
if (initialMonitor != null) {
VideoMode mode = initialMonitor.getPreferredVidMode(this.fullscreen ? this.preferredFullscreenVideoMode : Optional.empty());
this.windowedX = this.x = initialMonitor.getX() + mode.getWidth() / 2 - this.width / 2;
@@ -110,6 +_,7 @@
this.windowedX = this.x = actualX[0];
this.windowedY = this.y = actualY[0];
this.windowedX = this.x = initialMonitor.x() + (initialMonitor.w() - this.width) / 2;
this.windowedY = this.y = initialMonitor.y() + (initialMonitor.h() - this.height) / 2;
@@ -132,6 +_,7 @@
this.windowedY = this.y = actualY.get(0);
}
}
+ }
this.setMode();
this.refreshFramebufferSize();
@@ -300,6 +_,9 @@
GLFW.glfwGetFramebufferSize(this.handle, outWidth, outHeight);
this.framebufferWidth = outWidth[0] > 0 ? outWidth[0] : 1;
this.framebufferHeight = outHeight[0] > 0 ? outHeight[0] : 1;
+ if (this.framebufferHeight == 0 || this.framebufferWidth == 0) {
+ net.minecraftforge.fml.loading.ImmediateWindowHandler.updateFBSize(w -> this.framebufferWidth = w, h -> this.framebufferHeight = h);
+ }
@@ -148,6 +_,10 @@
}
private void onResize(final long handle, final int newWidth, final int newHeight) {
private long createWindow(final GpuBackend backend, final int width, final int height, final String title) {
+ return createWindowStatic(backend, width, height, title);
+ }
+
+ public static long createWindowStatic(final GpuBackend backend, final int width, final int height, final String title) {
long flags = 8224L;
long windowHandle = backend.createWindow(title, width, height, 8224L);
if (windowHandle == 0L) {
@@ -1,46 +0,0 @@
--- a/com/mojang/blaze3d/systems/GpuDevice.java
+++ b/com/mojang/blaze3d/systems/GpuDevice.java
@@ -61,6 +_,21 @@
return this.backend.createTexture(label, usage, format, width, height, depthOrLayers, mipLevels);
}
+ /** Forge: same as {@link #createTexture(Supplier, int, TextureFormat, int, int, int)} but with stencil support */
+ public GpuTexture createTexture(
+ final @Nullable Supplier<String> label,
+ @GpuTexture.Usage final int usage,
+ final TextureFormat format,
+ final int width,
+ final int height,
+ final int depthOrLayers,
+ final int mipLevels,
+ final boolean stencil
+ ) {
+ this.verifyTextureCreationArgs(usage, width, height, depthOrLayers, mipLevels);
+ return this.backend.createTexture(label, usage, format, width, height, depthOrLayers, mipLevels, stencil);
+ }
+
public GpuTexture createTexture(
final @Nullable String label,
@GpuTexture.Usage final int usage,
@@ -72,6 +_,21 @@
) {
this.verifyTextureCreationArgs(usage, width, height, depthOrLayers, mipLevels);
return this.backend.createTexture(label, usage, format, width, height, depthOrLayers, mipLevels);
+ }
+
+ /** Forge: same as {@link #createTexture(Supplier, int, TextureFormat, int, int, int)} but with stencil support */
+ public GpuTexture createTexture(
+ final @Nullable String label,
+ @GpuTexture.Usage final int usage,
+ final TextureFormat format,
+ final int width,
+ final int height,
+ final int depthOrLayers,
+ final int mipLevels,
+ final boolean stencil
+ ) {
+ this.verifyTextureCreationArgs(usage, width, height, depthOrLayers, mipLevels);
+ return this.backend.createTexture(label, usage, format, width, height, depthOrLayers, mipLevels, stencil);
}
private void verifyTextureCreationArgs(@GpuTexture.Usage final int usage, final int width, final int height, final int depthOrLayers, final int mipLevels) {
@@ -1,22 +0,0 @@
--- a/com/mojang/blaze3d/systems/GpuDeviceBackend.java
+++ b/com/mojang/blaze3d/systems/GpuDeviceBackend.java
@@ -30,9 +_,19 @@
@Nullable Supplier<String> label, @GpuTexture.Usage final int usage, TextureFormat format, int width, int height, int depthOrLayers, int mipLevels
);
+ /** Forge: same as {@link #createTexture(Supplier<String>, int, TextureFormat, int, int, int)} but with stencil support */
+ default GpuTexture createTexture(@Nullable Supplier<String> label, @GpuTexture.Usage final int usage, TextureFormat format, int width, int height, int depthOrLayers, int mipLevels, boolean stencil) {
+ return createTexture(label, usage, format, width, height, depthOrLayers, mipLevels);
+ }
+
GpuTexture createTexture(
@Nullable String label, @GpuTexture.Usage final int usage, TextureFormat format, int width, int height, int depthOrLayers, int mipLevels
);
+
+ /** Forge: same as {@link #createTexture(String, int, TextureFormat, int, int, int)} but with stencil support */
+ default GpuTexture createTexture(@Nullable String label, @GpuTexture.Usage final int usage, TextureFormat format, int width, int height, int depthOrLayers, int mipLevels, boolean stencil) {
+ return createTexture(label, usage, format, width, height, depthOrLayers, mipLevels);
+ }
GpuTextureView createTextureView(GpuTexture texture);
@@ -1,11 +0,0 @@
--- a/com/mojang/blaze3d/textures/GpuTexture.java
+++ b/com/mojang/blaze3d/textures/GpuTexture.java
@@ -8,7 +_,7 @@
import net.minecraftforge.api.distmarker.OnlyIn;
@OnlyIn(Dist.CLIENT)
-public abstract class GpuTexture implements AutoCloseable {
+public abstract class GpuTexture implements AutoCloseable, net.minecraftforge.client.extensions.IForgeGpuTexture {
public static final int USAGE_COPY_DST = 1;
public static final int USAGE_COPY_SRC = 2;
public static final int USAGE_TEXTURE_BINDING = 4;
@@ -1,6 +1,6 @@
--- a/com/mojang/blaze3d/vertex/PoseStack.java
+++ b/com/mojang/blaze3d/vertex/PoseStack.java
@@ -16,7 +_,7 @@
@@ -17,7 +_,7 @@
import org.joml.Vector3fc;
@OnlyIn(Dist.CLIENT)
@@ -1,32 +0,0 @@
--- a/com/mojang/blaze3d/vertex/VertexFormat.java
+++ b/com/mojang/blaze3d/vertex/VertexFormat.java
@@ -29,6 +_,7 @@
private final int[] offsetsByElement = new int[32];
private @Nullable GpuBuffer immediateDrawVertexBuffer;
private @Nullable GpuBuffer immediateDrawIndexBuffer;
+ private final com.google.common.collect.ImmutableMap<String, VertexFormatElement> elementMapping;
private VertexFormat(final List<VertexFormatElement> elements, final List<String> names, final IntList offsets, final int vertexSize) {
this.elements = elements;
@@ -41,6 +_,11 @@
int index = element != null ? elements.indexOf(element) : -1;
this.offsetsByElement[id] = index != -1 ? offsets.getInt(index) : -1;
}
+
+ ImmutableMap.Builder<String, VertexFormatElement> elementMapping = ImmutableMap.builder();
+ for (int i = 0; i < elements.size(); i++)
+ elementMapping.put(names.get(i), elements.get(i));
+ this.elementMapping = elementMapping.buildOrThrow();
}
public static VertexFormat.Builder builder() {
@@ -139,6 +_,9 @@
this.immediateDrawIndexBuffer = uploadToBuffer(this.immediateDrawIndexBuffer, buffer, 72, () -> "Immediate index buffer for " + this);
return this.immediateDrawIndexBuffer;
}
+
+ public ImmutableMap<String, VertexFormatElement> getElementMapping() { return elementMapping; }
+ public int getOffset(int index) { return offsetsByElement[index]; }
@OnlyIn(Dist.CLIENT)
public static class Builder {
@@ -1,6 +1,6 @@
--- a/com/mojang/math/Transformation.java
+++ b/com/mojang/math/Transformation.java
@@ -16,7 +_,7 @@
@@ -14,7 +_,7 @@
import org.joml.Vector3fc;
import org.jspecify.annotations.Nullable;
@@ -9,21 +9,21 @@
private static final Vector3fc ZERO_TRANSLATION = new Vector3f();
private static final Vector3fc UNIT_SCALE = new Vector3f(1.0F, 1.0F, 1.0F);
private static final Quaternionfc ZERO_ROTATION = new Quaternionf();
@@ -171,6 +_,20 @@
@@ -172,6 +_,20 @@
this.scale().lerp(that.scale(), progress, new Vector3f()),
this.rightRotation().slerp(that.rightRotation(), progress, new Quaternionf())
);
+ }
+
+ private Matrix3f normalTransform = null;
+ public Matrix3f getNormalMatrix() {
+ private org.joml.Matrix3f normalTransform = null;
+ public org.joml.Matrix3f getNormalMatrix() {
+ checkNormalTransform();
+ return normalTransform;
+ }
+
+ private void checkNormalTransform() {
+ if (normalTransform == null) {
+ normalTransform = new Matrix3f(matrix);
+ normalTransform = new org.joml.Matrix3f(matrix);
+ normalTransform.invert();
+ normalTransform.transpose();
+ }
@@ -5,8 +5,8 @@
@Override
+ public boolean keyPressed(net.minecraft.client.input.KeyEvent event) {
+ if (event.key() == org.lwjgl.glfw.GLFW.GLFW_KEY_ESCAPE) {
+ minecraft.setScreen(this.nextScreen);
+ if (event.isEscape()) {
+ minecraft.gui.setScreen(this.nextScreen);
+ return true;
+ }
+ return super.keyPressed(event);
@@ -1,6 +1,6 @@
--- a/com/mojang/realmsclient/gui/screens/RealmsResetWorldScreen.java
+++ b/com/mojang/realmsclient/gui/screens/RealmsResetWorldScreen.java
@@ -302,7 +_,7 @@
@@ -300,7 +_,7 @@
int y = this.getY();
graphics.blit(RenderPipelines.GUI_TEXTURED, this.image, x + 2, y + 2, 0.0F, 0.0F, 56, 56, 56, 56, 56, 56, color);
graphics.blitSprite(RenderPipelines.GUI_TEXTURED, SLOT_FRAME_SPRITE, x, y, 60, 60, color);
Loaded 100 of 1096 files, more files were not shown because too many files have changed in this diff. Show more