From ee773cb29f28d516565a79eb852ed72b8f175260 Mon Sep 17 00:00:00 2001 From: Chidoziealways Date: Sat, 12 Jul 2025 18:18:49 +0100 Subject: [PATCH 1/9] Update to support EventBus7 --- gradle.properties | 12 ++++- gradle/libs.versions.toml | 6 +-- .../kotlinforforge/KotlinModContainer.kt | 52 ++++++++++++++----- .../kotlinforforge/KotlinModLoadingContext.kt | 8 ++- ...rge.forgespi.language.IModLanguageProvider | 2 +- .../kotlinforforge/forge/Forge.kt | 4 +- 6 files changed, 62 insertions(+), 22 deletions(-) diff --git a/gradle.properties b/gradle.properties index a7eab569..c7d15c6b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,11 +2,21 @@ # This is required to provide enough memory for the Minecraft decompilation process. kotlin.code.style=official kotlin.stdlib.default.dependency=false -org.gradle.jvmargs=-Xmx1G +org.gradle.jvmargs=-Xmx4G -Xms2G -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 org.gradle.daemon=true org.gradle.parallel=true +org.gradle.workers.max=8 org.gradle.caching=true org.gradle.configuration-cache=true +systemProp.http.socketTimeout=600000 +systemProp.http.connectionTimeout=600000 +systemProp.https.socketTimeout=600000 +systemProp.https.connectionTimeout=600000 +systemProp.org.gradle.internal.http.connectionTimeout=2147483647 +# ~35 minutes +org.gradle.internal.http.socketTimeout=2147483647 +# ~35 minutes +systemProp.org.gradle.internal.repository.max.retries=1 min_mc_version = 1.20.6 unsupported_mc_version = 1.22 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 736ae616..193cef05 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ # https://github.com/JetBrains/kotlin # https://github.com/Kotlin/kotlinx.coroutines # https://github.com/Kotlin/kotlinx.serialization -kotlin = "2.1.21" +kotlin = "2.2.0" coroutines = "1.10.2" serialization = "1.8.1" @@ -12,10 +12,10 @@ neoforge = "21.6.11-beta" neoforge-fml = "[3,5)" neoforge-bus = "8.0.5" neoforge-mergetool = "2.0.7" -forge = "1.21-51.0.8" +forge = "1.21.7-57.0.2" forge-mergetool = "1.0" forge-spi = "7.1.4" -forge-bus = "6.2.6" +forge-bus = "7.0.0" joml = "1.10.5" log4j = "2.22.1" asm = "9.5" diff --git a/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt b/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt index 8fc52e02..b27d8293 100644 --- a/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt +++ b/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt @@ -26,25 +26,25 @@ public class KotlinModContainer( gameLayer: ModuleLayer, ) : ModContainer(info) { private var modInstance: Any? = null - internal val eventBus: IEventBus + internal val busGroup: BusGroup private val modClass: Class<*> + val ctx = KotlinModLoadingContext(this) + private val implAddExportsOrOpens = Module::class.java.getDeclaredMethod( + "implAddExportsOrOpens", String::class.java, Module::class.java, Boolean::class.javaPrimitiveType, Boolean::class.javaPrimitiveType + ).apply { + isAccessible = true + } init { LOGGER.debug(Logging.LOADING, "Creating KotlinModContainer instance for $className") - activityMap[ModLoadingStage.CONSTRUCT] = Runnable(::constructMod) - - eventBus = NewEventBusMaker.make(::onEventFailed) - - configHandler = Optional.of(Consumer { event -> - eventBus.post(event.self()) - }) - - val ctx = KotlinModLoadingContext(this) + eventBus = BusGroup.create("modBusFor ${info.getModId()}") contextExtension = Supplier {ctx} - + try { - val layer = gameLayer.findModule(info.owningFile.moduleName()).orElseThrow() + val moduleName = info.getOwningFile().moduleName() + val layer = gameLayer.findModule(moduleName).orElseThrow() + openModules(gameLayer, layer, info.getOwningFile().getFile().secureJar()) modClass = Class.forName(layer, className) LOGGER.trace(Logging.LOADING, "Loaded modclass {} with {}", modClass.name, modClass.classLoader) } catch (t: Throwable) { @@ -53,7 +53,7 @@ public class KotlinModContainer( } } - private fun onEventFailed(iEventBus: IEventBus, event: Event, listeners: Array, busId: Int, throwable: Throwable) { + private fun onEventFailed(iEventBus: BusGroup, event: Event, listeners: Array, busId: Int, throwable: Throwable) { LOGGER.error(EventBusErrorMessage(event, busId, listeners, throwable)) } @@ -85,9 +85,14 @@ public class KotlinModContainer( override fun getMod(): Any? = modInstance + fun getModBusGroup(): BusGroup { + return eventBus + } + public override fun acceptEvent(e: T) where T : Event, T : IModBusEvent { try { LOGGER.trace("Firing event for modid $modId : $e") + EventBus eventBus = IModBusEvent.getBus(busGroup, e.getClass()) eventBus.post(e) LOGGER.trace("Fired event for modid $modId : $e") } catch (t: Throwable) { @@ -95,4 +100,25 @@ public class KotlinModContainer( throw ModLoadingException(modInfo, modLoadingStage, "fml.modloading.errorduringevent", t) } } + + private fun openModules(layer: ModuleLayer, self: Module, jar: SecureJar) { + val manifest = jar.moduleDataProvider.manifest.mainAttributes + addOpenOrExports(layer, self, true, manifest) + addOpenOrExports(layer, self, false, manifest) + } + + private fun addOpenOrExports(layer: ModuleLayer, self: Module, open: Boolean, attrs: Attributes) { + val key = if (open) "Add-Opens" else "Add-Exports" + val entry = attrs.getValue(key) ?: return + entry.split(" ").forEach { pair -> + val pts = pair.trim().split("/") + if (pts.size == 2) { + val target = layer.findModule(pts[0]).orElse(null) + if (target != null && target.descriptor.packages().contains(pts[1])) { + implAddExportsOrOpens.invoke(target, pts[1], self, open, true) + } + } + } + } + } diff --git a/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/KotlinModLoadingContext.kt b/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/KotlinModLoadingContext.kt index ac4a168d..5d4ffe34 100644 --- a/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/KotlinModLoadingContext.kt +++ b/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/KotlinModLoadingContext.kt @@ -8,14 +8,18 @@ import net.minecraftforge.fml.ModLoadingContext */ public class KotlinModLoadingContext(private val container: KotlinModContainer) { /** Mods should access through [MOD_BUS] */ - public fun getKEventBus(): IEventBus { + public fun getKBusGroup(): BusGroup { return container.eventBus } + public fun getContainer(): KotlinModContainer { + return container + } + public companion object { /** Mods should access through [MOD_CONTEXT] */ public fun get(): KotlinModLoadingContext { - return ModLoadingContext.get().extension() + return ModLoadingContext.get().extension() as KotlinModLoadingContext } } } diff --git a/src/kfflang/forge/resources/META-INF/services/net.minecraftforge.forgespi.language.IModLanguageProvider b/src/kfflang/forge/resources/META-INF/services/net.minecraftforge.forgespi.language.IModLanguageProvider index c78667ed..264af3bb 100644 --- a/src/kfflang/forge/resources/META-INF/services/net.minecraftforge.forgespi.language.IModLanguageProvider +++ b/src/kfflang/forge/resources/META-INF/services/net.minecraftforge.forgespi.language.IModLanguageProvider @@ -1 +1 @@ -thedarkcolour.kotlinforforge.KotlinLanguageProvider +thedarkcolour.kotlinforforge.KotlinLanguageProvider \ No newline at end of file diff --git a/src/kfflib/forge/kotlin/thedarkcolour/kotlinforforge/forge/Forge.kt b/src/kfflib/forge/kotlin/thedarkcolour/kotlinforforge/forge/Forge.kt index 29e6b384..5e1aa2d8 100644 --- a/src/kfflib/forge/kotlin/thedarkcolour/kotlinforforge/forge/Forge.kt +++ b/src/kfflib/forge/kotlin/thedarkcolour/kotlinforforge/forge/Forge.kt @@ -35,8 +35,8 @@ public inline val FORGE_BUS: IEventBus * @see net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent * @see net.minecraftforge.registries.NewRegistryEvent */ -public inline val MOD_BUS: IEventBus - get() = KotlinModLoadingContext.get().getKEventBus() +public inline val MOD_BUS: BusGroup + get() = KotlinModLoadingContext.get().getKBusGroup() /** * Used in place of [net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext] From bad92baa43f367f7228af6c53601c096ba0e8bc4 Mon Sep 17 00:00:00 2001 From: Chidoziealways Date: Sun, 13 Jul 2025 06:15:29 +0100 Subject: [PATCH 2/9] Finished Updating to Kotlin P1 --- build.gradle.kts | 1 + gradle.properties | 4 ++-- gradle/libs.versions.toml | 4 +++- .../AutoKotlinEventBusSubscriber.kt | 5 +++-- .../kotlinforforge/KotlinModContainer.kt | 19 +++++++++---------- .../kotlinforforge/KotlinModLoadingContext.kt | 4 ++-- .../kotlinforforge/forge/Forge.kt | 1 + 7 files changed, 21 insertions(+), 17 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 156b4cb1..751bc0bd 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -153,6 +153,7 @@ dependencies { if (name.contains("lang")) { dependencies.add(compileOnly, libs.asm) dependencies.add(compileOnly, libs.log4j.core) + dependencies.add(compileOnly, libs.secure.jar) } } } diff --git a/gradle.properties b/gradle.properties index c7d15c6b..a0ce699b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -22,5 +22,5 @@ min_mc_version = 1.20.6 unsupported_mc_version = 1.22 # KOTLIN FOR FORGE VERSION -kff_version=5.9.0 -kff_max_version=6.0.0 +kff_version=6.0.0 +kff_max_version=6.1.0 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 193cef05..f0213ca5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,7 +15,8 @@ neoforge-mergetool = "2.0.7" forge = "1.21.7-57.0.2" forge-mergetool = "1.0" forge-spi = "7.1.4" -forge-bus = "7.0.0" +secureJar = "3.0.9" +forge-bus = "0.7.0" joml = "1.10.5" log4j = "2.22.1" asm = "9.5" @@ -47,6 +48,7 @@ forge-loader = { module = "net.minecraftforge:fmlloader", version.ref = "forge" forge-core = { module = "net.minecraftforge:fmlcore", version.ref = "forge" } forge-mergetool = { module = "net.minecraftforge:mergetool-api", version.ref = "forge-mergetool" } forge-spi = { module = "net.minecraftforge:forgespi", version.ref = "forge-spi" } +secure-jar = { module = "cpw.mods:securejarhandler", version.ref = "secureJar"} joml = { module = "org.joml:joml", version.ref = "joml" } asm = { module = "org.ow2.asm:asm", version.ref = "asm" } log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" } diff --git a/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt b/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt index df5291ef..453053ce 100644 --- a/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt +++ b/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt @@ -7,6 +7,7 @@ import net.minecraftforge.fml.loading.FMLEnvironment import net.minecraftforge.forgespi.language.ModFileScanData import net.minecraftforge.forgespi.language.ModFileScanData.EnumData import org.objectweb.asm.Type +import java.lang.invoke.MethodHandles import java.lang.reflect.Method import java.util.* @@ -132,9 +133,9 @@ public object AutoKotlinEventBusSubscriber { private fun registerTo(any: Any, target: Mod.EventBusSubscriber.Bus, mod: KotlinModContainer) { if (target == Mod.EventBusSubscriber.Bus.FORGE) { - target.bus().get().register(any) + target.bus().get()?.register(MethodHandles.lookup(), any) } else { - mod.eventBus.register(any) + mod.busGroup.register(MethodHandles.lookup(), any) } } } diff --git a/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt b/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt index b27d8293..7c5e4447 100644 --- a/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt +++ b/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt @@ -1,10 +1,10 @@ package thedarkcolour.kotlinforforge +import cpw.mods.jarhandling.SecureJar import net.minecraftforge.eventbus.EventBusErrorMessage -import net.minecraftforge.eventbus.api.BusBuilder import net.minecraftforge.eventbus.api.Event -import net.minecraftforge.eventbus.api.IEventBus import net.minecraftforge.eventbus.api.IEventListener +import net.minecraftforge.eventbus.api.bus.BusGroup import net.minecraftforge.fml.Logging import net.minecraftforge.fml.ModContainer import net.minecraftforge.fml.ModLoadingException @@ -12,9 +12,8 @@ import net.minecraftforge.fml.ModLoadingStage import net.minecraftforge.fml.event.IModBusEvent import net.minecraftforge.forgespi.language.IModInfo import net.minecraftforge.forgespi.language.ModFileScanData -import java.util.* -import java.util.function.Consumer import java.util.function.Supplier +import java.util.jar.Attributes /** * Kotlin mod container @@ -26,7 +25,7 @@ public class KotlinModContainer( gameLayer: ModuleLayer, ) : ModContainer(info) { private var modInstance: Any? = null - internal val busGroup: BusGroup + internal var busGroup: BusGroup private val modClass: Class<*> val ctx = KotlinModLoadingContext(this) private val implAddExportsOrOpens = Module::class.java.getDeclaredMethod( @@ -38,13 +37,13 @@ public class KotlinModContainer( init { LOGGER.debug(Logging.LOADING, "Creating KotlinModContainer instance for $className") activityMap[ModLoadingStage.CONSTRUCT] = Runnable(::constructMod) - eventBus = BusGroup.create("modBusFor ${info.getModId()}") + busGroup = BusGroup.create("modBusFor ${info.getModId()}") contextExtension = Supplier {ctx} try { val moduleName = info.getOwningFile().moduleName() val layer = gameLayer.findModule(moduleName).orElseThrow() - openModules(gameLayer, layer, info.getOwningFile().getFile().secureJar()) + openModules(gameLayer, layer, info.owningFile.file.secureJar) modClass = Class.forName(layer, className) LOGGER.trace(Logging.LOADING, "Loaded modclass {} with {}", modClass.name, modClass.classLoader) } catch (t: Throwable) { @@ -86,13 +85,13 @@ public class KotlinModContainer( override fun getMod(): Any? = modInstance fun getModBusGroup(): BusGroup { - return eventBus + return busGroup } public override fun acceptEvent(e: T) where T : Event, T : IModBusEvent { try { LOGGER.trace("Firing event for modid $modId : $e") - EventBus eventBus = IModBusEvent.getBus(busGroup, e.getClass()) + var eventBus = IModBusEvent.getBus(busGroup, e.javaClass) eventBus.post(e) LOGGER.trace("Fired event for modid $modId : $e") } catch (t: Throwable) { @@ -102,7 +101,7 @@ public class KotlinModContainer( } private fun openModules(layer: ModuleLayer, self: Module, jar: SecureJar) { - val manifest = jar.moduleDataProvider.manifest.mainAttributes + val manifest = jar.moduleDataProvider().manifest.mainAttributes addOpenOrExports(layer, self, true, manifest) addOpenOrExports(layer, self, false, manifest) } diff --git a/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/KotlinModLoadingContext.kt b/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/KotlinModLoadingContext.kt index 5d4ffe34..ff871ab0 100644 --- a/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/KotlinModLoadingContext.kt +++ b/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/KotlinModLoadingContext.kt @@ -1,6 +1,6 @@ package thedarkcolour.kotlinforforge -import net.minecraftforge.eventbus.api.IEventBus +import net.minecraftforge.eventbus.api.bus.BusGroup import net.minecraftforge.fml.ModLoadingContext /** @@ -9,7 +9,7 @@ import net.minecraftforge.fml.ModLoadingContext public class KotlinModLoadingContext(private val container: KotlinModContainer) { /** Mods should access through [MOD_BUS] */ public fun getKBusGroup(): BusGroup { - return container.eventBus + return container.busGroup } public fun getContainer(): KotlinModContainer { diff --git a/src/kfflib/forge/kotlin/thedarkcolour/kotlinforforge/forge/Forge.kt b/src/kfflib/forge/kotlin/thedarkcolour/kotlinforforge/forge/Forge.kt index 5e1aa2d8..1bc66904 100644 --- a/src/kfflib/forge/kotlin/thedarkcolour/kotlinforforge/forge/Forge.kt +++ b/src/kfflib/forge/kotlin/thedarkcolour/kotlinforforge/forge/Forge.kt @@ -6,6 +6,7 @@ import net.minecraftforge.common.MinecraftForge import net.minecraftforge.eventbus.api.EventPriority import net.minecraftforge.eventbus.api.GenericEvent import net.minecraftforge.eventbus.api.IEventBus +import net.minecraftforge.eventbus.api.bus.BusGroup import net.minecraftforge.fml.ModLoadingContext import net.minecraftforge.fml.config.ModConfig import net.minecraftforge.fml.loading.FMLEnvironment From f90836267bc85621d3bdca581e38f05e75765ee3 Mon Sep 17 00:00:00 2001 From: Chidoziealways Date: Sun, 13 Jul 2025 06:23:21 +0100 Subject: [PATCH 3/9] Finished Updating to Kotlin P2 --- .../java/net/minecraftforge/common/MinecraftForge.java | 4 ++-- .../forge/kotlin/thedarkcolour/kotlinforforge/forge/Forge.kt | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/fakecraft/java/net/minecraftforge/common/MinecraftForge.java b/src/fakecraft/java/net/minecraftforge/common/MinecraftForge.java index 5ffdd58a..54f6c863 100644 --- a/src/fakecraft/java/net/minecraftforge/common/MinecraftForge.java +++ b/src/fakecraft/java/net/minecraftforge/common/MinecraftForge.java @@ -1,7 +1,7 @@ package net.minecraftforge.common; -import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.eventbus.api.bus.BusGroup; public class MinecraftForge { - public static final IEventBus EVENT_BUS = null; + public static final BusGroup EVENT_BUS = null; } diff --git a/src/kfflib/forge/kotlin/thedarkcolour/kotlinforforge/forge/Forge.kt b/src/kfflib/forge/kotlin/thedarkcolour/kotlinforforge/forge/Forge.kt index 1bc66904..eba72a96 100644 --- a/src/kfflib/forge/kotlin/thedarkcolour/kotlinforforge/forge/Forge.kt +++ b/src/kfflib/forge/kotlin/thedarkcolour/kotlinforforge/forge/Forge.kt @@ -24,8 +24,6 @@ import kotlin.reflect.KProperty * @see net.minecraftforge.event.entity.living.LivingEvent * @see net.minecraftforge.event.world.BlockEvent */ -public inline val FORGE_BUS: IEventBus - get() = MinecraftForge.EVENT_BUS /** * Mod-specific event bus. From 19a2464ca27cd456a79afcb1636bfa7fd1413b8e Mon Sep 17 00:00:00 2001 From: Chidoziealways Date: Sun, 13 Jul 2025 08:11:40 +0100 Subject: [PATCH 4/9] Finished Updating to Kotlin P3 FINISHED --- build.gradle.kts | 5 ++- gradle/libs.versions.toml | 4 +- .../kotlinforforge/EventBusMakers.kt | 37 ------------------- .../kotlinforforge/KotlinModContainer.kt | 10 +---- .../neoforge/KotlinModContainer.kt | 1 - .../kotlinforforge/forge/Forge.kt | 16 ++++---- 6 files changed, 17 insertions(+), 56 deletions(-) delete mode 100644 src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/EventBusMakers.kt diff --git a/build.gradle.kts b/build.gradle.kts index 751bc0bd..742fd0a3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -108,13 +108,14 @@ repositories { mavenCentral() maven("https://maven.neoforged.net/releases") maven("https://maven.minecraftforge.net/") + maven("https://libraries.minecraft.net") } dependencies { + // fakecraft (fake class/field/method signatures used in place of the full Minecraft/Forge dependencies) "fakecraftCompileOnly"(libs.joml) "fakecraftCompileOnly"(libs.forge.core) - "fakecraftCompileOnly"(libs.forge.bus) "fakecraftCompileOnly"(libs.night.config) // kfflib/common @@ -132,6 +133,7 @@ dependencies { // kffmod/forge "modForgeCompileOnly"(libs.log4j.core) + sourceSets.forEach { sourceSet -> val name = sourceSet.name val compileOnly = sourceSet.compileOnlyConfigurationName @@ -154,6 +156,7 @@ dependencies { dependencies.add(compileOnly, libs.asm) dependencies.add(compileOnly, libs.log4j.core) dependencies.add(compileOnly, libs.secure.jar) + dependencies.add(compileOnly, libs.forge.bus) } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f0213ca5..8d47d4e7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ coroutines = "1.10.2" serialization = "1.8.1" # Misc -neoforge = "21.6.11-beta" +neoforge = "21.7.17-beta" neoforge-fml = "[3,5)" neoforge-bus = "8.0.5" neoforge-mergetool = "2.0.7" @@ -16,7 +16,7 @@ forge = "1.21.7-57.0.2" forge-mergetool = "1.0" forge-spi = "7.1.4" secureJar = "3.0.9" -forge-bus = "0.7.0" +forge-bus = "7.0-beta.10" joml = "1.10.5" log4j = "2.22.1" asm = "9.5" diff --git a/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/EventBusMakers.kt b/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/EventBusMakers.kt deleted file mode 100644 index 739cb8fb..00000000 --- a/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/EventBusMakers.kt +++ /dev/null @@ -1,37 +0,0 @@ -package thedarkcolour.kotlinforforge - -import net.minecraftforge.eventbus.api.BusBuilder -import net.minecraftforge.eventbus.api.IEventBus -import net.minecraftforge.eventbus.api.IEventExceptionHandler -import net.minecraftforge.fml.event.IModBusEvent - -// todo remove in 5.x.x -internal sealed interface EventBusMaker { - fun make(exceptionHandler: IEventExceptionHandler): IEventBus -} - -// Reflection is needed since KFF compiled on 1.19.2 assumes BusBuilder methods are interface methods -// which produces bytecode incompatible with older versions that assume the methods are not interface -internal object OldEventBusMaker : EventBusMaker { - private val builderMethod = BusBuilder::class.java.getDeclaredMethod("builder") - private val setExceptionHandlerMethod = BusBuilder::class.java.getDeclaredMethod("setExceptionHandler", IEventExceptionHandler::class.java) - private val setTrackPhasesMethod = BusBuilder::class.java.getDeclaredMethod("setTrackPhases", Boolean::class.java) - private val markerTypeMethod = BusBuilder::class.java.getDeclaredMethod("markerType", Class::class.java) - private val buildMethod = BusBuilder::class.java.getDeclaredMethod("build") - - override fun make(exceptionHandler: IEventExceptionHandler): IEventBus { - val builder = builderMethod.invoke(null) - - setExceptionHandlerMethod.invoke(builder, exceptionHandler) - setTrackPhasesMethod.invoke(builder, false) - markerTypeMethod.invoke(builder, IModBusEvent::class.java) - - return buildMethod.invoke(builder) as IEventBus - } -} - -internal object NewEventBusMaker: EventBusMaker { - override fun make(exceptionHandler: IEventExceptionHandler): IEventBus { - return BusBuilder.builder().setExceptionHandler(exceptionHandler).setTrackPhases(false).markerType(IModBusEvent::class.java).useModLauncher().build() - } -} \ No newline at end of file diff --git a/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt b/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt index 7c5e4447..7a50f694 100644 --- a/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt +++ b/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt @@ -1,10 +1,8 @@ package thedarkcolour.kotlinforforge import cpw.mods.jarhandling.SecureJar -import net.minecraftforge.eventbus.EventBusErrorMessage -import net.minecraftforge.eventbus.api.Event -import net.minecraftforge.eventbus.api.IEventListener import net.minecraftforge.eventbus.api.bus.BusGroup +import net.minecraftforge.eventbus.internal.Event import net.minecraftforge.fml.Logging import net.minecraftforge.fml.ModContainer import net.minecraftforge.fml.ModLoadingException @@ -52,10 +50,6 @@ public class KotlinModContainer( } } - private fun onEventFailed(iEventBus: BusGroup, event: Event, listeners: Array, busId: Int, throwable: Throwable) { - LOGGER.error(EventBusErrorMessage(event, busId, listeners, throwable)) - } - // Sets modInstance to a new instance of the mod class or the object instance private fun constructMod() { try { @@ -88,7 +82,7 @@ public class KotlinModContainer( return busGroup } - public override fun acceptEvent(e: T) where T : Event, T : IModBusEvent { + public fun acceptEvent(e: T) where T : Event, T : IModBusEvent { try { LOGGER.trace("Firing event for modid $modId : $e") var eventBus = IModBusEvent.getBus(busGroup, e.javaClass) diff --git a/src/kfflang/neoforge/kotlin/thedarkcolour/kotlinforforge/neoforge/KotlinModContainer.kt b/src/kfflang/neoforge/kotlin/thedarkcolour/kotlinforforge/neoforge/KotlinModContainer.kt index e56da118..b9e5800b 100644 --- a/src/kfflang/neoforge/kotlin/thedarkcolour/kotlinforforge/neoforge/KotlinModContainer.kt +++ b/src/kfflang/neoforge/kotlin/thedarkcolour/kotlinforforge/neoforge/KotlinModContainer.kt @@ -11,7 +11,6 @@ import net.neoforged.fml.ModContainer import net.neoforged.fml.ModLoadingException import net.neoforged.fml.ModLoadingIssue import net.neoforged.fml.event.IModBusEvent -import net.neoforged.fml.javafmlmod.AutomaticEventSubscriber import net.neoforged.fml.javafmlmod.FMLModContainer import net.neoforged.fml.loading.FMLLoader import net.neoforged.neoforgespi.language.IModInfo diff --git a/src/kfflib/forge/kotlin/thedarkcolour/kotlinforforge/forge/Forge.kt b/src/kfflib/forge/kotlin/thedarkcolour/kotlinforforge/forge/Forge.kt index eba72a96..87e22c8c 100644 --- a/src/kfflib/forge/kotlin/thedarkcolour/kotlinforforge/forge/Forge.kt +++ b/src/kfflib/forge/kotlin/thedarkcolour/kotlinforforge/forge/Forge.kt @@ -2,15 +2,16 @@ package thedarkcolour.kotlinforforge.forge import net.minecraftforge.api.distmarker.Dist import net.minecraftforge.common.ForgeConfigSpec -import net.minecraftforge.common.MinecraftForge -import net.minecraftforge.eventbus.api.EventPriority -import net.minecraftforge.eventbus.api.GenericEvent -import net.minecraftforge.eventbus.api.IEventBus import net.minecraftforge.eventbus.api.bus.BusGroup +import net.minecraftforge.eventbus.api.listener.EventListener +import net.minecraftforge.eventbus.api.listener.Priority +import net.minecraftforge.eventbus.internal.Event +import net.minecraftforge.eventbus.internal.EventListenerImpl import net.minecraftforge.fml.ModLoadingContext import net.minecraftforge.fml.config.ModConfig import net.minecraftforge.fml.loading.FMLEnvironment import thedarkcolour.kotlinforforge.KotlinModLoadingContext +import java.lang.invoke.MethodHandles import java.util.function.Consumer import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty @@ -94,12 +95,13 @@ public inline fun registerConfig(type: ModConfig.Type, spec: ForgeConfigSpec) { LOADING_CONTEXT.registerConfig(type, spec) } -public inline fun , reified F> IEventBus.addGenericListener( +public inline fun BusGroup.addGenericListener( listener: Consumer, - priority: EventPriority = EventPriority.NORMAL, + priority: Byte = Priority.NORMAL, receiveCancelled: Boolean = false ) { - addGenericListener(F::class.java, priority, receiveCancelled, listener) + val eventType = T::class.java + this.register(MethodHandles.lookup(), eventType) } /** From 9233794daa69d2aec5482c876076223ddf163771 Mon Sep 17 00:00:00 2001 From: Chidoziealways Date: Sun, 13 Jul 2025 08:22:56 +0100 Subject: [PATCH 5/9] Made required changes --- gradle.properties | 2 +- .../kotlinforforge/AutoKotlinEventBusSubscriber.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index a0ce699b..618b3f32 100644 --- a/gradle.properties +++ b/gradle.properties @@ -23,4 +23,4 @@ unsupported_mc_version = 1.22 # KOTLIN FOR FORGE VERSION kff_version=6.0.0 -kff_max_version=6.1.0 +kff_max_version=7.0.0 diff --git a/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt b/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt index 453053ce..33277463 100644 --- a/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt +++ b/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt @@ -133,7 +133,7 @@ public object AutoKotlinEventBusSubscriber { private fun registerTo(any: Any, target: Mod.EventBusSubscriber.Bus, mod: KotlinModContainer) { if (target == Mod.EventBusSubscriber.Bus.FORGE) { - target.bus().get()?.register(MethodHandles.lookup(), any) + target.bus().get()!!.register(MethodHandles.lookup(), any) } else { mod.busGroup.register(MethodHandles.lookup(), any) } From 1eac7491019eae36cc043f6860c49134f09aa3a9 Mon Sep 17 00:00:00 2001 From: Chidoziealways Date: Sun, 13 Jul 2025 08:40:03 +0100 Subject: [PATCH 6/9] removed unnecesary gradle properties --- gradle.properties | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/gradle.properties b/gradle.properties index 618b3f32..7183a05b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,21 +2,11 @@ # This is required to provide enough memory for the Minecraft decompilation process. kotlin.code.style=official kotlin.stdlib.default.dependency=false -org.gradle.jvmargs=-Xmx4G -Xms2G -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 +org.gradle.jvmargs=-Xmx1G org.gradle.daemon=true org.gradle.parallel=true -org.gradle.workers.max=8 org.gradle.caching=true org.gradle.configuration-cache=true -systemProp.http.socketTimeout=600000 -systemProp.http.connectionTimeout=600000 -systemProp.https.socketTimeout=600000 -systemProp.https.connectionTimeout=600000 -systemProp.org.gradle.internal.http.connectionTimeout=2147483647 -# ~35 minutes -org.gradle.internal.http.socketTimeout=2147483647 -# ~35 minutes -systemProp.org.gradle.internal.repository.max.retries=1 min_mc_version = 1.20.6 unsupported_mc_version = 1.22 From 3be40d874fa35b39a2f9087ad4c7be27f4f8c9a2 Mon Sep 17 00:00:00 2001 From: Chidoziealways Date: Sun, 13 Jul 2025 13:54:57 +0100 Subject: [PATCH 7/9] Continued update --- gradle/libs.versions.toml | 6 +- .../AutoKotlinEventBusSubscriber.kt | 69 +++++------ .../kotlinforforge/KotlinModContainer.kt | 27 ++++- .../thedarkcolour/kotlinforforge/Logger.kt | 2 + .../kotlinforforge/UnsafeFieldAccess.kt | 39 ++++++ .../kotlinforforge/UnsafeHacks.kt | 112 ++++++++++++++++++ 6 files changed, 207 insertions(+), 48 deletions(-) create mode 100644 src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/UnsafeFieldAccess.kt create mode 100644 src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/UnsafeHacks.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8d47d4e7..c6fa2335 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,13 +8,13 @@ coroutines = "1.10.2" serialization = "1.8.1" # Misc -neoforge = "21.7.17-beta" +neoforge = "21.7.20-beta" neoforge-fml = "[3,5)" neoforge-bus = "8.0.5" neoforge-mergetool = "2.0.7" forge = "1.21.7-57.0.2" -forge-mergetool = "1.0" -forge-spi = "7.1.4" +forge-mergetool = "1.2.3" +forge-spi = "7.1.5" secureJar = "3.0.9" forge-bus = "7.0-beta.10" joml = "1.10.5" diff --git a/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt b/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt index 33277463..8cc13259 100644 --- a/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt +++ b/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt @@ -1,6 +1,7 @@ package thedarkcolour.kotlinforforge import net.minecraftforge.api.distmarker.Dist +import net.minecraftforge.eventbus.api.listener.SubscribeEvent import net.minecraftforge.fml.Logging import net.minecraftforge.fml.common.Mod import net.minecraftforge.fml.loading.FMLEnvironment @@ -36,6 +37,7 @@ import java.util.* public object AutoKotlinEventBusSubscriber { // EventBusSubscriber annotation private val EVENT_BUS_SUBSCRIBER: Type = Type.getType(Mod.EventBusSubscriber::class.java) + private val SUBSCRIBE_EVENT: Type = Type.getType(SubscribeEvent::class.java) // Legacy EnumHolder private val enumHolderGetValue: Method? = try { @@ -62,9 +64,7 @@ public object AutoKotlinEventBusSubscriber { public fun inject(mod: KotlinModContainer, scanData: ModFileScanData, classLoader: ClassLoader) { LOGGER.debug(Logging.LOADING, "Attempting to inject @EventBusSubscriber kotlin objects in to the event bus for ${mod.modId}") - val data = scanData.annotations.filter { annotationData -> - EVENT_BUS_SUBSCRIBER == annotationData.annotationType - } + val data = scanData.annotations.filter { it.annotationData == EVENT_BUS_SUBSCRIBER } for (annotationData in data) { val annotationMap = annotationData.annotationData @@ -75,28 +75,26 @@ public object AutoKotlinEventBusSubscriber { if (mod.modId == modid && FMLEnvironment.dist in sides) { val kClass = Class.forName(annotationData.clazz.className, true, classLoader).kotlin - var ktObject: Any? - - try { - ktObject = kClass.objectInstance - } catch (unsupported: UnsupportedOperationException) { - if (unsupported.message?.contains("file facades") == false) { - throw unsupported - } else { - LOGGER.debug(Logging.LOADING, "Auto-subscribing kotlin file ${annotationData.annotationType.className} to $busTarget") - registerTo(kClass.java, busTarget, mod) - continue - } + val instance = try { + kClass.objectInstance + } catch (_: UnsupportedOperationException) { + null } - if (ktObject != null) { + if (instance != null) { try { - LOGGER.debug(Logging.LOADING, "Auto-subscribing kotlin object ${annotationData.annotationType.className} to $busTarget") - - registerTo(ktObject, busTarget, mod) - } catch (e: Throwable) { - LOGGER.fatal(Logging.LOADING, "Failed to load mod class ${annotationData.annotationType} for @EventBusSubscriber annotation", e) - throw RuntimeException(e) + val lookup = MethodHandles.privateLookupIn(instance::class.java, MethodHandles.lookup()) + val bus = if (busTarget == Mod.EventBusSubscriber.Bus.FORGE) { + busTarget.bus().get() + } else { + mod.busGroup + } + LOGGER.debug(LOADING, "Registering Kotlin object: ${annotationData.clazz.className} to $busTarget") + + bus?.register(lookup, instance) + } catch (t: Throwable) { + LOGGER.fatal(LOADING, "Failed to register Kotlin object ${annotationData.clazz.className}", t) + throw RuntimeException(t) } } } @@ -104,36 +102,29 @@ public object AutoKotlinEventBusSubscriber { } private fun getSides(annotationMap: Map): List { - val sidesHolders = annotationMap["value"] + val sidesHolders = annotationMap["value"] ?: return listOf(Dist.CLIENT, Dist.DEDICATED_SERVER) - return if (sidesHolders != null) { - if (enumHolderGetValue != null) { - (sidesHolders as List).map { data -> Dist.valueOf(enumHolderGetValue.invoke(data) as String) } - } else { - (sidesHolders as List).map { data -> Dist.valueOf(data.value()) } - } + return if (enumHolderGetValue != null) { + (sidesHolders as List<*>).map { Dist.valueOf(enumHolderGetValue.invoke(it) as String) } } else { - listOf(Dist.CLIENT, Dist.DEDICATED_SERVER) + (sidesHolders as List).map { Dist.valueOf(it.value()) } } } private fun getBusTarget(annotationMap: Map): Mod.EventBusSubscriber.Bus { - val busTargetHolder = annotationMap["bus"] + val holder = annotationMap["bus"] ?: return Mod.EventBusSubscriber.Bus.FORGE - return if (busTargetHolder != null) { - if (enumHolderGetValue != null) { - Mod.EventBusSubscriber.Bus.valueOf(enumHolderGetValue.invoke(busTargetHolder) as String) - } else { - Mod.EventBusSubscriber.Bus.valueOf((busTargetHolder as EnumData).value) - } + return if (enumHolderGetValue != null) { + Mod.EventBusSubscriber.Bus.valueOf(enumHolderGetValue.invoke(holder) as String) } else { - Mod.EventBusSubscriber.Bus.FORGE + Mod.EventBusSubscriber.Bus.valueOf((holder as EnumData).value) } } private fun registerTo(any: Any, target: Mod.EventBusSubscriber.Bus, mod: KotlinModContainer) { if (target == Mod.EventBusSubscriber.Bus.FORGE) { - target.bus().get()!!.register(MethodHandles.lookup(), any) + val lookup = MethodHandles.privateLookupIn(any::class.java, MethodHandles.lookup()) + target.bus().get()!!.register(lookup, any) } else { mod.busGroup.register(MethodHandles.lookup(), any) } diff --git a/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt b/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt index 7a50f694..99d4cb57 100644 --- a/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt +++ b/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt @@ -8,8 +8,10 @@ import net.minecraftforge.fml.ModContainer import net.minecraftforge.fml.ModLoadingException import net.minecraftforge.fml.ModLoadingStage import net.minecraftforge.fml.event.IModBusEvent +import net.minecraftforge.fml.javafmlmod.FMLModContainer import net.minecraftforge.forgespi.language.IModInfo import net.minecraftforge.forgespi.language.ModFileScanData +import java.lang.reflect.Method import java.util.function.Supplier import java.util.jar.Attributes @@ -26,11 +28,7 @@ public class KotlinModContainer( internal var busGroup: BusGroup private val modClass: Class<*> val ctx = KotlinModLoadingContext(this) - private val implAddExportsOrOpens = Module::class.java.getDeclaredMethod( - "implAddExportsOrOpens", String::class.java, Module::class.java, Boolean::class.javaPrimitiveType, Boolean::class.javaPrimitiveType - ).apply { - isAccessible = true - } + private var implAddExportsOrOpens: Method? = null init { LOGGER.debug(Logging.LOADING, "Creating KotlinModContainer instance for $className") @@ -108,10 +106,27 @@ public class KotlinModContainer( if (pts.size == 2) { val target = layer.findModule(pts[0]).orElse(null) if (target != null && target.descriptor.packages().contains(pts[1])) { - implAddExportsOrOpens.invoke(target, pts[1], self, open, true) + addOpenOrExports(target, pts[1], self, open) } } } } + private fun addOpenOrExports(target: Module, pkg: String, reader: Module, open: Boolean) { + if(implAddExportsOrOpens == null) { + implAddExportsOrOpens = Module::class.java.getDeclaredMethod("implAddExportsOrOpens", String::class.java, Module::class.java, java.lang.Boolean.TYPE, java.lang.Boolean.TYPE) + UnsafeHacks.setAccessible(implAddExportsOrOpens!!) + } + + LOGGER.info( + LOADING, + "{} {}/{} to {}", + if (open) "Opening" else "Exporting", + target.getName(), + pkg, + reader.getName() + ) + implAddExportsOrOpens?.invoke(target, pkg, reader, open, true) + } + } diff --git a/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/Logger.kt b/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/Logger.kt index bc6f9d47..edfa9681 100644 --- a/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/Logger.kt +++ b/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/Logger.kt @@ -1,6 +1,7 @@ package thedarkcolour.kotlinforforge import org.apache.logging.log4j.LogManager +import org.apache.logging.log4j.MarkerManager /** * Logger field for KotlinForForge. @@ -9,3 +10,4 @@ import org.apache.logging.log4j.LogManager * before [KotlinModContainer] should initialize. */ internal val LOGGER = LogManager.getLogger() +internal val LOADING = MarkerManager.getMarker("LOADING") diff --git a/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/UnsafeFieldAccess.kt b/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/UnsafeFieldAccess.kt new file mode 100644 index 00000000..432e38bb --- /dev/null +++ b/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/UnsafeFieldAccess.kt @@ -0,0 +1,39 @@ +@file:Suppress("UNCHECKED_CAST", "MemberVisibilityCanBePrivate", "unused") + +package thedarkcolour.kotlinforforge + +public open class UnsafeFieldAccess(protected val index: Long) { + public open fun get(instance: I): Type { + return UnsafeHacks.theUnsafe().getObject(instance, index) as Type + } + + public open fun set(instance: I, value: Type) { + UnsafeHacks.theUnsafe().putObject(instance, index, value) + } + + public class Int(index: Long) : UnsafeFieldAccess(index) { + override fun get(instance: I): kotlin.Int = getInt(instance) + override fun set(instance: I, value: kotlin.Int): Unit = setInt(instance, value) + + public fun getInt(instance: I): kotlin.Int { + return UnsafeHacks.theUnsafe().getInt(instance, index) + } + + public fun setInt(instance: I, value: kotlin.Int) { + UnsafeHacks.theUnsafe().putInt(instance, index, value) + } + } + + public class Bool(index: Long) : UnsafeFieldAccess(index) { + override fun get(instance: I): Boolean = getBoolean(instance) + override fun set(instance: I, value: Boolean): Unit = setBoolean(instance, value) + + public fun getBoolean(instance: I): Boolean { + return UnsafeHacks.theUnsafe().getBoolean(instance, index) + } + + public fun setBoolean(instance: I, value: Boolean) { + UnsafeHacks.theUnsafe().putBoolean(instance, index, value) + } + } +} diff --git a/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/UnsafeHacks.kt b/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/UnsafeHacks.kt new file mode 100644 index 00000000..70c19e38 --- /dev/null +++ b/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/UnsafeHacks.kt @@ -0,0 +1,112 @@ +@file:Suppress("UNCHECKED_CAST", "MemberVisibilityCanBePrivate", "unused") + +package thedarkcolour.kotlinforforge + +import sun.misc.Unsafe +import java.lang.reflect.AccessibleObject +import java.lang.reflect.Field +import java.util.function.Consumer + +public object UnsafeHacks { + private val UNSAFE: Unsafe = getUnsafe() + private val module: UnsafeFieldAccess, Any>? = findField(Class::class.java, "module") + private val SETACCESSIBLE: Consumer = getSetAccessible() + + public fun newInstance(clazz: Class): T { + return try { + UNSAFE.allocateInstance(clazz) as T + } catch (e: InstantiationException) { + sneak(e) + } + } + + public fun getField(field: Field, instance: Any): T { + return UNSAFE.getObject(instance, UNSAFE.objectFieldOffset(field)) as T + } + + public fun setField(field: Field, instance: Any, value: Any) { + UNSAFE.putObject(instance, UNSAFE.objectFieldOffset(field), value) + } + + fun getIntField(field: Field, instance: Any): Int { + return UNSAFE.getInt(instance, UNSAFE.objectFieldOffset(field)) + } + + fun setIntField(field: Field, instance: Any, value: Int) { + UNSAFE.putInt(instance, UNSAFE.objectFieldOffset(field), value) + } + + public fun findField(clazz: Class, name: String): UnsafeFieldAccess? { + return clazz.declaredFields + .firstOrNull { it.name == name } + ?.let { UnsafeFieldAccess(UNSAFE.objectFieldOffset(it)) } + } + + public fun findIntField(clazz: Class, name: String): UnsafeFieldAccess.Int? { + return clazz.declaredFields + .firstOrNull { it.name == name } + ?.let { UnsafeFieldAccess.Int(UNSAFE.objectFieldOffset(it)) } + } + + public fun findBooleanField(clazz: Class, name: String): UnsafeFieldAccess.Bool? { + return clazz.declaredFields + .firstOrNull { it.name == name } + ?.let { UnsafeFieldAccess.Bool(UNSAFE.objectFieldOffset(it)) } + } + + public fun setAccessible(target: AccessibleObject) { + SETACCESSIBLE.accept(target) + } + + public fun theUnsafe(): Unsafe = UNSAFE + + private fun getUnsafe(): Unsafe { + return try { + val field = Unsafe::class.java.getDeclaredField("theUnsafe") + field.isAccessible = true + field.get(null) as Unsafe + } catch (e: Exception) { + sneak(e) + } + } + + private fun getSetAccessible(): Consumer { + return try { + val method = try { + AccessibleObject::class.java.getDeclaredMethod("setAccessible0", Boolean::class.javaPrimitiveType) + } catch (e: NoSuchMethodException) { + AccessibleObject::class.java.getDeclaredMethod("setAccessible0", AccessibleObject::class.java, Boolean::class.javaPrimitiveType) + } + + setAccessibleFallback(method) + + Consumer { acc -> + try { + method.invoke(acc, true) + } catch (e: Exception) { + sneak(e) + } + } + } catch (e: Exception) { + sneak(e) + } + } + + private fun setAccessibleFallback(obj: AccessibleObject) { + if (module != null) { + val old = module.get(UnsafeHacks::class.java) + val base = module.get(Any::class.java) + module.set(UnsafeHacks::class.java, base) + + obj.isAccessible = true + + module.set(UnsafeHacks::class.java, old) + } else { + obj.isAccessible = true + } + } + + public inline fun sneak(e: Throwable): R { + throw e as E + } +} From 02e70ea93d123351163dc65a15e3e2268c3917fe Mon Sep 17 00:00:00 2001 From: Chidoziealways Date: Sun, 13 Jul 2025 17:20:40 +0100 Subject: [PATCH 8/9] Finished Update to 1.21.7 EventBus 7 Forge 1.21.7-57.0.2 --- gradle/libs.versions.toml | 2 +- .../AutoKotlinEventBusSubscriber.kt | 329 +++++++++++++++--- .../kotlinforforge/forge/Forge.kt | 5 + 3 files changed, 280 insertions(+), 56 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c6fa2335..3a21eae5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,7 +13,7 @@ neoforge-fml = "[3,5)" neoforge-bus = "8.0.5" neoforge-mergetool = "2.0.7" forge = "1.21.7-57.0.2" -forge-mergetool = "1.2.3" +forge-mergetool = "1.0" forge-spi = "7.1.5" secureJar = "3.0.9" forge-bus = "7.0-beta.10" diff --git a/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt b/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt index 8cc13259..49bec1e2 100644 --- a/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt +++ b/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt @@ -1,16 +1,32 @@ package thedarkcolour.kotlinforforge import net.minecraftforge.api.distmarker.Dist +import net.minecraftforge.api.distmarker.OnlyIn +import net.minecraftforge.eventbus.api.bus.BusGroup +import net.minecraftforge.eventbus.api.bus.CancellableEventBus +import net.minecraftforge.eventbus.api.bus.EventBus +import net.minecraftforge.eventbus.api.event.characteristic.Cancellable +import net.minecraftforge.eventbus.api.listener.EventListener +import net.minecraftforge.eventbus.api.listener.ObjBooleanBiConsumer +import net.minecraftforge.eventbus.api.listener.Priority import net.minecraftforge.eventbus.api.listener.SubscribeEvent +import net.minecraftforge.eventbus.internal.Event import net.minecraftforge.fml.Logging import net.minecraftforge.fml.common.Mod +import net.minecraftforge.fml.event.IModBusEvent import net.minecraftforge.fml.loading.FMLEnvironment import net.minecraftforge.forgespi.language.ModFileScanData import net.minecraftforge.forgespi.language.ModFileScanData.EnumData import org.objectweb.asm.Type +import java.lang.invoke.LambdaMetafactory +import java.lang.invoke.MethodHandle import java.lang.invoke.MethodHandles +import java.lang.invoke.MethodType import java.lang.reflect.Method +import java.lang.reflect.Modifier import java.util.* +import java.util.function.Consumer +import java.util.function.Predicate /** * Automatically registers `object` classes to @@ -37,15 +53,8 @@ import java.util.* public object AutoKotlinEventBusSubscriber { // EventBusSubscriber annotation private val EVENT_BUS_SUBSCRIBER: Type = Type.getType(Mod.EventBusSubscriber::class.java) - private val SUBSCRIBE_EVENT: Type = Type.getType(SubscribeEvent::class.java) - - // Legacy EnumHolder - private val enumHolderGetValue: Method? = try { - val klass = Class.forName("net.minecraftforge.fml.loading.moddiscovery.ModAnnotation\$EnumHolder") - klass.getDeclaredMethod("getValue") - } catch (e: ClassNotFoundException) { - null - } + private val MOD_TYPE: Type = Type.getType(Mod::class.java) + private val ONLY_IN_TYPE: Type = Type.getType(OnlyIn::class.java) /** * Allows the [Mod.EventBusSubscriber] annotation @@ -64,69 +73,279 @@ public object AutoKotlinEventBusSubscriber { public fun inject(mod: KotlinModContainer, scanData: ModFileScanData, classLoader: ClassLoader) { LOGGER.debug(Logging.LOADING, "Attempting to inject @EventBusSubscriber kotlin objects in to the event bus for ${mod.modId}") - val data = scanData.annotations.filter { it.annotationData == EVENT_BUS_SUBSCRIBER } + val targets = scanData.annotations.filter { data -> EVENT_BUS_SUBSCRIBER == data.annotationType }.toList() - for (annotationData in data) { - val annotationMap = annotationData.annotationData - val sides = getSides(annotationMap) - val modid = annotationMap.getOrDefault("modid", mod.modId) - val busTarget = getBusTarget(annotationMap) + val onlyIns: Set = if (FMLEnvironment.production) { + emptySet() + } else { + scanData.annotations + .asSequence() + .filter { it.annotationType() == ONLY_IN_TYPE } + .map { it.clazz().className } + .toSet() + } - if (mod.modId == modid && FMLEnvironment.dist in sides) { - val kClass = Class.forName(annotationData.clazz.className, true, classLoader).kotlin + val modids: Map = scanData.annotations + .asSequence() + .filter { it.annotationType() == MOD_TYPE } + .associate { it.clazz().className to (it.annotationData()["value"] as String) } - val instance = try { - kClass.objectInstance - } catch (_: UnsupportedOperationException) { - null - } + val defaultSides = listOf(EnumData(null, "CLIENT"), EnumData(null, "DEDICATED_SERVER")) + val defaultBus = EnumData(null, "FORGE") - if (instance != null) { - try { - val lookup = MethodHandles.privateLookupIn(instance::class.java, MethodHandles.lookup()) - val bus = if (busTarget == Mod.EventBusSubscriber.Bus.FORGE) { - busTarget.bus().get() - } else { - mod.busGroup - } - LOGGER.debug(LOADING, "Registering Kotlin object: ${annotationData.clazz.className} to $busTarget") + for (annotationData in targets) { + if (!FMLEnvironment.production && onlyIns.contains(annotationData.clazz.className)) { + throw RuntimeException("Found @OnlyIn on @EventBusSubscriber class ${annotationData.clazz().className} - this is not allowed as it causes crashes. Remove the OnlyIn and set value=Dist.CLIENT in the EventBusSubscriber annotation instead") + } + + var modId = modids.getOrDefault(annotationData.clazz.className, mod.modId) + modId = value(annotationData, "modId", modId) + + val sidesValue = value(annotationData, "value", defaultSides) + val sides = sidesValue + .map(EnumData::value) + .map(Dist::valueOf) + .toSet() + val busName = value(annotationData, "bus", defaultBus).value() + val busTarget = Mod.EventBusSubscriber.Bus.valueOf(busName) + if (mod.modId == modId && FMLEnvironment.dist in sides) { + try { + LOGGER.debug(LOADING, "Auto-subscribing {} to {}", annotationData.clazz().getClassName(), busTarget) + val clazz = Class.forName(annotationData.clazz.className, true, classLoader) - bus?.register(lookup, instance) - } catch (t: Throwable) { - LOGGER.fatal(LOADING, "Failed to register Kotlin object ${annotationData.clazz.className}", t) - throw RuntimeException(t) + when (busTarget) { + Mod.EventBusSubscriber.Bus.MOD -> { + EventBusSubscriberLogic.register(KotlinModLoadingContext.get().getKBusGroup(), clazz) + } + Mod.EventBusSubscriber.Bus.FORGE -> { + EventBusSubscriberLogic.register(BusGroup.DEFAULT, clazz) + } + Mod.EventBusSubscriber.Bus.BOTH -> { + EventBusSubscriberLogic.register(KotlinModLoadingContext.get().getKBusGroup(), clazz) + EventBusSubscriberLogic.register(BusGroup.DEFAULT, clazz) + } } + } catch (e: ClassNotFoundException){ + LOGGER.fatal(LOADING, "Failed to load mod class {} for @EventBusSubscriber annotation", annotationData.clazz(), e) + throw RuntimeException(e) } } } } - private fun getSides(annotationMap: Map): List { - val sidesHolders = annotationMap["value"] ?: return listOf(Dist.CLIENT, Dist.DEDICATED_SERVER) + @Suppress("UNCHECKED_CAST") + private fun value(data: ModFileScanData.AnnotationData, key: String, default: R): R { + return data.annotationData.getOrDefault(key, default) as R + } + + private object EventBusSubscriberLogic { + private val STRICT_RUNTIME_CHECKS = java.lang.Boolean.getBoolean("eventbus.api.strictRuntimeChecks") + private val STRICT_REGISTRATION_CHECKS = + STRICT_RUNTIME_CHECKS || java.lang.Boolean.getBoolean("eventbus.api.strictRegistrationChecks") - return if (enumHolderGetValue != null) { - (sidesHolders as List<*>).map { Dist.valueOf(enumHolderGetValue.invoke(it) as String) } - } else { - (sidesHolders as List).map { Dist.valueOf(it.value()) } + private val RETURNS_CONSUMER = MethodType.methodType(Consumer::class.java) + private val RETURNS_PREDICATE = MethodType.methodType(Predicate::class.java) + private val RETURNS_MONITOR = MethodType.methodType(ObjBooleanBiConsumer::class.java) + + private val CONSUMER_FI_TYPE = MethodType.methodType(Void.TYPE, Any::class.java) + private val PREDICATE_FI_TYPE = CONSUMER_FI_TYPE.changeReturnType(Boolean::class.javaPrimitiveType) + private val MONITOR_FI_TYPE = MethodType.methodType(Void.TYPE, Any::class.java, Boolean::class.javaPrimitiveType) + + @JvmStatic + fun register(busGroup: BusGroup?, listenerClass: Class<*>) { + if (STRICT_REGISTRATION_CHECKS) registerStrict(busGroup, listenerClass) + else registerLenient(busGroup, listenerClass) } - } - private fun getBusTarget(annotationMap: Map): Mod.EventBusSubscriber.Bus { - val holder = annotationMap["bus"] ?: return Mod.EventBusSubscriber.Bus.FORGE + @JvmStatic + fun registerLenient(busGroup: BusGroup?, listenerClass: Class<*>) { + val declaredMethods = listenerClass.declaredMethods + if (declaredMethods.isEmpty()) throw IllegalArgumentException("No declared methods found in $listenerClass") - return if (enumHolderGetValue != null) { - Mod.EventBusSubscriber.Bus.valueOf(enumHolderGetValue.invoke(holder) as String) - } else { - Mod.EventBusSubscriber.Bus.valueOf((holder as EnumData).value) + var firstValidEvent: Class<*>? = null + var listeners = 0 + + for (method in declaredMethods) { + if (!Modifier.isStatic(method.modifiers) || method.isSynthetic) continue + if (method.parameterCount !in 1..2) continue + if (method.returnType != Void.TYPE && method.returnType != Boolean::class.javaPrimitiveType) continue + if (!method.isAnnotationPresent(SubscribeEvent::class.java)) continue + + val paramType = method.parameterTypes[0] + if (!Event::class.java.isAssignableFrom(paramType)) + throw IllegalArgumentException("First parameter must be an Event") + + val eventType = paramType as Class + val annotation = method.getAnnotation(SubscribeEvent::class.java) + + registerListenerGeneric(busGroup, method.parameterCount, method.returnType, eventType, annotation, method) + if (firstValidEvent == null) firstValidEvent = eventType + listeners++ + } + + if (listeners == 0) throw IllegalArgumentException("No valid listeners found in $listenerClass") } - } - private fun registerTo(any: Any, target: Mod.EventBusSubscriber.Bus, mod: KotlinModContainer) { - if (target == Mod.EventBusSubscriber.Bus.FORGE) { - val lookup = MethodHandles.privateLookupIn(any::class.java, MethodHandles.lookup()) - target.bus().get()!!.register(lookup, any) - } else { - mod.busGroup.register(MethodHandles.lookup(), any) + @JvmStatic + fun registerStrict(busGroup: BusGroup?, listenerClass: Class<*>) { + val declaredMethods = listenerClass.declaredMethods.filterNot { it.isSynthetic } + if (declaredMethods.isEmpty()) { + var msg = "No declared methods found in ${listenerClass.name}" + listenerClass.superclass?.let { + if (it != Record::class.java && it != Enum::class.java) { + msg += ". Listener inheritance isn't supported. Use @Override + @SubscribeEvent on subclass." + } + } + throw fail(listenerClass, msg) + } + + var firstValidEvent: Class? = null + var listeners = 0 + + for (method in declaredMethods) { + val hasAnnotation = method.isAnnotationPresent(SubscribeEvent::class.java) + val paramCount = method.parameterCount + val paramTypes = method.parameterTypes + + if (hasAnnotation && (paramCount == 0 || paramCount > 2)) + throw fail(method, "Invalid parameter count: $paramCount") + + if (paramCount == 0) continue + if (!hasAnnotation && Event::class.java.isAssignableFrom(paramTypes[0])) + throw fail(method, "Missing @SubscribeEvent annotation") + + if (hasAnnotation) { + val annotation = method.getAnnotation(SubscribeEvent::class.java) + val isMonitor = annotation.priority == Priority.MONITOR + val returnType = method.returnType + + if (!Modifier.isStatic(method.modifiers)) throw fail(method, "Listener must be static") + if (!Event::class.java.isAssignableFrom(paramTypes[0])) throw fail(method, "First param must be Event") + + val eventType = paramTypes[0] as Class + val cancellable = Cancellable::class.java.isAssignableFrom(eventType) + + if (returnType != Void.TYPE && returnType != Boolean::class.javaPrimitiveType) + throw fail(method, "Invalid return type: ${returnType.name}") + + if (paramCount == 2) { + if (!cancellable) throw fail(method, "Second param only valid for cancellable events") + if (paramTypes[1] != Boolean::class.javaPrimitiveType) throw fail(method, "Second param must be boolean") + if (!isMonitor) throw fail(method, "Second param only valid for MONITOR priority") + } + + if (!cancellable) { + if (annotation.alwaysCancelling) + throw fail(method, "alwaysCancelling only valid on cancellable events") + if (returnType == Boolean::class.javaPrimitiveType) + throw fail(method, "boolean return type only valid on cancellable events") + } + + registerListenerGeneric(busGroup, paramCount, returnType, eventType, annotation, method) + if (firstValidEvent == null) firstValidEvent = eventType + listeners++ + } + } + + if (listeners == 0) throw fail(listenerClass, "No valid listeners found") + } + + @Suppress("UNCHECKED_CAST") + fun castToCancellableEvent(eventType: Class<*>): Class + where T : Event, T : Cancellable { + return eventType as Class + } + + @Suppress("UNCHECKED_CAST") + private fun registerListenerGeneric( + busGroup: BusGroup?, + paramCount: Int, + returnType: Class<*>, + eventType: Class, + annotation: SubscribeEvent, + method: Method + ) { + val bus = busGroup ?: if (IModBusEvent::class.java.isAssignableFrom(eventType)) + KotlinModLoadingContext.get().getKBusGroup() + else BusGroup.DEFAULT + + val listener: EventListener = when (paramCount) { + 1 -> { + val priority = annotation.priority + if (returnType == Void.TYPE) { + if (Cancellable::class.java.isAssignableFrom(eventType)) { + val busC = CancellableEventBus.create( + bus, + castToCancellableEvent(eventType) + ) + if (annotation.alwaysCancelling) + busC.addListener(priority, true, createConsumer(method)) + else + busC.addListener(priority, createConsumer(method)) + } else { + EventBus.create(bus, eventType).addListener(priority, createConsumer(method)) + } + } else { + if (annotation.alwaysCancelling) + throw fail(method, "Listeners returning boolean can't be alwaysCancelling") + if (!Cancellable::class.java.isAssignableFrom(eventType)) + throw fail(method, "boolean return type only valid for cancellable events") + + CancellableEventBus.create(bus, + castToCancellableEvent(eventType) + ) + .addListener(priority, createPredicate(method)) + } + } + + else -> { + if (returnType != Void.TYPE) throw fail(method, "Cancellation-monitoring listeners must return void") + if (annotation.alwaysCancelling) throw fail(method, "MONITOR listeners cannot cancel") + CancellableEventBus.create( + bus, + castToCancellableEvent(eventType) + ) + .addListener(createMonitor(method)) + } + } + + // Optionally store listener if needed + } + + private fun fail(clazz: Class<*>, reason: String): IllegalArgumentException = + IllegalArgumentException("Failed to register ${clazz.name}: $reason") + + private fun fail(method: Method, reason: String): IllegalArgumentException = + IllegalArgumentException("Failed to register ${method.declaringClass.name}.${method.name}: $reason") + + @Suppress("UNCHECKED_CAST") + private fun createConsumer(method: Method): Consumer = + makeFactory(method, RETURNS_CONSUMER, CONSUMER_FI_TYPE, "accept").invokeExact() as Consumer + + @Suppress("UNCHECKED_CAST") + private fun createPredicate(method: Method): Predicate = + makeFactory(method, RETURNS_PREDICATE, PREDICATE_FI_TYPE, "test").invokeExact() as Predicate + + @Suppress("UNCHECKED_CAST") + private fun createMonitor(method: Method): ObjBooleanBiConsumer = + makeFactory(method, RETURNS_MONITOR, MONITOR_FI_TYPE, "accept").invokeExact() as ObjBooleanBiConsumer + + private fun makeFactory( + method: Method, + factoryReturnType: MethodType, + fiMethodType: MethodType, + fiMethodName: String + ): MethodHandle { + val lookup = run { + val field = MethodHandles.Lookup::class.java.getDeclaredField("IMPL_LOOKUP") + UnsafeHacks.setAccessible(field) + field.get(null) as MethodHandles.Lookup + }.`in`(method.declaringClass) + + val target = lookup.unreflect(method) + return LambdaMetafactory.metafactory( + lookup, fiMethodName, factoryReturnType, fiMethodType, target, target.type() + ).target } } } diff --git a/src/kfflib/forge/kotlin/thedarkcolour/kotlinforforge/forge/Forge.kt b/src/kfflib/forge/kotlin/thedarkcolour/kotlinforforge/forge/Forge.kt index 87e22c8c..1814dad1 100644 --- a/src/kfflib/forge/kotlin/thedarkcolour/kotlinforforge/forge/Forge.kt +++ b/src/kfflib/forge/kotlin/thedarkcolour/kotlinforforge/forge/Forge.kt @@ -2,6 +2,7 @@ package thedarkcolour.kotlinforforge.forge import net.minecraftforge.api.distmarker.Dist import net.minecraftforge.common.ForgeConfigSpec +import net.minecraftforge.common.MinecraftForge import net.minecraftforge.eventbus.api.bus.BusGroup import net.minecraftforge.eventbus.api.listener.EventListener import net.minecraftforge.eventbus.api.listener.Priority @@ -26,6 +27,10 @@ import kotlin.reflect.KProperty * @see net.minecraftforge.event.world.BlockEvent */ +public inline val FORGE_BUS: BusGroup + get() = MinecraftForge.EVENT_BUS + + /** * Mod-specific event bus. * Mod lifecycle events are fired on this bus. From 337172a07d1503115532ab11faced0ff3de020c0 Mon Sep 17 00:00:00 2001 From: Chidoziealways Date: Wed, 16 Jul 2025 06:48:23 +0100 Subject: [PATCH 9/9] FINISHED UPDATING TO 1.21.7-57.0.2 ADDED REAL SUPPORT FOR EventBusSUbscribers. ADDED ACTUAL EVENT FIRING AND MANY MORE --- build.gradle.kts | 1 + gradle/libs.versions.toml | 4 +- .../kotlin/thedarkcolour/common/KotlinMod.kt | 51 +++ .../AutoKotlinEventBusSubscriber.kt | 391 ++++++++++-------- .../kotlinforforge/KotlinLanguageProvider.kt | 10 +- .../kotlinforforge/KotlinModContainer.kt | 52 ++- .../kotlinforforge/UnsafeFieldAccess.kt | 39 -- .../kotlinforforge/UnsafeHacks.kt | 112 ----- .../kotlinforforge/test/KotlinForForge.kt | 8 +- 9 files changed, 321 insertions(+), 347 deletions(-) create mode 100644 src/kfflang/forge/kotlin/thedarkcolour/common/KotlinMod.kt delete mode 100644 src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/UnsafeFieldAccess.kt delete mode 100644 src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/UnsafeHacks.kt diff --git a/build.gradle.kts b/build.gradle.kts index 742fd0a3..de8c8463 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -132,6 +132,7 @@ dependencies { // kffmod/forge "modForgeCompileOnly"(libs.log4j.core) + "modForgeCompileOnly"(sourceSets["langForge"].output) sourceSets.forEach { sourceSet -> diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3a21eae5..cfd4147b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,6 +14,7 @@ neoforge-bus = "8.0.5" neoforge-mergetool = "2.0.7" forge = "1.21.7-57.0.2" forge-mergetool = "1.0" +unsafe = "0.9.2" forge-spi = "7.1.5" secureJar = "3.0.9" forge-bus = "7.0-beta.10" @@ -45,6 +46,7 @@ neoforge-mergetool = { module = "net.neoforged:mergetool", version.ref = "neofor forge-fml = { module = "net.minecraftforge:javafmllanguage", version.ref = "forge" } forge-bus = { module = "net.minecraftforge:eventbus", version.ref = "forge-bus" } forge-loader = { module = "net.minecraftforge:fmlloader", version.ref = "forge" } +forge-unsafe = { module = "net.minecraftforge:unsafe", version.ref = "unsafe" } forge-core = { module = "net.minecraftforge:fmlcore", version.ref = "forge" } forge-mergetool = { module = "net.minecraftforge:mergetool-api", version.ref = "forge-mergetool" } forge-spi = { module = "net.minecraftforge:forgespi", version.ref = "forge-spi" } @@ -58,7 +60,7 @@ night-config = { module = "com.electronwill.night-config:core", version.ref = "n # Kotlin Reflect, Stdlib, Coroutines, Serialization JSON kotlin = ["kotlin-reflect", "kotlin-stdlib", "kotlin-stdlib-jdk7", "kotlin-stdlib-jdk8", "kotlinx-coroutines-core", "kotlinx-coroutines-core-jvm", "kotlinx-coroutines-jdk8", "kotlinx-serialization-core", "kotlinx-serialization-json"] neoforge = ["neoforge", "neoforge-bus", "neoforge-fml", "neoforge-mergetool"] -forge = ["forge-core", "forge-fml", "forge-loader", "forge-mergetool", "forge-spi", "forge-bus"] +forge = ["forge-core", "forge-fml", "forge-loader", "forge-mergetool", "forge-spi", "forge-bus", "forge-unsafe"] [plugins] ideaext = { id = "org.jetbrains.gradle.plugin.idea-ext", version.ref = "ideaext" } diff --git a/src/kfflang/forge/kotlin/thedarkcolour/common/KotlinMod.kt b/src/kfflang/forge/kotlin/thedarkcolour/common/KotlinMod.kt new file mode 100644 index 00000000..70a70540 --- /dev/null +++ b/src/kfflang/forge/kotlin/thedarkcolour/common/KotlinMod.kt @@ -0,0 +1,51 @@ +package thedarkcolour.common + +import net.minecraftforge.api.distmarker.Dist +import net.minecraftforge.eventbus.api.bus.BusGroup +import net.minecraftforge.fml.Bindings +import net.minecraftforge.fml.common.Mod +import thedarkcolour.kotlinforforge.KotlinModLoadingContext +import java.util.function.Supplier + +/** + * This defines a Kotlin Mod Class + * Any class found with this annotation applied will be loaded as a Mod. The instance that is loaded will + * represent the mod to other Mods in the system. It will be sent various subclasses of [ModLifecycleEvent] + * at pre-defined times during the loading of the game. + * @author Chidoziealways + * @since 6.0.0 + */ + +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.CLASS) +public annotation class KotlinMod(val value: String) { + + public annotation class KotlinEventBusSubscriber( + val value: Array = [Dist.CLIENT, Dist.DEDICATED_SERVER], + val modId: String = "", + val bus: KotlinBus = KotlinBus.BOTH + ) { + } +} + +public enum class KotlinBus(public val eventBusSupplier: Supplier) { + /** + * The main BusGroup that most game events are fired on. + */ + FORGE(Bindings.getForgeBus()), + + /** + * The Mod-Specific event BusGroup, usually for mod lifecycle events. + * @see KotlinModLoadingContext.getKBusGroup() + */ + MOD({ KotlinModLoadingContext.get().getKBusGroup() }), + + /** + * Both the [FORGE] and [MOD] buses. This is slower to register events in your class but + * allows you to listen to events from different BusGroup types without needing separate classes annotated + * with [thedarkcolour.common.KotlinMod.KotlinEventBusSubscriber]. + */ + BOTH({null}); + + public fun bus(): Supplier = eventBusSupplier +} diff --git a/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt b/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt index 49bec1e2..379a7eae 100644 --- a/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt +++ b/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt @@ -17,16 +17,22 @@ import net.minecraftforge.fml.event.IModBusEvent import net.minecraftforge.fml.loading.FMLEnvironment import net.minecraftforge.forgespi.language.ModFileScanData import net.minecraftforge.forgespi.language.ModFileScanData.EnumData +import net.minecraftforge.unsafe.UnsafeHacks import org.objectweb.asm.Type +import thedarkcolour.common.KotlinBus +import thedarkcolour.common.KotlinMod import java.lang.invoke.LambdaMetafactory import java.lang.invoke.MethodHandle import java.lang.invoke.MethodHandles import java.lang.invoke.MethodType import java.lang.reflect.Method import java.lang.reflect.Modifier -import java.util.* import java.util.function.Consumer import java.util.function.Predicate +import kotlin.reflect.KClass +import kotlin.reflect.KVisibility +import kotlin.reflect.full.declaredFunctions +import kotlin.reflect.full.findAnnotation /** * Automatically registers `object` classes to @@ -52,12 +58,12 @@ import java.util.function.Predicate */ public object AutoKotlinEventBusSubscriber { // EventBusSubscriber annotation - private val EVENT_BUS_SUBSCRIBER: Type = Type.getType(Mod.EventBusSubscriber::class.java) - private val MOD_TYPE: Type = Type.getType(Mod::class.java) + private val EVENT_BUS_SUBSCRIBER: Type = Type.getType(KotlinMod.KotlinEventBusSubscriber::class.java) + private val MOD_TYPE: Type = Type.getType(KotlinMod::class.java) private val ONLY_IN_TYPE: Type = Type.getType(OnlyIn::class.java) /** - * Allows the [Mod.EventBusSubscriber] annotation + * Allows the [KotlinMod.KotlinEventBusSubscriber] annotation * to target member functions of an `object` class. * * You **must** be using an `object` class, or the @@ -71,7 +77,7 @@ public object AutoKotlinEventBusSubscriber { * or [thedarkcolour.kotlinforforge.forge.MOD_BUS]. */ public fun inject(mod: KotlinModContainer, scanData: ModFileScanData, classLoader: ClassLoader) { - LOGGER.debug(Logging.LOADING, "Attempting to inject @EventBusSubscriber kotlin objects in to the event bus for ${mod.modId}") + LOGGER.debug(Logging.LOADING, "Attempting to inject @KotlinEventBusSubscriber kotlin objects in to the event bus for ${mod.modId}") val targets = scanData.annotations.filter { data -> EVENT_BUS_SUBSCRIBER == data.annotationType }.toList() @@ -95,7 +101,7 @@ public object AutoKotlinEventBusSubscriber { for (annotationData in targets) { if (!FMLEnvironment.production && onlyIns.contains(annotationData.clazz.className)) { - throw RuntimeException("Found @OnlyIn on @EventBusSubscriber class ${annotationData.clazz().className} - this is not allowed as it causes crashes. Remove the OnlyIn and set value=Dist.CLIENT in the EventBusSubscriber annotation instead") + throw RuntimeException("Found @OnlyIn on @KotlinEventBusSubscriber class ${annotationData.clazz().className} - this is not allowed as it causes crashes. Remove the OnlyIn and set value=Dist.CLIENT in the EventBusSubscriber annotation instead") } var modId = modids.getOrDefault(annotationData.clazz.className, mod.modId) @@ -107,23 +113,24 @@ public object AutoKotlinEventBusSubscriber { .map(Dist::valueOf) .toSet() val busName = value(annotationData, "bus", defaultBus).value() - val busTarget = Mod.EventBusSubscriber.Bus.valueOf(busName) + val busTarget = KotlinBus.valueOf(busName) if (mod.modId == modId && FMLEnvironment.dist in sides) { try { - LOGGER.debug(LOADING, "Auto-subscribing {} to {}", annotationData.clazz().getClassName(), busTarget) + LOGGER.debug(LOADING, "Auto-subscribing {} to {}", annotationData.clazz().className, busTarget) val clazz = Class.forName(annotationData.clazz.className, true, classLoader) + val instance = try { + val field = clazz.getDeclaredField("INSTANCE") + field.isAccessible = true + field.get(null) + } catch (e: Exception) { + LOGGER.warn(LOADING, "⚠️ Failed to get INSTANCE from ${clazz.name}: ${e.message}") + null + } - when (busTarget) { - Mod.EventBusSubscriber.Bus.MOD -> { - EventBusSubscriberLogic.register(KotlinModLoadingContext.get().getKBusGroup(), clazz) - } - Mod.EventBusSubscriber.Bus.FORGE -> { - EventBusSubscriberLogic.register(BusGroup.DEFAULT, clazz) - } - Mod.EventBusSubscriber.Bus.BOTH -> { - EventBusSubscriberLogic.register(KotlinModLoadingContext.get().getKBusGroup(), clazz) - EventBusSubscriberLogic.register(BusGroup.DEFAULT, clazz) - } + if (instance != null) { + EventBusSubscriberLogic.register(busTarget.bus().get(), instance) + } else { + LOGGER.warn(LOADING, "⚠️ Could not register ${clazz.name}, instance was null") } } catch (e: ClassNotFoundException){ LOGGER.fatal(LOADING, "Failed to load mod class {} for @EventBusSubscriber annotation", annotationData.clazz(), e) @@ -152,200 +159,236 @@ public object AutoKotlinEventBusSubscriber { private val MONITOR_FI_TYPE = MethodType.methodType(Void.TYPE, Any::class.java, Boolean::class.javaPrimitiveType) @JvmStatic - fun register(busGroup: BusGroup?, listenerClass: Class<*>) { + fun register(busGroup: BusGroup?, listenerClass: Any) { if (STRICT_REGISTRATION_CHECKS) registerStrict(busGroup, listenerClass) else registerLenient(busGroup, listenerClass) } - @JvmStatic - fun registerLenient(busGroup: BusGroup?, listenerClass: Class<*>) { - val declaredMethods = listenerClass.declaredMethods - if (declaredMethods.isEmpty()) throw IllegalArgumentException("No declared methods found in $listenerClass") - - var firstValidEvent: Class<*>? = null + fun registerLenient(busGroup: BusGroup?, listenerObject: Any) { + val kClass = listenerObject::class + val functions = kClass.declaredFunctions var listeners = 0 - for (method in declaredMethods) { - if (!Modifier.isStatic(method.modifiers) || method.isSynthetic) continue - if (method.parameterCount !in 1..2) continue - if (method.returnType != Void.TYPE && method.returnType != Boolean::class.javaPrimitiveType) continue - if (!method.isAnnotationPresent(SubscribeEvent::class.java)) continue - - val paramType = method.parameterTypes[0] - if (!Event::class.java.isAssignableFrom(paramType)) - throw IllegalArgumentException("First parameter must be an Event") - - val eventType = paramType as Class - val annotation = method.getAnnotation(SubscribeEvent::class.java) + LOGGER.debug("🔍 Inspecting Kotlin class: ${kClass.qualifiedName}") + for (function in functions) { + LOGGER.debug("→ Function: ${function.name}") - registerListenerGeneric(busGroup, method.parameterCount, method.returnType, eventType, annotation, method) - if (firstValidEvent == null) firstValidEvent = eventType - listeners++ - } + val annotation = function.findAnnotation() + if (annotation == null) { + LOGGER.debug(" ⛔ Skipped: No @SubscribeEvent") + continue + } else { + LOGGER.debug(" ✅ Found @SubscribeEvent") + } - if (listeners == 0) throw IllegalArgumentException("No valid listeners found in $listenerClass") - } + val params = function.parameters + LOGGER.debug(" 🧩 Params count: ${params.size}") + LOGGER.debug(" 🧩 Params: ${params.joinToString { it.type.toString() }}") + LOGGER.debug(" 🧩 Return type: ${function.returnType}") - @JvmStatic - fun registerStrict(busGroup: BusGroup?, listenerClass: Class<*>) { - val declaredMethods = listenerClass.declaredMethods.filterNot { it.isSynthetic } - if (declaredMethods.isEmpty()) { - var msg = "No declared methods found in ${listenerClass.name}" - listenerClass.superclass?.let { - if (it != Record::class.java && it != Enum::class.java) { - msg += ". Listener inheritance isn't supported. Use @Override + @SubscribeEvent on subclass." - } + if (params.size !in 2..3) { + LOGGER.warn(" ⛔ Skipped: Unexpected parameter count: ${params.size}") + continue } - throw fail(listenerClass, msg) - } - var firstValidEvent: Class? = null - var listeners = 0 + val classifier = params[1].type.classifier + val eventParamKClass = classifier as? KClass<*> + if (eventParamKClass == null) { + LOGGER.warn(" ⛔ Skipped: Param[1] classifier is not a KClass: $classifier") + continue + } - for (method in declaredMethods) { - val hasAnnotation = method.isAnnotationPresent(SubscribeEvent::class.java) - val paramCount = method.parameterCount - val paramTypes = method.parameterTypes + val eventParam = try { + eventParamKClass.java + } catch (e: Exception) { + LOGGER.warn(" ⛔ Skipped: Failed to get Java class for event param: ${e.message}") + continue + } - if (hasAnnotation && (paramCount == 0 || paramCount > 2)) - throw fail(method, "Invalid parameter count: $paramCount") + LOGGER.debug(" 📦 Event param class: ${eventParam.name}") - if (paramCount == 0) continue - if (!hasAnnotation && Event::class.java.isAssignableFrom(paramTypes[0])) - throw fail(method, "Missing @SubscribeEvent annotation") + if (!Event::class.java.isAssignableFrom(eventParam)) { + LOGGER.warn(" ⛔ Skipped: ${eventParam.name} is not a subtype of Event") + continue + } - if (hasAnnotation) { - val annotation = method.getAnnotation(SubscribeEvent::class.java) - val isMonitor = annotation.priority == Priority.MONITOR - val returnType = method.returnType + val cancellable = Cancellable::class.java.isAssignableFrom(eventParam) + val eventClass = eventParam as Class + val bus = busGroup ?: if (IModBusEvent::class.java.isAssignableFrom(eventClass)) + KotlinModLoadingContext.get().getKBusGroup() + else BusGroup.DEFAULT - if (!Modifier.isStatic(method.modifiers)) throw fail(method, "Listener must be static") - if (!Event::class.java.isAssignableFrom(paramTypes[0])) throw fail(method, "First param must be Event") + val priority = annotation.priority - val eventType = paramTypes[0] as Class - val cancellable = Cancellable::class.java.isAssignableFrom(eventType) + try { + when (params.size) { + 2 -> { + when (function.returnType.classifier) { + Unit::class -> { + if (cancellable) { + val busC = CancellableEventBus.create(bus, castToCancellableEvent(eventClass)) + if (annotation.alwaysCancelling) + busC.addListener(priority, true) { function.call(listenerObject, it) } + else + busC.addListener(priority, Consumer { function.call(listenerObject, it) }) + } else { + EventBus.create(bus, eventClass) + .addListener(priority) { function.call(listenerObject, it) } + } + } + + Boolean::class -> { + if (!cancellable) error("Boolean return type only valid on cancellable events") + if (annotation.alwaysCancelling) error("Can't use alwaysCancelling with boolean return") + + val busC = CancellableEventBus.create(bus, castToCancellableEvent(eventClass)) + busC.addListener(priority, Predicate { + function.call(listenerObject, it) as Boolean + }) + } + + else -> { + LOGGER.warn(" ⛔ Skipped: Unsupported return type: ${function.returnType}") + return + } + } + } - if (returnType != Void.TYPE && returnType != Boolean::class.javaPrimitiveType) - throw fail(method, "Invalid return type: ${returnType.name}") + 3 -> { + if (!cancellable) error("Two params only valid for cancellable events") + if (function.returnType.classifier != Unit::class) error("Third param requires void return") + if (params[2].type.classifier != Boolean::class) error("Third param must be boolean") + if (annotation.priority != Priority.MONITOR) error("Third param only allowed on MONITOR") - if (paramCount == 2) { - if (!cancellable) throw fail(method, "Second param only valid for cancellable events") - if (paramTypes[1] != Boolean::class.javaPrimitiveType) throw fail(method, "Second param must be boolean") - if (!isMonitor) throw fail(method, "Second param only valid for MONITOR priority") - } + val busC = CancellableEventBus.create(bus, castToCancellableEvent(eventClass)) + busC.addListener(ObjBooleanBiConsumer { event, cancelled -> + function.call(listenerObject, event, cancelled) + }) + } - if (!cancellable) { - if (annotation.alwaysCancelling) - throw fail(method, "alwaysCancelling only valid on cancellable events") - if (returnType == Boolean::class.javaPrimitiveType) - throw fail(method, "boolean return type only valid on cancellable events") + else -> { + LOGGER.warn(" ⛔ Skipped: Invalid param count ${params.size}") + return + } } - registerListenerGeneric(busGroup, paramCount, returnType, eventType, annotation, method) - if (firstValidEvent == null) firstValidEvent = eventType + LOGGER.info("✅ Registered: ${function.name} for ${eventClass.simpleName}") listeners++ + + } catch (e: Exception) { + LOGGER.error("💥 Error registering function ${function.name}: ${e.message}", e) } } - if (listeners == 0) throw fail(listenerClass, "No valid listeners found") + if (listeners == 0) { + LOGGER.error("❌ No valid listeners found in ${kClass.qualifiedName}") + error("❌ No valid listeners found in ${kClass.qualifiedName}") + } } - @Suppress("UNCHECKED_CAST") - fun castToCancellableEvent(eventType: Class<*>): Class - where T : Event, T : Cancellable { - return eventType as Class - } - @Suppress("UNCHECKED_CAST") - private fun registerListenerGeneric( - busGroup: BusGroup?, - paramCount: Int, - returnType: Class<*>, - eventType: Class, - annotation: SubscribeEvent, - method: Method - ) { - val bus = busGroup ?: if (IModBusEvent::class.java.isAssignableFrom(eventType)) - KotlinModLoadingContext.get().getKBusGroup() - else BusGroup.DEFAULT - - val listener: EventListener = when (paramCount) { - 1 -> { - val priority = annotation.priority - if (returnType == Void.TYPE) { - if (Cancellable::class.java.isAssignableFrom(eventType)) { - val busC = CancellableEventBus.create( - bus, - castToCancellableEvent(eventType) - ) - if (annotation.alwaysCancelling) - busC.addListener(priority, true, createConsumer(method)) - else - busC.addListener(priority, createConsumer(method)) - } else { - EventBus.create(bus, eventType).addListener(priority, createConsumer(method)) - } - } else { - if (annotation.alwaysCancelling) - throw fail(method, "Listeners returning boolean can't be alwaysCancelling") - if (!Cancellable::class.java.isAssignableFrom(eventType)) - throw fail(method, "boolean return type only valid for cancellable events") - - CancellableEventBus.create(bus, - castToCancellableEvent(eventType) - ) - .addListener(priority, createPredicate(method)) - } - } + fun registerStrict(busGroup: BusGroup?, listenerObject: Any) { + val kClass = listenerObject::class + val functions = kClass.declaredFunctions.filter { fn -> + // Filter out compiler-generated/internal stuff + fn.name !in setOf("equals", "hashCode", "toString") && + !fn.name.contains("\$") && // filters accessors/lambdas/inline$ methods + fn.visibility == KVisibility.PUBLIC + } - else -> { - if (returnType != Void.TYPE) throw fail(method, "Cancellation-monitoring listeners must return void") - if (annotation.alwaysCancelling) throw fail(method, "MONITOR listeners cannot cancel") - CancellableEventBus.create( - bus, - castToCancellableEvent(eventType) - ) - .addListener(createMonitor(method)) + if (functions.isEmpty()) { + val superClass = listenerObject::class.java.superclass + var msg = "No declared methods found in ${kClass.qualifiedName}" + if (superClass != null && superClass != Record::class.java && superClass != Enum::class.java) { + msg += ". Listener inheritance isn't supported. Use @Override + @SubscribeEvent on subclass." } + error(msg) } - // Optionally store listener if needed - } + var listeners = 0 - private fun fail(clazz: Class<*>, reason: String): IllegalArgumentException = - IllegalArgumentException("Failed to register ${clazz.name}: $reason") + for (func in functions) { + val annotation = func.findAnnotation() ?: continue + val params = func.parameters + val returnType = func.returnType + val isMonitor = annotation.priority == Priority.MONITOR - private fun fail(method: Method, reason: String): IllegalArgumentException = - IllegalArgumentException("Failed to register ${method.declaringClass.name}.${method.name}: $reason") + // expect instance + 1 or 2 parameters (event, [boolean]) + if (params.size !in 2..3) { + error("Invalid parameter count on ${func.name}: ${params.size - 1}") + } - @Suppress("UNCHECKED_CAST") - private fun createConsumer(method: Method): Consumer = - makeFactory(method, RETURNS_CONSUMER, CONSUMER_FI_TYPE, "accept").invokeExact() as Consumer + val eventParamClass = params[1].type.classifier as? Class<*> ?: continue + if (!Event::class.java.isAssignableFrom(eventParamClass)) { + error("First parameter of ${func.name} must be Event") + } - @Suppress("UNCHECKED_CAST") - private fun createPredicate(method: Method): Predicate = - makeFactory(method, RETURNS_PREDICATE, PREDICATE_FI_TYPE, "test").invokeExact() as Predicate + val cancellable = Cancellable::class.java.isAssignableFrom(eventParamClass) + val eventClass = eventParamClass as Class + val bus = busGroup ?: if (net.minecraftforge.fml.event.IModBusEvent::class.java.isAssignableFrom(eventClass)) + KotlinModLoadingContext.get().getKBusGroup() + else BusGroup.DEFAULT + + // Validate return type + if (returnType.classifier !in listOf(Void.TYPE.kotlin, Boolean::class)) { + error("Invalid return type on ${func.name}: $returnType") + } + + // Two-parameter (event, cancelled) validation + if (params.size == 3) { + if (!cancellable) error("Second param only valid for cancellable events in ${func.name}") + if (params[2].type.classifier != Boolean::class) error("Second param must be Boolean in ${func.name}") + if (!isMonitor) error("Second param only valid for MONITOR priority in ${func.name}") + if (returnType.classifier != Void.TYPE.kotlin) error("Cancellation-monitoring listener must return void in ${func.name}") + } + + // Cancel checks + if (!cancellable) { + if (annotation.alwaysCancelling) error("alwaysCancelling only valid on cancellable events in ${func.name}") + if (returnType.classifier == Boolean::class) error("boolean return type only valid on cancellable events in ${func.name}") + } + + // REGISTER + when (params.size) { + 2 -> { + val priority = annotation.priority + if (returnType.classifier == Void.TYPE.kotlin) { + if (cancellable) { + val busC = CancellableEventBus.create(bus, castToCancellableEvent(eventClass)) + if (annotation.alwaysCancelling) + busC.addListener(priority, true, Consumer { func.call(listenerObject, it) }) + else + busC.addListener(priority, Consumer { func.call(listenerObject, it) }) + } else { + EventBus.create(bus, eventClass).addListener(priority, Consumer { func.call(listenerObject, it) }) + } + } else { + if (!cancellable) error("Boolean return type only valid on cancellable events in ${func.name}") + if (annotation.alwaysCancelling) error("Cannot combine boolean return type with alwaysCancelling in ${func.name}") + val busC = CancellableEventBus.create(bus, castToCancellableEvent(eventClass)) + busC.addListener(priority, Predicate { func.call(listenerObject, it) as Boolean }) + } + } + + 3 -> { + val busC = CancellableEventBus.create(bus, castToCancellableEvent(eventClass)) + busC.addListener(ObjBooleanBiConsumer { event, cancelled -> + func.call(listenerObject, event, cancelled) + }) + } + } + + println("✅ Strictly registered: ${func.name} for ${eventClass.simpleName}") + listeners++ + } + + if (listeners == 0) error("❌ No valid listeners found in ${kClass.qualifiedName}") + } @Suppress("UNCHECKED_CAST") - private fun createMonitor(method: Method): ObjBooleanBiConsumer = - makeFactory(method, RETURNS_MONITOR, MONITOR_FI_TYPE, "accept").invokeExact() as ObjBooleanBiConsumer - - private fun makeFactory( - method: Method, - factoryReturnType: MethodType, - fiMethodType: MethodType, - fiMethodName: String - ): MethodHandle { - val lookup = run { - val field = MethodHandles.Lookup::class.java.getDeclaredField("IMPL_LOOKUP") - UnsafeHacks.setAccessible(field) - field.get(null) as MethodHandles.Lookup - }.`in`(method.declaringClass) - - val target = lookup.unreflect(method) - return LambdaMetafactory.metafactory( - lookup, fiMethodName, factoryReturnType, fiMethodType, target, target.type() - ).target + fun castToCancellableEvent(eventType: Class<*>): Class + where T : Event, T : Cancellable { + return eventType as Class } } } diff --git a/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/KotlinLanguageProvider.kt b/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/KotlinLanguageProvider.kt index a8d8578b..87091a63 100644 --- a/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/KotlinLanguageProvider.kt +++ b/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/KotlinLanguageProvider.kt @@ -21,9 +21,17 @@ public class KotlinLanguageProvider : IModLanguageProvider { override fun getFileVisitor(): Consumer { return Consumer { scanData -> + LOGGER.warn(">>> KotlinLanguageProvider scanning file: ${scanData.annotations.size} annotations total") + for (annotation in scanData.annotations) { + LOGGER.warn("📛 Annotation seen: ${annotation.annotationType} on ${annotation.clazz.className}") + if (annotation.annotationType.className.contains("KotlinMod")) { + LOGGER.error("⚠️ POTENTIAL MATCH: ${annotation.annotationType.className}") + } + } scanData.addLanguageLoader(scanData.annotations.filter { data -> data.annotationType == MOD_ANNOTATION }.associate { data -> + LOGGER.debug(Logging.SCAN, "Found annotations: ${scanData.annotations.map { it.annotationType }}") val modid = data.annotationData["value"] as String val modClass = data.clazz.className @@ -78,6 +86,6 @@ public class KotlinLanguageProvider : IModLanguageProvider { } private companion object { - private val MOD_ANNOTATION = Type.getType("Lnet/minecraftforge/fml/common/Mod;") + private val MOD_ANNOTATION = Type.getType("Lthedarkcolour/common/KotlinMod;") } } diff --git a/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt b/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt index 99d4cb57..8f17db00 100644 --- a/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt +++ b/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt @@ -2,15 +2,19 @@ package thedarkcolour.kotlinforforge import cpw.mods.jarhandling.SecureJar import net.minecraftforge.eventbus.api.bus.BusGroup +import net.minecraftforge.eventbus.api.bus.EventBus +import net.minecraftforge.eventbus.api.event.InheritableEvent import net.minecraftforge.eventbus.internal.Event import net.minecraftforge.fml.Logging import net.minecraftforge.fml.ModContainer import net.minecraftforge.fml.ModLoadingException import net.minecraftforge.fml.ModLoadingStage +import net.minecraftforge.fml.config.IConfigEvent import net.minecraftforge.fml.event.IModBusEvent -import net.minecraftforge.fml.javafmlmod.FMLModContainer import net.minecraftforge.forgespi.language.IModInfo import net.minecraftforge.forgespi.language.ModFileScanData +import net.minecraftforge.unsafe.UnsafeHacks +import java.lang.reflect.Constructor import java.lang.reflect.Method import java.util.function.Supplier import java.util.jar.Attributes @@ -37,11 +41,11 @@ public class KotlinModContainer( contextExtension = Supplier {ctx} try { - val moduleName = info.getOwningFile().moduleName() - val layer = gameLayer.findModule(moduleName).orElseThrow() + val moduleName = info.owningFile.moduleName() + val layer = gameLayer.findModule(moduleName).orElseThrow {IllegalStateException("Failed to find $moduleName in $gameLayer")} openModules(gameLayer, layer, info.owningFile.file.secureJar) modClass = Class.forName(layer, className) - LOGGER.trace(Logging.LOADING, "Loaded modclass {} with {}", modClass.name, modClass.classLoader) + LOGGER.trace(Logging.LOADING, "Loaded modclass {}/{} with {}", modClass.module.name, modClass.name, modClass.classLoader) } catch (t: Throwable) { LOGGER.error(Logging.LOADING, "Failed to load class $className", t) throw ModLoadingException(info, ModLoadingStage.CONSTRUCT, "fml.modloading.failedtoloadmodclass", t) @@ -50,15 +54,6 @@ public class KotlinModContainer( // Sets modInstance to a new instance of the mod class or the object instance private fun constructMod() { - try { - LOGGER.trace(Logging.LOADING, "Loading mod instance ${getModId()} of type ${modClass.name}") - modInstance = modClass.kotlin.objectInstance ?: modClass.getDeclaredConstructor().newInstance() - LOGGER.trace(Logging.LOADING, "Loaded mod instance ${getModId()} of type ${modClass.name}") - } catch (throwable: Throwable) { - LOGGER.error(Logging.LOADING, "Failed to create mod instance. ModID: ${getModId()}, class ${modClass.name}", throwable) - throw ModLoadingException(modInfo, ModLoadingStage.CONSTRUCT, "fml.modloading.failedtoloadmod", throwable, modClass) - } - try { LOGGER.trace(Logging.LOADING, "Injecting Automatic Kotlin event subscribers for ${getModId()}") // Inject into object EventBusSubscribers @@ -68,6 +63,15 @@ public class KotlinModContainer( LOGGER.error(Logging.LOADING, "Failed to register Automatic Kotlin subscribers. ModID: ${getModId()}, class ${modClass.name}", throwable) throw ModLoadingException(modInfo, ModLoadingStage.CONSTRUCT, "fml.modloading.failedtoloadmod", throwable, modClass) } + + try { + LOGGER.trace(Logging.LOADING, "Loading mod instance ${getModId()} of type ${modClass.name}") + modInstance = modClass.kotlin.objectInstance ?: modClass.getDeclaredConstructor().newInstance() + LOGGER.trace(Logging.LOADING, "Loaded mod instance ${getModId()} of type ${modClass.name}") + } catch (throwable: Throwable) { + LOGGER.error(Logging.LOADING, "Failed to create mod instance. ModID: ${getModId()}, class ${modClass.name}", throwable) + throw ModLoadingException(modInfo, ModLoadingStage.CONSTRUCT, "fml.modloading.failedtoloadmod", throwable, modClass) + } } override fun matches(mod: Any?): Boolean { @@ -76,11 +80,23 @@ public class KotlinModContainer( override fun getMod(): Any? = modInstance - fun getModBusGroup(): BusGroup { + public fun getModBusGroup(): BusGroup { return busGroup } - public fun acceptEvent(e: T) where T : Event, T : IModBusEvent { + override fun toString(): String { + val name = modInfo.modId + return "FMLModContainer[$name, ${javaClass.name}]" + } + + override fun dispatchConfigEvent(event: IConfigEvent) { + @Suppress("UNCHECKED_CAST") + val self = event.self() as InheritableEvent + val eventBus = EventBus.create( busGroup, self.javaClass) + eventBus.post(self) + } + + override fun acceptEvent(e: T){ try { LOGGER.trace("Firing event for modid $modId : $e") var eventBus = IModBusEvent.getBus(busGroup, e.javaClass) @@ -108,6 +124,8 @@ public class KotlinModContainer( if (target != null && target.descriptor.packages().contains(pts[1])) { addOpenOrExports(target, pts[1], self, open) } + } else { + LOGGER.warn(LOADING, "Invalid {} entry in {}: {}", key, self.name, pair) } } } @@ -122,9 +140,9 @@ public class KotlinModContainer( LOADING, "{} {}/{} to {}", if (open) "Opening" else "Exporting", - target.getName(), + target.name, pkg, - reader.getName() + reader.name ) implAddExportsOrOpens?.invoke(target, pkg, reader, open, true) } diff --git a/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/UnsafeFieldAccess.kt b/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/UnsafeFieldAccess.kt deleted file mode 100644 index 432e38bb..00000000 --- a/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/UnsafeFieldAccess.kt +++ /dev/null @@ -1,39 +0,0 @@ -@file:Suppress("UNCHECKED_CAST", "MemberVisibilityCanBePrivate", "unused") - -package thedarkcolour.kotlinforforge - -public open class UnsafeFieldAccess(protected val index: Long) { - public open fun get(instance: I): Type { - return UnsafeHacks.theUnsafe().getObject(instance, index) as Type - } - - public open fun set(instance: I, value: Type) { - UnsafeHacks.theUnsafe().putObject(instance, index, value) - } - - public class Int(index: Long) : UnsafeFieldAccess(index) { - override fun get(instance: I): kotlin.Int = getInt(instance) - override fun set(instance: I, value: kotlin.Int): Unit = setInt(instance, value) - - public fun getInt(instance: I): kotlin.Int { - return UnsafeHacks.theUnsafe().getInt(instance, index) - } - - public fun setInt(instance: I, value: kotlin.Int) { - UnsafeHacks.theUnsafe().putInt(instance, index, value) - } - } - - public class Bool(index: Long) : UnsafeFieldAccess(index) { - override fun get(instance: I): Boolean = getBoolean(instance) - override fun set(instance: I, value: Boolean): Unit = setBoolean(instance, value) - - public fun getBoolean(instance: I): Boolean { - return UnsafeHacks.theUnsafe().getBoolean(instance, index) - } - - public fun setBoolean(instance: I, value: Boolean) { - UnsafeHacks.theUnsafe().putBoolean(instance, index, value) - } - } -} diff --git a/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/UnsafeHacks.kt b/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/UnsafeHacks.kt deleted file mode 100644 index 70c19e38..00000000 --- a/src/kfflang/forge/kotlin/thedarkcolour/kotlinforforge/UnsafeHacks.kt +++ /dev/null @@ -1,112 +0,0 @@ -@file:Suppress("UNCHECKED_CAST", "MemberVisibilityCanBePrivate", "unused") - -package thedarkcolour.kotlinforforge - -import sun.misc.Unsafe -import java.lang.reflect.AccessibleObject -import java.lang.reflect.Field -import java.util.function.Consumer - -public object UnsafeHacks { - private val UNSAFE: Unsafe = getUnsafe() - private val module: UnsafeFieldAccess, Any>? = findField(Class::class.java, "module") - private val SETACCESSIBLE: Consumer = getSetAccessible() - - public fun newInstance(clazz: Class): T { - return try { - UNSAFE.allocateInstance(clazz) as T - } catch (e: InstantiationException) { - sneak(e) - } - } - - public fun getField(field: Field, instance: Any): T { - return UNSAFE.getObject(instance, UNSAFE.objectFieldOffset(field)) as T - } - - public fun setField(field: Field, instance: Any, value: Any) { - UNSAFE.putObject(instance, UNSAFE.objectFieldOffset(field), value) - } - - fun getIntField(field: Field, instance: Any): Int { - return UNSAFE.getInt(instance, UNSAFE.objectFieldOffset(field)) - } - - fun setIntField(field: Field, instance: Any, value: Int) { - UNSAFE.putInt(instance, UNSAFE.objectFieldOffset(field), value) - } - - public fun findField(clazz: Class, name: String): UnsafeFieldAccess? { - return clazz.declaredFields - .firstOrNull { it.name == name } - ?.let { UnsafeFieldAccess(UNSAFE.objectFieldOffset(it)) } - } - - public fun findIntField(clazz: Class, name: String): UnsafeFieldAccess.Int? { - return clazz.declaredFields - .firstOrNull { it.name == name } - ?.let { UnsafeFieldAccess.Int(UNSAFE.objectFieldOffset(it)) } - } - - public fun findBooleanField(clazz: Class, name: String): UnsafeFieldAccess.Bool? { - return clazz.declaredFields - .firstOrNull { it.name == name } - ?.let { UnsafeFieldAccess.Bool(UNSAFE.objectFieldOffset(it)) } - } - - public fun setAccessible(target: AccessibleObject) { - SETACCESSIBLE.accept(target) - } - - public fun theUnsafe(): Unsafe = UNSAFE - - private fun getUnsafe(): Unsafe { - return try { - val field = Unsafe::class.java.getDeclaredField("theUnsafe") - field.isAccessible = true - field.get(null) as Unsafe - } catch (e: Exception) { - sneak(e) - } - } - - private fun getSetAccessible(): Consumer { - return try { - val method = try { - AccessibleObject::class.java.getDeclaredMethod("setAccessible0", Boolean::class.javaPrimitiveType) - } catch (e: NoSuchMethodException) { - AccessibleObject::class.java.getDeclaredMethod("setAccessible0", AccessibleObject::class.java, Boolean::class.javaPrimitiveType) - } - - setAccessibleFallback(method) - - Consumer { acc -> - try { - method.invoke(acc, true) - } catch (e: Exception) { - sneak(e) - } - } - } catch (e: Exception) { - sneak(e) - } - } - - private fun setAccessibleFallback(obj: AccessibleObject) { - if (module != null) { - val old = module.get(UnsafeHacks::class.java) - val base = module.get(Any::class.java) - module.set(UnsafeHacks::class.java, base) - - obj.isAccessible = true - - module.set(UnsafeHacks::class.java, old) - } else { - obj.isAccessible = true - } - } - - public inline fun sneak(e: Throwable): R { - throw e as E - } -} diff --git a/src/kffmod/forge/kotlin/thedarkcolour/kotlinforforge/test/KotlinForForge.kt b/src/kffmod/forge/kotlin/thedarkcolour/kotlinforforge/test/KotlinForForge.kt index ca3ea6d6..8623fc00 100644 --- a/src/kffmod/forge/kotlin/thedarkcolour/kotlinforforge/test/KotlinForForge.kt +++ b/src/kffmod/forge/kotlin/thedarkcolour/kotlinforforge/test/KotlinForForge.kt @@ -1,10 +1,12 @@ package thedarkcolour.kotlinforforge.test -import net.minecraftforge.fml.common.Mod import org.apache.logging.log4j.LogManager +import thedarkcolour.common.KotlinMod +import thedarkcolour.kotlinforforge.KotlinModLoadingContext +import java.lang.annotation.ElementType -@Mod("kotlinforforge") -public object KotlinForForge { +@KotlinMod("kotlinforforge") +public class KotlinForForge(context: KotlinModLoadingContext) { private val LOGGER = LogManager.getLogger() init { LOGGER.info("Kotlin For Forge Enabled!")