Environment: Jupiter 2.3.7, Minecraft 1.21.1, NeoForge 21.1.248, Ice and Fire: Community Edition (which is what pulls Jupiter in), Wither: Reincarnated 1.1.0. Client and dedicated server alike; the import runs on both.
What happens
With loadForgeConfigs: true (the default in config/jupiter.json), ConfigSpecLoader.scanConfig walks other mods' NeoForge ModConfigSpec files. For every entry whose key string contains a ., NightConfigHolder.processEntry receives a null value and throws. The entry is then missing from Jupiter's config screen, and each failure prints a stack trace at startup.
This matters beyond the one mod below: Jupiter arrives as a dependency of Ice and Fire: Community Edition, so it is present in packs that never installed it deliberately, and it then tries to import the config of every other mod in the pack. Any single mod with a dot in a config key produces the errors.
Expected
A key that happens to contain a dot is a legal NeoForge config key - NeoForge stores the path verbatim when the mod passes a single-element List - so Jupiter should import it like any other key, and at worst skip it quietly rather than throwing.
Steps to reproduce
- Install Jupiter 2.3.7 on NeoForge 1.21.1 with
loadForgeConfigs: true.
- Add any mod that registers a spec with
builder.define(List.of("<key with a dot>"), <default>). A live example is Wither: Reincarnated 1.1.0, which embeds the default value in the key name.
- Start the game and watch the log during config import.
"Changes Time (default: true)" = true # loads fine - no dot
"Max Laser Length (default: 128)" = 128 # loads fine - no dot
"Health (default: 600.0)" = 600.0 # NPE - the dot in "600.0"
"Movement Speed (default: 0.0675)" = 0.0675 # NPE
Exactly the eleven entries in config/witherreincarnated/common.toml whose default is a decimal number fail; every integer, boolean and string entry in the same file loads.
Evidence
[main/ERROR] [com.iafenvoy.jupiter.Jupiter/]: Cannot load key=Health (default: 600.0), type=java.lang.Double in config=witherreincarnated:COMMON
java.lang.NullPointerException: Cannot invoke "Object.getClass()" because "value" is null
at com.iafenvoy.jupiter.compat.forgeconfigspec.NightConfigHolder.processEntry(NightConfigHolder.java:157)
at com.iafenvoy.jupiter.compat.forgeconfigspec.NightConfigHolder.process(NightConfigHolder.java:126)
at com.iafenvoy.jupiter.compat.forgeconfigspec.NightConfigHolder.buildGroup(NightConfigHolder.java:98)
at com.iafenvoy.jupiter.compat.forgeconfigspec.NightConfigHolder.buildGroups(NightConfigHolder.java:87)
...
at com.iafenvoy.jupiter.compat.ExtraConfigManager.scanConfigs(ExtraConfigManager.java:29)
The correlation is with the dot, not with the value type: the failing entries are exactly those whose key text contains ., and the exception is a null value rather than a type mismatch. Config.get(String) and getRaw(String) in NightConfig treat . as a path separator, so "Health (default: 600.0)" is split into "Health (default: 600" -> "0)" and resolves to nothing. NeoForge itself is unaffected because the mod passes a single-element List path, which is stored verbatim.
In my pack that is eleven ERROR lines on every start. The mod itself reads its own config correctly; only Jupiter's screen loses those entries. Nothing else in a set of 324 mods triggers it.
Suggested fix
The iteration in processEntry already holds the UnmodifiableConfig.Entry, so the value can be taken from it instead of being looked up again by string:
Object value = entry.getRawValue(); // or entry.getValue()
Where a lookup is unavoidable, use the single-element path form so no splitting happens:
Object value = config.get(List.of(key));
config.set(List.of(key), newValue);
The same applies to the write-back path - otherwise saving from Jupiter's screen would create a nested Health (default: 600 table instead of updating the entry.
A null check before value.getClass() would additionally downgrade this from a stack trace to a skipped entry, which is worth having regardless of the path handling.
Environment: Jupiter 2.3.7, Minecraft 1.21.1, NeoForge 21.1.248, Ice and Fire: Community Edition (which is what pulls Jupiter in), Wither: Reincarnated 1.1.0. Client and dedicated server alike; the import runs on both.
What happens
With
loadForgeConfigs: true(the default inconfig/jupiter.json),ConfigSpecLoader.scanConfigwalks other mods' NeoForgeModConfigSpecfiles. For every entry whose key string contains a.,NightConfigHolder.processEntryreceives a null value and throws. The entry is then missing from Jupiter's config screen, and each failure prints a stack trace at startup.This matters beyond the one mod below: Jupiter arrives as a dependency of Ice and Fire: Community Edition, so it is present in packs that never installed it deliberately, and it then tries to import the config of every other mod in the pack. Any single mod with a dot in a config key produces the errors.
Expected
A key that happens to contain a dot is a legal NeoForge config key - NeoForge stores the path verbatim when the mod passes a single-element
List- so Jupiter should import it like any other key, and at worst skip it quietly rather than throwing.Steps to reproduce
loadForgeConfigs: true.builder.define(List.of("<key with a dot>"), <default>). A live example is Wither: Reincarnated 1.1.0, which embeds the default value in the key name.Exactly the eleven entries in
config/witherreincarnated/common.tomlwhose default is a decimal number fail; every integer, boolean and string entry in the same file loads.Evidence
The correlation is with the dot, not with the value type: the failing entries are exactly those whose key text contains
., and the exception is a null value rather than a type mismatch.Config.get(String)andgetRaw(String)in NightConfig treat.as a path separator, so"Health (default: 600.0)"is split into"Health (default: 600"->"0)"and resolves to nothing. NeoForge itself is unaffected because the mod passes a single-elementListpath, which is stored verbatim.In my pack that is eleven
ERRORlines on every start. The mod itself reads its own config correctly; only Jupiter's screen loses those entries. Nothing else in a set of 324 mods triggers it.Suggested fix
The iteration in
processEntryalready holds theUnmodifiableConfig.Entry, so the value can be taken from it instead of being looked up again by string:Where a lookup is unavoidable, use the single-element path form so no splitting happens:
The same applies to the write-back path - otherwise saving from Jupiter's screen would create a nested
Health (default: 600table instead of updating the entry.A null check before
value.getClass()would additionally downgrade this from a stack trace to a skipped entry, which is worth having regardless of the path handling.