fabricEvent = ModConfigEvents.unloading(event.getConfig().getModId());
+ fabricEvent.invoker().onModConfigUnloading(event.getConfig());
+ }
+}
diff --git a/forgeconfigapiport/src/main/java/dev/su5ed/sinytra/connectorextras/forgeconfigapiport/ForgeConfigPathsImpl.java b/forgeconfigapiport/src/main/java/dev/su5ed/sinytra/connectorextras/forgeconfigapiport/ForgeConfigPathsImpl.java
new file mode 100644
index 0000000..dff6293
--- /dev/null
+++ b/forgeconfigapiport/src/main/java/dev/su5ed/sinytra/connectorextras/forgeconfigapiport/ForgeConfigPathsImpl.java
@@ -0,0 +1,34 @@
+package dev.su5ed.sinytra.connectorextras.forgeconfigapiport;
+
+import fuzs.forgeconfigapiport.api.config.v2.ForgeConfigPaths;
+import net.minecraft.server.MinecraftServer;
+import net.minecraftforge.fml.loading.FMLPaths;
+
+import java.nio.file.Path;
+
+public class ForgeConfigPathsImpl implements ForgeConfigPaths {
+ @Override
+ public Path getClientConfigDirectory() {
+ return FMLPaths.CONFIGDIR.get();
+ }
+
+ @Override
+ public Path getCommonConfigDirectory() {
+ return FMLPaths.CONFIGDIR.get();
+ }
+
+ @Override
+ public Path getServerConfigDirectory(MinecraftServer server) {
+ return FMLPaths.CONFIGDIR.get();
+ }
+
+ @Override
+ public boolean forceGlobalServerConfigs() {
+ return false;
+ }
+
+ @Override
+ public Path getDefaultConfigsDirectory() {
+ return FMLPaths.CONFIGDIR.get();
+ }
+}
diff --git a/forgeconfigapiport/src/main/java/dev/su5ed/sinytra/connectorextras/forgeconfigapiport/ForgeConfigRegistryImpl.java b/forgeconfigapiport/src/main/java/dev/su5ed/sinytra/connectorextras/forgeconfigapiport/ForgeConfigRegistryImpl.java
new file mode 100644
index 0000000..6afc7aa
--- /dev/null
+++ b/forgeconfigapiport/src/main/java/dev/su5ed/sinytra/connectorextras/forgeconfigapiport/ForgeConfigRegistryImpl.java
@@ -0,0 +1,29 @@
+package dev.su5ed.sinytra.connectorextras.forgeconfigapiport;
+
+import fuzs.forgeconfigapiport.api.config.v2.ForgeConfigRegistry;
+import net.minecraftforge.fml.ModContainer;
+import net.minecraftforge.fml.ModList;
+import net.minecraftforge.fml.config.IConfigSpec;
+import net.minecraftforge.fml.config.ModConfig;
+
+public class ForgeConfigRegistryImpl implements ForgeConfigRegistry {
+ @Override
+ public ModConfig register(String modId, ModConfig.Type type, IConfigSpec> spec) {
+ ModContainer container = getModContainer(modId);
+ ModConfig modConfig = new ModConfig(type, spec, container);
+ container.addConfig(modConfig);
+ return modConfig;
+ }
+
+ @Override
+ public ModConfig register(String modId, ModConfig.Type type, IConfigSpec> spec, String fileName) {
+ ModContainer container = getModContainer(modId);
+ ModConfig modConfig = new ModConfig(type, spec, container, fileName);
+ container.addConfig(modConfig);
+ return modConfig;
+ }
+
+ private static ModContainer getModContainer(String modid) {
+ return ModList.get().getModContainerById(modid).orElseThrow(() -> new RuntimeException("Mod container not found for mod " + modid));
+ }
+}
diff --git a/forgeconfigapiport/src/main/java/fuzs/forgeconfigapiport/api/config/v2/ForgeConfigPaths.java b/forgeconfigapiport/src/main/java/fuzs/forgeconfigapiport/api/config/v2/ForgeConfigPaths.java
new file mode 100644
index 0000000..5384ead
--- /dev/null
+++ b/forgeconfigapiport/src/main/java/fuzs/forgeconfigapiport/api/config/v2/ForgeConfigPaths.java
@@ -0,0 +1,63 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+package fuzs.forgeconfigapiport.api.config.v2;
+
+import dev.su5ed.sinytra.connectorextras.forgeconfigapiport.ForgeConfigPathsImpl;
+import net.minecraft.server.MinecraftServer;
+
+import java.nio.file.Path;
+
+/**
+ * Access to paths where different kinds of config files are stored by Forge.
+ */
+public interface ForgeConfigPaths {
+ /**
+ * implementation instance for retrieving config paths
+ */
+ ForgeConfigPaths INSTANCE = new ForgeConfigPathsImpl();
+
+ /**
+ * The directory where client configs are stored at.
+ *
+ * @return client config path
+ */
+ Path getClientConfigDirectory();
+
+ /**
+ * The directory where common configs are stored at.
+ *
+ * @return common config path
+ */
+ Path getCommonConfigDirectory();
+
+ /**
+ * The directory where server configs are stored at. By default, this is inside the world directory instead of the common .minecraft/config/
directory.
+ * Forge Config Api Port has a config setting (which is not present in Forge itself) to load server configs from the common directory instead.
+ * This method will always provide the directory that is currently set via the Forge Config Api Port config.
+ *
+ * @param server the current {@link MinecraftServer}
+ * @return server config path
+ */
+ Path getServerConfigDirectory(final MinecraftServer server);
+
+ /**
+ * Prevent server config files from being saved inside the current world directory. Instead, save them to the global config directory in .minecraft/config/
.
+ * This option effectively disables per world server configs, but helps a lot with avoiding user confusion.
+ *
+ * @return are server configs stored in the global .minecraft/config/
directory
+ */
+ boolean forceGlobalServerConfigs();
+
+ /**
+ * Path where default configs are stored, by default inside of .minecraft/defaultconfigs
.
+ *
This path is configurable via the Forge Config Api Port config.
+ *
Default configs are mainly intended for server configs to allow pre-configured configs to be applied to every
+ * newly created world (since server configs are handled per world). This mechanic also applies to other config types (although they are only created once).
+ *
+ * @return default configs path
+ */
+ Path getDefaultConfigsDirectory();
+}
diff --git a/forgeconfigapiport/src/main/java/fuzs/forgeconfigapiport/api/config/v2/ForgeConfigRegistry.java b/forgeconfigapiport/src/main/java/fuzs/forgeconfigapiport/api/config/v2/ForgeConfigRegistry.java
new file mode 100644
index 0000000..73a5955
--- /dev/null
+++ b/forgeconfigapiport/src/main/java/fuzs/forgeconfigapiport/api/config/v2/ForgeConfigRegistry.java
@@ -0,0 +1,46 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+package fuzs.forgeconfigapiport.api.config.v2;
+
+import dev.su5ed.sinytra.connectorextras.forgeconfigapiport.ForgeConfigRegistryImpl;
+import net.minecraftforge.fml.config.IConfigSpec;
+import net.minecraftforge.fml.config.ModConfig;
+
+/**
+ * Registry for adding your configs. On Forge this is done using net.minecraftforge.fml.ModLoadingContext
, which does not exist in Forge Config Api Port.
+ *
Note that opposed to Forge, configs are loaded and usable immediately after registration.
+ */
+public interface ForgeConfigRegistry {
+ /**
+ * implementation instance for registering configs
+ */
+ ForgeConfigRegistry INSTANCE = new ForgeConfigRegistryImpl();
+
+ /**
+ * Register a new mod config, only difference from registering on Forge is modId
has to be provided as there is no loading context to get that information from
+ *
+ * @param modId mod id of your mod
+ * @param type type of this mod config (client, common, or server)
+ * @param spec the built config spec
+ * @return the {@link ModConfig} instance
+ *
+ * @throws IllegalArgumentException when no mod container is found for modId
+ */
+ ModConfig register(String modId, ModConfig.Type type, IConfigSpec> spec);
+
+ /**
+ * Register a new mod config, only difference from registering on Forge is modId
has to be provided as there is no loading context to get that information from
+ *
+ * @param modId mod id of your mod
+ * @param type type of this mod config (client, common, or server)
+ * @param spec the built config spec
+ * @param fileName file name to use instead of default
+ * @return the {@link ModConfig} instance
+ *
+ * @throws IllegalArgumentException when no mod container is found for modId
+ */
+ ModConfig register(String modId, ModConfig.Type type, IConfigSpec> spec, String fileName);
+}
diff --git a/forgeconfigapiport/src/main/java/fuzs/forgeconfigapiport/api/config/v2/ModConfigEvents.java b/forgeconfigapiport/src/main/java/fuzs/forgeconfigapiport/api/config/v2/ModConfigEvents.java
new file mode 100644
index 0000000..bed2374
--- /dev/null
+++ b/forgeconfigapiport/src/main/java/fuzs/forgeconfigapiport/api/config/v2/ModConfigEvents.java
@@ -0,0 +1,149 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+package fuzs.forgeconfigapiport.api.config.v2;
+
+import com.google.common.collect.Maps;
+import net.fabricmc.fabric.api.event.Event;
+import net.fabricmc.fabric.api.event.EventFactory;
+import net.minecraftforge.fml.config.ModConfig;
+
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Mod config events adapted for Fabric's callback event style.
+ *
+ *
Unlike on Forge there are is no mod event bus for firing mod specific events on Fabric meaning general callbacks would be fired for every mod for every config which is undesirable.
+ * To solve this issue without introducing a manual mod id check in the events implementation itself, mod config event callbacks are instead created separately for every mod that has configs registered with Forge Config Api Port.
+ * Accessing events for a specific mod is done by calling {@link #loading(String)}, {@link #reloading(String)} or {@link #unloading(String)}.
+ */
+public final class ModConfigEvents {
+
+ private ModConfigEvents() {
+
+ }
+
+ /**
+ * access to mod specific loading event
+ *
+ * @param modId the mod id to access config event for
+ * @return the {@link Loading} event
+ */
+ public static Event loading(String modId) {
+ Objects.requireNonNull(modId, "mod id is null");
+ return ModConfigEventsHolder.modSpecific(modId).loading();
+ }
+
+ /**
+ * access to mod specific reloading event
+ *
+ * @param modId the mod id to access config event for
+ * @return the {@link Reloading} event
+ */
+ public static Event reloading(String modId) {
+ Objects.requireNonNull(modId, "mod id is null");
+ return ModConfigEventsHolder.modSpecific(modId).reloading();
+ }
+
+ /**
+ * access to mod specific unloading event
+ *
+ * @param modId the mod id to access config event for
+ * @return the {@link Unloading} event
+ */
+ public static Event unloading(String modId) {
+ Objects.requireNonNull(modId, "mod id is null");
+ return ModConfigEventsHolder.modSpecific(modId).unloading();
+ }
+
+ /**
+ * Called when a config is loaded or unloaded (unloading only applies for server configs)
+ */
+ @FunctionalInterface
+ public interface Loading {
+
+ /**
+ * @param config the mod config that is loading
+ */
+ void onModConfigLoading(ModConfig config);
+ }
+
+ /**
+ * Called when a config is reloaded which happens when the file is updated by ConfigWatcher and when it is synced from the server
+ */
+ @FunctionalInterface
+ public interface Reloading {
+
+ /**
+ * @param config the mod config that is reloading
+ */
+ void onModConfigReloading(ModConfig config);
+ }
+
+ /**
+ * Called when a config is unloaded which happens only for server configs when the corresponding world is unloaded
+ */
+ @FunctionalInterface
+ public interface Unloading {
+
+ /**
+ * @param config the mod config that is unloading
+ */
+ void onModConfigUnloading(ModConfig config);
+ }
+
+ /**
+ * internal mod specific event storage
+ *
+ * @param modId the mod
+ * @param loading loading event
+ * @param reloading reloading event
+ */
+ private record ModConfigEventsHolder(String modId, Event loading, Event reloading,
+ Event unloading) {
+ /**
+ * internal storage for mod specific config events
+ */
+ private static final Map MOD_SPECIFIC_EVENT_HOLDERS = Maps.newConcurrentMap();
+
+ /**
+ * internal access to mod specific config events
+ *
+ * the method is synchronized as access from different threads is possible (e.g. the config watcher thread)
+ *
+ * @param modId the mod id to access config events for
+ * @return access to a holder with both mod specific {@link Loading} and {@link Reloading} events
+ */
+ public static ModConfigEventsHolder modSpecific(String modId) {
+ return MOD_SPECIFIC_EVENT_HOLDERS.computeIfAbsent(modId, ModConfigEventsHolder::create);
+ }
+
+ /**
+ * creates a new holder duh
+ *
+ * @param modId the mod
+ * @return holder with newly created events
+ */
+ private static ModConfigEventsHolder create(String modId) {
+ Event loading = EventFactory.createArrayBacked(Loading.class, listeners -> config -> {
+ for (Loading event : listeners) {
+ event.onModConfigLoading(config);
+ }
+ });
+ Event reloading = EventFactory.createArrayBacked(Reloading.class, listeners -> config -> {
+ for (Reloading event : listeners) {
+ event.onModConfigReloading(config);
+ }
+ });
+ Event unloading = EventFactory.createArrayBacked(Unloading.class, listeners -> config -> {
+ for (Unloading event : listeners) {
+ event.onModConfigUnloading(config);
+ }
+ });
+ return new ModConfigEventsHolder(modId, loading, reloading, unloading);
+ }
+ }
+}
diff --git a/forgeconfigapiport/src/main/resources/META-INF/mods.toml b/forgeconfigapiport/src/main/resources/META-INF/mods.toml
new file mode 100644
index 0000000..2f28a52
--- /dev/null
+++ b/forgeconfigapiport/src/main/resources/META-INF/mods.toml
@@ -0,0 +1,30 @@
+modLoader="javafml"
+loaderVersion="[47,)"
+license="MPL-2.0"
+issueTrackerURL="https://github.com/Sinytra/ConnectorExtras/issues"
+
+[[mods]]
+modId="forgeconfigapiport"
+version="8.0.0"
+displayName="Forge Config API Port (ConnectorExtras)"
+authors="Su5eD"
+credits="Fuzs - upstream author"
+displayURL="https://github.com/Sinytra/ConnectorExtras"
+description='''
+Forge's whole config system provided to the Fabric ecosystem. Designed for a multiloader architecture.
+'''
+logoFile="mod_logo.png"
+displayTest = 'IGNORE_ALL_VERSION'
+
+[[dependencies.forgeconfigapiport]]
+ modId="forge"
+ mandatory=true
+ versionRange="[47,)"
+ ordering="NONE"
+ side="BOTH"
+[[dependencies.forgeconfigapiport]]
+ modId="minecraft"
+ mandatory=true
+ versionRange="[1.20.1,1.21)"
+ ordering="NONE"
+ side="BOTH"
diff --git a/forgeconfigapiport/src/main/resources/mod_logo.png b/forgeconfigapiport/src/main/resources/mod_logo.png
new file mode 100644
index 0000000..0e477a1
Binary files /dev/null and b/forgeconfigapiport/src/main/resources/mod_logo.png differ
diff --git a/forgeconfigapiport/src/main/resources/pack.mcmeta b/forgeconfigapiport/src/main/resources/pack.mcmeta
new file mode 100644
index 0000000..e6d4c33
--- /dev/null
+++ b/forgeconfigapiport/src/main/resources/pack.mcmeta
@@ -0,0 +1,6 @@
+{
+ "pack": {
+ "description": "The default data for forgeconfigapiport",
+ "pack_format": 15
+ }
+}
diff --git a/gradle.properties b/gradle.properties
index 6ae59d2..3b59ae5 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -9,7 +9,7 @@ fabric.loom.multiProjectOptimisation=true
# Versions
versionMc=1.20.1
versionForge=47.1.3
-versionConnectorExtras=1.6.1
+versionConnectorExtras=1.7.0
# Publishing
curseForgeId=913445
diff --git a/settings.gradle.kts b/settings.gradle.kts
index f8ca94a..013dea4 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -40,5 +40,6 @@ include(
"geckolib-fabric-compat",
"modmenu-bridge",
"continuity-compat",
- "amecs-api"
+ "amecs-api",
+ "forgeconfigapiport"
)