diff options
Diffstat (limited to 'src')
9 files changed, 362 insertions, 0 deletions
diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt b/src/main/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt new file mode 100644 index 0000000..b77a567 --- /dev/null +++ b/src/main/kotlin/thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber.kt @@ -0,0 +1,90 @@ +package thedarkcolour.kotlinforforge + +import net.minecraftforge.api.distmarker.Dist +import net.minecraftforge.fml.Logging +import net.minecraftforge.fml.ModContainer +import net.minecraftforge.fml.common.Mod +import net.minecraftforge.fml.loading.FMLEnvironment +import net.minecraftforge.fml.loading.moddiscovery.ModAnnotation +import net.minecraftforge.forgespi.language.ModFileScanData +import org.objectweb.asm.Type +import thedarkcolour.kotlinforforge.KotlinForForge.logger +import java.util.* +import java.util.stream.Collectors +import kotlin.reflect.full.companionObjectInstance + +/** + * Handles [net.minecraftforge.fml.common.Mod.EventBusSubscriber] + */ +@Suppress("unused") +object AutoKotlinEventBusSubscriber { + private val EVENT_BUS_SUBSCRIBER: Type = Type.getType(Mod.EventBusSubscriber::class.java) + + /** + * Registers Kotlin objects that are annotated with [Mod.EventBusSubscriber] + * This allows you to declare an object that subscribes to the event bus + * without making all the [net.minecraftforge.eventbus.api.SubscribeEvent] annotated with [JvmStatic] + * + * Example Usage: + * + * @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD) + * public object ExampleSubscriber { + * @SubscribeEvent + * public fun onItemRegistry(event: RegistryEvent.Register<Item>) { + * println("Look! We're in items :)") + * } + * } + * + * This also works with companion objects. + * You must define the [net.minecraftforge.eventbus.api.SubscribeEvent] methods inside the companion object. + * Example Usage: + * + * @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD) + * public class ExampleSubscriberClass { + * + * companion object ExampleSubscriberCompanion { + * @SubscribeEvent + * public fun onItemRegistry(event: RegistryEvent.Register<Item>) { + * println("Look! We're in items :)") + * } + * } + * } + */ + @Suppress("UNCHECKED_CAST", "unused") + fun inject(mod: ModContainer, scanData: ModFileScanData, classLoader: ClassLoader) { + logger.debug(Logging.LOADING, "Attempting to inject @EventBusSubscriber kotlin objects in to the event bus for {}", mod.modId) + val data: ArrayList<ModFileScanData.AnnotationData> = scanData.annotations.stream() + .filter { annotationData -> + EVENT_BUS_SUBSCRIBER == annotationData.annotationType + } + .collect(Collectors.toList()) as ArrayList<ModFileScanData.AnnotationData> + data.forEach { annotationData -> + val sidesValue: List<ModAnnotation.EnumHolder> = annotationData.annotationData.getOrDefault("value", listOf(ModAnnotation.EnumHolder(null, "CLIENT"), ModAnnotation.EnumHolder(null, "DEDICATED_SERVER"))) as List<ModAnnotation.EnumHolder> + val sides: EnumSet<Dist> = sidesValue.stream().map { eh -> Dist.valueOf(eh.value) } + .collect(Collectors.toCollection { EnumSet.noneOf(Dist::class.java) }) + val modid = annotationData.annotationData.getOrDefault("modid", mod.modId) + val busTargetHolder: ModAnnotation.EnumHolder = annotationData.annotationData.getOrDefault("bus", ModAnnotation.EnumHolder(null, "FORGE")) as ModAnnotation.EnumHolder + val busTarget = Mod.EventBusSubscriber.Bus.valueOf(busTargetHolder.value) + val clazz = Class.forName(annotationData.classType.className, true, classLoader) + val ktClass = clazz.kotlin + val ktObject = ktClass.objectInstance + if (ktObject != null && mod.modId == modid && sides.contains(FMLEnvironment.dist)) { + try { + logger.debug(Logging.LOADING, "Auto-subscribing kotlin object {} to {}", annotationData.classType.className, busTarget) + busTarget.bus().get().register(ktObject) + } catch (e: Throwable) { + logger.fatal(Logging.LOADING, "Failed to load mod class {} for @EventBusSubscriber annotation", annotationData.classType, e) + throw RuntimeException(e) + } + } else if (ktClass.companionObjectInstance != null) { + try { + logger.debug(Logging.LOADING, "Auto-subscribing kotlin companion object from {} to {}", ktClass.simpleName, busTarget) + busTarget.bus().get().register(ktClass.companionObjectInstance) + } catch (e: Throwable) { + logger.fatal(Logging.LOADING, "Failed to load kotlin companion object {} for @EventBusSubscriber annotation", annotationData.classType, e) + throw RuntimeException(e) + } + } + } + } +}
\ No newline at end of file diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinForForge.kt b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinForForge.kt new file mode 100644 index 0000000..e697f10 --- /dev/null +++ b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinForForge.kt @@ -0,0 +1,13 @@ +package thedarkcolour.kotlinforforge + +import net.minecraftforge.fml.common.Mod +import org.apache.logging.log4j.LogManager +import org.apache.logging.log4j.Logger + +/** + * Set 'modLoader' in mods.toml to "kotlinforforge". + */ +@Mod("kotlinforforge") +object KotlinForForge { + internal val logger: Logger = LogManager.getLogger() +}
\ No newline at end of file diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinLanguageProvider.kt b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinLanguageProvider.kt new file mode 100644 index 0000000..e860497 --- /dev/null +++ b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinLanguageProvider.kt @@ -0,0 +1,42 @@ +package thedarkcolour.kotlinforforge + +import net.minecraftforge.fml.Logging +import net.minecraftforge.fml.javafmlmod.FMLJavaModLanguageProvider +import net.minecraftforge.forgespi.language.ILifecycleEvent +import net.minecraftforge.forgespi.language.IModInfo +import net.minecraftforge.forgespi.language.IModLanguageProvider +import net.minecraftforge.forgespi.language.ModFileScanData +import java.util.function.BinaryOperator +import java.util.function.Consumer +import java.util.function.Function +import java.util.function.Supplier +import java.util.stream.Collectors + +class KotlinLanguageProvider : FMLJavaModLanguageProvider() { + override fun getFileVisitor(): Consumer<ModFileScanData> { + return Consumer { scanResult -> + val target = scanResult.annotations.stream() + .filter {data -> data.annotationType == MODANNOTATION } + .peek { data -> KotlinForForge.logger.debug(Logging.SCAN, "Found @Mod class {} with id {}", data.classType.className, data.annotationData["value"]) } + .map { data -> KotlinModTarget(data.classType.className, data.annotationData["value"] as String) } + .collect(Collectors.toMap<KotlinModTarget, String, KotlinModTarget>(Function { target: KotlinModTarget -> return@Function target.modId }, Function {return@Function it}, BinaryOperator { a, _ -> a })) + scanResult.addLanguageLoader(target) + } + } + + override fun <R : ILifecycleEvent<R>?> consumeLifecycleEvent(consumeEvent: Supplier<R>?) {} + + override fun name(): String { + return "kotlinforforge" + } + + internal class KotlinModTarget internal constructor(private val className: String, val modId: String) : IModLanguageProvider.IModLanguageLoader { + @Suppress("UNCHECKED_CAST") + override fun <T> loadMod(info: IModInfo, modClassLoader: ClassLoader, modFileScanResults: ModFileScanData): T { + val ktContainer = Class.forName("thedarkcolour.kotlinforforge.KotlinModContainer", true, Thread.currentThread().contextClassLoader) + KotlinForForge.logger.debug(Logging.LOADING, "Loading KotlinModContainer from classloader {} - got {}", Thread.currentThread().contextClassLoader, ktContainer.classLoader) + val constructor = ktContainer.getConstructor(IModInfo::class.java, String::class.java, ClassLoader::class.java, ModFileScanData::class.java)!! + return constructor.newInstance(info, className, modClassLoader, modFileScanResults) as T + } + } +}
\ No newline at end of file diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt new file mode 100644 index 0000000..26a65ea --- /dev/null +++ b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModContainer.kt @@ -0,0 +1,103 @@ +package thedarkcolour.kotlinforforge + +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.fml.* +import net.minecraftforge.forgespi.language.IModInfo +import net.minecraftforge.forgespi.language.ModFileScanData +import org.apache.logging.log4j.LogManager +import java.util.* +import java.util.function.Consumer +import java.util.function.Supplier + +@Suppress("UNUSED_PARAMETER") +class KotlinModContainer(info: IModInfo, className: String, loader: ClassLoader, private val scanData: ModFileScanData) : ModContainer(info) { + private lateinit var modInstance: Any + private val modClass: Class<*> + val eventBus: IEventBus + + // Use a separate logger because KotlinForForge.logger isn't initialized yet + private val logger = LogManager.getLogger() + + init { + logger.debug(Logging.LOADING, "Creating KotlinModContainer instance for {} with classLoader {} & {}", className, loader, javaClass.classLoader) + triggerMap[ModLoadingStage.CONSTRUCT] = dummy().andThen { beforeEvent(it) }.andThen { constructMod(it) }.andThen{ afterEvent(it) } + triggerMap[ModLoadingStage.CREATE_REGISTRIES] = dummy().andThen { beforeEvent(it) }.andThen { fireEvent(it) }.andThen{ afterEvent(it) } + triggerMap[ModLoadingStage.LOAD_REGISTRIES] = dummy().andThen { beforeEvent(it) }.andThen { fireEvent(it) }.andThen{ afterEvent(it) } + triggerMap[ModLoadingStage.COMMON_SETUP] = dummy().andThen { beforeEvent(it) }.andThen { preinitMod(it) }.andThen{ fireEvent(it) }.andThen { this.afterEvent(it) } + triggerMap[ModLoadingStage.SIDED_SETUP] = dummy().andThen { beforeEvent(it)}.andThen { fireEvent(it) }.andThen { afterEvent(it) } + triggerMap[ModLoadingStage.ENQUEUE_IMC] = dummy().andThen { beforeEvent(it)}.andThen { initMod(it) }.andThen{ fireEvent(it) }.andThen{ this.afterEvent(it) } + triggerMap[ModLoadingStage.PROCESS_IMC] = dummy().andThen { beforeEvent(it) }.andThen { fireEvent(it) }.andThen{ afterEvent(it) } + triggerMap[ModLoadingStage.COMPLETE] = dummy().andThen { beforeEvent(it) }.andThen { completeLoading(it) }.andThen{ fireEvent(it) }.andThen { this.afterEvent(it) } + triggerMap[ModLoadingStage.GATHERDATA] = dummy().andThen { beforeEvent(it) }.andThen { fireEvent(it) }.andThen{ afterEvent(it) } + eventBus = BusBuilder.builder().setExceptionHandler{ bus, event, listeners, index, throwable -> onEventFailed(bus, event, listeners, index, throwable) }.setTrackPhases(false).build() + configHandler = Optional.of(Consumer {event -> eventBus.post(event)}) + val ctx = KotlinModLoadingContext(this) + contextExtension = Supplier { return@Supplier ctx} + try { + modClass = Class.forName(className, true, loader) + logger.debug(Logging.LOADING, "Loaded kotlin modclass {} with {}", modClass.name, modClass.classLoader) + } catch (e: Throwable) { + logger.error(Logging.LOADING, "Failed to load kotlin class {}", className, e) + throw ModLoadingException(info, ModLoadingStage.CONSTRUCT, "fml.modloading.failedtoloadmodclass", e) + } + } + + private fun completeLoading(lifecycleEvent: LifecycleEventProvider.LifecycleEvent) {} + private fun initMod(lifecycleEvent: LifecycleEventProvider.LifecycleEvent) {} + private fun dummy(): Consumer<LifecycleEventProvider.LifecycleEvent> = Consumer {} + private fun onEventFailed(iEventBus: IEventBus, event: Event, iEventListeners: Array<IEventListener>, i: Int, throwable: Throwable) = logger.error(EventBusErrorMessage(event, i, iEventListeners, throwable)) + private fun beforeEvent(lifecycleEvent: LifecycleEventProvider.LifecycleEvent) {} + + private fun fireEvent(lifecycleEvent: LifecycleEventProvider.LifecycleEvent) { + val event = lifecycleEvent.getOrBuildEvent(this) + logger.debug(Logging.LOADING, "Firing event for modid {} : {}", this.getModId(), event) + try { + + } catch (e: Throwable) { + logger.error(Logging.LOADING,"An error occurred while dispatching event {} to {}", lifecycleEvent.fromStage(), modId) + throw ModLoadingException(modInfo, lifecycleEvent.fromStage(), "fml.modloading.errorduringevent", e) + } + } + + private fun afterEvent(lifecycleEvent: LifecycleEventProvider.LifecycleEvent) { + if (currentState == ModLoadingStage.ERROR) { + logger.error(Logging.LOADING, "An error occurred while dispatching event {} to {}", lifecycleEvent.fromStage(), modId) + } + } + + private fun preinitMod(lifecycleEvent: LifecycleEventProvider.LifecycleEvent) {} + + private fun constructMod(lifecycleEvent: LifecycleEventProvider.LifecycleEvent) { + try { + logger.debug(Logging.LOADING, "Loading mod instance {} of type {}", getModId(), modClass.name) + modInstance = modClass.kotlin.objectInstance ?: modClass.newInstance() + logger.debug(Logging.LOADING, "Loaded mod instance {} of type {}", getModId(), modClass.name) + } catch (e: Throwable) { + logger.error(Logging.LOADING, "Failed to create mod instance. ModID: {}, class {}", getModId(), modClass.name, e) + throw ModLoadingException(modInfo, lifecycleEvent.fromStage(), "fml.modloading.failedtoloadmod", e, modClass) + } + + try { + logger.debug(Logging.LOADING, "Injecting Automatic event subscribers for {}", getModId()) + // Inject into object EventBusSubscribers + AutoKotlinEventBusSubscriber.inject(this, scanData, modClass.classLoader) + logger.debug(Logging.LOADING, "Completed Automatic event subscribers for {}", getModId()) + } catch (e: Throwable) { + logger.error(Logging.LOADING, "Failed to register automatic subscribers. ModID: {}, class {}", getModId(), modClass.name, e) + throw ModLoadingException(modInfo, lifecycleEvent.fromStage(), "fml.modloading.failedtoloadmod", e, modClass) + } + + } + + override fun getMod(): Any { + return modInstance + } + + override fun matches(mod: Any?): Boolean { + return mod == modInstance + } +}
\ No newline at end of file diff --git a/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModLoadingContext.kt b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModLoadingContext.kt new file mode 100644 index 0000000..4b7edc0 --- /dev/null +++ b/src/main/kotlin/thedarkcolour/kotlinforforge/KotlinModLoadingContext.kt @@ -0,0 +1,17 @@ +package thedarkcolour.kotlinforforge + +import net.minecraftforge.eventbus.api.IEventBus +import net.minecraftforge.fml.ModLoadingContext + +class KotlinModLoadingContext internal constructor(private val container: KotlinModContainer) { + fun getEventBus(): IEventBus { + return container.eventBus + } + + companion object { + @JvmStatic + fun get() { + return ModLoadingContext.get().extension() + } + } +}
\ No newline at end of file diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml new file mode 100644 index 0000000..ff8cd97 --- /dev/null +++ b/src/main/resources/META-INF/mods.toml @@ -0,0 +1,39 @@ +modLoader="kotlinforforge" # IModLanguageProvider +loaderVersion="[25,)" # IModLanguageProvider version + +issueTrackerURL="https://github.com/thedarkcolour/Future-MC/issues" # Issues page + + +#displayURL="https://minecraft.curseforge.com/projects/kotlinforforge" #optional + +description=''' +Kotlin for Forge. Allows mods to use the Kotlin programming language. +''' # A description + +# A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional. +[[dependencies.kotlinforforge]] #optional + modId="forge" #mandatory + mandatory=true #mandatory + versionRange="[25,)" #mandatory + # An ordering relationship for the dependency - BEFORE or AFTER required if the relationship is not mandatory + ordering="NONE" + + side="BOTH" +# Dependency +[[dependencies.kotlinforforge]] + modId="minecraft" + mandatory=true + versionRange="[1.14.4]" + ordering="NONE" + side="BOTH" + +# --------------------------------------------------------- # +# --------------------------------------------------------- # +# --------------------------------------------------------- # + +[[mods]] #mandatory +displayName="Kotlin for Forge" # Name of mod +modId="kotlinforforge" # Modid +version="1.0.0" # Version of kotlinforforge +authors="TheDarkColour" # Author +credits="Herobrine knows all." # Credits
\ No newline at end of file diff --git a/src/main/resources/META-INF/services/net.minecraftforge.forgespi.language.IModLanguageProvider b/src/main/resources/META-INF/services/net.minecraftforge.forgespi.language.IModLanguageProvider new file mode 100644 index 0000000..c78667e --- /dev/null +++ b/src/main/resources/META-INF/services/net.minecraftforge.forgespi.language.IModLanguageProvider @@ -0,0 +1 @@ +thedarkcolour.kotlinforforge.KotlinLanguageProvider diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta new file mode 100644 index 0000000..167eba8 --- /dev/null +++ b/src/main/resources/pack.mcmeta @@ -0,0 +1,7 @@ +{ + "pack": { + "description": "kotlinforforge resources", + "pack_format": 4, + "_comment": "A pack_format of 4 requires json lang files. Note: we require v4 pack meta for all mods." + } +}
\ No newline at end of file diff --git a/src/main/resources/patcher.js b/src/main/resources/patcher.js new file mode 100644 index 0000000..fcfe312 --- /dev/null +++ b/src/main/resources/patcher.js @@ -0,0 +1,50 @@ +function initializeCoreMod() { +// Unused, remove + return { + 'KotlinPatcher': { + 'target': { + 'type': 'METHOD', + 'class': 'net.minecraftforge.fml.javafmlmod.FMLModContainer', + 'methodName': 'constructMod', + 'methodDesc': '(Lnet/minecraftforge/fml/LifecycleEventProvider$LifecycleEvent;)V' + }, + 'transformer': function (methodNode) { + var VarInsnNode = Java.type('org.objectweb.asm.tree.VarInsnNode'); + var FieldInsnNode = Java.type('org.objectweb.asm.tree.FieldInsnNode'); + var MethodInsnNode = Java.type('org.objectweb.asm.tree.MethodInsnNode'); + var InsnList = Java.type('org.objectweb.asm.tree.InsnList'); + var Opcodes = Java.type('org.objectweb.asm.Opcodes'); + var list = new InsnList(); + list.add(new FieldInsnNode(Opcodes.GETSTATIC, "thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber", "INSTANCE", "Lthedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber;")); + list.add(new VarInsnNode(Opcodes.ALOAD, 0)); + list.add(new VarInsnNode(Opcodes.ALOAD, 0)); + list.add(new FieldInsnNode(Opcodes.GETFIELD, 'net/minecraftforge/fml/javafmlmod/FMLModContainer', 'scanResults', 'Lnet/minecraftforge/forgespi/language/ModFileScanData;')); + list.add(new VarInsnNode(Opcodes.ALOAD, 0)); + list.add(new FieldInsnNode(Opcodes.GETFIELD, 'net/minecraftforge/fml/javafmlmod/FMLModContainer', 'modClass', 'Ljava/lang/Class;')); + list.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, 'java/lang/Class', 'getClassLoader', '()Ljava/lang/ClassLoader;', false)); + list.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "thedarkcolour/kotlinforforge/AutoKotlinEventBusSubscriber", "inject", "(Lnet/minecraftforge/fml/ModContainer;Lnet/minecraftforge/forgespi/language/ModFileScanData;Ljava/lang/ClassLoader;)V", false)); + + for (var i = 0; i < 1000; ++i) { + var insn = methodNode.instructions.get(i); + //print('bruh moment'); + if (insn instanceof MethodInsnNode) { + //print('FOUND A METHODINSNNODE'); + if (insn.desc === '(Lnet/minecraftforge/fml/ModContainer;Lnet/minecraftforge/forgespi/language/ModFileScanData;Ljava/lang/ClassLoader;)V') { + methodNode.instructions.insertBefore(insn.getPrevious().getPrevious().getPrevious().getPrevious().getPrevious().getPrevious(), list); + //print('PATCHED FMLMODCONTAINER'); + break; + } + } + } + + //var writer = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); + //methodNode.accept(writer); + //var reader = new ClassReader(writer.toByteArray()); + //var cn = new ClassNode(); + //reader.accept(cn, 0); + + return methodNode; + } + } + } +}
\ No newline at end of file |