Object LoaderDescriptors

  • All Implemented Interfaces:

    
    public class LoaderDescriptors
    
                        

    Which descriptor file a modloader reads, at a given Minecraft version. The single home for that fact.

    Loaders have moved their descriptor twice in ways that matter, and the version is part of the answer rather than a detail of it:

    Why this exists as its own object. ModScanner.scannerFor already owned the era boundaries, and serverpackcreator-clientside's pre-boot gate held a second, version-blind copy of the same knowledge — a flat descriptor→loader map. It therefore read every META-INF/mods.toml as Forge's, and refused 13 genuine NeoForge jars on Minecraft 1.20.2–1.20.4 as "not a NeoForge mod" (measured on the live grinder, 2026-09-10). That is the MetadataScanner/ModListCompiler drift this codebase has already paid for twice: two copies of one fact, only one of them maintained. Both callers now ask here.

    The two boundaries are different facts about NeoForge and must not be merged. The package rename (net.minecraftforgenet.neoforged) landed with Minecraft 1.20.2 and is what ends binary jar parity — that claim lives in the clientside engine's LoaderCompatibility and stays one version wide. The descriptor rename landed with 1.20.5 and is what this object is about. Between the two, Forge and NeoForge read the same file, so its presence tells a reader nothing about which of them a jar is for.

    Author:

    Griefed

    • Constructor Detail

    • Method Detail

      • descriptorsFor

         final Set<String> descriptorsFor(String modloader, String minecraftVersion)

        The descriptor paths whose presence evidences that a jar was built for modloader on minecraftVersion — empty when nothing distinguishes that loader, and for a loader this does not know.

        This is the gate's question, not the scanner's, and the two are not the same. A scanner needs the one file to parse (ModScanner.scannerFor asks forgeUsesToml / neoForgeUsesNeoToml for that). A reader asking "was this jar built for NeoForge?" wants every file that would say so — which is why NeoForge's set carries neoforge.mods.toml at every version. That file is NeoForge's whenever it appears, even on a Minecraft where the loader would not have read it, and treating its presence as meaningless there would let a NeoForge-only jar pass as a Forge mod.

        LegacyFabric is deliberately empty. It reads Fabric's fabric.mod.json, so a jar carrying one says nothing that separates the two, and LoaderCompatibility.alsoRuns already accepts a Fabric jar for a LegacyFabric boot. Returning {FABRIC} here would instead make every Fabric jar declare two loaders and make LegacyFabric refusable on evidence it never had.

        Empty is deliberately not an error, matching ModScanner.scannerFor's null: the caller decides what an unrecognised loader means, and the pre-boot gate treats it as "no opinion", which accepts.

        Parameters:
        modloader - Canonical modloader name, as de.griefed.serverpackcreator.api.config.
        minecraftVersion - The Minecraft version being read for, which decides the descriptor era.
      • forgeUsesToml

         final Boolean forgeUsesToml(String minecraftVersion)

        Whether Forge on minecraftVersion carries a mods.toml rather than the annotation-cache the 1.12-and-older scanner reads. Forge switched with Minecraft 1.13.

        Compares every version component through SemanticVersionComparator rather than testing the minor on its own: Minecraft has two versioning schemes (1.x.y and the newer YY.x.y), so 26.2's minor of 2 reads as the 1.2 era and would pick the wrong scanner. An unparseable version falls back to the modern descriptor — the annotation cache exists only in jars a decade old, so it is never the safer guess.

      • neoForgeUsesNeoToml

         final Boolean neoForgeUsesNeoToml(String minecraftVersion)

        Whether NeoForge on minecraftVersion uses neoforge.mods.toml rather than Forge's mods.toml.

        Falls back to the modern descriptor for an unparseable version, for the same reason and by the same route as forgeUsesToml. It used to throw instead: the comparison was unwrapped while Forge's was wrapped, so scannerFor("NeoForge", "26") propagated an ArrayIndexOutOfBoundsException out of ModScanner — and "26" is a legitimate shape under the newer scheme.