Compare commits

...
113 Commits
Author SHA1 Message Date
LexManos 7d0bd1898f 26.3 RB 1 2026-10-21 11:02:26 -07:00
LexManos 55f03a2d86 Fix incorrect filter when placing items on bundles. Closes #10902 2026-10-16 13:40:27 -07:00
LexManos c328912074 Add protection against large network channel lists 2026-10-12 19:18:15 -07:00
BonsUnleashed aa4e31beab Store object holder handlers per registry name (#10899) 2026-10-12 13:18:32 -07:00
LexManos 4249a25ec7 Fix Spectator Mode having block overlay when inside blocks. Fixes #10900 2026-10-09 13:18:39 -07:00
LexManos 939fb13d3b Fix MDK including wrong resources. Fixes #10892 2026-09-30 14:11:21 -07:00
LexManos 932c6e9649 Fix checkLicense tasks with projectInfo task 2026-09-28 11:55:03 -07:00
LexManos ed6d9ff0e4 Move tests and mdk to sub-project (#10887)
This introduces two new artifacts
forge-examiner: A new mod locator that allows for a single directory to be split into multiple modules
forge-tests: Our test mods, showing examples of data gen, game tests, and various Forge features
Adds a new system useful for projects with multiple sub-projects that use datagen
2026-09-28 10:56:58 -07:00
Matt 851a57e808 Match ForgeRenderTypes OIT pipelines to vanilla, fixes #10890 (#10891) 2026-09-27 09:17:33 -07:00
LexManos 49aac2c496 Fix entities not being pushed by fluids 2026-09-26 18:32:39 -07:00
LexManos 26eeb1cc70 Fix typo in BlockScanUtils patch, fixes placing cushions. Closes #10889 2026-09-25 19:13:42 -07:00
LexManos 5afe3c050c Migrate to fully declarative ForgeGradlePlugin and local build-logic 2026-09-22 16:16:56 -07:00
LexManos 839fd66e52 Fix players dropping items. Closes #10885 2026-09-19 14:02:52 -07:00
LexManos fea644f6d2 Fix players not drowning propely 2026-09-19 14:02:21 -07:00
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
Paint_Ninja 5e421e8b8b Add c:tools/trident item tag, update contents of c:tools/melee_weapon, remove c:tools/spear (#10808)
- Breaking change: `c:tools/spear` has been renamed to `c:tools/trident` to match other loaders
- The contents of the `c:tools/melee_weapon` tag now includes spears
2026-04-08 20:26:14 +01:00
LexManos 5f37e8af53 Work around 3rd party launchers who build incorrect classpaths Closes #10797 2026-04-03 09:31:40 -07:00
LexManos 5ab792f1d7 26.1.1 2026-04-01 10:40:56 -07:00
LexManos 38ebbf189c Fix arrows not being used correctly. 2026-03-29 15:44:31 -07:00
LexManos f247eebd55 Fix dyed Items are not rendered with color. Closes #10799 2026-03-29 02:21:41 -07:00
LexManos 555989cf10 Fix not remembering experimental warning confirmation 2026-03-29 02:20:50 -07:00
LexManos 49fe93b1b5 Fix incorrect argument passed to LootTableLoadEvent 2026-03-29 02:19:17 -07:00
LexManos 48167e93b4 Update MCPConfig to fix local variable names 2026-03-28 23:28:36 -07:00
LexManos 2bd132a6f8 Fix not regaining air bubbles. Closes #10798 2026-03-27 16:05:55 -07:00
LexManos b8168a12df Fix misaligned PlayerTick hook. Closes #10796 2026-03-25 18:35:02 -07:00
LexManos df271e0265 Fix loop in LoadingErrorScreen Closes #10795 2026-03-25 10:30:27 -07:00
LexManos d7687a72d5 Fix MDK by bumping to FG 7.0.17 2026-03-24 21:14:22 -07:00
LexManosandPaint_Ninja 047e5fe252 26.1 Update
Minecraft is now Unobfuscated and requires Java 25
Lots of changes/code cleanup.
See https://github.com/MinecraftForge/MinecraftForge-Experimental/tree/26.1-dev for non-squashed commits.

Co-authored-by: Paint_Ninja <[email protected]>
2026-03-24 14:07:41 -07:00
LexManos 5ac2675f87 Fix server data packs from mods using incorrect version. Closes #10794 2026-03-23 11:45:01 -07:00
LexManos b544849f7d Fix wrong color argument for FogColor event
Fix incorrect patch for carving pumpinks
2026-03-16 18:38:36 -07:00
LexManos 39683f28dd Fix swapped variables being passes to ItemStackedOnOtherEvent Closes #10781 2026-03-05 12:54:17 -08:00
LexManos da4fd10ee8 Remove --validate from forge data tasks, no longer needed 2026-03-05 12:53:46 -08:00
Paint_Ninja e1c2add370 Skip runtime annotation scanning for MC itself, blacklist common inconsequential annotations (#10773)
The results of scan data for MC itself aren't useful nor needed in practice. Skipping this speeds up the scanner and saves a bunch of memory. Also blacklisted common inconsequential annotations and cached common super types for further memory usage savings.
2026-02-18 23:13:06 +00:00
LexManos c71ba862cf 1.20.11 RB 1 2026-02-15 15:46:20 -08:00
Paint_Ninja 3c057d277b Deprecate RuntimeDistCleaner (#10771)
- Logs warnings when `@OnlyIn` is used by mod devs in their own code
- In a future release, only applies to MC classes during server runs
- Future FG7 and installer versions may shift RuntimeDistCleaner's server effects to compile/setup/install time to completely remove the need for runtime stripping
2026-02-15 15:04:34 -08:00
LexManos b99abff6ce Bump ASM to 9.9.1
Bump EventBus to 7.0.1
Bump MixinExtras to 0.5.3
2026-02-15 13:51:14 -08:00
LexManos 558e7d754e Fix bookshelves having too much enchanting power. Fixes #10770 2026-02-15 13:30:08 -08:00
Jonathing d29524bac3 Migrate the MDK to ForgeGradle 7 2026-02-15 13:13:55 -08:00
LexManos 929064ee69 Add support for multiple access transformer configs. Closes #10758 2026-02-11 10:38:24 -08:00
LexManos b4024a7884 Update discord links in issue templates 2026-02-09 08:26:00 -08:00
LexManos 07f6499143 Remove JavaScript CoreMods and Nashorn (#10746)
The ASMAPI helper methods have been split off into a separate dep which is included with Forge for Java transformers to use.
2026-01-23 22:28:27 +00:00
LexManos 236842f210 Update SecureModules to address ZipPath bug with brackets. Closes #10755 2026-01-20 18:05:10 -08:00
LexManos ef811aebe8 Optimize memory allocation for checking entity fluid collisions. Closes #10745 2026-01-10 11:06:46 -08:00
LexManos d61de63482 Allow accessing Registry context from TagValueOutput 2026-01-03 12:24:53 -08:00
LexManos 8368a1d20a Don't save capabilities if level is null as we don't have enough context 2026-01-03 11:41:49 -08:00
Matt 8b8d3fdef7 Fix not being able to sleep. Steve is very tired. (#10736) 2025-12-14 17:33:32 -08:00
LexManos f5d4fbbee5 Fix AT lines which didn't work on some old dev environments. Closes #10735 2025-12-11 11:07:23 -08:00
LexManos 6f4f60e213 Bump S2S, fixes patches in UserDev. Closes #1011 2025-12-10 01:07:59 -08:00
LexManos 01bfc33b28 1.21.11 Mounts of Mayhem Release
Lots of rendering related changes, specifically for BackedQuad and IQuadTransformers.
Removed our deprecated model generators, modders should have been using vanilla's for a while.
2025-12-09 18:28:42 -08:00
Matt 17e1002e86 Expose movevector field for usage with MovementInputUpdateEvent (#10732) 2025-12-06 10:42:49 -08:00
LexManos da1b8f4efa Mark applyServerBinpatches as a dependency of BundleList.
Fixes shim jar config
2025-12-03 08:56:24 -08:00
LexManos 42bbe44a34 Split setup and publish commands in actions 2025-12-02 13:58:34 -08:00
LexManos f095474adf Github Actions 2025-12-02 13:44:20 -08:00
Paint_Ninja 1b5ca64f20 Fix BusGroup.DEFAULT not being trimmed after mod loading completes, also trim mod BusGroups now too (#10721)
Saves some memory by trimming various backing collections of loaded EventBuses on the default BusGroup
2025-12-02 12:17:33 -08:00
LexManos b518c804fb 1.21.10 RB 2025-11-19 15:01:12 -08:00
Paint_Ninja e50a0ffc6c Add c:nuggets/copper item tag (#10716) 2025-11-19 09:12:27 -08:00
Matt 7ea0a41fe2 Fix ItemStack in RenderTooltipEvent always EMPTY, merges RenderTooltipEvent.Background into .Pre (#10655) (#10702) 2025-11-14 07:42:03 -08:00
Matt f3d16621c0 Provide LevelRenderState during frame pass construction Addresses #10867 (#10688) 2025-11-12 13:20:34 -08:00
Lemonnik6484 aa55a6a831 Make Mixin dev mode check a little looser (#10713) 2025-11-10 11:27:19 -08:00
cech12 3449217c65 Expand hooks for custom shears (#10704)
Fix shearing leads with custom shears (interacting with entity & leash knot)
Fix shearing poppy from copper golem for custom shears
2025-11-05 11:53:50 -08:00
JonathingandLexManos 41609a03d2 Introduce MixinExtras as an included, optional dependency (#10709)
Declare ME in userdev config as an effective compile transitive

Co-authored-by: LexManos <[email protected]>
2025-11-05 11:34:23 -08:00
LexManos 9b8e5c7109 Add workaround for Mixin bug. Closes #10710 2025-11-02 18:39:39 -08:00
LexManos f05ad3afd0 Fix ROIMFS not being on boot classloader for dedicated server. 2025-10-31 13:02:08 -07:00
LexManos 6a615f1a4b Add support for Forge shipping with JarInJar options. (#10683)
These are optional libraries that are only loaded when requested by mods.
Includes new ReadOnlyInMemoryFileSystem to improve performance of JarInJar parsing.
2025-10-30 14:48:46 -07:00
LexManos 4376cd6b85 Remove generic from AttachCapabilitiesEvent to help prevent modders from having unchecked casts. Closes #10707 2025-10-27 10:45:43 -07:00
LexManos d17a111dc0 Fix misaligned patch in DedicatedServer 2025-10-23 00:10:46 -07:00
Jonathing 1b99b2cea4 Revert "Remove runtime mods.toml string substitution (#10698)" (#10701)
This reverts commit fc80e30289, 17ce723edf, and 0e6341a23e.
2025-10-21 17:51:24 -07:00
Matt 4ef6695a30 Add support for custom particle groups (#10692) 2025-10-20 17:21:50 -07:00
Jonathing 0e6341a23e Fix Minecraft version being incorrectly included in Forge mods.toml (#10700) 2025-10-20 01:06:07 -07:00
Jonathing 17ce723edf Fix universal jar missing Forge version (#10699) 2025-10-20 00:41:21 +01:00
Paint_Ninja fc80e30289 Remove runtime mods.toml string substitution (#10698) 2025-10-19 21:45:17 +01:00
LexManos ecd1491f96 Fix Manually created RegistryObjects not being populated. Closes #10686
This partially reverts commit 1268d4c3a8acd9f780d2048d5a527206707f8e35.
But makes the classes package private so nobody can access.
2025-10-12 10:49:47 -07:00
Paint_Ninja 8f7fbc76e1 Simplify ModLoader#hasCompletedState, some more cleanup to FML internals 2025-10-11 13:43:14 +01:00
Paint_Ninja ec405958c9 Skip processing vanilla classes in RuntimeDistCleaner when running on a client (#10682) 2025-10-10 21:31:14 +01:00
Paint_Ninja e2ff990601 Require CapabilityToken subclasses to be anonymous (#10681) 2025-10-10 21:30:22 +01:00
Paint_Ninja 8443262e39 Improve event javadocs based on community feedback, apply jSpecify to more events 2025-10-10 14:57:38 +01:00
LexManos 8e09d47cd6 1.21.10 2025-10-07 10:20:47 -07:00
Paint_Ninja 0404d2d776 Bump EventBus for more javadocs 2025-10-06 20:14:28 +01:00
Paint_Ninja aea804cb6e Make BuildCreativeModeTabContentsEvent a record 2025-10-06 20:12:13 +01:00
LexManos 0bffcb83c5 Fix CustomizeGuiOverlayEvent mistakenly being a RecordEvent 2025-10-05 09:51:12 -07:00
Paint_Ninja 9a022d1451 Move more events away from the mod BusGroup (#10676)
Also adds more validation to strict mode. See the GitHub PR for further details.
2025-10-05 14:02:45 +01:00
LexManos 58aa3b276c Add the new vanilla info object to Keyboard and Mouse related events. 2025-10-02 12:09:01 -07:00
LexManosandPaint_Ninja b0ccf1a480 Minecraft 1.21.9 update.
There a lot of performance improvements related to events.
  No more InheritableEvent for abstract events that you shouldn't add listeners to anyway, like EntityEvent. Use their sub-events.
  Made a lot more events records and interfaces. Mostly just a binary break, but has better performance.
  Mod bus posting methods are now static, no more ModLoader.get() for those.

ObjectHolder was removed, use RegistryObject.
Changed RuntimeEnumExtender to only target Minecraft classes. If you want other modders to interact with your API, use an Interface instead of Enum.
Moved some abstract event methods to only the relevant sub-events now, e.g. can no longer check the side of a ClientTickEvent, because it'll always be client

Rendering has changed a lot, they now batch most things together so that the game will render faster.
RenderHighlightEvent changed a lot

See https://github.com/MinecraftForge/MinecraftForge-Experimental/tree/1.21.9-dev for unsquashed commits.

Co-authored-by: Paint_Ninja <[email protected]>
2025-10-01 13:05:12 -07:00
LexManos c1d703e395 Fix Crafters not inserting into IItemHandlers. Closes #10667 2025-09-23 13:42:29 -07:00
LexManos 728fd6ff24 Optimize ConcatenatedListView's Iterator and fix hidden CreativeTabs taking up page space. 2025-09-23 13:37:53 -07:00
Paint_Ninja 0a885a77e7 [1.21.8] Deprecate global IExtensibleEnum support (#10662)
This is intended for use with Minecraft classes only. Mod devs can support extending enums through other, more efficient means, such as converting their enum to a record and having static final fields of instances that correspond to each enum value.
2025-09-18 17:13:58 +01:00
Matt 15dbadaae6 Fix alpha channel of creative mode tab title color (#10661) 2025-09-18 08:56:02 -07:00
Jonathing 4c1c3aafdb Deprecate JS CoreMods, remove Forge's usages of them (#10659) 2025-09-16 20:13:30 +01:00
Joseph T. McQuigg f06727a205 Replace Blocks.FARMLAND checks with instanceof FarmBlock (#10647)
Signed-off-by: Joseph T. McQuigg <[email protected]>
2025-09-10 20:17:42 -07:00
2451 changed files with 31129 additions and 47090 deletions

No files matched your search

+1 -1
View File
@@ -10,4 +10,4 @@ assignees: ''
<!-- Please note, feature requests that are submitted as GitHub issues here are rarely implemented. -->
<!-- If you'd like to see something in Forge, you should be ready to implement it as a PR. -->
<!-- If you're not sure, open a Draft PR or discuss it in the Discord server. -->
<!-- Discord: https://discord.gg/UvedJ9m -->
<!-- Discord: https://discord..minecraftforge.net/ -->
+1
View File
@@ -11,6 +11,7 @@ assignees: ''
<!-- For support with crashes and issues, please create a post on the forum instead: -->
<!-- https://www.minecraftforge.net/forum/forum/18-support-bug-reports/ -->
<!-- You can also use the discord for support, https://discord.minecraftforge.net/ -->
**Minecraft Version:** {Minecraft version}
+28
View File
@@ -0,0 +1,28 @@
name: Publish
on:
push:
branches: [ '26.3' ]
permissions:
contents: read
jobs:
build:
uses: MinecraftForge/SharedActions/.github/workflows/gradle.yml@main
with:
java: 21
gradle_setup: './gradlew setup'
gradle_tasks: 'build publish'
artifact_name: 'forge'
secrets:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
PROMOTE_ARTIFACT_WEBHOOK: ${{ secrets.PROMOTE_ARTIFACT_WEBHOOK }}
PROMOTE_ARTIFACT_USERNAME: ${{ secrets.PROMOTE_ARTIFACT_USERNAME }}
PROMOTE_ARTIFACT_PASSWORD: ${{ secrets.PROMOTE_ARTIFACT_PASSWORD }}
MAVEN_USER: ${{ secrets.MAVEN_USER }}
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
SIGN_KEY_ALIAS: ${{ secrets.SIGN_KEY_ALIAS }}
SIGN_KEY_PASSWORD: ${{ secrets.SIGN_KEY_PASSWORD }}
SIGN_KEYSTORE_PASSWORD: ${{ secrets.SIGN_KEYSTORE_PASSWORD }}
SIGN_KEYSTORE_DATA: ${{ secrets.SIGN_KEYSTORE_DATA }}
+1 -1
View File
@@ -7,7 +7,7 @@ jobs:
name: Upload payload
runs-on: ubuntu-latest
steps:
- uses: actions/upload-artifact@v3 # Payload artifact is consumed in Actionable
- uses: actions/upload-artifact@v4 # Payload artifact is consumed in Actionable
with:
name: payload
path: ${{ github.event_path }}
@@ -7,7 +7,7 @@ jobs:
name: Upload payload
runs-on: ubuntu-latest
steps:
- uses: actions/upload-artifact@v3 # Payload artifact is consumed in Actionable
- uses: actions/upload-artifact@v4 # Payload artifact is consumed in Actionable
with:
name: payload
path: ${{ github.event_path }}
+20 -9
View File
@@ -1,3 +1,6 @@
#forgedev dev if the folder exists in here
forgedev
#eclipse
**/bin
**/.settings
@@ -8,7 +11,7 @@
/eclipse/
#idea
/.idea
**/.idea
/*/out
/classes
*.iml
@@ -22,18 +25,20 @@
/*/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/
/projects/**/out/
/projects/**/run/
/projects/**/*.launch
/*/runs/
/repo/
src/*/generated/**/.cache/
/rejects-*/
**/src/*/generated/**/.cache/
**/*.launch
**/.project_info.properties
# Generated by gradle every import
**/src/main/resources/META-INF/MANIFEST.MF
@@ -46,3 +51,9 @@ src/*/generated/**/.cache/
/*/.gitignore
/*/.factorypath
/*/.apt_generated/
/fmlcore/logs/
/lib/
/_actual/
/runs/
/forge.ipr
/forge.iws
-104
View File
@@ -1,104 +0,0 @@
<?xml version="1.0"?>
<project>
<modelVersion>4.0.0</modelVersion>
<name>MinecraftForge_Forge Config DSL Script</name>
<groupId>MinecraftForge_Forge</groupId>
<artifactId>MinecraftForge_Forge_dsl</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.jetbrains.teamcity</groupId>
<artifactId>configs-dsl-kotlin-parent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<repositories>
<repository>
<id>jetbrains-all</id>
<url>https://download.jetbrains.com/teamcity-repository</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>teamcity-server</id>
<url>https://teamcity.minecraftforge.net/app/dsl-plugins-repository</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>JetBrains</id>
<url>https://download.jetbrains.com/teamcity-repository</url>
</pluginRepository>
</pluginRepositories>
<build>
<sourceDirectory>${basedir}</sourceDirectory>
<plugins>
<plugin>
<artifactId>kotlin-maven-plugin</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
<version>${kotlin.version}</version>
<configuration/>
<executions>
<execution>
<id>compile</id>
<phase>process-sources</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>process-test-sources</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jetbrains.teamcity</groupId>
<artifactId>teamcity-configs-maven-plugin</artifactId>
<version>${teamcity.dsl.version}</version>
<configuration>
<format>kotlin</format>
<dstDir>target/generated-configs</dstDir>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.jetbrains.teamcity</groupId>
<artifactId>configs-dsl-kotlin</artifactId>
<version>${teamcity.dsl.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.teamcity</groupId>
<artifactId>configs-dsl-kotlin-plugins</artifactId>
<version>1.0-SNAPSHOT</version>
<type>pom</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>${kotlin.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-script-runtime</artifactId>
<version>${kotlin.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
-193
View File
@@ -1,193 +0,0 @@
import jetbrains.buildServer.configs.kotlin.v2019_2.*
import jetbrains.buildServer.configs.kotlin.v2019_2.projectFeatures.githubIssues
import jetbrains.buildServer.configs.kotlin.*
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.gradle
/*
The settings script is an entry point for defining a TeamCity
project hierarchy. The script should contain a single call to the
project() function with a Project instance or an init function as
an argument.
VcsRoots, BuildTypes, Templates, and subprojects can be
registered inside the project using the vcsRoot(), buildType(),
template(), and subProject() methods respectively.
To debug settings scripts in command-line, run the
mvnDebug org.jetbrains.teamcity:teamcity-configs-maven-plugin:generate
command and attach your debugger to the port 8000.
To debug in IntelliJ Idea, open the 'Maven Projects' tool window (View
-> Tool Windows -> Maven Projects), find the generate task node
(Plugins -> teamcity-configs -> teamcity-configs:generate), the
'Debug' option is available in the context menu for the task.
*/
version = "2021.2"
project {
buildType(Build)
buildType(BuildSecondaryBranches)
buildType(PullRequests)
buildType(PullRequestChecks)
buildType(PullRequestCompatibility)
params {
text("docker_jdk_version", "21", label = "Gradle version", description = "The version of the JDK to use during execution of tasks in a JDK.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
text("docker_gradle_version", "8.7", label = "Gradle version", description = "The version of Gradle to use during execution of Gradle tasks.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
text("git_main_branch", "1.21.x", label = "Git Main Branch", description = "The git main or default branch to use in VCS operations.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
text("git_branch_spec", """
+:refs/heads/(main*)
+:refs/heads/(master*)
+:refs/heads/(develop|release|staging|main|master)
+:refs/heads/(1.*)
""".trimIndent(), label = "The branch specification of the repository", description = "By default all main branches are build by the configuration. Modify this value to adapt the branches build.", display = ParameterDisplay.HIDDEN, allowEmpty = true)
text("github_repository_name", "MinecraftForge", label = "The github repository name. Used to connect to it in VCS Roots.", description = "This is the repository slug on github. So for example `MinecraftForge` or `MinecraftForge`. It is interpolated into the global VCS Roots.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
text("env.PUBLISHED_JAVA_ARTIFACT_ID", "forge", label = "Published artifact id", description = "The maven coordinate artifact id that has been published by this build. Can not be empty.", allowEmpty = false)
text("env.PUBLISHED_JAVA_GROUP", "net.minecraftforge", label = "Published group", description = "The maven coordinate group that has been published by this build. Can not be empty.", allowEmpty = false)
//These are references and not actually keys
password("env.CROWDIN_KEY", "credentialsJSON:a3102dbe-805d-4177-9f54-3d2c2eb08fd5", display = ParameterDisplay.HIDDEN)
password("env.KEYSTORE_URL", "credentialsJSON:a7ae1c82-8058-4061-8d12-7f6bc2618d2e", display = ParameterDisplay.HIDDEN)
password("env.KEYSTORE_PASSWORD", "credentialsJSON:d7b964e3-a1fd-47a8-b892-6f601fe47479", display = ParameterDisplay.HIDDEN)
text("additional_publishing_gradle_parameters", "-PcrowdinKey=%env.CROWDIN_KEY% -PkeystoreKeyPass=%env.KEYSTORE_PASSWORD% -PkeystoreStorePass=%env.KEYSTORE_PASSWORD% -Pkeystore=%env.KEYSTORE_URL%", label = "Additional gradle parameters for publish", description = "Contains the additional gradle parameters used during publishing.", display = ParameterDisplay.HIDDEN, allowEmpty = true)
checkbox("should_execute_build", "false", label = "Should build", description = "Indicates if the build task should be executed.", display = ParameterDisplay.HIDDEN,
checked = "true", unchecked = "false")
checkbox("should_execute_test", "false", label = "Should build", description = "Indicates if the build task should be executed.", display = ParameterDisplay.HIDDEN,
checked = "true", unchecked = "false")
}
features {
githubIssues {
id = "MinecraftForge_MinecraftForge__IssueTracker"
displayName = "MinecraftForge/MinecraftForge"
repositoryURL = "https://github.com/MinecraftForge/MinecraftForge"
}
}
}
object Build : BuildType({
templates(AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_BuildMainBranches"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"), AbsoluteId("MinecraftForge_BuildUsingGradle"), AbsoluteId("MinecraftForge_PublishProjectUsingGradle"), AbsoluteId("MinecraftForge_TriggersStaticFilesWebpageGenerator"))
id("MinecraftForge_MinecraftForge__Build")
name = "Build"
description = "Builds and Publishes the main branches of the project."
})
object BuildSecondaryBranches : BuildType({
templates(AbsoluteId("MinecraftForge_ExcludesBuildingDefaultBranch"), AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_BuildMainBranches"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"), AbsoluteId("MinecraftForge_BuildUsingGradle"))
id("MinecraftForge_MinecraftForge__BuildSecondaryBranches")
name = "Build - Secondary Branches"
description = "Builds and Publishes the secondary branches of the project."
params {
text("git_branch_spec", """
+:refs/heads/(*)
-:refs/heads/noci*
""".trimIndent(), label = "The branch specification of the repository", description = "By default all main branches are build by the configuration. Modify this value to adapt the branches build.", display = ParameterDisplay.HIDDEN, allowEmpty = true)
}
vcs {
branchFilter = """
+:*
-:1.*
-:<default>
""".trimIndent()
}
})
object PullRequests : BuildType({
templates(AbsoluteId("MinecraftForge_BuildPullRequests"), AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"), AbsoluteId("MinecraftForge_BuildUsingGradle"))
id("MinecraftForge_MinecraftForge__PullRequests")
name = "Pull Requests"
description = "Builds pull requests for the project"
params {
checkbox("should_execute_build", "true", label = "Should build", description = "Indicates if the build task should be executed.", display = ParameterDisplay.HIDDEN,
checked = "true", unchecked = "false")
text(
"gradle_build_task",
"assemble",
label = "Gradle build task to execute during build",
description = "Determines the build task that is executed to build the project.",
display = ParameterDisplay.HIDDEN,
allowEmpty = false
)
}
vcs {
branchFilter = """
+:*
-:1.*
-:<default>
""".trimIndent()
}
})
object PullRequestChecks : BuildType({
templates(AbsoluteId("MinecraftForge_BuildPullRequests"), AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"))
id("MinecraftForge_MinecraftForge__PullRequestChecks")
name = "Pull Requests (Checks)"
description = "Checks pull requests for the project"
steps {
gradle {
name = "Check"
id = "RUNNER_10_Check"
tasks = "checkAll"
gradleParams = "--continue %gradle_custom_args%"
enableStacktrace = true
dockerImage = "%docker_gradle_image%"
dockerRunParameters = """
-v "/opt/cache/agent/gradle:/home/gradle/.gradle"
-v "/opt/cache/shared/gradle:/home/gradle/rocache:ro"
--network=host
-u 1000:1000
%docker_additional_args%
""".trimIndent()
}
}
vcs {
branchFilter = """
+:*
-:1.*
-:<default>
""".trimIndent()
}
})
object PullRequestCompatibility : BuildType({
templates(AbsoluteId("MinecraftForge_BuildPullRequests"), AbsoluteId("MinecraftForge_SetupGradleUtilsCiEnvironmen"), AbsoluteId("MinecraftForge_BuildWithDiscordNotifications"), AbsoluteId("MinecraftForge_SetupProjectUsingGradle"))
id("MinecraftForge_MinecraftForge__PullRequestCompatibility")
name = "Pull Requests (Compatibility)"
description = "Validates binary compatibility for pull requests made to the project"
steps {
gradle {
name = "Validate"
id = "RUNNER_10_Compatibility"
tasks = "checkJarCompatibility"
gradleParams = "--continue %gradle_custom_args%"
enableStacktrace = true
dockerImage = "%docker_gradle_image%"
dockerRunParameters = """
-v "/opt/cache/agent/gradle:/home/gradle/.gradle"
-v "/opt/cache/shared/gradle:/home/gradle/rocache:ro"
--network=host
-u 1000:1000
%docker_additional_args%
""".trimIndent()
}
}
vcs {
branchFilter = """
+:*
-:1.*
-:<default>
""".trimIndent()
}
})
-65
View File
@@ -1,65 +0,0 @@
Minecraft Forge: Credits/Thank You
Forge is a set of tools and modifications to the Minecraft base game code to assist
mod developers in creating new and exciting content. It has been in development for
several years now, but I would like to take this time thank a few people who have
helped it along its way.
First, the people who originally created the Forge projects way back in Minecraft
alpha. Eloraam of RedPower, and SpaceToad of Buildcraft, without their acceptiance
of me taking over the project, who knows what Minecraft modding would be today.
Secondly, someone who has worked with me, and developed some of the core features
that allow modding to be as functional, and as simple as it is, cpw. For developing
FML, which stabilized the client and server modding ecosystem. As well as the base
loading system that allows us to modify Minecraft's code as elegently as possible.
Mezz, who has stepped up as the issue and pull request manager. Helping to keep me
sane as well as guiding the community into creating better additions to Forge.
Searge, Bspks, Fesh0r, ProfMobious, and all the rest over on the MCP team {of which
I am a part}. For creating some of the core tools needed to make Minecraft modding
both possible, and as stable as can be.
On that note, here is some specific information of the MCP data we use:
* Minecraft Coder Pack (MCP) *
Forge Mod Loader and Minecraft Forge have permission to distribute and automatically
download components of MCP and distribute MCP data files. This permission is not
transitive and others wishing to redistribute the Minecraft Forge source independently
should seek permission of MCP or remove the MCP data files and request their users
to download MCP separately.
And lastly, the countless community members who have spent time submitting bug reports,
pull requests, and just helping out the community in general. Thank you.
--LexManos
=========================================================================
This is Forge Mod Loader.
You can find the source code at all times at https://github.com/MinecraftForge/MinecraftForge/tree/1.12.x/src/main/java/net/minecraftforge/fml
This minecraft mod is a clean open source implementation of a mod loader for minecraft servers
and minecraft clients.
The code is authored by cpw.
It began by partially implementing an API defined by the client side ModLoader, authored by Risugami.
https://www.minecraftforum.net/topic/75440-
This support has been dropped as of Minecraft release 1.7, as Risugami no longer maintains ModLoader.
It also contains suggestions and hints and generous helpings of code from LexManos, author of MinecraftForge.
https://minecraftforge.net/
Additionally, it contains an implementation of topological sort based on that
published at http://keithschwarz.com/interesting/code/?dir=topological-sort
It also contains code from the Maven project for performing versioned dependency
resolution. http://maven.apache.org/
It also contains a partial repackaging of the javaxdelta library from http://sourceforge.net/projects/javaxdelta/
with credit to it's authors.
Forge Mod Loader downloads components from the Minecraft Coder Pack
(http://mcp.ocean-labs.de/index.php/Main_Page) with kind permission from the MCP team.
+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
+42
View File
@@ -0,0 +1,42 @@
plugins {
id("groovy-gradle-plugin")
alias(libs.plugins.licenser)
}
repositories {
mavenCentral()
gradlePluginPortal()
maven { url = 'https://maven.minecraftforge.net/' }
//mavenLocal()
}
dependencies {
compileOnly gradleApi()
// Manually import plugins so that we can apply them by id.
implementation plugin('licenser')
implementation plugin('gradleutils')
implementation plugin('gitversion')
implementation plugin('changelog')
implementation plugin('forgedev')
}
gradlePlugin {
plugins {
register('convention') {
id = 'net.minecraftforge.forge.build.convention'
implementationClass = 'net.minecraftforge.forge.build.ConventionPlugin'
}
}
}
final Provider<String> plugin(String alias) {
return (Provider<String>)project.extensions.getByType(VersionCatalogsExtension)
.named("libs")
.findPlugin(alias).get()
.map { it.pluginId + ":" + it.pluginId + ".gradle.plugin:" + it.version }
}
license {
header = file('../LICENSE-header.txt')
}
Binary file not shown.
+7
View File
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+251
View File
@@ -0,0 +1,251 @@
#!/bin/sh
#
# 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.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# 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/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# 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 -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
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# 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" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * 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" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
+94
View File
@@ -0,0 +1,94 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@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 ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
+30
View File
@@ -0,0 +1,30 @@
pluginManagement {
repositories {
gradlePluginPortal()
maven { url = 'https://maven.minecraftforge.net/' }
mavenLocal()
}
}
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
}
//enableFeaturePreview 'TYPESAFE_PROJECT_ACCESSORS'
dependencyResolutionManagement.versionCatalogs {
register('bootLibs') {
description = 'Important version numbers that are externally declared for easy modification.'
from(files('../minecraft.versions.toml'))
}
create('libs') {
description = 'Libraries used by Forge and its subprojects.'
from(files("../gradle/libs.versions.toml"))
}
}
if (new File('forgedev/settings.gradle').exists())
includeBuild '../forgedev'
@@ -0,0 +1,166 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.forge.build
import groovy.transform.CompileStatic
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.ExternalModuleDependencyBundle
import org.gradle.api.artifacts.MinimalExternalModuleDependency
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Provider
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.tasks.TaskProvider
import org.gradle.jvm.tasks.Jar
import javax.inject.Inject
@CompileStatic
abstract class ConventionExtension implements Meta {
private final Project project;
private final Runs runs
protected abstract @Inject ObjectFactory getObjects()
protected abstract @Inject ProviderFactory getProviders()
@Inject
ConventionExtension(Project project) {
this.project = project
this.runs = objects.newInstance(Runs, this)
}
@Override
Project getProject() {
this.project
}
void manifest(String pkg) {
this.project.tasks.named('jar', Jar).configure {
this.gradleutils.manifestDefaults(it.manifest, pkg)
}
}
void manifest(String pkg, Map<String, String> info) {
manifest(pkg)
this.project.tasks.named('jar', Jar).configure {
it.manifest.getAttributes().with {
for (var entry : info.entrySet())
put(entry.key, entry.value)
}
}
}
// region Maven publishing ==============================================================
void publish() {
publish(pub -> pub.from(project.components.getByName('java')))
}
void publish(Action<MavenPublication> config) {
project.with {
publishing.with {
repositories.with {
maven gradleutils.publishingForgeMaven
}
publications.register('mavenJava', MavenPublication) {
changelog.publish(it)
gradleutils.promote(it)
// The actual artifacts to be published should be configured, so run the config
config.execute(it)
it.pom.with {
description = project.description
gradleutils.pom.addRemoteDetails(it)
license.with {
gradleutils.pom.licenses.LGPLv2_1
}
}
}
}
}
}
// endregion
// region Runs ================================================================
private TaskProvider<Task> genAllData = null
TaskProvider<Task> getGenAllData() {
if (genAllData == null)
genAllData = this.project.tasks.register('genAllData')
return genAllData
}
TaskProvider<Task> getGenAllData(Action<Task> config) {
getGenAllData().configure(config)
return getGenAllData()
}
Runs getRuns() {
return this.runs
}
Runs runs(Action<Runs> config) {
config.execute(this.runs)
return this.runs
}
Runs basicRuns() {
return basicRuns({})
}
Runs basicRuns(Action<Runs> config) {
this.runs.main(null, null)
config.execute(this.runs)
return getRuns()
}
Runs basicRuns(String mods, File output) {
return basicRuns(mods, output, {})
}
Runs basicRuns(String mods, File output, Action<Runs> config) {
this.runs.main(mods, output)
config.execute(this.runs)
return getRuns()
}
Runs testRuns(String mods, File output) {
return testRuns(mods, output, {})
}
Runs testRuns(String mods, File output, Action<Runs> config) {
this.runs.test(mods, output)
config.execute(this.runs)
return getRuns()
}
// endregion
// region Forge Version Json ===================================================
private TaskProvider<WriteForgeVersionJson> writeForgeVersionJson = null
TaskProvider<WriteForgeVersionJson> getForgeVersionJson() {
if (writeForgeVersionJson == null) {
writeForgeVersionJson = this.project.tasks.register('writeForgeVersionJson', WriteForgeVersionJson)
final versions = this.versions
writeForgeVersionJson.configure {
it.forgeVersion.set(versions.forge)
it.minecraftVersion.set(versions.minecraft)
it.mcpVersion.set(versions.mcp)
}
}
return writeForgeVersionJson
}
TaskProvider<WriteForgeVersionJson> forgeVersionJson(Action<WriteForgeVersionJson> config) {
forgeVersionJson.configure(config)
return forgeVersionJson
}
// endregion
// region Natives helper =========================================================
// 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.
List<MinimalExternalModuleDependency> nativeVariants(Provider<ExternalModuleDependencyBundle> bundle) {
final natives = "natives-" + forgedev.os
final deps = this.project.dependencies
bundle.get().collect { dep ->
(deps.variantOf(providers.provider { (MinimalExternalModuleDependency)dep }) {
it.classifier(natives)
}).get()
}
}
// endregion
}
@@ -0,0 +1,96 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.forge.build
import groovy.transform.CompileStatic
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.jvm.toolchain.JavaLanguageVersion
@CompileStatic
class ConventionPlugin implements Plugin<Project>, Meta {
Project project
@Override
Project getProject() {
this.project
}
@Override
void apply(Project project) {
this.project = project
project.with {
pluginManager.with {
apply 'java-library'
apply 'maven-publish'
apply pluginId('licenser')
apply pluginId('gradleutils')
apply pluginId('gitversion')
apply pluginId('changelog')
apply pluginId('forgedev')
}
final versions = extensions.create("versions", VersionsExtension, project)
final convention = extensions.create('convention', ConventionExtension, project)
logger.lifecycle(name + " " + version)
gradleutils.with {
vendor.set('Forge Development LLC')
version.set(gitversion.version)
}
project.setGroup('net.minecraftforge')
java.with {
toolchain.languageVersion.set(JavaLanguageVersion.of(versions.java))
// This is needed for IDE runtime classpath to be correct/not have multiple of the same artifact
consistentResolution {
it.useCompileClasspathVersions()
}
// Forge needs to build its own sources jar due to patching, everything else should use the default
if (project != rootProject)
it.withSourcesJar()
}
tasks.withType(JavaCompile).configureEach {
it.options.with {
encoding = 'UTF-8'
compilerArgs << '-Xlint:-unchecked' // This is mainly for vanilla code, but just makes it quiet down outside the IDE
}
}
// NOTE: We are adding dependencies to projects instead of through settings, so we don't have to apply forgedev in settings.
// Applying forgedev in settings will break IDE linting support, which makes it very hard to work with.
// We can reconsider this if/when JetBrains gets their shit together.
// - I doubt they will ever fix their shit 8/28/2006 - Lex
repositories.with {
// Libraries has to be before maven central because Mojang hosts a classifer that central doesn't (org.lwjgl:lwjgl-freetype:3.3.3:natives-macos-patch)
maven { url = 'https://libraries.minecraft.net/' }
mavenCentral()
maven gradleutils.forgeMaven
project.pluginManager.withPlugin('net.minecraftforge.forgedev') {
maven forgedev.mavenizer
}
mavenLocal()
}
changelog.with {
from versions.changelogBase
}
license.with {
header = rootProject.file('LICENSE-header.txt')
exclude('.project_info.properties')
}
// Common dependencies,
project.dependencies.with {
add('compileOnly', libs.findLibrary('jetbrains.annotations').get())
}
}
}
}
@@ -0,0 +1,72 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.forge.build
import groovy.transform.CompileStatic
import net.minecraftforge.forgedev.ForgeDevExtension
import net.minecraftforge.gitversion.gradle.GitVersionExtension
import net.minecraftforge.gitversion.gradle.changelog.ChangelogExtension
import net.minecraftforge.gradleutils.GradleUtilsExtensionForProject
import net.minecraftforge.licenser.LicenseExtension
import org.gradle.api.Project
import org.gradle.api.artifacts.VersionCatalog
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.api.plugins.ExtraPropertiesExtension
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.publish.PublishingExtension
@CompileStatic
interface Meta {
Project getProject();
default VersionCatalog getLibs() {
project.extensions.getByType(VersionCatalogsExtension).named("libs")
}
default ExtraPropertiesExtension getExt() {
project.extensions.getByType(ExtraPropertiesExtension)
}
default JavaPluginExtension getJava() {
project.extensions.getByType(JavaPluginExtension)
}
default GradleUtilsExtensionForProject getGradleutils() {
project.extensions.getByType(GradleUtilsExtensionForProject)
}
default GitVersionExtension getGitversion() {
project.extensions.getByType(GitVersionExtension)
}
default ForgeDevExtension getForgedev() {
project.extensions.getByType(ForgeDevExtension)
}
default ChangelogExtension getChangelog() {
project.extensions.getByType(ChangelogExtension)
}
default LicenseExtension getLicense() {
project.extensions.getByType(LicenseExtension)
}
default PublishingExtension getPublishing() {
project.extensions.getByType(PublishingExtension)
}
default VersionsExtension getVersions() {
project.extensions.getByType(VersionsExtension)
}
default ConventionExtension getConvention() {
project.extensions.getByType(ConventionExtension)
}
default String pluginId(String alias) {
getLibs().findPlugin(alias).orElseThrow { new IllegalStateException("Could not find plugin " + alias) }.get().pluginId
}
}
@@ -0,0 +1,220 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.forge.build
import groovy.transform.CompileStatic
import net.minecraftforge.licenser.LicenseExtension
import net.minecraftforge.licenser.tasks.LicenseCheck
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.WriteProperties
import org.jspecify.annotations.Nullable
import javax.inject.Inject
import javax.naming.spi.ObjectFactory
@CompileStatic
class Runs implements Meta {
private final ConventionExtension ext
private boolean hasDependency = false
@Inject
Runs(ConventionExtension ext) {
this.ext = ext
}
@Override
Project getProject() {
this.ext.project
}
void main(@Nullable String mods, @Nullable File output) {
final project = this.project
final genAllData = this.ext.genAllData
project.with {
final SourceSet sourceSet = getJava().getSourceSets().named("main").get()
if (!hasDependency && project != project.rootProject) {
dependencies.with {
add('implementation', it.project(':')) // Root project is forge itself
}
hasDependency = true
}
if (output != null)
sourceSet.resources.with { srcDir output }
forgedev.runs.with {
final File existing = sourceSet.getResources().getSrcDirs()[0]
configureEach {
it.with {
cache.set(project.layout.projectDirectory.dir('runs/cache'))
eclipsePrefix = project.name
// If we're not forge, we need to get the metadata from the forge project
if (project != project.rootProject)
metadata = project.files(getForgedev().consumeFile('metadata', project.rootProject))
var runName = it.name.toLowerCase(Locale.ENGLISH)
if (runName.contains('client') || runName.contains('data')) {
options.with {
// LWJGL needs native access, Mojang gets away with just saying 'ALL-UNNAMED' because it doesnt boot into module land
// We need a system to translate that to module names
//jvmArgs '--enable-native-access=ALL-UNNAMED', '--add-exports', 'java.base/jdk.internal.misc=ALL-UNNAMED'
args '--assetsDir', '{assets_root}', '--assetIndex', '{asset_index}'
systemProperty 'org.lwjgl.system.SharedLibraryExtractDirectory', 'lwjgl_dll'
}
}
if (runName.contains('data')) {
options.with(sourceSet).with {
systemProperties.put("forge.project.list", getProjectList().flatMap { it.destinationFile.map{ it.asFile.absolutePath } })
if (mods != null) args('--mod', mods)
if (output != null) args('--output', output)
}
}
options.with {
getWorkingDir().set(project.layout.projectDirectory.dir("runs/$name"))
getMainClass().set('net.minecraftforge.bootstrap.ForgeBootstrap')
args '--gameDir', '.'
jvmArgs '-Djava.net.preferIPv6Addresses=system', '-XX:+UseCompactObjectHeaders', '-XX:StackShadowPages=32'
//systemProperty 'forge.logging.marker.registries', 'ACCEPT'
//systemProperty 'forge.logging.console.level', 'debug'
systemProperty 'bsl.debug', 'true'
systemProperty 'terminal.jline', 'true'
systemProperty 'forge.enableGameTest', 'true'
systemProperty 'eventbus.api.strictRuntimeChecks', 'true'
}
}
}
maybeCreate('client').with {
options.with {
args '--launchTarget', 'forge_dev_client',
'--username', 'Dev',
'--version', project.name,
'--accessToken', '0',
'--userType', 'mojang',
'--versionType', 'release'
}
}
maybeCreate('server').with {
options.with {
args '--launchTarget', 'forge_dev_server'
}
}
maybeCreate('gameTestServer').with {
options.with {
args '--launchTarget', 'forge_dev_server_gametest',
'--uniqueWorld' // Unique world is used so that the world regenerates, as well as the world config isn't influenced by other runs
}
}
maybeCreate('data').with {
options.with {
args '--launchTarget', 'forge_dev_data',
'--all',
'--existing', existing
}
}
maybeCreate('clientData').with {
options.with {
args '--launchTarget', 'forge_dev_client_data',
'--all',
'--existing', existing
}
}
}
genAllData.configure {
it.dependsOn forgedev.runs.getByName('data').with(sourceSet).run, forgedev.runs.getByName('clientData').with(sourceSet).run
}
}
}
void test(@Nullable String mods, @Nullable File output) {
final project = this.project
final genAllData = this.ext.genAllData
project.with {
final SourceSet sourceSet = getJava().getSourceSets().named("test").get()
forgedev.runs.with {
final File existing = sourceSet.getResources().getSrcDirs()[0]
configureEach {
it.with {
var runName = it.name.toLowerCase(Locale.ENGLISH)
options.with(sourceSet).with {
systemProperty 'forgedev.enableTestMods', 'true'
}
if (runName.contains('data')) {
options.with(sourceSet).with {
if (mods != null) args('--mod', mods)
if (output != null) args('--output', output)
args('--existing', existing)
}
}
}
}
}
genAllData.configure {
it.dependsOn forgedev.runs.getByName('data').with(sourceSet).run, forgedev.runs.getByName('clientData').with(sourceSet).run
}
}
}
private TaskProvider<WriteProperties> projectInfo = null
TaskProvider<WriteProperties> getProjectInfo() {
if (projectInfo == null) {
projectInfo = this.project.tasks.register("writeProjectInfo", WriteProperties)
projectInfo.configure {
it.destinationFile.set(this.project.file('src/main/resources/.project_info.properties'))
it.property('name', this.project.rootProject.name + this.project.isolated.path.replaceAll(':', '.'))
it.property('output', 'src/main/generated')
}
this.project.tasks.named("generateResources").configure { it.dependsOn(projectInfo) }
this.project.tasks.withType(LicenseCheck).configureEach {
it.dependsOn(projectInfo)
}
}
return projectInfo
}
TaskProvider<WriteProperties> projectInfo(Action<? extends WriteProperties> action) {
getProjectInfo().configure(action)
return getProjectInfo()
}
void setFlat(boolean value) {
projectInfo(task -> task.property('flat', value))
}
private TaskProvider<WriteProperties> projectList = null
TaskProvider<WriteProperties> getProjectList() {
if (projectList == null) {
projectList = this.project.tasks.register("writeProjectList", WriteProperties)
projectList.configure { task ->
task.destinationFile.set(this.project.layout.buildDirectory.file('project_list.properties'))
task.encoding = 'UTF-8'
final root = this.project.rootProject.name
this.project.rootProject.allprojects.forEach { p ->
task.property(root + p.isolated.path.replaceAll(':', '.'), p.isolated.projectDirectory.asFile.absolutePath)
}
}
}
return projectList
}
TaskProvider<WriteProperties> projectList(Action<? extends WriteProperties> action) {
getProjectList().configure(action)
return getProjectList()
}
}
@@ -0,0 +1,41 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.forge.build;
import net.minecraftforge.gitversion.gradle.GitVersionExtension;
import org.gradle.api.Project;
import org.gradle.api.artifacts.VersionCatalog;
import org.gradle.api.artifacts.VersionCatalogsExtension;
import javax.inject.Inject;
public abstract class VersionsExtension {
private final VersionCatalog libs;
public final String java, minecraft, forge, forgeSpec, minecraftNext, mcp, changelogBase;
@Inject
public VersionsExtension(Project project) {
final var extensions = project.getExtensions();
final var gitversion = extensions.getByType(GitVersionExtension.class);
libs = extensions.getByType(VersionCatalogsExtension.class).named("bootLibs");
java = get("java");
minecraft = get("minecraft");
minecraftNext = get("minecraft.next");
mcp = get("mcp");
changelogBase = get("changelog.base");
forge = gitversion.of(project.getRootProject()).getVersion();
forgeSpec = forge.split("\\.")[0];
project.setVersion(minecraft + '-' + gitversion.getVersion());
}
private String get(String name) {
var ver = this.libs.findVersion(name);
if (ver.isEmpty())
throw new IllegalStateException("No version found for " + name);
return ver.get().toString();
}
}
@@ -0,0 +1,42 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.forge.build
import groovy.json.JsonBuilder
import groovy.transform.CompileDynamic
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.Input
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import javax.inject.Inject
@CompileStatic
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 JsonBuilder()
json {
forge this.forgeVersion.get()
mc this.minecraftVersion.get()
mcp this.mcpVersion.get()
}
this.outputFile.asFile.get().text = json.toPrettyString()
}
}
+569 -33
View File
@@ -1,54 +1,590 @@
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 'eclipse'
id 'de.undercouch.download' version '5.4.0'
id 'net.minecraftforge.gradle.patcher' version '[6.0.22,6.2)' apply false
id 'net.minecraftforge.gradle.mcp' version '[6.0.22,6.2)' apply false
id 'net.minecraftforge.gradlejarsigner' version '1.0.4'
id 'org.barfuin.gradle.taskinfo' version '2.1.0'
alias libs.plugins.versions
alias libs.plugins.jarsigner
alias libs.plugins.forgedev
id 'net.minecraftforge.forge.build.convention'
}
Util.init() //Init all our extension methods!
gradleutils.displayName = 'Forge'
description = 'Modifications to Minecraft to enable mod developers.'
ext {
VERSION = gitversion.getMCTagOffsetBranch(MC_VERSION)
FORGE_VERSION = VERSION.substring(MC_VERSION.length() + 1)
jarSigner.autoDetect('forge')
// We have to tell ForgeDev to use the exact versions of tools we ship in the installer
fdtools {
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 = versions.minecraft
}
}
*/
final minecraftFiles = forgedev.minecraftFiles(versions.minecraft)
final mcpBase = forgedev.mcpBase {
mcpVersion = "${versions.minecraft}-${versions.mcp}"
accessTransformers.from file('src/main/resources/META-INF/accesstransformer.cfg')
sideAnnotationStrippers.from file('src/main/resources/forge.sas')
}
forgedev.provideFile('metadata', mcpBase.metadata)
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'
license {
include 'net/minecraftforge/'
exclude 'net/minecraftforge/common/LenientUnboundedMapCodec.java'
}
project(':mcp') {
apply plugin: 'net.minecraftforge.gradle.mcp'
mcp {
config MC_VERSION + '-' + MCP_VERSION
pipeline = 'joined'
changelog {
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)
// Inherit vanilla Minecraft's dependencies, and let downstream access them
extendsFrom(mcpBase.dependencyConfiguration)
}
}
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}']"
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
}
convention.basicRuns('forge', file('src/main/generated/'))
final EXTRA_TXTS = [
file('LICENSE.txt'),
changelog.task.flatMap(task -> task.outputFile)
]
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 = versions.minecraft
}
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'
])
gradleutils.manifestDefaults(it, 'net/minecraftforge/versions/forge/')
gradleutils.manifestDefaults(it, 'net/minecraftforge/versions/mcp/', [
'Specification-Title' : 'Minecraft',
'Specification-Version' : versions.minecraft,
'Implementation-Title' : 'MCP',
'Implementation-Version': 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(versions.minecraft) {
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 = "${versions.minecraft}-${project.name}-${versions.forge}"
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 = versions.minecraft
profileVersion = launcherId
final projectArtifact = "${project.group}:${project.name}:${project.version}"
data 'MC_UNPACKED', "[net.minecraft:client:${versions.minecraft}]", "[net.minecraft:server:${versions.minecraft}: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 = versions.minecraft
mainClass = 'net.minecraftforge.bootstrap.ForgeBootstrap'
gameArgs = [ '--launchTarget', 'forge_client' ]
jvmArgs = [ '-Djava.net.preferIPv6Addresses=system', '-XX:+UseCompactObjectHeaders', '-XX:StackShadowPages=32' ]
}
}
/*
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 - ${versions.forge} for Minecraft ${versions.minecraft}"
header = "<div style=\"margin-top: 9px;padding: 5px 6px;\"><strong>${versions.forge} for Minecraft ${versions.minecraft}</strong></div>"
}
doLast {
project.copy {
from docsDir
exclude '/stylesheet.css'
into destinationDir
}
}
}
*/
final mdkZip = forgedev.consumeFile("mdkZip", project(':forge-mdk'))
convention.publish {
artifact universalJar
artifact forgedev.installer.jar
artifact(mdkZip.map { it.singleFile }) {
builtBy mdkZip
classifier = "mdk"
extension = "zip"
}
artifact forgedev.userDev.jar
artifact sourcesJar
artifact serverShim.jar
}
/*
// 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 }
}
// 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/
-13
View File
@@ -1,13 +0,0 @@
repositories {
maven { url = 'https://maven.minecraftforge.net/' }
mavenCentral()
}
dependencies {
implementation 'org.ow2.asm:asm:9.8'
implementation 'org.ow2.asm:asm-tree:9.8'
implementation 'net.minecraftforge:srgutils:0.5.10'
implementation 'commons-io:commons-io:2.13.0'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'org.eclipse.jgit:org.eclipse.jgit:6.7.0.202309050840-r'
}
@@ -1,70 +0,0 @@
package net.minecraftforge.forge.tasks
import org.gradle.api.tasks.*
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.bundling.AbstractArchiveTask
import java.util.zip.ZipFile
abstract class BundleList extends DefaultTask {
@InputFiles abstract ConfigurableFileCollection getConfig()
@InputFiles abstract ConfigurableFileCollection getConfigExtra()
@InputFile abstract RegularFileProperty getServerBundle()
@OutputFile abstract RegularFileProperty getOutput()
BundleList() {
config.setFrom(project.configurations.installer)
configExtra.setFrom(project.configurations.installerextra)
output.convention(project.layout.buildDirectory.file("$name/output.list"))
configure {
dependsOn(project.tasks.universalJar)
inputs.file(project.tasks.universalJar.archiveFile)
}
}
@TaskAction
void run() {
def entries = [:] as TreeMap
def resolved = project.configurations.installer.resolvedConfiguration.resolvedArtifacts
for (def dep : resolved) {
def info = Util.getMavenInfoFromDep(dep)
//println("$dep.file.sha1\t$info.name\t$info.path")
entries.put("$info.art.group:$info.art.name", "$dep.file.sha256\t$info.name\t$info.path")
}
resolved = project.configurations.installerextra.resolvedConfiguration.resolvedArtifacts
for (def dep : resolved) {
def info = Util.getMavenInfoFromDep(dep)
//println("$dep.file.sha1\t$info.name\t$info.path")
entries.put("$info.art.group:$info.art.name", "$dep.file.sha256\t$info.name\t$info.path")
}
var packed = (AbstractArchiveTask) project.tasks.universalJar
var info = Util.getMavenInfoFromTask(packed)
def file = packed.archiveFile.get().asFile
entries.put("$info.art.group:$info.art.name:$info.art.classifier", "$file.sha256\t$info.name\t$info.path")
var classifier = 'server'
var genned = project.tasks.applyServerBinPatches
info = Util.getMavenInfoFromTask(genned, classifier)
file = genned.output.get().asFile
entries.put("$info.art.group:$info.art.name:$info.art.classifier", "$file.sha256\t$info.name\t$info.path")
try (def zip = new ZipFile(serverBundle.get().asFile)) {
def entry = zip.getEntry('META-INF/libraries.list')
def data = zip.getInputStream(entry).text.split('\n')
for (def line : data) {
def (sha, artifact, path) = line.split('\t')
def (group, name, other) = artifact.split(':', 3)
//println("Group: $group Name: $name")
def key = "$group:$name"
if (!entries.containsKey(key))
entries.put(key, line)
}
}
output.get().asFile.text = entries.values().join('\n')
}
}
@@ -1,52 +0,0 @@
package net.minecraftforge.forge.tasks
import groovy.json.JsonBuilder
import groovy.transform.CompileStatic
import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.TaskAction
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.FieldNode
import org.objectweb.asm.tree.MethodNode
@CompileStatic
abstract class BytecodeFinder extends DefaultTask {
@InputFile abstract RegularFileProperty getJar()
// It should be fine to mark the output as internal as we want to control when we run it anyways.
// This also shuts Gradle 8 up about implicit task dependencies.
@Internal abstract RegularFileProperty getOutput()
BytecodeFinder() {
output.convention(project.layout.buildDirectory.dir(name).map { it.file("output.json") })
}
@TaskAction
protected void exec() {
Util.init()
var outputFile = output.get().asFile
if (outputFile.exists())
outputFile.delete()
pre()
Util.processClassNodes(jar.get().asFile, this.&process)
post()
outputFile.text = new JsonBuilder(getData()).toPrettyString()
}
protected process(ClassNode node) {
if (node.fields !== null) node.fields.each { process(node, it) }
if (node.methods !== null) node.methods.each { process(node, it) }
}
protected pre() {}
protected process(ClassNode parent, FieldNode node) {}
protected process(ClassNode parent, MethodNode node) {}
protected post() {}
protected abstract Object getData()
}
@@ -1,48 +0,0 @@
package net.minecraftforge.forge.tasks
import groovy.transform.CompileStatic
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Internal
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.MethodNode
@CompileStatic
abstract class BytecodePredicateFinder extends BytecodeFinder {
@Internal
abstract Property<Closure<Boolean>> getPredicate()
private final Map<String, List<String>> matches = new TreeMap<>(Comparator.naturalOrder())
@Override
protected process(ClassNode parent, MethodNode node) {
for (final current : node.instructions) {
if (predicate.get().call(parent, node, current)) {
var methods = matches.compute(parent.name, { k, v -> v ?: new ArrayList<>() })
// only add non-synthetic methods
if ((node.access & Opcodes.ACC_SYNTHETIC) == 0) {
methods.add(node.name + node.desc)
}
return
}
}
}
@Internal
@Override
protected Object getData() {
var array = new ArrayList<Object>()
matches.forEach { c, m ->
{
array.add(
'class': c,
'methods': m
)
}
}
return array ?: { throw new RuntimeException('Failed to find any targets, please ensure that method names and descriptors are correct.') }()
}
}
@@ -1,97 +0,0 @@
package net.minecraftforge.forge.tasks;
import groovy.transform.CompileDynamic;
import groovy.transform.CompileStatic;
import java.util.Properties;
import java.util.Enumeration;
import java.util.Map;
import java.util.TreeSet;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.Reader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Set;
/**
* Eclipse config files are literally just java properties, with the header cleaned up.
* https://github.com/eclipse/buildship/blob/5b2c7fca7fa86cd74d71b3c099c7b1559eba038e/org.eclipse.buildship.core/src/main/java/org/eclipse/buildship/core/internal/configuration/PreferenceStore.java#L238
*
* This does the same thing, as well as sorting alphabetically.
* It also ignores all comments. We can add them latter if someone cares.
*/
@CompileStatic
public class CleanProperties extends Properties {
private static final long serialVersionUID = 1L;
private static final String LINE_SEP = System.getProperty("line.separator");
private static final String UNIX_LINE_SEP = "\n";
private static final Charset ENCODING = StandardCharsets.UTF_8;
public CleanProperties load(File input) throws IOException {
if (input.exists()) {
try (Reader is = new InputStreamReader(new FileInputStream(input), ENCODING)) {
super.load(is);
}
}
return this;
}
public void store(File out) throws IOException {
if (!out.getParentFile().exists())
out.getParentFile().mkdirs();
try (OutputStream os = new FileOutputStream(out)) {
store(os, null);
}
}
@Override
public synchronized Enumeration<Object> keys() {
Set<Object> ret = new TreeSet<>();
for (Enumeration<?> e = super.keys(); e.hasMoreElements();)
ret.add(e.nextElement());
return Collections.enumeration(ret);
}
@CompileDynamic
@Override
public Set<Map.Entry<Object, Object>> entrySet() {
Set<Map.Entry<Object, Object>> ret = new TreeSet<>((l, r) -> ((String)l.getKey()).compareTo((String)r.getKey()));
ret.addAll(super.entrySet());
return ret;
}
@Override
public void store(OutputStream out, String comments) throws IOException {
out.write(clean().getBytes(ENCODING));
out.flush();
}
@Override
public void store(Writer out, String comments) throws IOException {
out.write(clean());
out.flush();
}
private String clean() throws IOException {
ByteArrayOutputStream tmp = new ByteArrayOutputStream();
try {
super.store(tmp, null);
} finally {
tmp.close();
}
String ret = tmp.toString(ENCODING).replace(LINE_SEP, UNIX_LINE_SEP);
ret = ret.substring(ret.indexOf(UNIX_LINE_SEP) + 1);
return ret;
}
}
@@ -1,33 +0,0 @@
package net.minecraftforge.forge.tasks
import groovy.transform.stc.FirstParam
import java.util.function.BiConsumer
public class ClosureHelper {
BiConsumer<String, Closure> callback
public ClosureHelper(Closure cl, BiConsumer<String, Closure> callback) {
this.callback = callback
apply(this, cl)
}
def methodMissing(String name, Object args) {
if (!args.class.isArray()) return
Object[] aargs = (Object[])args
if (aargs.length == 1 && aargs[0] instanceof Closure) {
this.callback.accept(name, (Closure)aargs[0])
} else {
throw new IllegalArgumentException('Unknown method: "' + name + '" with arguments ' + args + ' for ' + this)
}
}
static <T> T apply(T obj, @DelegatesTo(value = FirstParam, strategy = Closure.DELEGATE_FIRST) Closure cl) {
cl.delegate = obj
cl.resolveStrategy = Closure.DELEGATE_FIRST
cl()
return obj
}
}
@@ -1,54 +0,0 @@
package net.minecraftforge.forge.tasks
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.OutputFiles
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.OutputDirectory
import java.nio.file.Files
abstract class DownloadLibraries extends DefaultTask {
@InputFile abstract RegularFileProperty getInput()
@OutputDirectory abstract DirectoryProperty getOutput()
@OutputFile abstract RegularFileProperty getLibrariesOutput()
DownloadLibraries() {
output.convention(project.layout.buildDirectory.dir(name))
librariesOutput.convention(project.layout.buildDirectory.dir(name).map(d -> d.file('libraries.txt')))
}
@TaskAction
def run() {
Util.init()
File outputDir = output.get().asFile
var libraries = new ArrayList<String>()
def json = input.get().asFile.json().libraries.each { lib ->
//TODO: Thread?
def artifacts = [lib.downloads.artifact] + lib.downloads.get('classifiers', [:]).values()
artifacts.each{ art ->
def target = new File(outputDir, art.path)
libraries.add(target.absolutePath)
if (!target.exists() || art.sha1 != target.sha1()) {
project.logger.lifecycle("Downloading ${art.url}")
if (!target.parentFile.exists()) {
target.parentFile.mkdirs()
}
new URL(art.url).withInputStream { i ->
target.withOutputStream { it << i }
}
if (art.sha1 != target.sha1()) {
throw new IllegalStateException("Failed to download ${art.url} to ${target.canonicalPath} SHA Mismatch")
}
}
}
}
Files.write(librariesOutput.get().asFile.toPath(), libraries)
}
}
@@ -1,28 +0,0 @@
package net.minecraftforge.forge.tasks
import groovy.transform.CompileStatic
import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
import java.util.zip.ZipFile
@CompileStatic
abstract class ExtractFile extends DefaultTask {
@InputFile abstract RegularFileProperty getInput()
@Input abstract Property<String> getTarget()
@OutputFile abstract RegularFileProperty getOutput()
@TaskAction
protected void exec() {
var zip = new ZipFile(input.get().asFile);
var entry = zip.getEntry(target.get())
if (entry == null)
throw new IllegalStateException(input.get().asFile.absolutePath + " does not contain " + target.get())
var stream = zip.getInputStream(entry)
output.get().asFile.withOutputStream { out ->
stream.transferTo(out)
}
}
}
@@ -1,101 +0,0 @@
package net.minecraftforge.forge.tasks
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import org.gradle.api.tasks.*
import org.objectweb.asm.tree.AbstractInsnNode
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.MethodNode
import static org.objectweb.asm.Opcodes.*
abstract class FieldCompareFinder extends BytecodeFinder {
@Nested
Map<String, Search> fields = [:] as HashMap
@Internal
Map<Search, String> fieldsReverse = [:] as HashMap
@Internal
Map<String, Set<ObjectTarget>> targets = [:] as TreeMap
@Override
protected pre() {
//fields.each{ k,v -> logger.lifecycle("Fields: " + k + ' ' + v) }
}
@Override
protected process(ClassNode parent, MethodNode node) {
AbstractInsnNode last = null
def parentInstance = new ObjectTarget(owner: parent.name, name: '', desc: '')
for (int x = 0; x < node.instructions.size(); x++) {
def current = node.instructions.get(x)
if (current.opcode === IF_ACMPEQ || current.opcode === IF_ACMPNE) {
if (last !== null && (last.opcode === GETSTATIC || last.opcode === GETFIELD)) {
def target = new Search(cls: last.owner, name: last.name)
def wanted = fieldsReverse.get(target)
def original = fields.get(wanted)
def instance = new ObjectTarget(owner: parent.name, name: node.name, desc: node.desc)
if (wanted !== null && (original.blacklist === null || (!original.blacklist.contains(instance) && !original.blacklist.contains(parentInstance)))) {
targets.computeIfAbsent(wanted, { k -> new TreeSet() }).add(instance)
}
}
}
last = current
}
}
@Internal
@Override
protected Object getData() {
def ret = [:] as HashMap
targets.forEach{ k, v ->
def e = fields.get(k)
ret[k] = [
cls: e.cls,
name: e.name,
replacement: e.replacement,
targets: v
]
}
return ret
}
@CompileStatic
@EqualsAndHashCode(excludes = ['replacement', 'blacklist'])
static class Search {
@Input
String cls
@Input
String name
@Input
String replacement
@Nested
@Optional
Set<ObjectTarget> blacklist
@Override
String toString() {
return cls + '.' + name
}
def blacklist(String owner, String name, String desc) {
if (blacklist === null)
blacklist = new HashSet<>()
blacklist.add(new ObjectTarget(owner: owner, name: name, desc: desc))
}
def blacklist(String owner) {
blacklist(owner, '', '')
}
}
void fields(Closure cl) {
new ClosureHelper(cl, {name, ccl ->
def search = ClosureHelper.apply(new Search(), ccl)
this.fields.put(name, search)
this.fieldsReverse.put(search, name)
})
}
}
@@ -1,49 +0,0 @@
package net.minecraftforge.forge.tasks
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import groovy.transform.CompileStatic
import groovy.transform.TupleConstructor
@CompileStatic
@TupleConstructor
final class InheritanceData implements Annotatable {
String name
int access
String superName
List<String> interfaces = []
Map<String, Method> methods = [:]
Map<String, Field> fields = [:]
List<Annotation> annotations = []
@TupleConstructor
static final class Method implements Annotatable {
int access
String override
List<Annotation> annotations = []
}
@TupleConstructor
static final class Field implements Annotatable {
int access
String desc
List<Annotation> annotations = []
}
@TupleConstructor
static final class Annotation {
String desc
}
private static final Gson GSON = new Gson()
static Map<String, InheritanceData> parse(File file) {
try (final reader = file.newReader()) {
return GSON.fromJson(reader, new TypeToken<Map<String, InheritanceData>>() {})
}
}
}
@CompileStatic
interface Annotatable {
List<InheritanceData.Annotation> getAnnotations()
}
@@ -1,158 +0,0 @@
package net.minecraftforge.forge.tasks
import org.gradle.api.file.DuplicatesStrategy
import org.gradle.api.tasks.bundling.AbstractArchiveTask
import org.gradle.api.tasks.bundling.Zip
import org.gradle.api.tasks.*
import org.gradle.api.DefaultTask
import org.gradle.api.provider.Property
import groovy.json.JsonSlurper
abstract class InstallerJar extends Zip {
@Input @Optional abstract Property<Boolean> getFat()
@Input @Optional abstract Property<Boolean> getOffline()
InstallerJar() {
archiveClassifier.set('installer')
archiveExtension.set('jar') // Needs to be Zip task to not override Manifest, so set extension
destinationDirectory.set(project.layout.buildDirectory.dir('libs'))
fat.convention(false)
offline.convention(false)
def installerJson = project.tasks.installerJson
def launcherJson = project.tasks.launcherJson
def downloadInstaller = project.tasks.downloadInstaller
dependsOn(installerJson, launcherJson, downloadInstaller, project.configurations.installer)
from(installerJson, launcherJson)
from(project.rootProject.file('/src/main/resources/url.png'))
project.afterEvaluate {
from(project.zipTree(downloadInstaller.output)) {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
if (fat.get() || offline.get()) {
def cfg = project.tasks.register(name + "Config", Configure)
cfg.get().configure {
parent = this
dependsOn(project.tasks.installerJson, project.tasks.launcherJson)
}
dependsOn(cfg)
} else {
// Things we ALWAYS bundle, this just the server shim jar, because the installer spec only says to extract the file.
// I should make it allow downloads but thats a spec break, and this is just a ~14KB jar
[
project.tasks.serverShimJar // Server bootstrap executable jar
].forEach { AbstractArchiveTask packed ->
def path = Util.getMavenInfoFromTask(packed).path
from(packed) {
rename { "maven/$path" }
}
}
}
}
}
static abstract class Configure extends DefaultTask {
public Zip parent
private int count = 0;
@TaskAction
protected void exec() {
def deps = [:] as java.util.TreeMap
// Gather all things that need downloading
[
project.tasks.installerJson,
project.tasks.launcherJson
].each { task ->
def json = task.output.get().asFile.json
json.libraries.each { lib ->
if (lib.downloads?.artifact?.url !== null && !lib.downloads.artifact.url.isEmpty())
deps.put(lib.name, lib.downloads.artifact)
}
}
// First find things we build in this project.
[
project.tasks.universalJar, // Forge runtime code
project.tasks.serverShimJar // Shim jar for dedicated server
].forEach { AbstractArchiveTask packed ->
def name = Util.getMavenInfoFromTask(packed).name
def info = deps.remove(name)
if (info !== null) {
println("Adding: $packed.path $name")
parent.from(packed) {
rename { "maven/$info.path" }
}
}
}
// Find any artifacts from the 'installer' config
// This config specifies the runtime files we intend for the interaller to have.
// And are typically what we would be developing and testing alongside Forge.
// So we may have local modified versions
def cfg = project.configurations.installer
while (cfg != null) {
//println('')
def resolved = cfg.resolvedConfiguration.resolvedArtifacts
int found = 0
for (def dep : resolved) {
def name = Util.getMavenInfoFromDep(dep).name
def info = deps.remove(name)
if (info == null) {
//println("Skipping: $name")
continue
}
//println("-$name")
found++
addFile(dep.file, info)
}
if (deps.isEmpty()) {
cfg = null
continue
}
// Prevent infinite loops if something fucky happens
if (found == 0)
throw new IllegalStateException("Failed to find any installer dependencies")
def seen = [] as Set
cfg = project.configurations.detachedConfiguration()
cfg.transitive = false
for (def key : deps.keySet()) {
def (group, artifact, other) = key.split(':', 3)
// Only resolve unique group:artifact so we don't get version overrides
if (seen.add(group + ':' + artifact)) {
//println("+$key")
cfg.dependencies.add(project.dependencies.create(key))
}
}
}
}
void addFile(file, info) {
boolean pack = parent.offline.get() || info.url.isEmpty()
// If it's a offline jar just always pack
if (!pack) {
try {
var remote = new URL("${info.url}.sha1").getText('UTF-8')
pack = info.sha1 != remote
} catch (FileNotFoundException e) {
pack = !info.url.startsWith('https://libraries.minecraft.net/')
// Oh noes its not there!
}
}
if (pack) {
println("Adding: $file.absolutePath")
parent.from(file) {
rename { "maven/$info.path" }
}
}
}
}
}
@@ -1,73 +0,0 @@
package net.minecraftforge.forge.tasks
import groovy.json.JsonBuilder
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.SetProperty
import org.gradle.api.tasks.*
import org.gradle.api.tasks.bundling.AbstractArchiveTask
import java.nio.file.Files
abstract class InstallerJson extends DefaultTask {
@OutputFile abstract RegularFileProperty getOutput()
@InputFiles abstract ConfigurableFileCollection getInput()
@Input @Optional final Map<String, Object> libraries = new LinkedHashMap<>()
@Input Map<String, Object> json = new LinkedHashMap<>()
@InputFile abstract RegularFileProperty getIcon()
@Input abstract Property<String> getLauncherJsonName()
@Input abstract Property<String> getLogo()
@Input abstract Property<String> getMirrors()
@Input abstract Property<String> getWelcome()
InstallerJson() {
launcherJsonName.convention('/version.json')
logo.convention('/big_logo.png')
mirrors.convention('https://files.minecraftforge.net/mirrors-2.0.json')
welcome.convention("Welcome to the ${project.name.capitalize()} installer.")
output.convention(project.layout.buildDirectory.file('libs/install_profile.json'))
project.afterEvaluate {
[
project.tasks.universalJar,
project.tasks.serverShimJar
].forEach { packed ->
dependsOn(packed)
input.from packed.archiveFile
}
}
}
@TaskAction
protected void exec() {
def libs = libraries
[
project.tasks.universalJar,
project.tasks.serverShimJar
].forEach { AbstractArchiveTask packed ->
def info = Util.getMavenInfoFromTask(packed)
libs.put(info.name, [
name: info.name,
downloads: [
artifact: [
path: info.path,
url: "https://maven.minecraftforge.net/$info.path",
sha1: packed.archiveFile.get().asFile.sha1(),
size: packed.archiveFile.get().asFile.length()
]
]
])
}
json.libraries = libs.values().sort{a,b -> a.name.compareTo(b.name)}
json.icon = "data:image/png;base64," + new String(Base64.getEncoder().encode(Files.readAllBytes(icon.get().asFile.toPath())))
json.json = launcherJsonName.get()
json.logo = logo.get()
if (!mirrors.get().isEmpty())
json.mirrorList = mirrors.get()
json.welcome = welcome.get()
Files.writeString(output.get().getAsFile().toPath(), new JsonBuilder(json).toPrettyString())
}
}
@@ -1,96 +0,0 @@
package net.minecraftforge.forge.tasks
import groovy.json.JsonBuilder
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.SetProperty
import org.gradle.api.tasks.*
import org.gradle.api.tasks.bundling.AbstractArchiveTask
import java.nio.file.Files
import static net.minecraftforge.forge.tasks.Util.getArtifacts
import static net.minecraftforge.forge.tasks.Util.iso8601Now
abstract class LauncherJson extends DefaultTask {
@OutputFile abstract RegularFileProperty getOutput()
@InputFiles abstract ConfigurableFileCollection getInput()
@Input Map<String, Object> json = new LinkedHashMap<>()
LauncherJson() {
output.convention(project.layout.buildDirectory.file('libs/version.json'))
dependsOn(project.tasks.universalJar)
input.from(project.tasks.universalJar.archiveFile)
input.from(project.configurations.installer)
input.from(project.configurations.installerextra)
configure {
def mc = project.rootProject.ext.MC_VERSION
def forge = project.rootProject.ext.FORGE_VERSION
def timestamp = iso8601Now()
json.putAll([
_comment: [
"Please do not automate the download and installation of Forge.",
"Our efforts are supported by ads from the download page.",
"If you MUST automate this, please consider supporting the project through https://www.patreon.com/LexManos/"
],
id: "$mc-$project.name-$forge",
time: timestamp,
releaseTime: timestamp,
inheritsFrom: mc,
type: 'release',
logging: [:],
mainClass: '',
libraries: []
] as LinkedHashMap)
[
project.tasks.universalJar
].forEach { packed ->
dependsOn(packed)
input.from packed.archiveFile
}
def patched = project.tasks.applyClientBinPatches
dependsOn(patched)
input.from patched.output
}
}
@TaskAction
protected void exec() {
var packed = (AbstractArchiveTask) project.tasks.universalJar
def info = Util.getMavenInfoFromTask(packed)
json.libraries.add([
name: info.name,
downloads: [
artifact: [
path: info.path,
url: "https://maven.minecraftforge.net/$info.path",
sha1: packed.archiveFile.get().asFile.sha1(),
size: packed.archiveFile.get().asFile.length()
]
]
])
var classifier = 'client'
var genned = project.tasks.applyClientBinPatches
info = Util.getMavenInfoFromTask(genned, classifier)
json.libraries.add([
name: info.name,
downloads: [
artifact: [
path: info.path,
url: "",
sha1: genned.output.get().asFile.sha1(),
size: genned.output.get().asFile.length()
]
]
])
json.libraries.addAll(getArtifacts(project.configurations.installerextra).values())
json.libraries.addAll(getArtifacts(project.configurations.installer).values())
Files.writeString(output.get().asFile.toPath(), new JsonBuilder(json).toPrettyString())
}
}
@@ -1,46 +0,0 @@
package net.minecraftforge.forge.tasks
import groovy.transform.CompileStatic
import org.apache.commons.io.IOUtils
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
@CompileStatic
abstract class MergeJars extends DefaultTask {
MergeJars() {
output.convention(project.layout.buildDirectory.dir(name).map { it.file('output.jar') })
}
@TaskAction
void run() {
def jars = inputJars.files
try (ZipOutputStream zout = new ZipOutputStream(new FileOutputStream(output.get().asFile))) {
for (def jar : jars) {
try (ZipInputStream zin = new ZipInputStream(new FileInputStream(jar))) {
ZipEntry entry
while ((entry = zin.getNextEntry()) !== null) {
ZipEntry _new = new ZipEntry(entry.getName())
_new.setTime(0) //SHOULD be the same time as the main entry, but NOOOO _new.setTime(entry.getTime()) throws DateTimeException, so you get 0, screw you!
zout.putNextEntry(_new)
IOUtils.copy(zin, zout)
}
}
}
}
}
@InputFiles
abstract ConfigurableFileCollection getInputJars()
@OutputFile
abstract RegularFileProperty getOutput()
}
@@ -1,32 +0,0 @@
package net.minecraftforge.forge.tasks
import groovy.transform.EqualsAndHashCode
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Nested
import org.gradle.api.tasks.Optional
@EqualsAndHashCode
public class ObjectTarget implements Comparable<ObjectTarget> {
@Input
String owner
@Input
String name
@Input
@Optional
String desc
@Override
String toString() {
if (desc == null)
return owner + '.' + name
return owner + '.' + name + desc
}
@Override
int compareTo(ObjectTarget o) {
return toString() <=> o.toString()
}
}
@@ -1,47 +0,0 @@
package net.minecraftforge.forge.tasks
import org.gradle.api.DefaultTask
import org.gradle.api.artifacts.Dependency
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import java.nio.file.Files
import java.nio.file.StandardCopyOption
abstract class SetupCheckJarCompatibility extends DefaultTask {
SetupCheckJarCompatibility() {
group = 'jar compatibility'
onlyIf {
inputVersion.getOrNull() !== null
}
outputs.upToDateWhen { false } // Never up to date, because this setup task should always run
baseBinPatchesOutput.convention(project.layout.buildDirectory.dir(name).map { it.file('joined.lzma') })
}
@TaskAction
void run() {
def inputVersion = inputVersion.get()
def baseForgeUserdev = project.layout.buildDirectory.dir(name).map { it.file("forge-${inputVersion}-userdev.jar") }.get().asFile
project.rootProject.extensions.download.run {
src "https://maven.minecraftforge.net/net/minecraftforge/forge/${inputVersion}/forge-${inputVersion}-userdev.jar"
dest baseForgeUserdev
}
def joinedLzma = project.zipTree(baseForgeUserdev).matching { it.include('joined.lzma') }.singleFile
Files.copy(joinedLzma.toPath(), baseBinPatchesOutput.get().asFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
}
@Input
@Optional
abstract Property<String> getInputVersion()
@OutputFile
abstract RegularFileProperty getBaseBinPatchesOutput()
}
@@ -1,74 +0,0 @@
package net.minecraftforge.forge.tasks
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import groovy.transform.CompileStatic
import org.eclipse.jgit.api.Git
import javax.annotation.Nullable
import java.util.stream.Collectors
@CompileStatic
class TeamcityRequests {
public static final Gson GSON = new Gson()
@Nullable
static <T> T jsonRequest(TypeToken<T> clazz, String url) throws Exception {
final HttpURLConnection conn = (HttpURLConnection) URI.create(url).toURL().openConnection()
conn.setRequestProperty('Accept', 'application/json')
conn.setReadTimeout(5 * 1000)
conn.setConnectTimeout(5 * 1000)
conn.connect()
if (conn.responseCode !== 200) {
return null
}
try (final InputStream is = conn.getInputStream()) {
GSON.fromJson(new InputStreamReader(is), clazz.getType())
}
}
@Nullable
static Map<String, Build> buildsByCommit() throws IOException {
final Map<String, Build> builds = [:]
jsonRequest(new TypeToken<Builds>() {}, 'https://teamcity.minecraftforge.net/guestAuth/app/rest/builds?fields=build:(revisions,number)&locator=buildType:(id:MinecraftForge_MinecraftForge_MinecraftForge_MinecraftForge__Build),defaultFilter:false,count:300,status:SUCCESS')
?.build?.forEach {
it.revisions.revision.each { rev -> builds[rev.version] = it }
}
return builds
}
@Nullable
static String attemptFindBase(File gitPath) {
try (final git = Git.open(gitPath)) {
final Map<String, Build> buildByCommit = buildsByCommit()
for (final commit in git.log().setMaxCount(100).call()) {
// Find the first commit which was built on CI
final build = buildByCommit[commit.id.name]
if (build) {
return build.number
}
}
} catch (Exception ignored) {}
return null
}
static final class Builds {
public List<Build> build
}
static final class Build {
public String number
public Revisions revisions
}
static final class Revisions {
public int count
public List<Revision> revision
}
static final class Revision {
public String version
}
}
@@ -1,230 +0,0 @@
package net.minecraftforge.forge.tasks
import groovy.json.JsonBuilder
import groovy.json.JsonSlurper
import groovy.transform.CompileStatic
import groovy.transform.stc.ClosureParams
import groovy.transform.stc.SimpleType
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ResolvedArtifact
import org.gradle.api.tasks.bundling.AbstractArchiveTask
import org.objectweb.asm.ClassReader
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.ClassNode
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.security.MessageDigest
import java.text.SimpleDateFormat
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Semaphore
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
final class Util {
public static final int ASM_LEVEL = Opcodes.ASM9
private static final HttpClient HTTP = HttpClient.newBuilder().build()
static void init() {
File.metaClass.sha1 = { ->
MessageDigest md = MessageDigest.getInstance('SHA-1')
delegate.eachByte(4096) { byte[] bytes, int size ->
md.update(bytes, 0, size)
}
return md.digest().collect(this.&toHex).join('')
}
File.metaClass.getSha1 = { !delegate.exists() ? null : delegate.sha1() }
File.metaClass.sha256 = { ->
MessageDigest md = MessageDigest.getInstance('SHA-256')
delegate.eachByte(4096) { byte[] bytes, int size ->
md.update(bytes, 0, size)
}
return md.digest().collect(this.&toHex).join('')
}
File.metaClass.getSha256 = { !delegate.exists() ? null : delegate.sha256() }
File.metaClass.json = { -> new JsonSlurper().parseText(delegate.text) }
File.metaClass.getJson = { return delegate.exists() ? new JsonSlurper().parse(delegate) : [:] }
File.metaClass.setJson = { json -> delegate.text = new JsonBuilder(json).toPrettyString() }
Date.metaClass.iso8601 = { ->
var format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
var result = format.format(delegate)
return result[0..21] + ':' + result[22..-1]
}
String.metaClass.rsplit = { String del, int limit = -1 ->
var lst = new ArrayList<String>()
int x = 0
int idx
String tmp = delegate
while ((idx = tmp.lastIndexOf(del)) != -1 && (limit === -1 || x++ < limit)) {
lst.add(0, tmp.substring(idx + del.length(), tmp.length()))
tmp = tmp.substring(0, idx)
}
lst.add(0, tmp)
return lst
}
}
static String[] getClasspath(Project project, Map libs, String artifact) {
def ret = []
artifactTree(project, artifact).each { key, lib ->
libs[lib.name] = lib
if (lib.name != artifact)
ret.add(lib.name)
}
return ret
}
@CompileStatic
static Map getArtifacts(Configuration config) {
var ret = [:]
var semaphore = new Semaphore(1, true)
config.resolvedConfiguration.resolvedArtifacts.parallelStream().forEachOrdered(dep -> {
var info = getMavenInfoFromDep(dep)
var domain = 'libraries.minecraft.net'
var url = "https://$domain/$info.path"
if (!checkExists(url))
url.values[0] = 'maven.minecraftforge.net'
var sha1 = sha1(dep.file)
semaphore.acquire()
ret[info.key] = [
name: info.name,
downloads: [
artifact: [
path: info.path,
url: url.toString(),
sha1: sha1,
size: dep.file.length()
]
]
]
semaphore.release()
})
return ret
}
@CompileStatic
static Map getMavenInfoFromDep(ResolvedArtifact dep) {
return getMavenInfoFromMap([
group: dep.moduleVersion.id.group,
name: dep.moduleVersion.id.name,
version: dep.moduleVersion.id.version,
classifier: dep.classifier,
extension: dep.extension
])
}
@CompileStatic
static Map getMavenInfoFromTask(AbstractArchiveTask task) {
return getMavenInfoFromMap([
group: task.project.group.toString(),
name: task.project.name,
version: task.project.version.toString(),
classifier: task.archiveClassifier.get(),
extension: task.archiveExtension.get()
])
}
@CompileStatic
static Map getMavenInfoFromTask(Task task, String classifier) {
return getMavenInfoFromMap([
group: task.project.group.toString(),
name: task.project.name,
version: task.project.version.toString(),
classifier: classifier,
extension: 'jar'
])
}
@CompileStatic
private static Map getMavenInfoFromMap(Map<String, String> art) {
var key = "$art.group:$art.name"
var name = "$art.group:$art.name:$art.version"
var path = "${art.group.replace('.', '/')}/$art.name/$art.version/$art.name-$art.version"
if (art.classifier !== null) {
name += ":$art.classifier"
path += "-$art.classifier"
}
if ('jar' != art.extension) {
name += "@$art.extension"
path += ".$art.extension"
} else {
path += ".jar"
}
return [
key: key.toString(),
name: name.toString(),
path: path.toString(),
art: art
]
}
static String iso8601Now() { new Date().iso8601() }
@CompileStatic
static String sha1(File file) {
MessageDigest md = MessageDigest.getInstance('SHA-1')
file.eachByte(4096) { byte[] bytes, int size ->
md.update(bytes, 0, size)
}
return md.digest().collect(this.&toHex).join('')
}
@CompileStatic
private static String toHex(byte bite) {
return String.format('%02x', bite)
}
private static Map artifactTree(Project project, String artifact, boolean transitive = true) {
if (!project.ext.has('tree_resolver'))
project.ext.tree_resolver = 1
def cfg = project.configurations.create('tree_resolver_' + project.ext.tree_resolver++)
cfg.transitive = transitive
def dep = project.dependencies.create(artifact)
cfg.dependencies.add(dep)
def files = cfg.resolve()
return getArtifacts(cfg)
}
@CompileStatic
static boolean checkExists(String url) {
try {
return HTTP.send(HttpRequest.newBuilder(new URI(url))
.method('HEAD', HttpRequest.BodyPublishers.noBody()).build(), HttpResponse.BodyHandlers.discarding()
).statusCode() === 200
} catch (Exception e) {
if (e.toString().contains('unable to find valid certification path to requested target'))
throw new RuntimeException("Failed to connect to $url: Missing certificate root authority, try updating Java")
throw e
}
}
static String getLatestForgeVersion(String mcVersion) {
final json = new JsonSlurper().parseText(new URL('https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json').getText('UTF-8'))
final ver = json.promos["$mcVersion-latest"]
ver === null ? null : (mcVersion + '-' + ver)
}
@CompileStatic
static void processClassNodes(File file, @ClosureParams(value = SimpleType, options = 'org.objectweb.asm.tree.ClassNode') Closure process) {
file.withInputStream { i ->
new ZipInputStream(i).withCloseable { zin ->
ZipEntry zein
while ((zein = zin.nextEntry) !== null) {
if (zein.name.endsWith('.class')) {
var node = new ClassNode(ASM_LEVEL)
new ClassReader(zin).accept(node, 0)
process(node)
}
}
}
}
}
}
@@ -1,94 +0,0 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.forge.tasks
import groovy.transform.CompileStatic
import net.minecraftforge.srgutils.MinecraftVersion
import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.TaskAction
import org.objectweb.asm.tree.AnnotationNode
import org.objectweb.asm.tree.ClassNode
abstract class ValidateDeprecations extends DefaultTask {
@InputFile
abstract RegularFileProperty getInput()
@Input
abstract Property<String> getMcVersion()
ValidateDeprecations() {
this.onlyIf { !System.env.TEAMCITY_VERSION }
}
@TaskAction
protected void exec() {
var mcVer = MinecraftVersion.from(mcVersion.get())
List<String> errors = []
Util.processClassNodes(input.get().asFile) {
processNode(mcVer, errors, it)
}
if (!errors.isEmpty()) {
errors.forEach {
this.logger.error("Deprecated ${it[0]} is marked for removal in ${it[1]} but is not yet removed")
}
throw new IllegalStateException("Found deprecated members marked for removal but not yet removed in ${mcVer}; see log for details")
}
}
protected processNode(MinecraftVersion mcVer, List<String> errors, ClassNode node) {
node.visibleAnnotations?.each { annotation ->
ValidateDeprecations.processAnnotations(annotation, mcVer, errors) {
"class ${node.name}"
}
}
node.fields?.each { field ->
field.visibleAnnotations?.each { annotation ->
ValidateDeprecations.processAnnotations(annotation, mcVer, errors) {
"field ${node.name}#${field.name}"
}
}
}
node.methods?.each { method ->
method.visibleAnnotations?.each { annotation ->
ValidateDeprecations.processAnnotations(annotation, mcVer, errors) {
"method ${node.name}#${method.name}${method.desc}"
}
}
}
}
private static void processAnnotations(AnnotationNode annotation, MinecraftVersion mcVer, List<String> errors, Closure<String> context) {
def values = annotation.values
if (values === null)
return
int forRemoval = values.indexOf('forRemoval')
int since = values.indexOf('since')
if (annotation.desc == 'Ljava/lang/Deprecated;' && forRemoval !== -1 && since !== -1 && values.size() >= 4 && values[forRemoval + 1] === true) {
def oldVersion = MinecraftVersion.from(values[since + 1])
int[] split = ValidateDeprecations.splitDots(oldVersion.toString())
if (split.length < 2)
return
def removeVersion = MinecraftVersion.from("${split[0]}.${split[1] + 1}")
if (removeVersion <= mcVer)
errors.add([context(), removeVersion])
}
}
@CompileStatic
private static int[] splitDots(String version) {
String[] pts = version.split('\\.')
int[] values = new int[pts.length]
for (int x = 0; x < pts.length; x++)
values[x] = Integer.parseInt(pts[x])
return values
}
}
@@ -1,310 +0,0 @@
package net.minecraftforge.forge.tasks.checks
import groovy.transform.CompileStatic
import groovy.transform.TupleConstructor
import net.minecraftforge.forge.tasks.InheritanceData
import net.minecraftforge.srgutils.IMappingFile
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFile
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Optional
import org.objectweb.asm.Opcodes
@CompileStatic
abstract class CheckATs extends CheckTask {
@InputFile abstract RegularFileProperty getInheritance()
@InputFiles abstract ConfigurableFileCollection getAts()
@InputFile @Optional abstract RegularFileProperty getMappings()
@Override
void check(Reporter reporter, boolean fix) {
final IMappingFile mappings = mappings.map { RegularFile it -> IMappingFile.load(it.asFile) }.getOrNull()
final inheritance = InheritanceData.parse(this.inheritance.get().asFile)
ats.each {
final lines = process(it, reporter, inheritance)
if (fix) {
it.text = joinBack(lines, inheritance, mappings).join('\n')
}
}
}
private static TreeMap<String, ATParser.Entry> process(File file, Reporter reporter, Map<String, InheritanceData> inheritance) {
final TreeMap<String, ATParser.Entry> lines = ATParser.parse(file.readLines(), reporter)
final Map<String, ATParser.Entry> constructorGroups = [:]
final itr = lines.entrySet().iterator()
final toRemove = []
while (itr.hasNext()) {
final next = itr.next()
String key = next.key
final entry = next.value
if (entry === null) continue
final binaryName = entry.cls.replaceAll('\\.', '/')
// Process Groups, this will remove any entries outside the group that is covered by the group
if (entry.group) {
final jcls = inheritance[binaryName]
if (jcls === null) {
itr.remove()
reporter.report("Invalid group: $key")
} else if ('*' == entry.desc) {
if (!jcls.fields) {
itr.remove()
reporter.report("Invalid group, class has no fields: $key")
} else {
jcls.fields.each { field, value ->
final fkey = entry.cls + ' ' + field
if (accessLevel(value.access) < accessStr(entry.modifier)) {
if (lines.containsKey(fkey)) {
toRemove.add(fkey)
} else if (!entry.existing.contains(fkey)) {
reporter.report("Missing group entry: $fkey")
}
entry.children.add(fkey)
} else if (lines.containsKey(fkey)) {
toRemove.add(fkey)
reporter.report("Found invalid group entry: $fkey")
}
}
entry.existing.findAll { it !in entry.children }.each { println('Removed: ' + it) }
}
} else if ('*()' == entry.desc) {
if (!jcls.methods) {
itr.remove()
reporter.report("Invalid group, class has no methods: $key")
} else {
jcls.methods.each { mtd, value ->
if (mtd.startsWith('<clinit>') || mtd.startsWith('lambda$')) return
key = entry.cls + ' ' + mtd.replace(' ', '')
if (accessLevel(value.access) < accessStr(entry.modifier)) {
if (lines.containsKey(key)) {
toRemove.add(key)
} else if (!entry.existing.contains(key)) {
reporter.report("Missing group entry: $key")
}
entry.children.add(key)
} else if (lines.containsKey(key)) {
toRemove.add(key)
reporter.report("Found invalid group entry: $key")
}
}
entry.existing.findAll { it !in entry.children }.each { println('Removed: ' + it) }
}
} else if ('<init>' == entry.desc) { //Make all public non-abstract subclasses
constructorGroups.put(binaryName, entry)
}
}
// Process normal lines, remove invalid and remove narrowing
else {
def jcls = inheritance.get(binaryName)
if (jcls === null) {
itr.remove()
reporter.report("Invalid: $key")
} else if (entry.desc == '') {
if (accessLevel(jcls.access) > accessStr(entry.modifier) && (entry.comment === null || !entry.comment.startsWith('#force '))) {
itr.remove()
reporter.report("Invalid Narrowing: $key")
}
} else if (!entry.desc.contains('(')) {
if (!jcls.fields || !jcls.fields.containsKey(entry.desc)) {
itr.remove()
reporter.report("Invalid: $key")
} else {
final value = jcls.fields[entry.desc]
if (accessLevel(value.access) > accessStr(entry.modifier) && (entry.comment === null || !entry.comment.startsWith('#force '))) {
itr.remove()
reporter.report("Invalid Narrowing: $key - ${entry.comment}")
}
}
} else {
final jdesc = entry.desc.replace('(', ' (')
if (!jcls.methods || !jcls.methods.containsKey(jdesc)) {
itr.remove()
reporter.report("Invalid: $key")
} else {
final value = jcls.methods[jdesc]
if (accessLevel(value.access) > accessStr(entry.modifier) && (entry.comment === null || !entry.comment.startsWith('#force '))) {
itr.remove()
reporter.report("Invalid Narrowing: $key")
}
}
}
}
}
inheritance.each { tcls, value ->
if (!value.methods || ((value.access & Opcodes.ACC_ABSTRACT) !== 0)) return
String parent = tcls
while (parent !== null) {
constructorGroups[parent]?.tap { entry ->
value.methods.each { mtd, v ->
if (mtd.startsWith('<init>')) {
final child = tcls.replaceAll('/', '\\.') + ' ' + mtd.replace(' ', '')
if (accessLevel(v.access) < 3) {
if (lines.containsKey(child)) {
toRemove.add(child)
} else if (child !in entry.existing) {
reporter.report("Missing group entry: $child")
}
entry.children.add(child)
} else if (lines.containsKey(child)) {
toRemove.add(child)
reporter.report("Found invalid group entry: $child")
}
}
}
}
parent = inheritance[parent]?.superName
}
}
constructorGroups.values().each { entry -> entry.existing.findAll { it !in entry.children }.each{ reporter.report("Found invalid group entry: $it") } }
toRemove.each(lines.&remove)
return lines
}
private static List<String> joinBack(TreeMap<String, ATParser.Entry> lines, Map<String, InheritanceData> inheritance, IMappingFile mappings) {
final data = [] as List<String>
final remapComment = { ATParser.Entry entry ->
if (!mappings || !entry || !entry.desc) return null
final comment = entry.comment?.substring(1)?.trim()
final jsonCls = inheritance.get(entry.cls.replaceAll('\\.', '/'))
final mappingsClass = mappings?.getClass(jsonCls.name)
if (mappingsClass === null) return entry.comment
final idx = entry.desc.indexOf('(')
String mappedName = idx == -1
? mappingsClass.remapField(entry.desc)
: mappingsClass.remapMethod(entry.desc.substring(0, idx), entry.desc.substring(idx))
if (!mappedName) return entry.comment
if (mappedName == '<init>')
mappedName = 'constructor'
if (comment?.startsWith(mappedName))
return '# ' + comment
if (comment && comment.indexOf(' ') !== -1) {
def split = comment.split(' - ').toList()
if (split[0].indexOf(' ') !== -1)
// The first string is more than one word, so append before it
return "# ${mappedName} - ${comment}"
split.remove(0)
return "# ${mappedName} - ${String.join(' - ', split)}"
}
return '# ' + mappedName
}
lines.each { key, value ->
if (!value.group) {
def comment = remapComment.call(value)
data.add(value.modifier + ' ' + key + (comment ? ' ' + comment : ''))
} else {
data.add(('#group ' + value.modifier + ' ' + key + ' ' + (value.comment ?: '')).trim())
value.children.each {
final line = value.modifier + ' ' + it
final entry = ATParser.parseEntry(line)
final comment = remapComment(entry)
data.add(line + (comment ? ' ' + comment : ''))
}
data.add('#endgroup')
}
}
return data
}
static int accessStr(String access) {
if (access.endsWith('-f') || access.endsWith('+f')) return 4
switch (access.toLowerCase()) {
case 'public': return 3
case 'protected': return 2
case 'default': return 1
case 'private': return 0
default: return -1
}
}
static int accessLevel(int access) {
if ((access & Opcodes.ACC_PUBLIC) !== 0) return 3
if ((access & Opcodes.ACC_PROTECTED) !== 0) return 2
if ((access & Opcodes.ACC_PRIVATE) !== 0) return 0
return 1
}
}
@CompileStatic
class ATParser {
static TreeMap<String, Entry> parse(List<String> lines, CheckTask.Reporter reporter) {
TreeMap<String, Entry> outLines = new TreeMap<>()
Entry group = null
for (final line : lines) {
if (line.isEmpty()) continue
if (line.startsWith('#group ')) {
final entry = parseEntry(line.substring(7))
if (entry.desc != '*' && entry.desc != '*()' && entry.desc != '<init>') {
reporter.report("Invalid group: $line", false)
}
entry.group = true
entry.children = []
entry.existing = []
group = entry
if (outLines.containsKey(entry.key)) {
reporter.report("Duplicate group: $line", false)
}
outLines[entry.key] = group
} else if (group !== null) {
if (line.startsWith('#endgroup')) {
group = null
} else {
final key = parseEntry(line).key
group.existing.add(key)
}
} else if (line.startsWith('#endgroup')) {
reporter.report("Invalid group ending: $line", false)
} else if (line.startsWith('#')) {
//Nom
} else {
final entry = parseEntry(line)
if (outLines.containsKey(entry.key)) {
reporter.report("Found duplicate: $line")
continue
}
outLines[entry.key] = entry
}
}
return outLines
}
static Entry parseEntry(String line) {
final idx = line.indexOf('#')
final String comment = idx === -1 ? null : line.substring(idx)
if (idx !== -1) line = line.substring(0, idx - 1)
final data = (line.trim() + ' ').split(' ', -1)
new Entry(data[0], data[1], data[2], comment)
}
@TupleConstructor
static final class Entry {
String modifier, cls, desc, comment
Set<String> existing
TreeSet<String> children
boolean group = false
@Lazy
String key = {cls + (desc.isEmpty() ? '' : ' ' + desc)}()
Object getAt(String key) {
return getProperty(key)
}
}
}
@@ -1,95 +0,0 @@
package net.minecraftforge.forge.tasks.checks
import net.minecraftforge.forge.tasks.Util
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Type
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
abstract class CheckExcs extends CheckTask {
@InputFile abstract RegularFileProperty getBinary()
@InputFiles abstract ConfigurableFileCollection getExcs()
@Override
void check(Reporter reporter, boolean fix) {
final Set<String> known = []
collectKnown(known)
excs.each { f ->
final lines = []
f.eachLine { line ->
int idx = line.indexOf('#')
if (idx === 0 || line.isEmpty()) {
return
}
if (idx !== -1) line = line.substring(0, idx - 1)
if (!line.contains('=')) {
reporter.report("Invalid: $line")
return
}
def (String key, String value) = line.split('=', 2)
if (!known.contains(key)) {
reporter.report("Unknown: $line")
return
}
String desc = key.split('\\.', 2)[1]
if (!desc.contains('(')) {
reporter.report("Invalid: $line")
return
}
desc = '(' + desc.split('\\(', 2)[1]
def (exceptions, String args) = value.contains('|') ? value.split('|', 2) : [value, '']
if (args.split(',').length !== Type.getArgumentTypes(desc).length) {
reporter.report("Invalid: $line")
return
}
lines.add(line)
return
}
if (fix) f.text = lines.sort().join('\n')
}
}
private void collectKnown(Collection<String> known) {
binary.get().asFile.withInputStream { i ->
new ZipInputStream(i).withCloseable { zin ->
final visitor = new ClassVisitor(Util.ASM_LEVEL) {
private String cls
@Override
void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
this.cls = name
}
@Override
MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
known.add(this.cls + '.' + name + descriptor)
super.visitMethod(access, name, descriptor, signature, exceptions)
}
}
ZipEntry zein
while ((zein = zin.nextEntry) !== null) {
if (zein.name.endsWith('.class')) {
ClassReader reader = new ClassReader(zin)
reader.accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES)
}
}
}
}
}
}
@@ -1,11 +0,0 @@
package net.minecraftforge.forge.tasks.checks
import groovy.transform.CompileStatic
@CompileStatic
enum CheckMode {
CHECK,
FIX
CheckMode() {}
}
@@ -1,182 +0,0 @@
package net.minecraftforge.forge.tasks.checks
import groovy.transform.CompileStatic
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.Optional
import java.nio.file.Files
import java.nio.file.Path
import java.util.regex.Pattern
@CompileStatic
abstract class CheckPatches extends CheckTask {
private static final Pattern HUNK_START_PATTERN = Pattern.compile('^@@ -[0-9,]* \\+[0-9,_]* @@$')
private static final Pattern WHITESPACE_PATTERN = Pattern.compile('^[+\\-]\\s*$')
private static final Pattern IMPORT_PATTERN = Pattern.compile('^[+\\-]\\s*import.*')
private static final Pattern FIELD_PATTERN = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final)?([^=;]*)(=.*)?;\\s*$')
private static final Pattern METHOD_PATTERN = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final)?([^(]*)[(]([^)]*)?[)]\\s*[{]\\s*$')
private static final Pattern CLASS_PATTERN = Pattern.compile('^[+\\-][\\s]*((public|protected|private)[\\s]*)?(static[\\s]*)?(final[\\s]*)?(class|interface)([^{]*)[{]\\s*$')
private static final Map<String, Integer> ACCESS_MAP = [private: 0, protected: 2, public: 3].tap { it.put(null, 1) }
@InputDirectory abstract DirectoryProperty getPatchDir()
@Input @Optional abstract ListProperty<String> getPatchesWithS2SArtifact()
@Override
void check(Reporter reporter, boolean fix) {
final patchDir = getPatchDir().get().asFile.toPath()
Files.walk(patchDir).withCloseable {
it.filter(Files.&isRegularFile).forEach { path ->
final String relativeName = patchDir.relativize(path).toString()
verifyPatch(path, reporter, fix, relativeName, patchesWithS2SArtifact.get().contains(relativeName.replace('\\', '/')))
}
}
}
static boolean accessChange(String previous, String current) {
//return ACCESS_MAP[previous] < ACCESS_MAP[current]
return previous != current
}
void verifyPatch(Path patch, Reporter reporter, boolean fix, String patchPath, boolean hasS2SArtifact) {
int oldFixedErrors = reporter.fixed.size()
final lines = Files.readAllLines(patch)
int hunksStart = 0
boolean onlyWhiteSpace = false
final List<String> newLines = []
// First two lines are file name ++/-- and we do not care
newLines.add(lines[0] + '\n')
newLines.add(lines[1] + '\n')
int i
for (i = 2; i < lines.size(); ++i) {
def line = lines[i]
newLines.add(line + '\n')
if (HUNK_START_PATTERN.matcher(line).find()) {
if (onlyWhiteSpace) {
if (!hasS2SArtifact)
reporter.report("Patch contains only white space hunk starting at line ${hunksStart + 1}, file: $patchPath")
int toRemove = i - hunksStart
while (toRemove-- > 0)
newLines.remove(newLines.size() - 1)
}
hunksStart = i
onlyWhiteSpace = true
continue
}
if (line.startsWithAny('+','-')) {
def prefixChange = false
def prevLine = lines[i - 1]
if (line.charAt(0) == (char)'+' && prevLine.charAt(0) == (char)'-') {
def prevTrim = prevLine.substring(1).replaceAll("\\s", "")
def currTrim = line.substring(1).replaceAll("\\s", "")
if (prevTrim == currTrim) {
prefixChange = true
}
def pMatcher = FIELD_PATTERN.matcher(prevLine)
def cMatcher = FIELD_PATTERN.matcher(line)
if (pMatcher.find() && cMatcher.find() &&
pMatcher.group(6) == cMatcher.group(6) && // = ...
pMatcher.group(5) == cMatcher.group(5) && // field name
pMatcher.group(3) == cMatcher.group(3) && // static
(accessChange(pMatcher.group(2), cMatcher.group(2)) || pMatcher.group(4) != cMatcher.group(4))) {
reporter.report("Patch contains access changes or final removal at line ${i + 1}, file: $patchPath", false)
}
pMatcher = METHOD_PATTERN.matcher(prevLine)
cMatcher = METHOD_PATTERN.matcher(line)
if (pMatcher.find() && cMatcher.find() &&
pMatcher.group(6) == cMatcher.group(6) && // params
pMatcher.group(5) == cMatcher.group(5) && // <T> void name
pMatcher.group(3) == cMatcher.group(3) && // static
(accessChange(pMatcher.group(2), cMatcher.group(2)) || pMatcher.group(4) != cMatcher.group(4))) {
reporter.report("Patch contains access changes or final removal at line ${i + 1}, file: $patchPath", false)
}
pMatcher = CLASS_PATTERN.matcher(prevLine)
cMatcher = CLASS_PATTERN.matcher(line)
if (pMatcher.find() && cMatcher.find() &&
pMatcher.group(6) == cMatcher.group(6) && // ClassName<> extends ...
pMatcher.group(5) == cMatcher.group(5) && // class | interface
pMatcher.group(3) == cMatcher.group(3) && // static
(accessChange(pMatcher.group(2), cMatcher.group(2)) || pMatcher.group(4) != cMatcher.group(4))) {
reporter.report("Patch contains access changes or final removal at line ${i + 1}, file: $patchPath", false)
}
}
if (line.charAt(0) == (char)'-' && i + 1 < lines.size()) {
final nextLine = lines[i + 1]
if (nextLine.charAt(0) == (char)'+') {
final nextTrim = nextLine.substring(1).replaceAll("\\s", "")
final currTrim = line.substring(1).replaceAll("\\s", "")
if (nextTrim == currTrim) {
prefixChange = true
}
}
}
final isWhiteSpaceChange = WHITESPACE_PATTERN.matcher(line).find()
if (!prefixChange && !isWhiteSpaceChange) {
onlyWhiteSpace = hasS2SArtifact && IMPORT_PATTERN.matcher(line).find()
} else if (isWhiteSpaceChange) {
final prevLineChange = prevLine.startsWithAny('+','-')
final nextLineChange = i + 1 < lines.size() && lines[i + 1].startsWithAny('+','-')
if (!prevLineChange && !nextLineChange) {
reporter.report("Patch contains white space change in valid hunk at line ${i + 1}, file: $patchPath \n$prevLine\n$line\n${lines[i+1]}", false)
}
}
if (line.contains('\t')) {
reporter.report("Patch contains tabs on line ${i + 1}, file: $patchPath")
line = line.replaceAll('\t', ' ')
newLines.remove(newLines.size() - 1)
newLines.add(line + '\n')
}
if (IMPORT_PATTERN.matcher(line).find() && !hasS2SArtifact) {
reporter.report("Patch contains import change on line ${i + 1}, file: $patchPath", false)
}
}
}
if (onlyWhiteSpace) {
if (!hasS2SArtifact)
reporter.report("Patch contains only white space hunk starting at line ${hunksStart + 1}, file: $patchPath")
def toRemove = i - hunksStart;
while (toRemove-- > 0)
newLines.remove(newLines.size() - 1)
}
if ((reporter.fixed.size() > oldFixedErrors && fix) || hasS2SArtifact) {
if (newLines.size() <= 2) {
logger.lifecycle("Patch is now empty removing, file: {}", patchPath)
Files.delete(patch)
}
else {
if (!hasS2SArtifact)
logger.lifecycle("*** Updating patch file. Please run setup then genPatches again. ***")
Files.newBufferedWriter(patch).withCloseable {
newLines.each { l -> it.write(l) }
}
}
}
}
}
@@ -1,110 +0,0 @@
package net.minecraftforge.forge.tasks.checks
import groovy.transform.CompileStatic
import net.minecraftforge.forge.tasks.Annotatable
import net.minecraftforge.forge.tasks.InheritanceData
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
@CompileStatic
abstract class CheckSAS extends CheckTask {
@InputFile abstract RegularFileProperty getInheritance()
@InputFiles abstract ConfigurableFileCollection getSass()
@Override
void check(Reporter reporter, boolean fix) {
final inheritance = InheritanceData.parse(this.inheritance.get().asFile)
sass.each { f ->
final lines = []
f.eachLine { line ->
if (line[0] == '\t') return // Skip any tabbed lines, those are ones we add
final idx = line.indexOf('#')
if (idx == 0 || line.isEmpty()) {
lines.add(line)
return
}
def comment = idx == -1 ? null : line.substring(idx)
if (idx != -1) line = line.substring(0, idx - 1)
final spl = (line.trim().replace('(', ' (') + ' ').split(' ', -1)
def (String cls, String name, String desc) = [spl[0], spl[1], spl[2]]
cls = cls.replaceAll('\\.', '/')
if (inheritance[cls] === null) {
reporter.report("Invalid: $line")
} else if (name.isEmpty()) { //Class SAS
final toAdd = []
final clsSided = isSided(inheritance[cls])
boolean sided = clsSided
/* TODO: MergeTool doesn't do fields
if (json[cls]['fields'] != null) {
for (entry in json[cls]['fields']) {
if (isSided(entry.value)) {
sided = true
toAdd.add('\t' + cls + ' ' + entry.key)
}
}
} */
final clsInh = inheritance[cls]
if (clsInh.methods) {
for (entry in clsInh.methods) {
if (isSided(entry.value)) {
sided = true
toAdd.add('\t' + cls + ' ' + entry.key.replaceAll(' ', ''))
findChildMethods(inheritance, cls, entry.key).each { lines.add('\t' + it) }
findChildMethods(inheritance, cls, entry.key).each { println(line + ' -- ' + it) }
} else if (clsSided) {
findChildMethods(inheritance, cls, entry.key).each { lines.add('\t' + it) }
findChildMethods(inheritance, cls, entry.key).each { println(line + ' -- ' + it) }
}
}
}
if (sided) {
lines.add(cls + (comment == null ? '' : ' ' + comment))
lines.addAll(toAdd.sort())
} else {
reporter.report("Invalid: $line")
}
} else if (desc.isEmpty()) { // Fields
/* TODO: MergeTool doesn't do fields
if (json[cls]['fields'] != null && isSided(json[cls]['fields'][name]))
lines.add(cls + ' ' + name + (comment == null ? '' : ' ' + comment))
else */
reporter.report("Invalid: $line")
} else { // Methods
final clsInh = inheritance[cls]
if (clsInh.methods === null || !isSided(clsInh.methods[name + ' ' + desc]))
reporter.report("Invalid: $line")
else {
lines.add(cls + ' ' + name + desc + (comment == null ? '' : ' ' + comment))
findChildMethods(inheritance, cls, name + ' ' + desc).each { println(line + ' -- ' + it) }
findChildMethods(inheritance, cls, name + ' ' + desc).each { lines.add('\t' + it) }
}
}
}
if (fix) f.text = lines.join('\n')
}
}
protected static boolean isSided(Annotatable annotatable) {
if (annotatable === null) return false
for (ann in annotatable.annotations) {
if ('Lnet/minecraftforge/api/distmarker/OnlyIn;' == ann.desc)
return true
}
return false
}
protected static findChildMethods(Map<String, InheritanceData> json, String cls, String desc) {
return json.values().findAll{ it.methods !== null && it.methods[desc] !== null && it.methods[desc].override == cls && isSided(it.methods[desc]) }
.collect { it.name + ' ' + desc.replace(' ', '') } as TreeSet
}
}
@@ -1,114 +0,0 @@
package net.minecraftforge.forge.tasks.checks
import groovy.transform.CompileStatic
import groovy.transform.stc.ClosureParams
import groovy.transform.stc.ThirdParam
import net.minecraftforge.forge.tasks.Util
import org.gradle.api.Action
import org.gradle.api.DefaultTask
import org.gradle.api.logging.LogLevel
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.TaskContainer
import org.gradle.api.tasks.VerificationTask
@CompileStatic
abstract class CheckTask extends DefaultTask implements VerificationTask {
@Input
abstract Property<CheckMode> getMode()
private boolean ignoreFailures = false
@Input
@Override
boolean getIgnoreFailures() {
return ignoreFailures
}
@Override
void setIgnoreFailures(boolean ignoreFailures) {
this.ignoreFailures = ignoreFailures
}
@TaskAction
void run() {
Util.init()
boolean doFix = getMode().get() === CheckMode.FIX
final Reporter reporter = new Reporter(doFix)
check(reporter, doFix)
if (reporter.messages) {
if (getMode().get() === CheckMode.CHECK) {
logger.error("Check task '{}' found errors:\n{}", name, reporter.messages.join('\n'))
if (!ignoreFailures) {
throw new IllegalArgumentException("${reporter.messages.size()} errors were found!")
}
} else {
if (logger.isEnabled(LogLevel.DEBUG)) {
logger.warn("Check task '{}' found {} errors and fixed {}:\n{}", name, reporter.messages.size(), reporter.fixed.size(), reporter.fixed.join('\n'))
} else {
logger.warn("Check task '{}' found {} errors and fixed {}.", name, reporter.messages.size(), reporter.fixed.size())
}
if (reporter.notFixed) {
logger.error('{} errors could not be fixed:\n{}', reporter.notFixed.size(), reporter.notFixed.join('\n'))
if (!ignoreFailures) {
throw new IllegalArgumentException("${reporter.notFixed.size()} errors which cannot be fixed were found!")
}
}
}
}
}
abstract void check(Reporter reporter, boolean fix)
@CompileStatic
static final class Reporter {
final boolean trackFixed
Reporter(boolean trackFixed) {
this.trackFixed = trackFixed
}
public final List<String> messages = []
public final List<String> fixed = []
public final List<String> notFixed = []
void report(String message, boolean canBeFixed = true) {
messages.add(message)
if (trackFixed) {
if (canBeFixed) {
fixed.add(message)
} else {
notFixed.add(message)
}
}
}
}
static <T extends CheckTask> void registerTask(TaskContainer tasks, String taskName, @DelegatesTo.Target('type') Class<T> clazz,
@DelegatesTo(genericTypeIndex = 0, target = 'type')
@ClosureParams(ThirdParam.FirstGenericType) Closure configuration) {
taskName = taskName.capitalize()
tasks.register("check$taskName", clazz) { CheckTask task ->
def castedTask = task as T
configuration.setDelegate(castedTask)
configuration.call(castedTask)
castedTask.mode.set(CheckMode.CHECK)
castedTask.group = 'checks'
}
tasks.named('checkAll').configure { it.dependsOn("check$taskName") }
tasks.register("checkAndFix$taskName", clazz) { CheckTask task ->
def castedTask = task as T
configuration.setDelegate(castedTask)
configuration.call(castedTask)
castedTask.mode.set(CheckMode.FIX)
castedTask.group = 'checks'
}
tasks.named('checkAllAndFix').configure { it.dependsOn("checkAndFix$taskName") }
}
}
-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'
}
-1102
View File
File diff suppressed because it is too large. Load diff
-85
View File
@@ -1,85 +0,0 @@
import net.minecraftforge.forge.tasks.CleanProperties
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'net.minecraftforge.gradleutils'
group = 'net.minecraftforge'
version = VERSION
println("Version: $version")
repositories {
mavenCentral()
maven gradleutils.forgeMaven
maven gradleutils.minecraftLibsMaven
//mavenLocal()
}
tasks.withType(Javadoc).configureEach {
options.tags = [
'apiNote:a:<em>API Note:</em>',
'implSpec:a:<em>Implementation Requirements:</em>',
'implNote:a:<em>Implementation Note:</em>'
]
options.addStringOption('Xdoclint:all,-missing', '-public')
}
// We need to write the manifest to the binary file so we have properly versioned packaged at dev time.
tasks.register('writeManifest') {
doLast {
if (plugins.findPlugin('net.minecraftforge.gradle.patcher')) // Forge project
universalJar.manifest.writeTo(rootProject.file('src/main/resources/META-INF/MANIFEST.MF'))
else
jar.manifest.writeTo(project.file('src/main/resources/META-INF/MANIFEST.MF'))
}
}
tasks.register('generateResources') {
dependsOn('writeManifest')
}
// Make sure out manifests get written before compiling the code, IDEA calls this task if you tell it to use the gradle build.
tasks.withType(JavaCompile).configureEach {
dependsOn 'generateResources'
dependsOn 'processResources' // Needed because we merge the output of this with the output of the compile task. And gradle detects downstream tasks using the output without a hard dep
options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation
options.warnings = false // Shutup deprecated for removal warnings
options.forkOptions.jvmArgs += '-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
}
}
+11 -9
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.8-&query=$.promos["1.21.8-recommended"]&color=brightgreen&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtc3RhciBmYS13LTE4IiBhcmlhLWhpZGRlbj0idHJ1ZSIgZGF0YS1pY29uPSJzdGFyIiBkYXRhLXByZWZpeD0iZmFzIiBmb2N1c2FibGU9ImZhbHNlIiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCA1NzYgNTEyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNMjU5LjMgMTcuOEwxOTQgMTUwLjIgNDcuOSAxNzEuNWMtMjYuMiAzLjgtMzYuNyAzNi4xLTE3LjcgNTQuNmwxMDUuNyAxMDMtMjUgMTQ1LjVjLTQuNSAyNi4zIDIzLjIgNDYgNDYuNCAzMy43TDI4OCA0MzkuNmwxMzAuNyA2OC43YzIzLjIgMTIuMiA1MC45LTcuNCA0Ni40LTMzLjdsLTI1LTE0NS41IDEwNS43LTEwM2MxOS0xOC41IDguNS01MC44LTE3LjctNTQuNkwzODIgMTUwLjIgMzE2LjcgMTcuOGMtMTEuNy0yMy42LTQ1LjYtMjMuOS01Ny40IDB6IiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K)][Download]
[![Latest Release](https://img.shields.io/badge/dynamic/json?url=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json&label=Latest&prefix=1.21.8-&query=$.promos["1.21.8-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.3-&query=$.promos["26.3-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!
@@ -34,12 +34,13 @@ For support and questions, visit [the Support Forum][Support-Forum] or [the Forg
We require the version of Java that Minecraft requires, which is currently:
| Minecraft | Java |
|---------------|-------------------|
| 1.20.6+ | [21][Adoptium-21] |
| 1.18 - 1.20.4 | [17][Adoptium-17] |
| 1.17.1 | [16][Adoptium-16] |
| 1.16.5- | [8][Adoptium-8] |
| Minecraft | Java |
|------------------|-------------------|
| 26.1+ | [25][Adoptium-25] |
| 1.20.6 - 1.21.11 | [21][Adoptium-21] |
| 1.18 - 1.20.4 | [17][Adoptium-17] |
| 1.17.1 | [16][Adoptium-16] |
| 1.16.5- | [8][Adoptium-8] |
**If you are on Windows and have difficulty using Java to launch JAR files, consider using [Jarfix by Johann Löfflmann][Jarfix].**
@@ -73,13 +74,14 @@ Please ensure you have a valid email address associated with your GitHub account
### Thanks
[![YourKit](https://www.yourkit.com/images/yklogo.png)](https://www.yourkit.com/)
YourKit for providing us accesss to their [YourKit Java Profiler] which helps identify bottlenecks and places for improvment.
YourKit for providing us accesss to their [YourKit Java Profiler] which helps identify bottlenecks and places for improvement.
[Contributing]: ./CONTRIBUTING.md
[CLA]: https://cla-assistant.io/MinecraftForge/MinecraftForge
[Download]: https://files.minecraftforge.net/
[Adoptium-25]: https://adoptium.net/temurin/releases/?version=25
[Adoptium-21]: https://adoptium.net/temurin/releases/?version=21
[Adoptium-17]: https://adoptium.net/temurin/releases/?version=17
[Adoptium-16]: https://adoptium.net/temurin/releases/?version=16
+24
View File
@@ -0,0 +1,24 @@
#!/usr/bin/env bash
for file in $(find repo | grep --invert-match -e md5 -e sha | grep -e .jar -e .zip); do
file_path=$(realpath $file)
file_name=$(basename $file)
echo "$file_name"
files=$(unzip -l $file | awk '{print $4}')
for f in $files; do
out="_actual/$file_name/$f"
parent=$(dirname "$out")
echo $out
if [ ! -d "$parent" ]; then
mkdir -p "$parent"
fi
if [[ $f == *.class ]]; then
javap -v -p jar:file://$file_path!/$f | tail -n +4 >$out.txt
elif [[ $f != */ && $f != "Name" && $f != "----" ]]; then
unzip -o -q $file $f -d _actual/$file_name
fi
done
done
+11 -59
View File
@@ -1,68 +1,20 @@
import net.minecraftforge.gradleutils.PomUtils
plugins {
id 'java-library'
id 'maven-publish'
id 'net.minecraftforge.licenser'
id 'net.minecraftforge.gradleutils'
id 'net.minecraftforge.forge.build.convention'
}
apply from: rootProject.file('build_shared.gradle')
gradleutils.displayName = 'FML'
description = 'Core library used for Forge ModLoader.'
dependencies {
compileOnly(libs.jetbrains.annotations)
api libs.eventbus
api(libs.eventbus)
annotationProcessor(libs.eventbus.validator)
implementation(project(':fmlloader'))
implementation(libs.commons.io)
implementation projects.fmlloader
implementation libs.commons.io
}
java {
toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION)
withSourcesJar()
}
convention.manifest('net/minecraftforge/fml/', [
'Automatic-Module-Name': 'net.minecraftforge.fmlcore',
'FMLModType' : 'LIBRARY'
])
tasks.named('jar', Jar).configure {
manifest {
attributes([
'Automatic-Module-Name': 'net.minecraftforge.fmlcore',
'FMLModType': 'LIBRARY'
] as LinkedHashMap)
attributes([
'Specification-Title': 'FML',
'Specification-Vendor': 'Forge Development LLC',
'Specification-Version': '1',
'Implementation-Title': 'FML',
'Implementation-Version': '1.0',
'Implementation-Vendor': 'Forge Development LLC'
] as LinkedHashMap, 'net/minecraftforge/fml/')
}
}
tasks.withType(JavaCompile).configureEach {
options.compilerArgs << '-Xlint:-unchecked'
}
license {
header = rootProject.file('LICENSE-header.txt')
}
publishing {
publications.register('mavenJava', MavenPublication).configure {
from components.java
artifactId = 'fmlcore'
pom {
name = project.name
description = 'Modifactions to Minecraft to enable mod developers.'
url = 'https://github.com/MinecraftForge/MinecraftForge'
PomUtils.setGitHubDetails(pom, 'MinecraftForge')
license PomUtils.Licenses.LGPLv2_1
}
}
repositories {
maven gradleutils.publishingForgeMaven
}
}
convention.publish()
@@ -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);
}
}
@@ -1,273 +0,0 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.fml.loading.FMLEnvironment;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.function.Supplier;
/**
* Use to execute code conditionally based on sidedness.
* <ul>
* <li>When you want to call something on one side and return a result {@link #safeCallWhenOn(Dist, Supplier)}</li>
* <li>When you want to call one thing on one side, another thing on the other and return a result {@link #safeRunForDist(Supplier, Supplier)}</li>
* <li>When you want to run something on one side {@link #safeRunWhenOn(Dist, Supplier)}</li>
* </ul>
*
* @deprecated Historically, this allowed for safe execution of code on the correct side by exploiting a low-level
* detail in Java 8, but in newer versions a simple if check is enough.
* <br>
* For example:
* <pre>{@code
* if (FMLEnvironment.dist.isClient()) {
* // client code here
* }
* }</pre>
*/
@Deprecated(since = "1.21.5", forRemoval = true)
public final class DistExecutor
{
private static final Logger LOGGER = LogManager.getLogger();
private DistExecutor() {}
/**
* Run the callable in the supplier only on the specified {@link Dist}.
* This method is NOT sided-safe and special care needs to be taken in code using this method that implicit class
* loading is not triggered by the Callable.
*
* This method can cause unexpected ClassNotFound exceptions.
*
* Use {@link #safeCallWhenOn(Dist, Supplier)} where possible.
*
* @param dist The dist to run on
* @param toRun A supplier of the callable to run (Supplier wrapper to ensure classloading only on the appropriate dist)
* @param <T> The return type from the callable
* @return The callable's result
* @deprecated use {@link #safeCallWhenOn(Dist, Supplier)} instead. This remains for advanced use cases.
*/
@Deprecated
public static <T> T callWhenOn(Dist dist, Supplier<Callable<T>> toRun) {
return unsafeCallWhenOn(dist, toRun);
}
public static <T> T unsafeCallWhenOn(Dist dist, Supplier<Callable<T>> toRun) {
if (dist == FMLEnvironment.dist) {
try
{
return toRun.get().call();
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
return null;
}
/**
* Call the SafeCallable when on the correct {@link Dist}.
*
* <strong>The lambda supplied here is required to be a method reference to a method defined in
* another class, otherwise an invalid SafeReferent error will be thrown</strong>
* @param dist the dist which this will run on
* @param toRun the SafeCallable to run and return the result from
* @param <T> The type of the SafeCallable
* @return the result of the SafeCallable or null if on the wrong side
*/
public static <T> T safeCallWhenOn(Dist dist, Supplier<SafeCallable<T>> toRun) {
validateSafeReferent(toRun);
return callWhenOn(dist, toRun::get);
}
/**
* Runs the supplied Runnable on the speicified side. Same warnings apply as {@link #callWhenOn(Dist, Supplier)}.
*
* This method can cause unexpected ClassNotFound exceptions.
*
* @see #callWhenOn(Dist, Supplier)
* @param dist Dist to run this code on
* @param toRun The code to run
* @deprecated use {@link #safeRunWhenOn(Dist, Supplier)} where possible. Advanced uses only.
*/
@Deprecated
public static void runWhenOn(Dist dist, Supplier<Runnable> toRun) {
unsafeRunWhenOn(dist, toRun);
}
/**
* Runs the supplied Runnable on the speicified side. Same warnings apply as {@link #unsafeCallWhenOn(Dist, Supplier)}.
*
* This method can cause unexpected ClassNotFoundException problems in common scenarios. Understand the pitfalls of
* the way the class verifier works to load classes before using this.
*
* Use {@link #safeRunWhenOn(Dist, Supplier)} if you can.
*
* @see #unsafeCallWhenOn(Dist, Supplier)
* @param dist Dist to run this code on
* @param toRun The code to run
*/
public static void unsafeRunWhenOn(Dist dist, Supplier<Runnable> toRun) {
if (dist == FMLEnvironment.dist) {
toRun.get().run();
}
}
/**
* Call the supplied SafeRunnable when on the correct Dist.
* @param dist The dist to run on
* @param toRun The code to run
*/
public static void safeRunWhenOn(Dist dist, Supplier<SafeRunnable> toRun) {
validateSafeReferent(toRun);
if (dist == FMLEnvironment.dist) {
toRun.get().run();
}
}
/**
* Executes one of the two suppliers, based on which side is active.
*
* <p>
* Example (replacement for old SidedProxy):<br/>
* {@code Proxy p = DistExecutor.runForDist(()->ClientProxy::new, ()->ServerProxy::new);}
*
* NOTE: the double supplier is required to avoid classloading the secondary target.
*
* @param clientTarget The supplier supplier to run when on the {@link Dist#CLIENT}
* @param serverTarget The supplier supplier to run when on the {@link Dist#DEDICATED_SERVER}
* @param <T> The common type to return
* @return The returned instance
* @deprecated Use {@link #safeRunForDist(Supplier, Supplier)}
*/
@Deprecated
public static <T> T runForDist(Supplier<Supplier<T>> clientTarget, Supplier<Supplier<T>> serverTarget) {
return unsafeRunForDist(clientTarget, serverTarget);
}
/**
* Unsafe version of {@link #safeRunForDist(Supplier, Supplier)}. Use only when you know what you're doing
* and understand why the verifier can cause unexpected ClassNotFoundException crashes even when code is apparently
* not sided. Ensure you test both sides fully to be confident in using this.
*
* @param clientTarget The supplier supplier to run when on the {@link Dist#CLIENT}
* @param serverTarget The supplier supplier to run when on the {@link Dist#DEDICATED_SERVER}
* @param <T> The common type to return
* @return The returned instance
*/
public static <T> T unsafeRunForDist(Supplier<Supplier<T>> clientTarget, Supplier<Supplier<T>> serverTarget) {
switch (FMLEnvironment.dist)
{
case CLIENT:
return clientTarget.get().get();
case DEDICATED_SERVER:
return serverTarget.get().get();
default:
throw new IllegalArgumentException("UNSIDED?");
}
}
/**
* Executes one of the two suppliers, based on which side is active.
*
* <p>
* Example (replacement for old SidedProxy):<br/>
* {@code Proxy p = DistExecutor.safeRunForDist(()->ClientProxy::new, ()->ServerProxy::new);}
*
* NOTE: the double supplier is required to avoid classloading the secondary target.
*
* @param clientTarget The supplier supplier to run when on the {@link Dist#CLIENT}
* @param serverTarget The supplier supplier to run when on the {@link Dist#DEDICATED_SERVER}
* @param <T> The common type to return
* @return The returned instance
*/
public static <T> T safeRunForDist(Supplier<SafeSupplier<T>> clientTarget, Supplier<SafeSupplier<T>> serverTarget) {
validateSafeReferent(clientTarget);
validateSafeReferent(serverTarget);
switch (FMLEnvironment.dist)
{
case CLIENT:
return clientTarget.get().get();
case DEDICATED_SERVER:
return serverTarget.get().get();
default:
throw new IllegalArgumentException("UNSIDED?");
}
}
/**
* A safe referent. This will assert that it is being called via a separated class method reference. This will
* avoid the common pitfalls of {@link #callWhenOn(Dist, Supplier)} above.
*
* SafeReferents assert that they are defined as a separate method outside the scope of the calling class.
*
* <strong>Implementations need to be defined in a separate class to the referring site, with appropriate
* visibility to be accessible at the callsite (generally, avoid private methods).</strong>
*
* <p>
* Valid:<br/>
*
* {@code DistExecutor.safeCallWhenOn(Dist.CLIENT, ()->AnotherClass::clientOnlyMethod);}
*
* <p>
* Invalid:<br/>
*
* {@code DistExecutor.safeCallWhenOn(Dist.CLIENT, ()->()->Minecraft.getInstance().world);}
*/
public interface SafeReferent {}
/**
* SafeCallable version of {@link SafeReferent}.
* @see SafeReferent
* @param <T> The return type of the Callable
*/
public interface SafeCallable<T> extends SafeReferent, Callable<T>, Serializable {}
/**
* SafeSupplier version of {@link SafeReferent}
* @param <T> The return type of the Supplier
*/
public interface SafeSupplier<T> extends SafeReferent, Supplier<T>, Serializable {}
/**
* SafeRunnable version of {@link SafeReferent}
*/
public interface SafeRunnable extends SafeReferent, Runnable, Serializable {}
private static final void validateSafeReferent(Supplier<? extends SafeReferent> safeReferentSupplier) {
if (FMLEnvironment.production) return;
final SafeReferent setter;
try {
setter = safeReferentSupplier.get();
} catch (Exception e) {
// Typically a class cast exception, just return out, expected.
return;
}
for (Class<?> cl = setter.getClass(); cl != null; cl = cl.getSuperclass()) {
try {
Method m = cl.getDeclaredMethod("writeReplace");
m.setAccessible(true);
Object replacement = m.invoke(setter);
if (!(replacement instanceof SerializedLambda))
break;// custom interface implementation
SerializedLambda l = (SerializedLambda) replacement;
if (Objects.equals(l.getCapturingClass(), l.getImplClass())) {
LOGGER.fatal("Detected unsafe referent usage, please view the code at {}",Thread.currentThread().getStackTrace()[3]);
throw new RuntimeException("Unsafe Referent usage found in safe referent method");
}
} catch (NoSuchMethodException e) {
} catch (IllegalAccessException | InvocationTargetException e) {
break;
}
}
}
}
@@ -6,13 +6,14 @@
package net.minecraftforge.fml;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.ToIntFunction;
import java.util.function.IntSupplier;
import java.util.function.Supplier;
/**
* A mod loading state. During mod loading, the mod loader transitions between states in a defined sorted list of states,
@@ -20,6 +21,7 @@ import java.util.function.ToIntFunction;
*
* @see IModStateProvider
*/
@NullMarked
public interface IModLoadingState {
/**
* {@return the name of this state}
@@ -42,19 +44,21 @@ public interface IModLoadingState {
/**
* {@return a function returning a human-friendly message for this state}
*/
Function<ModList, String> message();
Supplier<String> message();
/**
* @return a function that computes the size of this transition based on the size of the modlist.
* Used to compute progress.
*/
ToIntFunction<ModList> size();
default IntSupplier size() {
return ModList::size;
}
/**
* {@return an optional runnable, which runs before starting the transition from this state to the next}
* @see #buildTransition(Executor, Executor, ProgressMeter, Function, Function)
*/
Optional<Consumer<ModList>> inlineRunnable();
@Nullable Runnable inlineRunnable();
/**
* Builds the transition task for this state with a blank pre-sync and post-sync task.
@@ -65,14 +69,14 @@ public interface IModLoadingState {
* @return a transition task for this state
* @see #buildTransition(Executor, Executor, ProgressMeter, Function, Function)
*/
default Optional<CompletableFuture<Void>> buildTransition(
default @Nullable CompletableFuture<Void> buildTransition(
final Executor syncExecutor,
final Executor parallelExecutor,
final ProgressMeter progressBar
) {
return buildTransition(syncExecutor, parallelExecutor, progressBar,
e -> CompletableFuture.completedFuture(null),
e -> CompletableFuture.completedFuture(null)
_ -> CompletableFuture.completedFuture(null),
_ -> CompletableFuture.completedFuture(null)
);
}
@@ -87,7 +91,7 @@ public interface IModLoadingState {
* @param postSyncTask a function which returns a task to run after event post-dispatch hook
* @return a transition task for this state
*/
Optional<CompletableFuture<Void>> buildTransition(final Executor syncExecutor,
@Nullable CompletableFuture<Void> buildTransition(final Executor syncExecutor,
final Executor parallelExecutor,
final ProgressMeter progressBar,
final Function<Executor, CompletableFuture<Void>> preSyncTask,
@@ -6,21 +6,16 @@
package net.minecraftforge.fml;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import org.jetbrains.annotations.Nullable;
public interface IModStateTransition {
/** Magic value to allow me to optimize the futures list by ignoring the default value without making old methods nullable. */
BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> NULL_HOOK = (e, g) -> CompletableFuture.completedFuture(null);
static IModStateTransition buildNoopTransition() {
return ModStateTransitionHelper.NOOP;
}
@@ -40,15 +35,6 @@ public interface IModStateTransition {
return ModLoadingStage::nextState;
}
/**
* This used to allow you to fire multiple events during the transition. However, in doing so it would cause issues with the default
* ModContainer's event handlers causing issues such as mod classes being constructed multiple times.
*/
@Deprecated(since = "1.21.3", forRemoval = true)
default Supplier<Stream<EventGenerator<?>>> eventFunctionStream() {
return () -> Stream.ofNullable(eventFunction());
}
@Nullable
default <T extends IModBusEvent> EventGenerator<T> eventFunction() {
return null;
@@ -57,25 +43,9 @@ public interface IModStateTransition {
ThreadSelector threadSelector();
BiFunction<Executor, CompletableFuture<Void>, CompletableFuture<Void>> finalActivityGenerator();
/**
* I think this was meant as a way to do some things for each mod container beforge/after the transition had been sent to the container.
* However, the Future returned by this was never linked to the main transition future in any way. Which means that it was run
* in parallel and couldn't guarantee the state of the ModContainer.
* <p>
* Plus all existing code that I could find returned a completedFuture, so I don't think anyone ever used this.
* <p>
* If I were to add this back it would be a CompletableFuture wrap(ModContainer, CompletableFuture)
* <p>
* Added magic value NULL_HOOK to allow me to optimize the futures list by ignoring the default value without making this method nullable.
*/
@Deprecated(since = "1.21.3", forRemoval = true)
default BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> preDispatchHook() { return NULL_HOOK; }
@Deprecated(since = "1.21.3", forRemoval = true)
default BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> postDispatchHook() { return NULL_HOOK; }
interface EventGenerator<T extends IModBusEvent> extends Function<ModContainer, T> {
static <FN extends IModBusEvent> EventGenerator<FN> fromFunction(Function<ModContainer, FN> fn) {
return fn::apply;
}
}
}
}
@@ -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;
}
}
@@ -5,63 +5,20 @@
package net.minecraftforge.fml;
import java.util.Iterator;
import java.util.Spliterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
public class InterModComms
{
public record IMCMessage(String senderModId, String modId, String method, Supplier<?> messageSupplier) {
public final class InterModComms {
public record IMCMessage(String senderModId, String modId, String method, Supplier<?> messageSupplier) {}
/**
* Deprecated: use {@link #senderModId()}
* @return The modid of the sender. This is supplied by the caller, or by the active mod container context.
* Consider it unreliable.
*/
@Deprecated
public final String getSenderModId() {
return this.senderModId;
}
/**
* Deprecated: use {@link #modId()}
* @return The modid being sent to.
*/
@Deprecated
public final String getModId() {
return this.modId;
}
/**
* Deprecated: use {@link #method()}
* @return The method being sent to.
*/
@Deprecated
public final String getMethod() {
return this.method;
}
/**
* @param <T> The type of the message.
* @return A {@link Supplier} of the message.
* Use {@link #messageSupplier()}
*/
@SuppressWarnings("unchecked")
@Deprecated
public final <T> Supplier<T> getMessageSupplier() {
return (Supplier<T>) this.messageSupplier;
}
}
private static final ConcurrentMap<String, ConcurrentLinkedQueue<IMCMessage>> containerQueues = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<String, ConcurrentLinkedQueue<IMCMessage>> containerQueues = new ConcurrentHashMap<>();
/**
* Send IMC to remote. Sender will default to the active modcontainer, or minecraft if not.
@@ -72,8 +29,9 @@ public class InterModComms
* @return true if the message was enqueued for sending (the target modid is loaded)
*/
public static boolean sendTo(final String modId, final String method, final Supplier<?> thing) {
if (!ModList.get().isLoaded(modId)) return false;
containerQueues.computeIfAbsent(modId, k->new ConcurrentLinkedQueue<>()).add(new IMCMessage(ModLoadingContext.get().getActiveContainer().getModId(), modId, method, thing));
if (!ModList.isLoaded(modId)) return false;
containerQueues.computeIfAbsent(modId, _ -> new ConcurrentLinkedQueue<>())
.add(new IMCMessage(ModLoadingContext.get().getContainer().getModId(), modId, method, thing));
return true;
}
@@ -87,8 +45,9 @@ public class InterModComms
* @return true if the message was enqueued for sending (the target modid is loaded)
*/
public static boolean sendTo(final String senderModId, final String modId, final String method, final Supplier<?> thing) {
if (!ModList.get().isLoaded(modId)) return false;
containerQueues.computeIfAbsent(modId, k->new ConcurrentLinkedQueue<>()).add(new IMCMessage(senderModId, modId, method, thing));
if (!ModList.isLoaded(modId)) return false;
containerQueues.computeIfAbsent(modId, _ -> new ConcurrentLinkedQueue<>())
.add(new IMCMessage(senderModId, modId, method, thing));
return true;
}
@@ -112,7 +71,7 @@ public class InterModComms
* @return All messages
*/
public static Stream<IMCMessage> getMessages(final String modId) {
return getMessages(modId, s->Boolean.TRUE);
return getMessages(modId, _ -> true);
}
private record QueueFilteringSpliterator(
@@ -136,13 +95,10 @@ public class InterModComms
}
@Override
public boolean tryAdvance(final Consumer<? super IMCMessage> action)
{
public boolean tryAdvance(final Consumer<? super IMCMessage> action) {
IMCMessage next;
do
{
if (!iterator.hasNext())
{
do {
if (!iterator.hasNext()) {
return false;
}
next = this.iterator.next();
@@ -157,6 +113,5 @@ public class InterModComms
public Spliterator<IMCMessage> trySplit() {
return null;
}
}
}
@@ -5,6 +5,7 @@
package net.minecraftforge.fml;
import net.minecraftforge.eventbus.api.bus.BusGroup;
import net.minecraftforge.fml.config.IConfigEvent;
import net.minecraftforge.fml.config.ModConfig;
import net.minecraftforge.fml.event.IModBusEvent;
@@ -18,7 +19,6 @@ import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Supplier;
/**
@@ -40,11 +40,6 @@ public abstract class ModContainer {
protected final Map<Class<? extends IExtensionPoint<?>>, Supplier<?>> extensionPoints = new IdentityHashMap<>();
protected final EnumMap<ModConfig.Type, ModConfig> configs = new EnumMap<>(ModConfig.Type.class);
final Set<ModContainer> dependencies = new HashSet<>();
/**
* If you want to handle the event, override {@link #dispatchConfigEvent(IConfigEvent)}
*/
@Deprecated(since = "1.21.3", forRemoval = true)
protected Optional<Consumer<IConfigEvent>> configHandler = Optional.empty();
public ModContainer(IModInfo info) {
this.modId = info.getModId();
@@ -58,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
@@ -137,12 +132,7 @@ public abstract class ModContainer {
configs.put(modConfig.getType(), modConfig);
}
public void dispatchConfigEvent(IConfigEvent event) {
var handler = configHandler.orElse(null);
if (handler != null) {
handler.accept(event);
}
}
public void dispatchConfigEvent(IConfigEvent event) {}
/**
* Does this mod match the supplied mod?
@@ -157,6 +147,14 @@ public abstract class ModContainer {
*/
public abstract Object getMod();
/**
* @return the mod bus group for this specific mod, or null if there is none.
* Leave null if you use {@link BusGroup#DEFAULT} for your mod lifecycle events.
*/
public BusGroup getModBusGroup() {
return null;
}
/**
* Accept an arbitrary event for processing by the mod. Probably posted to an event bus in the lower level container.
* @param e Event to accept
@@ -5,14 +5,14 @@
package net.minecraftforge.fml;
import net.minecraftforge.fml.loading.LoadingModList;
import net.minecraftforge.forgespi.language.IModFileInfo;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.forgespi.language.ModFileScanData;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
import net.minecraftforge.forgespi.locating.IModFile;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
@@ -30,33 +30,36 @@ import java.util.stream.Stream;
* Master list of all mods - game-side version. This is classloaded in the game scope and
* can dispatch game level events as a result.
*/
public class ModList {
private static ModList INSTANCE;
private final List<IModFileInfo> modFiles;
private final List<IModInfo> sortedList;
private final Map<String, IModFileInfo> fileById;
private List<ModContainer> mods;
private Map<String, ModContainer> indexedMods;
private List<ModFileScanData> modFileScanData;
private List<ModContainer> sortedContainers;
public final class ModList {
private ModList() {}
private ModList(final List<ModFile> modFiles, final List<ModInfo> sortedList) {
this.modFiles = modFiles.stream().map(ModFile::getModFileInfo).toList();
this.sortedList = sortedList.stream().map(IModInfo.class::cast).toList();
private static final List<IModFileInfo> MOD_FILES;
private static final List<IModInfo> SORTED_LIST;
private static final Map<String, IModFileInfo> FILE_BY_ID;
private static List<ModContainer> mods;
private static Map<String, ModContainer> indexedMods;
private static List<ModContainer> sortedContainers;
static {
var loadingModFiles = LoadingModList.getModFiles().stream().map(ModFileInfo::getFile).toList();
var loadingSortedList = LoadingModList.getMods();
var modFileInfos = MOD_FILES = loadingModFiles.stream().map(ModFile::getModFileInfo).toList();
SORTED_LIST = loadingSortedList.stream().map(IModInfo.class::cast).toList();
var byId = new HashMap<String, IModFileInfo>();
for (var file : this.modFiles) {
for (var file : modFileInfos) {
for (var mod : file.getMods())
byId.put(mod.getModId(), mod.getOwningFile());
}
this.fileById = Collections.unmodifiableMap(byId);
CrashReportCallables.registerCrashCallable("Mod List", this::crashReport);
FILE_BY_ID = Map.copyOf(byId);
CrashReportCallables.registerCrashCallable("Mod List", ModList::crashReport);
}
private String getModContainerState(String modId) {
private static String getModContainerState(String modId) {
return getModContainerById(modId).map(ModContainer::getCurrentState).map(Object::toString).orElse("NONE");
}
private String fileToLine(IModFile mf) {
private static String fileToLine(IModFile mf) {
var mainMod = mf.getModInfos().getFirst();
return String.format(Locale.ENGLISH, "%-50.50s|%-30.30s|%-30.30s|%-20.20s|%-10.10s|Manifest: %s", mf.getFileName(),
mainMod.getDisplayName(),
@@ -66,93 +69,92 @@ public class ModList {
((ModFileInfo)mf.getModFileInfo()).getCodeSigningFingerprint().orElse("NOSIGNATURE"));
}
private String crashReport() {
return "\n"+applyForEachModFile(this::fileToLine).collect(Collectors.joining("\n\t\t", "\t\t", ""));
private static String crashReport() {
return "\n" + applyForEachModFile(ModList::fileToLine).collect(Collectors.joining("\n\t\t", "\t\t", ""));
}
public static ModList of(List<ModFile> modFiles, List<ModInfo> sortedList) {
INSTANCE = new ModList(modFiles, sortedList);
return INSTANCE;
public static void init() {}
public static List<IModFileInfo> getModFiles() {
return MOD_FILES;
}
public static ModList get() {
return INSTANCE;
public static IModFileInfo getModFileById(String modid) {
return FILE_BY_ID.get(modid);
}
public List<IModFileInfo> getModFiles() {
return modFiles;
static void setLoadedMods(final List<ModContainer> modContainers) {
mods = modContainers;
sortedContainers = modContainers.stream().sorted(Comparator.comparingInt(c -> SORTED_LIST.indexOf(c.getModInfo()))).toList();
indexedMods = modContainers.stream().collect(Collectors.toUnmodifiableMap(ModContainer::getModId, Function.identity()));
}
public IModFileInfo getModFileById(String modid) {
return this.fileById.get(modid);
}
void setLoadedMods(final List<ModContainer> modContainers) {
this.mods = modContainers;
this.sortedContainers = modContainers.stream().sorted(Comparator.comparingInt(c -> sortedList.indexOf(c.getModInfo()))).toList();
this.indexedMods = modContainers.stream().collect(Collectors.toMap(ModContainer::getModId, Function.identity()));
static void clearLoadedMods() {
mods = List.of();
indexedMods = Map.of();
sortedContainers = List.of();
}
@SuppressWarnings("unchecked")
public <T> Optional<T> getModObjectById(String modId) {
public static <T> Optional<T> getModObjectById(String modId) {
return getModContainerById(modId).map(ModContainer::getMod).map(o -> (T) o);
}
public Optional<? extends ModContainer> getModContainerById(String modId) {
return Optional.ofNullable(this.indexedMods.get(modId));
public static Optional<? extends ModContainer> getModContainerById(String modId) {
return Optional.ofNullable(indexedMods == null ? null : indexedMods.get(modId));
}
public Optional<? extends ModContainer> getModContainerByObject(Object obj) {
public static Optional<? extends ModContainer> getModContainerByObject(Object obj) {
return mods.stream().filter(mc -> mc.getMod() == obj).findFirst();
}
public List<IModInfo> getMods() {
return this.sortedList;
public static List<IModInfo> getMods() {
return SORTED_LIST;
}
public boolean isLoaded(String modTarget) {
return this.indexedMods.containsKey(modTarget);
public static boolean isLoaded(String modTarget) {
return indexedMods.containsKey(modTarget);
}
public int size() {
public static int size() {
return mods.size();
}
public List<ModFileScanData> getAllScanData() {
if (modFileScanData == null) {
modFileScanData = this.sortedList.stream().
map(IModInfo::getOwningFile).
filter(Objects::nonNull).
map(IModFileInfo::getFile).
distinct().
map(IModFile::getScanResult).
collect(Collectors.toList());
public static List<ModFileScanData> getAllScanData() {
final class LazyInit {
private LazyInit() {}
private static final List<ModFileScanData> MOD_FILE_SCAN_DATA = SORTED_LIST.stream()
.map(IModInfo::getOwningFile)
.filter(Objects::nonNull)
.map(IModFileInfo::getFile)
.distinct()
.map(IModFile::getScanResult)
.toList();
}
return modFileScanData;
return LazyInit.MOD_FILE_SCAN_DATA;
}
public void forEachModFile(Consumer<IModFile> fileConsumer) {
modFiles.stream().map(IModFileInfo::getFile).forEach(fileConsumer);
public static void forEachModFile(Consumer<IModFile> fileConsumer) {
MOD_FILES.stream().map(IModFileInfo::getFile).forEach(fileConsumer);
}
public <T> Stream<T> applyForEachModFile(Function<IModFile, T> function) {
return modFiles.stream().map(IModFileInfo::getFile).map(function);
public static <T> Stream<T> applyForEachModFile(Function<IModFile, T> function) {
return MOD_FILES.stream().map(IModFileInfo::getFile).map(function);
}
public void forEachModContainer(BiConsumer<String, ModContainer> modContainerConsumer) {
public static void forEachModContainer(BiConsumer<String, ModContainer> modContainerConsumer) {
indexedMods.forEach(modContainerConsumer);
}
public void forEachModInOrder(Consumer<ModContainer> containerConsumer) {
this.sortedContainers.forEach(containerConsumer);
public static void forEachModInOrder(Consumer<ModContainer> containerConsumer) {
sortedContainers.forEach(containerConsumer);
}
public List<ModContainer> getLoadedMods() {
return this.sortedContainers;
public static List<ModContainer> getLoadedMods() {
return sortedContainers;
}
public <T> Stream<T> applyForEachModContainer(Function<ModContainer, T> function) {
public static <T> Stream<T> applyForEachModContainer(Function<ModContainer, T> function) {
return indexedMods.values().stream().map(function);
}
}
@@ -5,15 +5,11 @@
package net.minecraftforge.fml;
import com.google.common.collect.ImmutableList;
import net.minecraftforge.eventbus.api.bus.BusGroup;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.loading.FMLEnvironment;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.fml.loading.ImmediateWindowHandler;
import net.minecraftforge.fml.loading.LoadingModList;
import net.minecraftforge.fml.loading.*;
import net.minecraftforge.fml.loading.moddiscovery.InvalidModIdentifier;
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
import net.minecraftforge.forgespi.language.IModInfo;
@@ -70,92 +66,76 @@ import static net.minecraftforge.fml.Logging.LOADING;
* and completes the mod loading sequence.</dd>
* </dl>
*/
public class ModLoader {
public final class ModLoader {
private ModLoader() {}
private static final Logger LOGGER = LogManager.getLogger();
private final LoadingModList loadingModList;
private final List<ModLoadingException> loadingExceptions;
private final Set<IModInfo> erroredModInfos;
private final List<ModLoadingWarning> loadingWarnings;
private final ModStateManager stateManager;
private static final ArrayList<ModLoadingException> LOADING_EXCEPTIONS = new ArrayList<>();
private static final ArrayList<ModLoadingWarning> LOADING_WARNINGS = new ArrayList<>();
private static final Consumer<String> STATUS_CONSUMER = StartupNotificationManager.modLoaderConsumer().orElse(_ -> {});
private static final HashSet<IModLoadingState> COMPLETED_STATES = new HashSet<>();
private static boolean loadingStateValid;
private final Consumer<String> statusConsumer = StartupNotificationManager.modLoaderConsumer().orElse(msg -> {});
private final Set<IModLoadingState> completedStates = new HashSet<>();
private ModList modList;
private ModLoader() {
this.loadingModList = FMLLoader.getLoadingModList();
this.loadingExceptions = this.loadingModList.getErrors().stream()
.flatMap(ModLoadingException::fromEarlyException)
.collect(Collectors.toList());
this.loadingWarnings = this.loadingModList.getBrokenFiles().stream()
.map(file -> new ModLoadingWarning(null, ModLoadingStage.VALIDATE, InvalidModIdentifier.identifyJarProblem(file.getFilePath()).orElse("fml.modloading.brokenfile"), file.getFileName()))
.collect(Collectors.toList());
if (this.loadingExceptions.isEmpty()) {
this.erroredModInfos = Collections.emptySet();
} else {
this.erroredModInfos = Collections.newSetFromMap(new IdentityHashMap<>());
this.erroredModInfos.addAll(this.loadingExceptions.stream().map(ModLoadingException::getModInfo).toList());
}
this.loadingModList.getModFiles().stream()
.filter(ModFileInfo::missingLicense)
.filter(modFileInfo -> modFileInfo.getMods().stream().noneMatch(this.erroredModInfos::contains)) //Ignore files where any other mod already encountered an error
.map(modFileInfo -> new ModLoadingException(null, ModLoadingStage.VALIDATE, "fml.modloading.missinglicense", null, modFileInfo.getFile()))
.forEach(this.loadingExceptions::add);
this.stateManager = new ModStateManager();
CrashReportCallables.registerCrashCallable("ModLauncher", FMLLoader::getLauncherInfo);
CrashReportCallables.registerCrashCallable("ModLauncher launch target", FMLLoader::launcherHandlerName);
CrashReportCallables.registerCrashCallable("ModLauncher naming", FMLLoader::getNaming);
CrashReportCallables.registerCrashCallable("ModLauncher services", ModLoader::computeModLauncherServiceList);
CrashReportCallables.registerCrashCallable("FML Language Providers", ModLoader::computeLanguageList);
}
private static String computeLanguageList() {
return "\n" + FMLLoader.getLanguageLoadingProvider()
.applyForEach(lp -> lp.name() + "@" + lp.getClass().getPackage().getImplementationVersion())
return "\n" + LanguageLoadingProvider
.applyForEach(lp -> lp.name() + '@' + lp.getClass().getPackage().getImplementationVersion())
.collect(Collectors.joining("\n\t\t", "\t\t", ""));
}
private static String computeModLauncherServiceList() {
final List<Map<String, String>> mods = FMLLoader.modLauncherModList();
return "\n"+mods.stream().map(mod->mod.getOrDefault("file","nofile")+
" "+mod.getOrDefault("name", "missing")+
" "+mod.getOrDefault("type","NOTYPE")+
" "+mod.getOrDefault("description", "")).
return "\n" + mods.stream().map(mod -> mod.getOrDefault("file","nofile") +
' ' + mod.getOrDefault("name", "missing") +
' ' + mod.getOrDefault("type","NOTYPE") +
' ' + mod.getOrDefault("description", "")).
collect(Collectors.joining("\n\t\t","\t\t",""));
}
public static ModLoader get() {
return LazyInit.INSTANCE;
}
private static final class LazyInit {
private static final ModLoader INSTANCE = new ModLoader();
private LazyInit() {}
}
/**
* Run on the primary starting thread by ClientModLoader and ServerModLoader
* @param syncExecutor An executor to run tasks on the main thread
* @param parallelExecutor An executor to run tasks on a parallel loading thread pool
* @param periodicTask Optional periodic task to perform on the main thread while other activities run
*/
public void gatherAndInitializeMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
public static void gatherAndInitializeMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
var loadingExceptions = LoadingModList.getErrors().stream()
.flatMap(ModLoadingException::fromEarlyException)
.toList();
ModLoader.LOADING_EXCEPTIONS.addAll(loadingExceptions);
LOADING_WARNINGS.addAll(LoadingModList.getBrokenFiles().stream()
.map(file -> new ModLoadingWarning(null, ModLoadingStage.VALIDATE, InvalidModIdentifier.identifyJarProblem(file.getFilePath()).orElse("fml.modloading.brokenfile"), file.getFileName()))
.toList());
Set<IModInfo> erroredModInfos;
if (loadingExceptions.isEmpty()) {
erroredModInfos = Collections.emptySet();
} else {
erroredModInfos = Collections.newSetFromMap(new IdentityHashMap<>());
erroredModInfos.addAll(loadingExceptions.stream().map(ModLoadingException::getModInfo).toList());
}
LoadingModList.getModFiles().stream()
.filter(ModFileInfo::missingLicense)
.filter(modFileInfo -> modFileInfo.getMods().stream().noneMatch(erroredModInfos::contains)) //Ignore files where any other mod already encountered an error
.map(modFileInfo -> new ModLoadingException(null, ModLoadingStage.VALIDATE, "fml.modloading.missinglicense", null, modFileInfo.getFile()))
.forEach(LOADING_EXCEPTIONS::add);
CrashReportCallables.registerCrashCallable("ModLauncher", FMLLoader::getLauncherInfo);
CrashReportCallables.registerCrashCallable("ModLauncher launch target", FMLLoader::launcherHandlerName);
CrashReportCallables.registerCrashCallable("ModLauncher naming", FMLLoader::getNaming);
CrashReportCallables.registerCrashCallable("ModLauncher services", ModLoader::computeModLauncherServiceList);
CrashReportCallables.registerCrashCallable("FML Language Providers", ModLoader::computeLanguageList);
ForgeFeature.registerFeature("javaVersion", ForgeFeature.VersionFeatureTest.forVersionString(IModInfo.DependencySide.BOTH, System.getProperty("java.version")));
ForgeFeature.registerFeature("openGLVersion", ForgeFeature.VersionFeatureTest.forVersionString(IModInfo.DependencySide.CLIENT, ImmediateWindowHandler.getGLVersion()));
loadingStateValid = true;
FMLLoader.backgroundScanHandler.waitForScanToComplete(periodicTask);
final ModList modList = ModList.of(loadingModList.getModFiles().stream().map(ModFileInfo::getFile).toList(),
loadingModList.getMods());
if (!this.loadingExceptions.isEmpty()) {
LOGGER.fatal(CORE, "Error during pre-loading phase", loadingExceptions.getFirst());
statusConsumer.accept("ERROR DURING MOD LOADING");
modList.setLoadedMods(Collections.emptyList());
if (!LOADING_EXCEPTIONS.isEmpty()) {
LOGGER.fatal(CORE, "Error during pre-loading phase", LOADING_EXCEPTIONS.getFirst());
STATUS_CONSUMER.accept("ERROR DURING MOD LOADING");
ModList.clearLoadedMods();
loadingStateValid = false;
throw new LoadingFailedException(loadingExceptions);
throw new LoadingFailedException(LOADING_EXCEPTIONS);
}
var failedBounds = new ArrayList<ForgeFeature.Bound>();
for (var mod : loadingModList.getMods()) {
for (var mod : LoadingModList.getMods()) {
for (var feature : mod.getForgeFeatures()) {
if (!ForgeFeature.testFeature(FMLEnvironment.dist, feature))
failedBounds.add(feature);
@@ -164,8 +144,8 @@ public class ModLoader {
if (!failedBounds.isEmpty()) {
LOGGER.fatal(CORE, "Failed to validate feature bounds for mods: {}", failedBounds);
statusConsumer.accept("ERROR DURING MOD LOADING");
modList.setLoadedMods(Collections.emptyList());
STATUS_CONSUMER.accept("ERROR DURING MOD LOADING");
ModList.clearLoadedMods();
loadingStateValid = false;
throw new LoadingFailedException(failedBounds.stream()
.map(fb -> new ModLoadingException(fb.modInfo(), ModLoadingStage.CONSTRUCT, "fml.modloading.feature.missing", null, fb, ForgeFeature.featureValue(fb)))
@@ -173,18 +153,18 @@ public class ModLoader {
}
final var modContainers = new HashMap<String, ModContainer>();
for (var file : loadingModList.getModFiles()) {
var containers = this.buildMods(file.getFile());
for (var file : LoadingModList.getModFiles()) {
var containers = buildMods(file.getFile());
for (var container : containers)
modContainers.put(container.getModId(), container);
}
if (!loadingExceptions.isEmpty()) {
LOGGER.fatal(CORE, "Failed to initialize mod containers", loadingExceptions.getFirst());
statusConsumer.accept("ERROR DURING MOD LOADING");
modList.setLoadedMods(Collections.emptyList());
if (!LOADING_EXCEPTIONS.isEmpty()) {
LOGGER.fatal(CORE, "Failed to initialize mod containers", LOADING_EXCEPTIONS.getFirst());
STATUS_CONSUMER.accept("ERROR DURING MOD LOADING");
ModList.clearLoadedMods();
loadingStateValid = false;
throw new LoadingFailedException(loadingExceptions);
throw new LoadingFailedException(LOADING_EXCEPTIONS);
}
// Gather all dependencies, so we can make sure they have finished their transition events before we fire ours
@@ -207,36 +187,47 @@ public class ModLoader {
}
}
modList.setLoadedMods(modContainers.values().stream().toList());
this.modList = modList;
var stateList = stateManager.getStates(ModLoadingPhase.GATHER);
var progress = StartupMessageManager.addProgressBar("Mod Gather", stateList.stream().mapToInt(mls -> mls.size().applyAsInt(this.modList)).sum());
ModList.setLoadedMods(modContainers.values().stream().toList());
var stateList = ModStateManager.getStates(ModLoadingPhase.GATHER);
var progress = StartupMessageManager.addProgressBar("Mod Gather", stateList.stream().mapToInt(mls -> mls.size().getAsInt()).sum());
for (IModLoadingState mls : stateList) {
dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress);
}
progress.complete();
}
public void loadMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
var stateList = stateManager.getStates(ModLoadingPhase.LOAD);
var progress = StartupMessageManager.addProgressBar("Mod Loading", stateList.stream().mapToInt(mls -> mls.size().applyAsInt(modList)).sum());
public static void loadMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
var stateList = ModStateManager.getStates(ModLoadingPhase.LOAD);
var progress = StartupMessageManager.addProgressBar("Mod Loading", stateList.stream().mapToInt(mls -> mls.size().getAsInt()).sum());
for (IModLoadingState mls : stateList) {
dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress);
}
progress.complete();
}
public void finishMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
var stateList = stateManager.getStates(ModLoadingPhase.COMPLETE);
var progress = StartupMessageManager.addProgressBar("Mod Complete", stateList.stream().mapToInt(mls -> mls.size().applyAsInt(modList)).sum());
public static void finishMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
var stateList = ModStateManager.getStates(ModLoadingPhase.COMPLETE);
var progress = StartupMessageManager.addProgressBar("Mod Complete", stateList.stream().mapToInt(mls -> mls.size().getAsInt()).sum());
for (IModLoadingState mls : stateList) {
dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress);
}
statusConsumer.accept(String.format("Mod loading complete - %d mods loaded", this.modList.size()));
var modBusGroups = Set.of(
ModList.getLoadedMods().stream()
.map(ModContainer::getModBusGroup)
.filter(Objects::nonNull)
.toArray(BusGroup[]::new)
);
for (var modBusGroup : modBusGroups) {
parallelExecutor.execute(modBusGroup::trim);
}
parallelExecutor.execute(BusGroup.DEFAULT::trim);
STATUS_CONSUMER.accept("Mod loading complete - %d mods loaded".formatted(ModList.size()));
progress.complete();
}
private void dispatchAndHandleError(IModLoadingState state, ModWorkManager.DrivenExecutor syncExecutor, Executor parallelExecutor, final Runnable ticker, final ProgressMeter progressBar) {
private static void dispatchAndHandleError(IModLoadingState state, ModWorkManager.DrivenExecutor syncExecutor, Executor parallelExecutor, final Runnable ticker, final ProgressMeter progressBar) {
if (!isLoadingStateValid()) {
LOGGER.error("Cowardly refusing to process mod state change request from {}", state);
return;
@@ -244,25 +235,25 @@ public class ModLoader {
progressBar.label(progressBar.name() + " working");
syncExecutor.drive(ticker);
var inlineRunnable = state.inlineRunnable().orElse(null);
var inlineRunnable = state.inlineRunnable();
if (inlineRunnable != null) handleInlineTransition(inlineRunnable, state, syncExecutor, ticker);
var transition = state.buildTransition(syncExecutor, parallelExecutor, progressBar).orElse(null);
var transition = state.buildTransition(syncExecutor, parallelExecutor, progressBar);
if (transition != null) waitForTransition(state, syncExecutor, ticker, transition);
completedStates.add(state);
COMPLETED_STATES.add(state);
}
private void handleInlineTransition(final Consumer<ModList> transition, final IModLoadingState state, final ModWorkManager.DrivenExecutor syncExecutor, final Runnable ticker) {
var pb = StartupMessageManager.addProgressBar("State transition " +state.name()+" running", 0);
private static void handleInlineTransition(final Runnable transition, final IModLoadingState state, final ModWorkManager.DrivenExecutor syncExecutor, final Runnable ticker) {
var pb = StartupMessageManager.addProgressBar("State transition " + state.name() + " running", 0);
syncExecutor.drive(ticker);
transition.accept(this.modList);
transition.run();
syncExecutor.drive(ticker);
pb.complete();
syncExecutor.drive(ticker);
}
private void waitForTransition(final IModLoadingState state, final ModWorkManager.DrivenExecutor syncExecutor, final Runnable ticker, final CompletableFuture<Void> transition) {
private static void waitForTransition(final IModLoadingState state, final ModWorkManager.DrivenExecutor syncExecutor, final Runnable ticker, final CompletableFuture<Void> transition) {
while (!transition.isDone()) {
syncExecutor.drive(ticker);
}
@@ -276,7 +267,7 @@ public class ModLoader {
.anyMatch(obj -> !(obj instanceof ModLoadingException));
if (hasNotModLoadingEx) {
LOGGER.fatal("Encountered non-modloading exceptions!", e);
statusConsumer.accept("ERROR DURING MOD LOADING");
STATUS_CONSUMER.accept("ERROR DURING MOD LOADING");
throw e;
}
@@ -285,12 +276,12 @@ public class ModLoader {
.map(ModLoadingException.class::cast)
.toList();
LOGGER.fatal(LOADING,"Failed to complete lifecycle event {}, {} errors found", state.name(), modLoadingExceptions.size());
statusConsumer.accept("ERROR DURING MOD LOADING");
STATUS_CONSUMER.accept("ERROR DURING MOD LOADING");
throw new LoadingFailedException(modLoadingExceptions);
}
}
private List<ModContainer> buildMods(final IModFile modFile) {
private static List<ModContainer> buildMods(final IModFile modFile) {
final Map<String, IModInfo> modInfoMap = new HashMap<>();
for (var mod : modFile.getModFileInfo().getMods())
modInfoMap.put(mod.getModId(), mod);
@@ -320,14 +311,14 @@ public class ModLoader {
missingMods.removeAll(modIds);
LOGGER.fatal(LOADING, "The following mods are missing, but have classes in the jar: {}", missingMods);
loadingExceptions.add(new ModLoadingException(null, ModLoadingStage.CONSTRUCT, "fml.modloading.missingclasses", null, modFile.getFilePath()));
LOADING_EXCEPTIONS.add(new ModLoadingException(null, ModLoadingStage.CONSTRUCT, "fml.modloading.missingclasses", null, modFile.getFilePath()));
}
// remove errored mod containers
return containers.stream().filter(mc -> mc.modLoadingStage != ModLoadingStage.ERROR).toList();
}
private ModContainer buildModContainerFromTOML(final IModFile modFile, final Map<String, IModInfo> modInfoMap,
private static ModContainer buildModContainerFromTOML(final IModFile modFile, final Map<String, IModInfo> modInfoMap,
final String modId, final IModLanguageProvider.IModLanguageLoader languageLoader
) {
try {
@@ -339,7 +330,7 @@ public class ModLoader {
return languageLoader.loadMod(info, modFile.getScanResult(), FMLLoader.getGameLayer());
} catch (ModLoadingException mle) {
// exceptions are caught and added to the error list for later handling
loadingExceptions.add(mle);
LOADING_EXCEPTIONS.add(mle);
// return an errored container instance here, because we tried and failed building a container.
return new ErroredModContainer();
}
@@ -353,66 +344,68 @@ public class ModLoader {
return loadingStateValid;
}
public boolean hasCompletedState(final String stateName) {
IModLoadingState state = stateManager.findState(stateName);
return completedStates.contains(state);
public static boolean hasCompletedState(IModLoadingState state) {
return COMPLETED_STATES.contains(state);
}
public <T extends IModBusEvent> void runEventGenerator(Function<ModContainer, T> generator) {
public static <T extends IModBusEvent> void runEventGenerator(Function<ModContainer, T> generator) {
if (!loadingStateValid) {
LOGGER.error("Cowardly refusing to send event generator to a broken mod state");
return;
}
for (var mod : ModList.get().getLoadedMods())
for (var mod : ModList.getLoadedMods())
mod.acceptEvent(generator.apply(mod));
}
public <T extends IModBusEvent> void postEvent(T e) {
var cls = e.getClass();
public static <T extends IModBusEvent> void postEvent(T e) {
if (!loadingStateValid) {
LOGGER.error("Cowardly refusing to send event {} to a broken mod state", e.getClass().getName());
return;
}
for (var mod : ModList.get().getLoadedMods())
for (var mod : ModList.getLoadedMods())
mod.acceptEvent(e);
}
public <T extends IModBusEvent> T postEventWithReturn(T e) {
public static <T extends IModBusEvent> void postEvent(ModContainer mod, T e) {
mod.acceptEvent(e);
}
public static <T extends IModBusEvent> T postEventWithReturn(T e) {
if (!loadingStateValid) {
LOGGER.error("Cowardly refusing to send event {} to a broken mod state", e.getClass().getName());
return e;
}
for (var mod : ModList.get().getLoadedMods())
for (var mod : ModList.getLoadedMods())
mod.acceptEvent(e);
return e;
}
@SuppressWarnings("removal")
public <T extends IModBusEvent> void postEventWrapContainerInModOrder(T event) {
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)
);
}
public <T extends IModBusEvent> void postEventWithWrapInModOrder(T e, BiConsumer<ModContainer, T> pre, BiConsumer<ModContainer, T> post) {
public static <T extends IModBusEvent> void postEventWithWrapInModOrder(T e, BiConsumer<ModContainer, T> pre, BiConsumer<ModContainer, T> post) {
if (!loadingStateValid) {
LOGGER.error("Cowardly refusing to send event {} to a broken mod state", e.getClass().getName());
return;
}
for (var mod : ModList.get().getLoadedMods()) {
for (var mod : ModList.getLoadedMods()) {
pre.accept(mod, e);
mod.acceptEvent(e);
post.accept(mod, e);
}
}
public List<ModLoadingWarning> getWarnings() {
return ImmutableList.copyOf(this.loadingWarnings);
public static List<ModLoadingWarning> getWarnings() {
return List.copyOf(LOADING_WARNINGS);
}
public void addWarning(ModLoadingWarning warning) {
this.loadingWarnings.add(warning);
public static void addWarning(ModLoadingWarning warning) {
LOADING_WARNINGS.add(warning);
}
private static boolean runningDataGen = false;
@@ -19,31 +19,24 @@ public class ModLoadingContext {
private Object languageExtension;
/**
* @deprecated Use the context provided by your language loader in your mod's constructor
* @deprecated Use the context provided by your language loader in your mod's constructor. Example for Java mods: {@snippet :
* @Mod("examplemod")
* public final class ExampleMod {
* public ExampleMod(FMLJavaModLoadingContext context) {
* // Use context from the param instead of FMLJavaModLoadingContext.get() or ModLoadingContext.get()
* }
* }}
*/
@Deprecated(forRemoval = true, since="1.21.1")
public static ModLoadingContext get() {
return context.get();
}
/**
* @deprecated Going to be moved to ForgeHooks for Internal use.
*/
@Deprecated(forRemoval = true, since="1.21.1")
public void setActiveContainer(final ModContainer container) {
void setActiveContainer(final ModContainer container) {
this.activeContainer = container;
this.languageExtension = container == null ? null : container.contextExtension.get();
}
/**
* Going to be moved to ForgeHooks for Internal use.
* @deprecated Override/Use {@link ModLoadingContext#getContainer()}
*/
@Deprecated(forRemoval = true, since="1.21.1")
public ModContainer getActiveContainer() {
return activeContainer == null ? ModList.get().getModContainerById("minecraft").orElseThrow(()->new RuntimeException("Where is minecraft???!")) : activeContainer;
}
/**
* @deprecated Going to be moved to ForgeHooks for Internal use.
*/
@@ -52,11 +45,10 @@ public class ModLoadingContext {
return activeContainer == null ? "minecraft" : activeContainer.getNamespace();
}
/**
* @return {@link ModLoadingContext#getActiveContainer()} by default.
*/
public ModContainer getContainer() {
return getActiveContainer();
return activeContainer == null
? ModList.getModContainerById("minecraft").orElseThrow(()->new RuntimeException("Where is minecraft???!"))
: activeContainer;
}
/**
@@ -108,8 +100,7 @@ public class ModLoadingContext {
}
public void registerConfig(ModConfig.Type type, IConfigSpec<?> spec) {
if (spec.isEmpty())
{
if (spec.isEmpty()) {
// This handles the case where a mod tries to register a config, without any options configured inside it.
LOGGER.debug("Attempted to register an empty config for type {} on mod {}", type, getContainer().getModId());
return;
@@ -119,8 +110,7 @@ public class ModLoadingContext {
}
public void registerConfig(ModConfig.Type type, IConfigSpec<?> spec, String fileName) {
if (spec.isEmpty())
{
if (spec.isEmpty()) {
// This handles the case where a mod tries to register a config, without any options configured inside it.
LOGGER.debug("Attempted to register an empty config for type {} on mod {} using file name {}", type, getContainer().getModId(), fileName);
return;
@@ -129,7 +119,6 @@ public class ModLoadingContext {
getContainer().addConfig(new ModConfig(type, spec, getContainer(), fileName));
}
@SuppressWarnings("unchecked")
public <T> T extension() {
return (T)languageExtension;
@@ -6,13 +6,14 @@
package net.minecraftforge.fml;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.ToIntFunction;
import java.util.function.IntSupplier;
import java.util.function.Supplier;
/**
* Implementation of the {@link IModLoadingState} interface.
@@ -24,27 +25,26 @@ import java.util.function.ToIntFunction;
* @param inlineRunnable an optional runnable, which runs before starting the transition from this state to the next
* @param transition optional state transition information
*/
@NullMarked
public record ModLoadingState(
String name,
String previous,
Function<ModList, String> message,
ToIntFunction<ModList> size,
Supplier<String> message,
IntSupplier size,
ModLoadingPhase phase,
Optional<Consumer<ModList>> inlineRunnable,
Optional<IModStateTransition> transition
@Nullable Runnable inlineRunnable,
@Nullable IModStateTransition transition
) implements IModLoadingState {
@Override
public Optional<CompletableFuture<Void>> buildTransition(
public @Nullable CompletableFuture<Void> buildTransition(
final Executor syncExecutor,
final Executor parallelExecutor,
final ProgressMeter progressBar,
final Function<Executor, CompletableFuture<Void>> preSyncTask,
final Function<Executor, CompletableFuture<Void>> postSyncTask
) {
var transition = this.transition.orElse(null);
return transition == null
? Optional.empty()
: Optional.ofNullable(transition.build(name, syncExecutor, parallelExecutor, progressBar, preSyncTask, postSyncTask));
if (transition == null) return null;
return transition.build(name, syncExecutor, parallelExecutor, progressBar, preSyncTask, postSyncTask);
}
/**
@@ -59,7 +59,7 @@ public record ModLoadingState(
*/
@Deprecated(since = "1.21.3", forRemoval = true)
public static ModLoadingState empty(final String name, final String previous, final ModLoadingPhase phase) {
return of(name, phase).after(previous).empty();
return new ModLoadingState(name, previous, () -> "", () -> 0, phase, null, null);
}
/**
@@ -93,7 +93,7 @@ public record ModLoadingState(
*/
@Deprecated(since = "1.21.3", forRemoval = true)
public static ModLoadingState withTransition(final String name, final String previous,
final Function<ModList, String> message, final ModLoadingPhase phase,
final Supplier<String> message, final ModLoadingPhase phase,
final IModStateTransition transition) {
return of(name, phase).after(previous).message(message).withTransition(transition);
}
@@ -112,7 +112,7 @@ public record ModLoadingState(
*/
@Deprecated(since = "1.21.3", forRemoval = true)
public static ModLoadingState withInline(final String name, final String previous, final ModLoadingPhase phase,
final Consumer<ModList> inline) {
final Runnable inline) {
return of(name, phase).after(previous).withInline(inline);
}
@@ -120,12 +120,28 @@ public record ModLoadingState(
return new Builder(name, phase);
}
public static class Builder {
@Override
public boolean equals(Object o) {
return o instanceof ModLoadingState that
&& this.phase == that.phase
&& this.name.equals(that.name)
&& this.previous.equals(that.previous);
}
@Override
public int hashCode() {
int result = phase.hashCode();
result = 31 * result + name.hashCode();
result = 31 * result + previous.hashCode();
return result;
}
public static final class Builder {
private final String name;
private final ModLoadingPhase phase;
private String after;
private Function<ModList, String> message = null;
private ToIntFunction<ModList> size = null;
private String after = "";
private @Nullable Supplier<String> message = null;
private @Nullable IntSupplier size = null;
private Builder(final String name, final ModLoadingPhase phase) {
this.name = name;
@@ -138,28 +154,28 @@ public record ModLoadingState(
return this;
}
public Builder message(final String value) { return message(ml -> value); }
public Builder message(final Function<ModList, String> value) {
public Builder message(final String value) { return message(() -> value); }
public Builder message(final Supplier<String> value) {
this.message = value;
return this;
}
public Builder size(final int value) { return size(ml -> value); }
public Builder size(final ToIntFunction<ModList> value) {
public Builder size(final int value) { return size(() -> value); }
public Builder size(final IntSupplier value) {
this.size = value;
return this;
}
public ModLoadingState empty() {
return new ModLoadingState(name, after, message != null ? message : ml -> "", size != null ? size : ml -> 0, phase, Optional.empty(), Optional.empty());
return new ModLoadingState(name, after, message != null ? message : () -> "", size != null ? size : () -> 0, phase, null, null);
}
public ModLoadingState withTransition(final IModStateTransition transition) {
return new ModLoadingState(name, after, message != null ? message : ml -> "Processing transition " + name, size != null ? size : ModList::size, phase, Optional.empty(), Optional.of(transition));
return new ModLoadingState(name, after, message != null ? message : () -> "Processing transition " + name, size != null ? size : ModList::size, phase, null, transition);
}
public ModLoadingState withInline(final Consumer<ModList> inline) {
return new ModLoadingState(name, after, message != null ? message : ml -> "Processing work " + name, size != null ? size : ml -> 0, phase, Optional.of(inline), Optional.empty());
public ModLoadingState withInline(final Runnable inline) {
return new ModLoadingState(name, after, message != null ? message : () -> "Processing work " + name, size != null ? size : () -> 0, phase, inline, null);
}
}
}
}
@@ -5,41 +5,27 @@
package net.minecraftforge.fml;
import com.google.common.collect.Streams;
import net.minecraftforge.forgespi.language.IModInfo;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class ModLoadingWarning {
/**
* Mod Info for mod with warning
*/
private final IModInfo modInfo;
/**
* The stage where this warning was encountered
*/
private final ModLoadingStage warningStage;
/**
* I18N message to use for display
*/
private final String i18nMessage;
/**
* Context for message display
*/
private final List<Object> context;
/**
* @param modInfo Mod info for mod with warning
* @param warningStage The stage where this warning was encountered
* @param i18nMessage I18N message to use for display
* @param context Context for message display
*/
public record ModLoadingWarning(IModInfo modInfo, ModLoadingStage warningStage, String i18nMessage, List<Object> context) {
public ModLoadingWarning(final IModInfo modInfo, final ModLoadingStage warningStage, final String i18nMessage, Object... context) {
this.modInfo = modInfo;
this.warningStage = warningStage;
this.i18nMessage = i18nMessage;
this.context = Arrays.asList(context);
this(modInfo, warningStage, i18nMessage, List.of(context));
}
public ModLoadingWarning {
context = List.copyOf(context);
}
public String formatToString() {
return Bindings.getMessageParser().get().parseMessage(i18nMessage, Streams.concat(Stream.of(modInfo, warningStage), context.stream()).toArray());
return Bindings.getMessageParser().get().parseMessage(i18nMessage, Stream.concat(Stream.of(modInfo, warningStage), context.stream()).toArray());
}
}
@@ -15,18 +15,19 @@ import java.util.function.Function;
import java.util.stream.Collectors;
@SuppressWarnings("UnstableApiUsage")
public class ModStateManager {
private final EnumMap<ModLoadingPhase, List<IModLoadingState>> stateMap;
public final class ModStateManager {
private ModStateManager() {}
private static final EnumMap<ModLoadingPhase, List<IModLoadingState>> stateMap;
public ModStateManager() {
static {
final var sp = ServiceLoader.load(FMLLoader.getGameLayer(), IModStateProvider.class);
this.stateMap = ServiceLoaderUtils.streamWithErrorHandling(sp, sce->{})
stateMap = ServiceLoaderUtils.streamWithErrorHandling(sp, _ -> {})
.map(IModStateProvider::getAllStates)
.flatMap(List::stream)
.collect(Collectors.groupingBy(IModLoadingState::phase, ()->new EnumMap<>(ModLoadingPhase.class), Collectors.toUnmodifiableList()));
.collect(Collectors.groupingBy(IModLoadingState::phase, () -> new EnumMap<>(ModLoadingPhase.class), Collectors.toUnmodifiableList()));
}
public List<IModLoadingState> getStates(final ModLoadingPhase phase) {
public static List<IModLoadingState> getStates(final ModLoadingPhase phase) {
var nodes = stateMap.get(phase);
var lookup = nodes.stream().collect(Collectors.toMap(IModLoadingState::name, Function.identity()));
@@ -40,12 +41,14 @@ public class ModStateManager {
return TopologicalSort.topologicalSort(graph, Comparator.comparingInt(nodes::indexOf)).stream().filter(st->st!=dummy).toList();
}
public IModLoadingState findState(final String stateName) {
return stateMap.values()
public static IModLoadingState findState(final String stateName) {
var result = stateMap.values()
.stream()
.flatMap(List::stream)
.filter(mls -> mls.name().equals(stateName))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Unknown IModLoadingState: " + stateName));
.orElse(null);
if (result != null) return result;
else throw new IllegalArgumentException("Unknown IModLoadingState: " + stateName);
}
}
@@ -24,7 +24,7 @@ import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
@ApiStatus.Internal
class ModStateTransitionHelper {
final class ModStateTransitionHelper {
static final IModStateTransition NOOP = new NoopTransition();
record NoopTransition() implements IModStateTransition {
@Override
@@ -62,7 +62,30 @@ class ModStateTransitionHelper {
}
}
record FutureResult<V>(V value, Throwable exception){}
/// Same as [#completableFutureFromExceptionList(List)] but specialised for a single future
static <V> CompletionStage<Void> completableFutureFromException(FutureResult<V> t) {
if (t.exception() == null) {
return CompletableFuture.completedFuture(null);
} else {
var exception = t.exception();
CompletableFuture<Void> cf = new CompletableFuture<>();
RuntimeException accumulator = new RuntimeException();
cf.completeExceptionally(accumulator);
if (exception instanceof CompletionException) {
exception = exception.getCause();
}
if (exception.getSuppressed().length != 0) {
for (Throwable throwable : exception.getSuppressed()) {
accumulator.addSuppressed(throwable);
}
} else {
accumulator.addSuppressed(exception);
}
return cf;
}
}
record FutureResult<V>(V value, Throwable exception) {}
static <V> CompletableFuture<List<FutureResult<V>>> gather(Collection<? extends CompletableFuture<? extends V>> futures) {
var list = new ArrayList<FutureResult<V>>(futures.size());
@@ -76,25 +99,17 @@ 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> void addCompletableFutureTaskForModDispatch(
final IModStateTransition transition,
private static <T extends IModBusEvent> CompletableFuture<Void> addCompletableFutureTaskForModDispatch(
final Executor executor,
final List<CompletableFuture<Void>> completableFutures,
final ProgressMeter progressBar,
final EventGenerator<T> eventGenerator,
final BiFunction<ModLoadingStage, Throwable, ModLoadingStage> nextState
) {
@SuppressWarnings("removal")
var preDispatchHook = getHook(transition.preDispatchHook(), executor, eventGenerator);
if (preDispatchHook != null)
completableFutures.add(preDispatchHook);
var modFutures = new LinkedHashMap<String, CompletableFuture<Void>>();
for (var mod : ModList.get().getLoadedMods()) {
for (var mod : ModList.getLoadedMods()) {
CompletableFuture<Void> parent = null;
if (mod.dependencies.isEmpty()) {
@@ -120,7 +135,7 @@ 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);
@@ -129,20 +144,7 @@ class ModStateTransitionHelper {
modFutures.put(mod.getModId(), dispatch);
}
var dispatch = gather(modFutures.values()).thenComposeAsync(ModStateTransitionHelper::completableFutureFromExceptionList, executor);
completableFutures.add(dispatch);
@SuppressWarnings("removal")
var postDispatchHook = getHook(transition.preDispatchHook(), executor, eventGenerator);
if (postDispatchHook != null)
completableFutures.add(postDispatchHook);
}
private static <T extends IModBusEvent> CompletableFuture<Void> getHook(BiFunction<Executor, ? extends EventGenerator<?>, CompletableFuture<Void>> hook, Executor executor, EventGenerator<T> eventGenerator) {
if (hook == null || hook == IModStateTransition.NULL_HOOK) return null;
@SuppressWarnings("unchecked")
var hookTyped = (BiFunction<Executor, EventGenerator<T>, CompletableFuture<Void>>)hook;
return hookTyped.apply(executor, eventGenerator);
return gather(modFutures.values()).thenComposeAsync(ModStateTransitionHelper::completableFutureFromExceptionList, executor);
}
static <T extends IModBusEvent> CompletableFuture<Void> build(
@@ -154,31 +156,25 @@ class ModStateTransitionHelper {
final Function<Executor, CompletableFuture<Void>> preSyncTask,
final Function<Executor, CompletableFuture<Void>> postSyncTask
) {
List<CompletableFuture<Void>> futures = new ArrayList<>();
CompletableFuture<Void> future;
final var executor = transition.threadSelector().apply(syncExecutor, parallelExecutor);
@SuppressWarnings({ "removal", "unchecked" })
var events = transition.eventFunctionStream().get().map(f -> (EventGenerator<T>)f).toList();
for (int x = 0; x < events.size(); x++) {
var gen = events.get(x);
BiFunction<ModLoadingStage, Throwable, ModLoadingStage> state = x == events.size() - 1
? transition.nextModLoadingStage()
: ModLoadingStage::currentState;
addCompletableFutureTaskForModDispatch(transition, executor, futures, progressBar, gen, state);
final CompletableFuture<Void> preSyncTaskCF = preSyncTask.apply(syncExecutor);
CompletableFuture<Void> postEventDispatchCF = preSyncTaskCF
.thenRunAsync(() -> progressBar.label(progressBar.name() + ": dispatching " + name), parallelExecutor);
var eventGenerator = transition.eventFunction();
if (eventGenerator != null) {
BiFunction<ModLoadingStage, Throwable, ModLoadingStage> state = transition.nextModLoadingStage();
future = addCompletableFutureTaskForModDispatch(executor, progressBar, eventGenerator, state);
final CompletableFuture<Void> eventDispatchCF = future.handle(FutureResult::new)
.thenCompose(ModStateTransitionHelper::completableFutureFromException);
postEventDispatchCF = postEventDispatchCF.thenComposeAsync(_ -> eventDispatchCF, parallelExecutor);
}
final CompletableFuture<Void> preSyncTaskCF = preSyncTask.apply(syncExecutor);
final CompletableFuture<Void> eventDispatchCF = gather(futures).thenCompose(ModStateTransitionHelper::completableFutureFromExceptionList);
final CompletableFuture<Void> postEventDispatchCF = preSyncTaskCF
.thenApplyAsync(n -> {
progressBar.label(progressBar.name() + ": dispatching " + name);
return null;
}, parallelExecutor)
.thenComposeAsync(n -> eventDispatchCF, parallelExecutor)
.thenApply(r -> {
postSyncTask.apply(syncExecutor);
return null;
});
postEventDispatchCF = postEventDispatchCF.thenRun(() -> postSyncTask.apply(syncExecutor));
return transition.finalActivityGenerator().apply(syncExecutor, postEventDispatchCF);
}
}
@@ -14,25 +14,12 @@ import java.util.concurrent.locks.LockSupport;
import static net.minecraftforge.fml.Logging.LOADING;
public class ModWorkManager {
public final class ModWorkManager {
private static final Logger LOGGER = LogManager.getLogger();
private static final long PARK_TIME = TimeUnit.MILLISECONDS.toNanos(1);
public interface DrivenExecutor extends Executor {
boolean selfDriven();
boolean driveOne();
default void drive(Runnable ticker) {
if (!selfDriven()) {
ticker.run();
while (true) {
if (!driveOne()) break;
}
} else {
// park for a bit so other threads can schedule
LockSupport.parkNanos(PARK_TIME);
}
}
public sealed interface DrivenExecutor extends Executor {
void drive(Runnable ticker);
}
private record SyncExecutor(ConcurrentLinkedDeque<Runnable> tasks) implements DrivenExecutor {
@@ -40,8 +27,7 @@ public class ModWorkManager {
this(new ConcurrentLinkedDeque<>());
}
@Override
public boolean driveOne() {
private boolean driveOne() {
final Runnable task = tasks.pollFirst();
if (task != null) {
task.run();
@@ -50,31 +36,30 @@ public class ModWorkManager {
}
@Override
public boolean selfDriven() {
return false;
public void execute(final Runnable command) {
tasks.addLast(command);
}
@Override
public void execute(final Runnable command) {
tasks.addLast(command);
public void drive(Runnable ticker) {
ticker.run();
while (true) {
if (!driveOne()) break;
}
}
}
private record WrappingExecutor(Executor wrapped) implements DrivenExecutor {
@Override
public boolean selfDriven() {
return true;
}
@Override
public boolean driveOne() {
return false;
}
@Override
public void execute(final Runnable command) {
wrapped.execute(command);
}
@Override
public void drive(Runnable ticker) {
// Park for a bit so other threads can schedule
LockSupport.parkNanos(PARK_TIME);
}
}
private static final SyncExecutor syncExecutor = new SyncExecutor();
@@ -43,7 +43,7 @@ public final class OptionalMod<T>
private T getValue()
{
if (!searched) {
this.value = ModList.get().<T>getModObjectById(this.modId).orElse(null);
this.value = ModList.<T>getModObjectById(this.modId).orElse(null);
searched = true;
}
return this.value;
@@ -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;
@@ -93,8 +93,8 @@ public class VersionChecker {
return;
}
var mc = ModList.get().getModContainerById("minecraft");
var forge = ModList.get().getModContainerById("forge");
var mc = ModList.getModContainerById("minecraft");
var forge = ModList.getModContainerById("forge");
this.mcVersion = mc.get().getModInfo().getVersion().toString();
this.agent = "Java-http-client/" + System.getProperty("java.version")
@@ -223,7 +223,7 @@ public class VersionChecker {
// Gather a list of mods that have opted in to this update system by providing a URL.
private static List<IModInfo> gatherMods() {
List<IModInfo> ret = new LinkedList<>();
for (IModInfo info : ModList.get().getMods()) {
for (IModInfo info : ModList.getMods()) {
if (info.getUpdateURL().isPresent())
ret.add(info);
}
@@ -8,8 +8,7 @@ package net.minecraftforge.fml.config;
import com.electronwill.nightconfig.core.CommentedConfig;
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import com.mojang.logging.LogUtils;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.fml.DistExecutor;
import net.minecraftforge.fml.loading.FMLEnvironment;
import net.minecraftforge.fml.loading.FMLPaths;
import org.slf4j.Logger;
import org.slf4j.Marker;
@@ -19,57 +18,54 @@ import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class ConfigTracker {
public final class ConfigTracker {
private ConfigTracker() {}
private static final Logger LOGGER = LogUtils.getLogger();
static final Marker CONFIG = MarkerFactory.getMarker("CONFIG");
public static final ConfigTracker INSTANCE = new ConfigTracker();
private final ConcurrentHashMap<String, ModConfig> fileMap;
private final EnumMap<ModConfig.Type, Set<ModConfig>> configSets;
private final ConcurrentHashMap<String, Map<ModConfig.Type, ModConfig>> configsByMod;
private static final ConcurrentHashMap<String, ModConfig> FILE_MAP = new ConcurrentHashMap<>();
private static final Map<ModConfig.Type, Set<ModConfig>> CONFIG_SETS = Map.of(
ModConfig.Type.CLIENT, Collections.synchronizedSet(new LinkedHashSet<>()),
ModConfig.Type.COMMON, Collections.synchronizedSet(new LinkedHashSet<>()),
ModConfig.Type.SERVER, Collections.synchronizedSet(new LinkedHashSet<>())
);
private static final ConcurrentHashMap<String, Map<ModConfig.Type, ModConfig>> CONFIGS_BY_MOD = new ConcurrentHashMap<>();
private ConfigTracker() {
this.fileMap = new ConcurrentHashMap<>();
this.configSets = new EnumMap<>(ModConfig.Type.class);
this.configsByMod = new ConcurrentHashMap<>();
this.configSets.put(ModConfig.Type.CLIENT, Collections.synchronizedSet(new LinkedHashSet<>()));
this.configSets.put(ModConfig.Type.COMMON, Collections.synchronizedSet(new LinkedHashSet<>()));
// this.configSets.put(ModConfig.Type.PLAYER, new ConcurrentSkipListSet<>());
this.configSets.put(ModConfig.Type.SERVER, Collections.synchronizedSet(new LinkedHashSet<>()));
}
void trackConfig(final ModConfig config) {
if (this.fileMap.containsKey(config.getFileName())) {
LOGGER.error(CONFIG,"Detected config file conflict {} between {} and {}", config.getFileName(), this.fileMap.get(config.getFileName()).getModId(), config.getModId());
static void trackConfig(final ModConfig config) {
if (FILE_MAP.containsKey(config.getFileName())) {
LOGGER.error(CONFIG,"Detected config file conflict {} between {} and {}", config.getFileName(), FILE_MAP.get(config.getFileName()).getModId(), config.getModId());
throw new RuntimeException("Config conflict detected!");
}
this.fileMap.put(config.getFileName(), config);
this.configSets.get(config.getType()).add(config);
this.configsByMod.computeIfAbsent(config.getModId(), (k)->new EnumMap<>(ModConfig.Type.class)).put(config.getType(), config);
FILE_MAP.put(config.getFileName(), config);
CONFIG_SETS.get(config.getType()).add(config);
CONFIGS_BY_MOD.computeIfAbsent(config.getModId(), _ -> new EnumMap<>(ModConfig.Type.class)).put(config.getType(), config);
LOGGER.debug(CONFIG, "Config file {} for {} tracking", config.getFileName(), config.getModId());
}
public void loadConfigs(ModConfig.Type type, Path configBasePath) {
public static void loadConfigs(ModConfig.Type type, Path configBasePath) {
LOGGER.debug(CONFIG, "Loading configs type {}", type);
for (ModConfig config : this.configSets.get(type)) {
for (ModConfig config : CONFIG_SETS.get(type)) {
openConfig(config, configBasePath);
}
}
// TODO: [FML] This is only called for the server (outside of forceUnload)
// rethink config implementation for eventual FML rewrite
public void unloadConfigs(ModConfig.Type type, Path configBasePath) {
public static void unloadConfigs(ModConfig.Type type, Path configBasePath) {
LOGGER.debug(CONFIG, "Unloading configs type {}", type);
for (ModConfig config : this.configSets.get(type)) {
for (ModConfig config : CONFIG_SETS.get(type)) {
closeConfig(config, configBasePath);
}
ConfigFileTypeHandler.get(type).stopWatcher();
}
// If there is a better way to do this, please tell me. Because this is FUCKED
public void forceUnload() {
public static void forceUnload() {
// This is how ModStateProvider handles loading configs. So we're doing the same but with unloadConfigs instead
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> this.unloadConfigs(ModConfig.Type.CLIENT, FMLPaths.CONFIGDIR.get()));
this.unloadConfigs(ModConfig.Type.COMMON, FMLPaths.CONFIGDIR.get());
if (FMLEnvironment.dist.isClient())
unloadConfigs(ModConfig.Type.CLIENT, FMLPaths.CONFIGDIR.get());
unloadConfigs(ModConfig.Type.COMMON, FMLPaths.CONFIGDIR.get());
// just in case server watcher is still alive somehow...
ConfigFileTypeHandler.get(ModConfig.Type.SERVER).stopWatcher();
@@ -96,8 +92,8 @@ public class ConfigTracker {
}
}
public void loadDefaultServerConfigs() {
configSets.get(ModConfig.Type.SERVER).forEach(modConfig -> {
public static void loadDefaultServerConfigs() {
CONFIG_SETS.get(ModConfig.Type.SERVER).forEach(modConfig -> {
final CommentedConfig commentedConfig = CommentedConfig.inMemory();
modConfig.getSpec().correct(commentedConfig);
modConfig.setConfigData(commentedConfig);
@@ -105,16 +101,16 @@ public class ConfigTracker {
});
}
public String getConfigFileName(String modId, ModConfig.Type type) {
return Optional.ofNullable(configsByMod.getOrDefault(modId, Collections.emptyMap()).getOrDefault(type, null)).
public static String getConfigFileName(String modId, ModConfig.Type type) {
return Optional.ofNullable(CONFIGS_BY_MOD.getOrDefault(modId, Collections.emptyMap()).getOrDefault(type, null)).
map(ModConfig::getFullPath).map(Object::toString).orElse(null);
}
public Map<ModConfig.Type, Set<ModConfig>> configSets() {
return configSets;
public static Map<ModConfig.Type, Set<ModConfig>> configSets() {
return CONFIG_SETS;
}
public ConcurrentHashMap<String, ModConfig> fileMap() {
return fileMap;
public static ConcurrentHashMap<String, ModConfig> fileMap() {
return FILE_MAP;
}
}
@@ -13,6 +13,7 @@ import java.util.function.Function;
import org.jetbrains.annotations.Nullable;
public interface IConfigEvent {
// TODO: [FML] Get rid of this indirection somehow
record ConfigConfig(Function<ModConfig, IConfigEvent> loading, Function<ModConfig, IConfigEvent> reloading, @Nullable Function<ModConfig, IConfigEvent> unloading) {}
ConfigConfig CONFIGCONFIG = Bindings.getConfigConfiguration().get();
@@ -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
{
@@ -31,7 +29,7 @@ public class ModConfig
this.fileName = fileName;
this.container = container;
this.configHandler = ConfigFileTypeHandler.get(type);
ConfigTracker.INSTANCE.trackConfig(this);
ConfigTracker.trackConfig(this);
}
public ModConfig(final Type type, final IConfigSpec<?> spec, final ModContainer activeContainer) {
@@ -40,8 +38,9 @@ public class ModConfig
private static String defaultConfigName(Type type, String modId) {
// config file name would be "forge-client.toml" and "forge-server.toml"
return String.format(Locale.ROOT, "%s-%s.toml", modId, type.extension());
return modId + '-' + type.extension() + ".toml";
}
public Type getType() {
return type;
}
@@ -8,16 +8,22 @@ package net.minecraftforge.fml.event;
import net.minecraftforge.eventbus.api.bus.BusGroup;
import net.minecraftforge.eventbus.api.bus.EventBus;
import net.minecraftforge.eventbus.api.event.InheritableEvent;
import org.jspecify.annotations.NonNull;
/**
* Marker interface for events dispatched on the ModLifecycle event bus instead of the primary event bus
* Marker interface for mod lifecycle events dispatched on the
* {@linkplain net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext#getModBusGroup() mod BusGroup} instead
* of the {@linkplain BusGroup#DEFAULT default BusGroup}.
*
* @apiNote Each mod gets its own unique mod BusGroup instance, obtained from the mod loading context. Most events
* should go on the default BusGroup.
*/
public interface IModBusEvent extends InheritableEvent {
/**
* @param modEventBusGroup Obtained from {@link net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext#getModBusGroup()} or your language provider's equivalent.
* @param eventClass The event type you want to get the associated bus for.
*/
static <T extends IModBusEvent> EventBus<T> getBus(BusGroup modEventBusGroup, Class<T> eventClass) {
static <T extends IModBusEvent> @NonNull EventBus<@NonNull T> getBus(@NonNull BusGroup modEventBusGroup, @NonNull Class<T> eventClass) {
// Do not copy! Temporary solution until the FML rewrite is complete. May throw in future
// without prior notice.
//
@@ -7,8 +7,11 @@ package net.minecraftforge.fml.util.thread;
import net.minecraftforge.fml.LogicalSide;
public class EffectiveSide
{
/**
* Attempts to determine the effect side of the current thread, based on know threads in networking.
* This is not guaranteed to be correct, and modders should use other methods to determine the logical side when possible.
*/
public class EffectiveSide {
public static LogicalSide get() {
final ThreadGroup group = Thread.currentThread().getThreadGroup();
return group instanceof SidedThreadGroup ? ((SidedThreadGroup) group).getSide() : LogicalSide.CLIENT;
@@ -7,11 +7,13 @@ package net.minecraftforge.fml.util.thread;
import net.minecraftforge.fml.LogicalSide;
public final class SidedThreadGroups
{
public final class SidedThreadGroups {
public static final SidedThreadGroup CLIENT = new SidedThreadGroup(LogicalSide.CLIENT);
public static final SidedThreadGroup SERVER = new SidedThreadGroup(LogicalSide.SERVER);
private SidedThreadGroups() {
public static SidedThreadGroup get(boolean client) {
return client ? CLIENT : SERVER;
}
private SidedThreadGroups() { }
}
+21 -79
View File
@@ -1,92 +1,34 @@
import net.minecraftforge.gradleutils.PomUtils
import org.gradle.api.plugins.jvm.JvmTestSuite
plugins {
id 'java-library'
id 'maven-publish'
id 'net.minecraftforge.licenser'
id 'net.minecraftforge.gradleutils'
id 'jvm-test-suite'
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
}
java {
toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION)
withSourcesJar()
}
gradleutils.displayName = 'FML Early Display'
description = 'A pretty looking, but prone to erroring screen displayed during Forge loading.'
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")
implementation projects.fmlloader
implementation projects.fmlcore
implementation libs.bundles.lwjgl
implementation earlyDisplayLibs.slf4j.api
implementation libs.jopt.simple
}
tasks.named('test', Test).configure {
useJUnitPlatform()
}
testing.suites.named('test', JvmTestSuite) {
useJUnitJupiter('5.8.2')
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/')
dependencies {
implementation earlyDisplayTestLibs.powermock.core
runtimeOnly earlyDisplayLibs.slf4j.jdk14
runtimeOnly.bundle convention.nativeVariants(earlyDisplayTestLibs.bundles.lwjgl)
}
}
tasks.withType(JavaCompile).configureEach {
options.compilerArgs << '-Xlint:unchecked'
}
convention.manifest('net/minecraftforge/fml/earlydisplay/', [
'Automatic-Module-Name': 'net.minecraftforge.earlydisplay',
'Forge-Module-Layer' : 'boot'
])
license {
header = rootProject.file('LICENSE-header.txt')
}
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
}
}
convention.publish()
@@ -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,19 +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.IntSupplier;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL.createCapabilities;
import static org.lwjgl.opengl.GL32C.*;
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.
@@ -70,7 +69,7 @@ import static org.lwjgl.opengl.GL32C.*;
* Based on the prior ClientVisualization, with some personal touches.
*/
public class DisplayWindow implements ImmediateWindowProvider {
private static final int[][] GL_VERSIONS = new int[][] {{4,6}, {4,5}, {4,4}, {4,3}, {4,2}, {4,1}, {4,0}, {3,3}, {3,2}};
private static final int[][] GL_VERSIONS = new int[][] {{4,6}, {4,5}, {4,4}, {4,3}, {4,2}, {4,1}, {4,0}, {3,3}};
private static final Logger LOGGER = LoggerFactory.getLogger("EARLYDISPLAY");
private final AtomicBoolean animationTimerTrigger = new AtomicBoolean(true);
@@ -85,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);
@@ -115,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();
@@ -139,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) {
@@ -178,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();
@@ -190,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();
}
}
@@ -206,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 {
@@ -221,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
@@ -247,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);
}
/**
@@ -288,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.
*/
@@ -314,17 +333,18 @@ public class DisplayWindow implements ImmediateWindowProvider {
LOGGER.error("ERROR DISPLAY\n"+msgBuilder);
// we show the display on a new dedicated thread
Executors.newSingleThreadExecutor().submit(()-> {
var res = TinyFileDialogs.tinyfd_messageBox("Minecraft: Forge", msgBuilder.toString(), "yesno", "error", false);
if (res) {
var res = TinyFileDialogs.tinyfd_messageBox("Minecraft: Forge", msgBuilder.toString(), "yesno", "error", 0);
if (res != 0) {
try {
Desktop.getDesktop().browse(URI.create(ERROR_URL));
} catch (IOException ioe) {
TinyFileDialogs.tinyfd_messageBox("Minecraft: Forge", "Sadly, we couldn't open your browser.\nVisit " + ERROR_URL, "ok", "error", false);
TinyFileDialogs.tinyfd_messageBox("Minecraft: Forge", "Sadly, we couldn't open your browser.\nVisit " + ERROR_URL, "ok", "error", 0);
}
}
System.exit(1);
});
}
/**
* Called to initialize the window when preparing for the Render Thread.
*
@@ -335,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.
@@ -411,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 IntSupplier width, final IntSupplier height, final Supplier<String> title, final LongSupplier monitorSupplier) {
@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);
@@ -549,11 +554,11 @@ public class DisplayWindow implements ImmediateWindowProvider {
var renderlockticket = false;
do {
try {
renderlockticket = renderLock.tryAcquire(100, TimeUnit.MILLISECONDS);
if (++tries > 9) {
Thread.dumpStack();
crashElegantly("We seem to be having trouble handing off the window, tried for 1 second");
}
renderlockticket = renderLock.tryAcquire(100, TimeUnit.MILLISECONDS);
if (++tries > 9) {
Thread.dumpStack();
crashElegantly("We seem to be having trouble handing off the window, tried for 1 second");
}
} catch (InterruptedException e) {
Thread.interrupted();
}
@@ -561,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.get());
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;
@@ -634,7 +635,7 @@ public class DisplayWindow implements ImmediateWindowProvider {
@Override
public void periodicTick() {
glfwPollEvents();
pollEvents();
repaintTick.run();
}
@@ -649,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) {
+74 -105
View File
@@ -1,126 +1,95 @@
import net.minecraftforge.gradleutils.PomUtils
import net.minecraftforge.forgedev.legacy.tasks.JarJarMetadataOptions
import org.gradle.api.plugins.jvm.JvmTestSuite
plugins {
id 'java-library'
id 'maven-publish'
id 'net.minecraftforge.licenser'
id 'net.minecraftforge.gradleutils'
alias(libs.plugins.apt)
id 'jvm-test-suite'
id 'net.minecraftforge.forge.build.convention'
alias libs.plugins.apt
}
apply from: rootProject.file('build_shared.gradle')
configurations.forEach{ it.transitive = false }
gradleutils.displayName = 'FMLLoader'
description = 'Mod Locator and manager for Minecraft Forge.'
dependencies {
compileOnly(libs.jetbrains.annotations)
// TODO [FMLLoader] Figure out a better way to lock transitive dependencies to the explicit versions shipped by the installer
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 }
api(libs.bundles.asm) // Needed by all the black magic
api(libs.forgespi)
api(libs.mergetool.api)
api(libs.log4j.api)
api(libs.slf4j.api)
api(libs.guava)
api(libs.gson)
api(libs.maven.artifact)
api(libs.apache.commons)
api(libs.bundles.night.config)
api(libs.modlauncher)
api(libs.coremods)
api(libs.mojang.logging)
api(libs.jarjar.selector)
api(libs.jarjar.meta)
implementation(libs.jopt.simple)
implementation(libs.securemodules)
implementation(libs.accesstransformers)
implementation(libs.terminalconsoleappender)
implementation(libs.jimfs)
implementation(libs.jopt.simple ){ transitive = false }
implementation(libs.securemodules ){ transitive = false }
implementation(libs.accesstransformers ){ transitive = false }
implementation(libs.terminalconsoleappender){ 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)
annotationProcessor(libs.bundles.log4j)
testCompileOnly(libs.jetbrains.annotations)
testRuntimeOnly(libs.bootstrap)
implementation(libs.log4j.core ){ transitive = false }
annotationProcessor(libs.bundles.log4j){ transitive = false }
}
java {
toolchain.languageVersion = JavaLanguageVersion.of(JAVA_VERSION)
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/')
testing.suites.named('test', JvmTestSuite) {
dependencies {
compileOnly libs.jetbrains.annotations
runtimeOnly libs.bootstrap
}
}
tasks.withType(JavaCompile).configureEach {
options.compilerArgs << '-Xlint:unchecked'
convention.manifest('net/minecraftforge/fml/loading/', [
'Automatic-Module-Name': 'net.minecraftforge.fmlloader',
'Forge-Module-Layer' : 'boot'
])
convention.publish()
final forgeJson = convention.forgeVersionJson {
outputFile = file('src/main/resources/forge_version.json')
}
license {
header = rootProject.file('LICENSE-header.txt')
final jarJarJson = tasks.register('jarJarOptionsJson', JarJarMetadataOptions) {
metadataFile = 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')
add(libs.mixinextras.forge) {
containedJarMetadata {
path = ''
}
resource = 'com/llamalad7/mixinextras/platform/forge/MixinExtrasConfigPlugin.class'
layer = 'GAMELIBRARY'
id = 'mixinextras'
nested = true
}
}
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
tasks.named('generateResources') {
dependsOn(
tasks.named('eclipseJdt'),
tasks.named('eclipseJdtApt'),
tasks.named('eclipseFactorypath'),
forgeJson,
jarJarJson
)
}
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'
}
}
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
]
}
}
tasks.named('generateResources').configure {
dependsOn('eclipseJdt')
dependsOn('eclipseJdtApt')
dependsOn('eclipseFactorypath')
dependsOn('writeForgeVersionJson')
}
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'
}
}
}
}
@@ -34,11 +34,10 @@ import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
* }</code>
* </pre>
*/
public class CapabilityTokenSubclass implements ILaunchPluginService {
public final class CapabilityTokenSubclass implements ILaunchPluginService {
private static final String FUNC_NAME = "getType";
private static final String FUNC_DESC = "()Ljava/lang/String;";
private static final String CAP_INJECT = "net/minecraftforge/common/capabilities/CapabilityToken"; //Don't directly reference this to prevent class loading.
private static final String CAP_INJECT = "net/minecraftforge/common/capabilities/CapabilityToken"; // Don't directly reference this to prevent class loading.
@Override
public String name() {
@@ -56,8 +55,11 @@ public class CapabilityTokenSubclass implements ILaunchPluginService {
String internalName = classType.getInternalName();
if (internalName.startsWith("net/minecraft/") || internalName.startsWith("com/mojang/"))
return NAY;
if (internalName.startsWith("net/minecraftforge/common/capabilities/"))
return YAY;
return YAY;
return internalName.contains("$") ? YAY : NAY; // Anonymous subclasses only
}
@Override
@@ -70,7 +72,7 @@ public class CapabilityTokenSubclass implements ILaunchPluginService {
}
return ComputeFlags.SIMPLE_REWRITE;
} else if (CAP_INJECT.equals(classNode.superName)) {
Holder cls = new Holder();
var cls = new Object() { String value = null; };
SignatureReader reader = new SignatureReader(classNode.signature); // Having a node version of this would probably be useful.
reader.accept(new SignatureVisitor(Opcodes.ASM9) {
@@ -106,8 +108,4 @@ public class CapabilityTokenSubclass implements ILaunchPluginService {
return ComputeFlags.NO_REWRITE;
}
}
private static final class Holder {
String value;
}
}
@@ -1,131 +0,0 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.common.asm;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
/**
* Removes the final modifier from fields with the @ObjectHolder annotation, prevents the JITer from in lining them so our runtime replacements can work.
* Will also de-finalize all fields in on class level annotations.
*/
public class ObjectHolderDefinalize implements ILaunchPluginService {
// Hardcoded map of vanilla classes that should have object holders for each field of the given registry type.
// IMPORTANT: Updates to this collection must be reflected in ObjectHolderRegistry. Duplicated cuz classloaders, yay!
// Classnames are validated in ObjectHolderRegistry.
private static final Map<String, VanillaObjectHolderData> VANILLA_OBJECT_HOLDERS = Stream.of(
new VanillaObjectHolderData("net.minecraft.world.level.block.Blocks", "block", "net.minecraft.world.level.block.Block"),
new VanillaObjectHolderData("net.minecraft.world.item.Items", "item", "net.minecraft.world.item.Item"),
new VanillaObjectHolderData("net.minecraft.world.item.enchantment.Enchantments", "enchantment", "net.minecraft.world.item.enchantment.Enchantment"),
new VanillaObjectHolderData("net.minecraft.world.effect.MobEffects", "mob_effect", "net.minecraft.world.effect.MobEffect"),
new VanillaObjectHolderData("net.minecraft.core.particles.ParticleTypes", "particle_type", "net.minecraft.core.particles.ParticleType"),
new VanillaObjectHolderData("net.minecraft.sounds.SoundEvents", "sound_event", "net.minecraft.sounds.SoundEvent")
).collect(Collectors.toUnmodifiableMap(VanillaObjectHolderData::holderClass, Function.identity()));
private static final Set<String> VANILLA_OBJECT_HOLDER_CLASSES = VANILLA_OBJECT_HOLDERS.keySet().stream()
.map(it -> it.replace('.', '/'))
.collect(Collectors.toUnmodifiableSet());
private static final String OBJECT_HOLDER = "Lnet/minecraftforge/registries/ObjectHolder;"; //Don't directly reference this to prevent class loading.
private static final int PUBLIC_STATIC_FINAL_FLAGS = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL;
@Override
public String name() {
return "object_holder_definalize";
}
private static final EnumSet<Phase> YAY = EnumSet.of(Phase.AFTER);
private static final EnumSet<Phase> NAY = EnumSet.noneOf(Phase.class);
@Override
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty) {
if (isEmpty)
return NAY;
// Let's skip processing classes that definitely don't have object holders
String internalName = classType.getInternalName();
// Forge classes that aren't in the debug package (that package is used for tests)
if (internalName.startsWith("net/minecraftforge/") && !internalName.substring(18).contains("debug"))
return NAY;
// Vanilla classes that don't have object holders
if (internalName.startsWith("com/mojang/"))
return NAY;
// ...except specific ones we added to the VANILLA_OBJECT_HOLDERS map
if (internalName.startsWith("net/minecraft/") && !VANILLA_OBJECT_HOLDER_CLASSES.contains(internalName))
return NAY;
return YAY;
}
private static boolean hasHolder(List<AnnotationNode> lst) {
return lst != null && lst.stream().anyMatch(n -> n.desc.equals(OBJECT_HOLDER));
}
@SuppressWarnings("unused")
private String getValue(List<AnnotationNode> lst)
{
AnnotationNode ann = lst.stream().filter(n -> n.desc.equals(OBJECT_HOLDER)).findFirst().get();
if (ann.values != null)
{
for (int x = 0; x < ann.values.size() - 1; x += 2) {
if (ann.values.get(x).equals("value")) {
return (String)ann.values.get(x + 1);
}
}
}
return null;
}
@Override
public int processClassWithFlags(final Phase phase, final ClassNode classNode, final Type classType, final String reason)
{
final AtomicBoolean changes = new AtomicBoolean();
//Must be public static finals, and non-array objects
//Fix Annotated Fields before injecting from class level
classNode.fields.stream().filter(f -> ((f.access & PUBLIC_STATIC_FINAL_FLAGS) == PUBLIC_STATIC_FINAL_FLAGS) && f.desc.startsWith("L") && hasHolder(f.visibleAnnotations)).forEach(f ->
{
int prev = f.access;
f.access &= ~Opcodes.ACC_FINAL; //Strip final
f.access |= Opcodes.ACC_SYNTHETIC; //Add Synthetic so we can check in runtime. ? Good idea?
changes.compareAndSet(false, prev != f.access);
});
if (VANILLA_OBJECT_HOLDERS.containsKey(classType.getClassName())) //Class level, de-finalize all fields and add @ObjectHolder to them!
{
classNode.fields.stream().filter(f -> ((f.access & PUBLIC_STATIC_FINAL_FLAGS) == PUBLIC_STATIC_FINAL_FLAGS) && f.desc.startsWith("L")).forEach(f ->
{
int prev = f.access;
f.access &= ~Opcodes.ACC_FINAL;
f.access |= Opcodes.ACC_SYNTHETIC;
/*if (!hasHolder(f.visibleAnnotations)) //Add field level annotation, doesn't do anything until after we figure out how ASMDataTable is gatherered
{
if (value == null)
f.visitAnnotation(OBJECT_HOLDER, true);
else
f.visitAnnotation(OBJECT_HOLDER, true).visit("value", value + ":" + f.name.toLowerCase());
}*/
changes.compareAndSet(false, prev != f.access);
});
}
return changes.get() ? ComputeFlags.SIMPLE_REWRITE : ComputeFlags.NO_REWRITE;
}
private record VanillaObjectHolderData(String holderClass, String registryName, String registryType) {}
}
@@ -7,9 +7,9 @@ package net.minecraftforge.fml.common.asm;
import java.util.EnumSet;
import java.util.List;
import java.util.stream.Collectors;
import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.loading.LoadingModList;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
@@ -47,16 +47,21 @@ public class RuntimeEnumExtender implements ILaunchPluginService {
private static final EnumSet<Phase> YAY = EnumSet.of(Phase.AFTER);
private static final EnumSet<Phase> NAY = EnumSet.noneOf(Phase.class);
private static final class LazyInit {
private LazyInit() {}
private static final boolean ANY_MODS = LoadingModList.getMods().size() > 2; // 2: forge, minecraft
}
@Override
public EnumSet<Phase> handlesClass(Type classType, boolean isEmpty) {
if (isEmpty)
if (isEmpty || !LazyInit.ANY_MODS)
return NAY;
String internalName = classType.getInternalName();
if (internalName.startsWith("net/minecraftforge/") || internalName.startsWith("com/mojang/"))
return NAY;
if (internalName.startsWith("net/minecraft/client/model") || internalName.startsWith("net/minecraft/world"))
return YAY;
return YAY;
return NAY;
}
@Override
@@ -131,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 {
@@ -16,7 +16,6 @@ import net.minecraftforge.accesstransformer.service.AccessTransformerService;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.fml.loading.targets.CommonLaunchHandler;
import net.minecraftforge.forgespi.Environment;
import net.minecraftforge.forgespi.coremod.ICoreModProvider;
import org.slf4j.Logger;
import java.nio.file.Path;
@@ -31,14 +30,11 @@ import java.util.function.BiFunction;
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
import static net.minecraftforge.fml.loading.LogMarkers.SCAN;
public class FMLLoader {
public final class FMLLoader {
private static final Logger LOGGER = LogUtils.getLogger();
private static AccessTransformerService accessTransformer;
private static ICoreModProvider coreModProvider;
private static LanguageLoadingProvider languageLoadingProvider;
private static Dist dist;
private static String naming;
private static LoadingModList loadingModList;
private static RuntimeDistCleaner runtimeDistCleaner;
private static Path gamePath;
private static final VersionInfo versionInfo = VersionInfo.detect();
@@ -57,8 +53,6 @@ public class FMLLoader {
checkPackage(ITransformationService.class, "4.0", "ModLauncher");
accessTransformer = getPlugin(env, "accesstransformer", "1.0", "AccessTransformer");
runtimeDistCleaner = getPlugin(env, "runtimedistcleaner", "1.0", "RuntimeDistCleaner");
coreModProvider = getSingleService(ICoreModProvider.class, "CoreMod");
LOGGER.debug(CORE, "FML found CoreMod version : {}", JarVersionLookupHandler.getInfo(coreModProvider.getClass()).impl().version().orElse("MISSING"));
checkPackage(Environment.class, "2.0", "ForgeSPI");
try {
@@ -96,6 +90,7 @@ public class FMLLoader {
}
}
@SuppressWarnings("unused")
private static <T> T getSingleService(Class<T> clazz, String name) throws IncompatibleEnvironmentException {
var providers = new ArrayList<T>();
for (var itr = ServiceLoader.load(FMLLoader.class.getModule().getLayer(), clazz).iterator(); itr.hasNext(); ) {
@@ -146,35 +141,25 @@ public class FMLLoader {
}
public static List<ITransformationService.Resource> beginModScan(final Map<String,?> arguments) {
LOGGER.debug(SCAN,"Scanning for Mod Locators");
var modDiscoverer = new ModDiscoverer(arguments);
modValidator = modDiscoverer.discoverMods();
LOGGER.debug(SCAN, "Scanning for Mod Locators");
modValidator = ModDiscoverer.discoverMods(arguments);
var pluginResources = modValidator.getPluginResources();
return List.of(pluginResources);
}
public static List<ITransformationService.Resource> completeScan(IModuleLayerManager layerManager) {
moduleLayerManager = layerManager;
languageLoadingProvider = new LanguageLoadingProvider();
LanguageLoadingProvider.reload();
backgroundScanHandler = modValidator.stage2Validation();
loadingModList = backgroundScanHandler.getLoadingModList();
return List.of(modValidator.getModResources());
}
public static ICoreModProvider getCoreModProvider() {
return coreModProvider;
}
public static LanguageLoadingProvider getLanguageLoadingProvider() {
return languageLoadingProvider;
}
public static CommonLaunchHandler getLaunchHandler() {
return commonLaunchHandler;
}
public static void addAccessTransformer(Path atPath, ModFile modName) {
LOGGER.debug(SCAN, "Adding Access Transformer in {}", modName.getFilePath());
LOGGER.debug(SCAN, "Adding Access Transformer {} in {}", atPath, modName.getFilePath());
accessTransformer.offerResource(atPath, modName.getFileName());
}
@@ -188,10 +173,6 @@ public class FMLLoader {
progressWindowTick.run();
}
public static LoadingModList getLoadingModList() {
return loadingModList;
}
public static Path getGamePath() {
return gamePath;
}
@@ -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);
@@ -68,7 +67,6 @@ public class FMLServiceProvider implements ITransformationService {
@SuppressWarnings("rawtypes")
@Override
public @NotNull List<ITransformer> transformers() {
LOGGER.debug(CORE, "Loading coremod transformers");
return new ArrayList<>(FMLLoader.getCoreModProvider().getCoreModTransformers());
return List.of();
}
}
@@ -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 IntSupplier width, final IntSupplier height, final Supplier<String> title, final LongSupplier monitor) {
return provider.setupMinecraftWindow(width, height, title, monitor);
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 IntSupplier width, final IntSupplier height, final Supplier<String> title, final LongSupplier monitor) {
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);
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);
@@ -8,8 +8,6 @@ package net.minecraftforge.fml.loading;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
import java.util.function.IntSupplier;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
/**
@@ -26,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.
*
@@ -63,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 IntSupplier width, final IntSupplier height, final Supplier<String> title, final LongSupplier monitor);
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
@@ -18,7 +18,6 @@ import org.slf4j.Logger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
import java.util.function.Consumer;
@@ -28,43 +27,36 @@ import java.util.stream.Stream;
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
import static net.minecraftforge.fml.loading.LogMarkers.LOADING;
public class LanguageLoadingProvider {
public void forEach(Consumer<IModLanguageProvider> consumer) {
public final class LanguageLoadingProvider {
public static void forEach(Consumer<IModLanguageProvider> consumer) {
providers.forEach(consumer);
}
public <T> Stream<T> applyForEach(Function<IModLanguageProvider, T> function) {
public static <T> Stream<T> applyForEach(Function<IModLanguageProvider, T> function) {
return providers.stream().map(function);
}
public void reload() {
public static void reload() {
loadLanguageProviders();
}
/** This doesn't actually do anything with the argument, so just call reload(). Not sure when cpw broke that. */
@Deprecated(forRemoval = true, since = "1.20.2")
public void addAdditionalLanguages(List<ModFile> modFiles) {
reload();
}
/*==========================================================================*
* INTERNAL SHIT *
* INTERNAL *
*==========================================================================*/
private static final Logger LOGGER = LogUtils.getLogger();
private final ServiceLoader<IModLanguageProvider> serviceLoader;
private final List<IModLanguageProvider> providers = new ArrayList<>();
private final Map<String, Wrapper> providersByName = new HashMap<>();
private static final ServiceLoader<IModLanguageProvider> serviceLoader;
private static final ArrayList<IModLanguageProvider> providers = new ArrayList<>();
private static final HashMap<String, Wrapper> providersByName = new HashMap<>();
private static final DefaultArtifactVersion IN_DEV = new DefaultArtifactVersion("0.0-dev");
private record Wrapper(ArtifactVersion version, IModLanguageProvider service) {}
LanguageLoadingProvider() {
static {
var sl = Launcher.INSTANCE.environment().findModuleLayerManager().flatMap(lm -> lm.getLayer(Layer.PLUGIN)).orElseThrow();
serviceLoader = ServiceLoader.load(sl, IModLanguageProvider.class);
loadLanguageProviders();
}
private void loadLanguageProviders() {
private static void loadLanguageProviders() {
providers.clear();
providersByName.clear();
@@ -97,7 +89,7 @@ public class LanguageLoadingProvider {
}
}
public IModLanguageProvider findLanguage(ModFile mod, String language, VersionRange versionRange) {
public static IModLanguageProvider findLanguage(ModFile mod, String language, VersionRange versionRange) {
var consumer = mod.getFileName();
var wrapper = providersByName.get(language);
@@ -5,113 +5,46 @@
package net.minecraftforge.fml.loading;
import net.minecraftforge.fml.loading.moddiscovery.BackgroundScanHandler;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
import net.minecraftforge.forgespi.locating.IModFile;
import org.jetbrains.annotations.Nullable;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* Master list of all mods <em>in the loading context. This class cannot refer outside the
* loading package</em>
*/
public class LoadingModList {
private static LoadingModList INSTANCE;
private final List<ModFileInfo> modFiles;
private final List<ModInfo> sortedList;
private final Map<String, ModFileInfo> fileById;
private final List<EarlyLoadingException> preLoadErrors;
private List<IModFile> brokenFiles;
private LoadingModList(final List<ModFile> modFiles, final List<ModInfo> sortedList) {
this.modFiles = modFiles.stream()
.map(ModFile::getModFileInfo)
.map(ModFileInfo.class::cast)
.toList();
this.sortedList = sortedList.stream()
.map(ModInfo.class::cast)
.toList();
this.fileById = this.modFiles.stream()
.map(ModFileInfo::getMods)
.flatMap(Collection::stream)
.map(ModInfo.class::cast)
.collect(Collectors.toMap(ModInfo::getModId, ModInfo::getOwningFile));
this.preLoadErrors = new ArrayList<>();
/// Master list of all mods in the loading context
public sealed interface LoadingModList permits LoadingModListImpl {
static ModFileInfo getModFileById(String modId) {
return get().fileById().get(modId);
}
public static LoadingModList of(List<ModFile> modFiles, List<ModInfo> sortedList, final EarlyLoadingException earlyLoadingException) {
INSTANCE = new LoadingModList(modFiles, sortedList);
if (earlyLoadingException != null)
INSTANCE.preLoadErrors.add(earlyLoadingException);
return INSTANCE;
static List<ModFileInfo> getModFiles() {
return get().getModFiles();
}
public static LoadingModList get() {
return INSTANCE;
static List<ModInfo> getMods() {
return get().getMods();
}
public void addCoreMods() {
modFiles.stream()
.map(ModFileInfo::getFile)
.map(ModFile::getCoreMods)
.flatMap(List::stream)
.forEach(FMLLoader.getCoreModProvider()::addCoreMod);
}
public void addAccessTransformers() {
for (ModFileInfo modFile : modFiles) {
ModFile mod = modFile.getFile();
var at = mod.getAccessTransformer().orElse(null);
if (at != null) {
FMLLoader.addAccessTransformer(at, mod);
}
}
}
public void addForScanning(BackgroundScanHandler backgroundScanHandler) {
backgroundScanHandler.setLoadingModList(this);
modFiles.stream()
.map(ModFileInfo::getFile)
.forEach(backgroundScanHandler::submitForScanning);
}
public List<ModFileInfo> getModFiles() {
return modFiles;
}
public Path findResource(final String className) {
for (ModFileInfo mf : modFiles) {
final Path resource = mf.getFile().findResource(className);
static @Nullable Path findResource(String className) {
for (ModFileInfo modFileInfo : get().getModFiles()) {
Path resource = modFileInfo.getFile().findResource(className);
if (Files.exists(resource)) return resource;
}
return null;
}
public ModFileInfo getModFileById(String modid) {
return this.fileById.get(modid);
static List<EarlyLoadingException> getErrors() {
return get().getErrors();
}
public List<ModInfo> getMods() {
return this.sortedList;
static List<IModFile> getBrokenFiles() {
return get().getBrokenFiles();
}
public List<EarlyLoadingException> getErrors() {
return preLoadErrors;
}
public void setBrokenFiles(final List<IModFile> brokenFiles) {
this.brokenFiles = brokenFiles;
}
public List<IModFile> getBrokenFiles() {
return this.brokenFiles;
private static LoadingModListImpl get() {
return LoadingModListImpl.get();
}
}
@@ -0,0 +1,90 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import net.minecraftforge.fml.loading.EarlyLoadingException.ExceptionData;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
import net.minecraftforge.forgespi.locating.IModFile;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import com.mojang.logging.LogUtils;
/**
* Master list of all mods <em>in the loading context. This class cannot refer outside the
* loading package</em>
*/
record LoadingModListImpl(
List<ModFileInfo> getModFiles,
List<ModInfo> getMods,
Map<String, ModFileInfo> fileById,
ArrayList<EarlyLoadingException> getErrors,
ArrayList<IModFile> getBrokenFiles
) implements LoadingModList {
private static final Logger LOGGER = LogUtils.getLogger();
private static ModSorter.State temp;
LoadingModListImpl(List<ModFile> modFiles, List<ModInfo> sortedList) {
var modFileInfos = modFiles.stream()
.map(ModFile::getModFileInfo)
.map(ModFileInfo.class::cast)
.toList();
sortedList = sortedList.stream().toList();
var fileById = modFileInfos.stream()
.map(ModFileInfo::getMods)
.flatMap(Collection::stream)
.map(ModInfo.class::cast)
.collect(Collectors.toUnmodifiableMap(ModInfo::getModId, ModInfo::getOwningFile));
this(modFileInfos, sortedList, fileById, new ArrayList<>(), new ArrayList<>());
}
static LoadingModListImpl init(ModSorter.State modSorterState, EarlyLoadingException earlyLoadingException) {
temp = modSorterState;
var instance = get();
if (earlyLoadingException != null)
instance.getErrors.add(earlyLoadingException);
temp = null;
instance.addAccessTransformers();
return instance;
}
static LoadingModListImpl get() {
final class LazyInit {
private LazyInit() {}
private static final LoadingModListImpl INSTANCE
= new LoadingModListImpl(LoadingModListImpl.temp.files(), LoadingModListImpl.temp.mods());
}
return LazyInit.INSTANCE;
}
private void addAccessTransformers() {
var errors = new ArrayList<ExceptionData>();
for (ModFileInfo modFile : getModFiles) {
ModFile mod = modFile.getFile();
for (var at : mod.getAccessTransformers()) {
if (!Files.exists(at)) {
var message = "Invalid mod file: " + modFile.getFile().getFileName() + ". Missing Access Transformer: " + at;
errors.add(new ExceptionData(message));
LOGGER.error(message);
} else
FMLLoader.addAccessTransformer(at, mod);
}
}
if (!errors.isEmpty())
getErrors.add(new EarlyLoadingException("Invalid Access Transformers", null, errors));
}
}
@@ -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.
@@ -7,8 +7,10 @@ package net.minecraftforge.fml.loading;
import com.mojang.logging.LogUtils;
import cpw.mods.jarhandling.SecureJar;
import cpw.mods.modlauncher.Launcher;
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
import cpw.mods.modlauncher.api.NamedPath;
import cpw.mods.modlauncher.api.TypesafeMap;
import cpw.mods.modlauncher.serviceapi.ITransformerDiscoveryService;
import org.jetbrains.annotations.ApiStatus;
@@ -40,6 +42,17 @@ public class ModDirTransformerDiscoverer implements ITransformerDiscoveryService
@Override
public void earlyInitialization(final String launchTarget, final String[] arguments) {
boolean isMixinEnabledInDev = false;
for (String arg : arguments) {
if (arg.startsWith("-mixin") || arg.startsWith("--mixin")) {
isMixinEnabledInDev = true;
break;
}
}
Launcher.INSTANCE.blackboard().putIfAbsent(
TypesafeMap.Key.getOrCreate(Launcher.INSTANCE.blackboard(), "isMixinEnabledInDev", Boolean.class),
isMixinEnabledInDev
);
ImmediateWindowHandler.load(launchTarget, arguments);
}
@@ -33,10 +33,10 @@ import java.util.stream.Stream;
import static net.minecraftforge.fml.loading.LogMarkers.LOADING;
public class ModSorter {
public final class ModSorter {
private static final Logger LOGGER = LogUtils.getLogger();
private record State(List<ModFile> files, List<ModInfo> mods) {}
record State(List<ModFile> files, List<ModInfo> mods) {}
private ModSorter() {}
@@ -46,13 +46,13 @@ public class ModSorter {
List<ModFile> modFiles;
try {
modFiles = new UniqueModListBuilder(mods).buildUniqueList().modFiles();
modFiles = UniqueModListBuilder.buildUniqueList(mods).modFiles();
} catch (EarlyLoadingException e) {
// We cannot build any list with duped mods. We have to abort immediately and report it
// Note this will never actually throw an error because the duplicate checks are done in ModDiscovererer before we get to this phase
// So all this is really doing is wasting time.
// But i'm leaving it here until I rewrite all of cpw's mod loading code because its such a clusterfuck.
return LoadingModList.of(systemMods.files(), systemMods.mods(), e);
return LoadingModListImpl.init(systemMods, e);
}
var named = new HashMap<String, ModInfo>();
@@ -66,15 +66,15 @@ public class ModSorter {
// if we miss one or the other, we abort now
if (!failedList.isEmpty()) {
return LoadingModList.of(systemMods.files(), systemMods.mods(), new EarlyLoadingException("failure to validate mod list", null, failedList));
return LoadingModListImpl.init(systemMods, new EarlyLoadingException("failure to validate mod list", null, failedList));
} else {
// Otherwise, lets try and sort the modlist and proceed
try {
var sorted = sort(modFiles, named);
return LoadingModList.of(sorted.files(), sorted.mods(), null);
return LoadingModListImpl.init(sorted, null);
} catch (EarlyLoadingException e) {
// The only exception that can happen here is a cyclic exception, but fall back to system mods so we can display the nice screen.
return LoadingModList.of(systemMods.files(), systemMods.mods(), e);
return LoadingModListImpl.init(systemMods, e);
}
}
}
Loaded 100 of 2451 files, more files were not shown because too many files have changed in this diff. Show more