package moe.nea.firmament.features.debug import java.io.File import java.nio.file.Path import java.util.concurrent.CompletableFuture import org.objectweb.asm.ClassReader import org.objectweb.asm.Type import org.objectweb.asm.tree.ClassNode import org.spongepowered.asm.mixin.Mixin import kotlinx.serialization.json.encodeToStream import kotlin.io.path.absolute import kotlin.io.path.exists import net.minecraft.client.MinecraftClient import net.minecraft.text.Text import moe.nea.firmament.Firmament import moe.nea.firmament.annotations.Subscribe import moe.nea.firmament.events.DebugInstantiateEvent import moe.nea.firmament.events.TickEvent import moe.nea.firmament.features.FirmamentFeature import moe.nea.firmament.gui.config.ManagedConfig import moe.nea.firmament.init.MixinPlugin import moe.nea.firmament.util.MC import moe.nea.firmament.util.TimeMark import moe.nea.firmament.util.asm.AsmAnnotationUtil import moe.nea.firmament.util.iterate object DeveloperFeatures : FirmamentFeature { override val identifier: String get() = "developer" override val config: TConfig get() = TConfig override val defaultEnabled: Boolean get() = Firmament.DEBUG val gradleDir = Path.of(".").absolute() .iterate { it.parent } .find { it.resolve("settings.gradle.kts").exists() } object TConfig : ManagedConfig("developer", Category.DEV) { val autoRebuildResources by toggle("auto-rebuild") { false } } var missingTranslations: Set? = null @JvmStatic fun hookMissingTranslations(missingTranslations: Set) { this.missingTranslations = missingTranslations } @Subscribe fun loadAllMixinClasses(event: DebugInstantiateEvent) { val allMixinClasses = mutableSetOf() MixinPlugin.instances.forEach { plugin -> val prefix = plugin.mixinPackage + "." val classes = plugin.mixins.map { prefix + it } allMixinClasses.addAll(classes) for (cls in classes) { val targets = javaClass.classLoader.getResourceAsStream("${cls.replace(".", "/")}.class").use { val node = ClassNode() ClassReader(it).accept(node, 0) val mixins = mutableListOf() (node.visibleAnnotations.orEmpty() + node.invisibleAnnotations.orEmpty()).forEach { val annotationType = Type.getType(it.desc) val mixinType = Type.getType(Mixin::class.java) if (mixinType == annotationType) { mixins.add(AsmAnnotationUtil.createProxy(Mixin::class.java, it)) } } mixins.flatMap { it.targets.toList() } + mixins.flatMap { it.value.map { it.java.name } } } for (target in targets) try { Firmament.logger.debug("Loading ${target} to force instantiate ${cls}") Class.forName(target, true, javaClass.classLoader) } catch (ex: Throwable) { Firmament.logger.error("Could not load class ${target} that has been mixind by $cls", ex) } } } Firmament.logger.info("Forceloaded all Firmament mixins:") val applied = MixinPlugin.instances.flatMap { it.appliedMixins }.toSet() applied.forEach { Firmament.logger.info(" - ${it}") } require(allMixinClasses == applied) } @Subscribe fun dumpMissingTranslations(tickEvent: TickEvent) { val toDump = missingTranslations ?: return missingTranslations = null File("missing_translations.json").outputStream().use { Firmament.json.encodeToStream(toDump.associateWith { "Mis" + "sing translation" }, it) } } @JvmStatic fun hookOnBeforeResourceReload(client: MinecraftClient): CompletableFuture { val reloadFuture = if (TConfig.autoRebuildResources && isEnabled && gradleDir != null) { val builder = ProcessBuilder("./gradlew", ":processResources") builder.directory(gradleDir.toFile()) builder.inheritIO() val process = builder.start() MC.sendChat(Text.translatable("firmament.dev.resourcerebuild.start")) val startTime = TimeMark.now() process.toHandle().onExit().thenApply { MC.sendChat(Text.stringifiedTranslatable( "firmament.dev.resourcerebuild.done", startTime.passedTime())) Unit } } else { CompletableFuture.completedFuture(Unit) } return reloadFuture.thenCompose { client.reloadResources() } } }