fix: Accidental hard dependency on sodium in mixins

This commit is contained in:
Linnea Gräf
2025-04-12 14:49:41 +02:00
parent a3b3ec6490
commit c3bf4a82a2
11 changed files with 256 additions and 109 deletions

View File

@@ -26,6 +26,7 @@ import net.fabricmc.loader.api.Version
import net.fabricmc.loader.api.metadata.ModMetadata
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.Logger
import org.spongepowered.asm.launch.MixinBootstrap
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job

View File

@@ -262,7 +262,8 @@ fun firmamentCommand() = literal("firmament") {
source.sendFeedback(Text.stringifiedTranslatable("firmament.sbinfo.gametype", locrawInfo.gametype))
source.sendFeedback(Text.stringifiedTranslatable("firmament.sbinfo.mode", locrawInfo.mode))
source.sendFeedback(Text.stringifiedTranslatable("firmament.sbinfo.map", locrawInfo.map))
source.sendFeedback(tr("firmament.sbinfo.custommining", "Custom Mining: ${formatBool(locrawInfo.skyblockLocation?.hasCustomMining ?: false)}"))
source.sendFeedback(tr("firmament.sbinfo.custommining",
"Custom Mining: ${formatBool(locrawInfo.skyblockLocation?.hasCustomMining ?: false)}"))
}
}
}
@@ -313,13 +314,15 @@ fun firmamentCommand() = literal("firmament") {
}
thenLiteral("mixins") {
thenExecute {
source.sendFeedback(Text.translatable("firmament.mixins.start"))
MixinPlugin.appliedMixins
.map { it.removePrefix(MixinPlugin.mixinPackage) }
.forEach {
source.sendFeedback(Text.literal(" - ").withColor(0xD020F0)
.append(Text.literal(it).withColor(0xF6BA20)))
}
MixinPlugin.instances.forEach { plugin ->
source.sendFeedback(tr("firmament.mixins.start.package", "Mixins (base ${plugin.mixinPackage}):"))
plugin.appliedMixins
.map { it.removePrefix(plugin.mixinPackage) }
.forEach {
source.sendFeedback(Text.literal(" - ").withColor(0xD020F0)
.append(Text.literal(it).withColor(0xF6BA20)))
}
}
}
}
thenLiteral("repo") {

View File

@@ -3,6 +3,10 @@ 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
@@ -10,11 +14,14 @@ 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 {
@@ -41,6 +48,42 @@ object DeveloperFeatures : FirmamentFeature {
this.missingTranslations = missingTranslations
}
@Subscribe
fun loadAllMixinClasses(event: DebugInstantiateEvent) {
val allMixinClasses = mutableSetOf<String>()
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<Mixin>()
(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

View File

@@ -0,0 +1,89 @@
package moe.nea.firmament.util.asm
import com.google.common.base.Defaults
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method
import java.lang.reflect.Proxy
import org.objectweb.asm.Type
import org.objectweb.asm.tree.AnnotationNode
object AsmAnnotationUtil {
class AnnotationProxy(
val originalType: Class<out Annotation>,
val annotationNode: AnnotationNode,
) : InvocationHandler {
val offsets = annotationNode.values.withIndex()
.chunked(2)
.map { it.first() }
.associate { (idx, value) -> value as String to idx + 1 }
fun nestArrayType(depth: Int, comp: Class<*>): Class<*> =
if (depth == 0) comp
else java.lang.reflect.Array.newInstance(nestArrayType(depth - 1, comp), 0).javaClass
fun unmap(
value: Any?,
comp: Class<*>,
depth: Int,
): Any? {
value ?: return null
if (depth > 0)
return ((value as List<Any>)
.map { unmap(it, comp, depth - 1) } as java.util.List<Any>)
.toArray(java.lang.reflect.Array.newInstance(nestArrayType(depth - 1, comp), 0) as Array<*>)
if (comp.isEnum) {
comp as Class<out Enum<*>>
when (value) {
is String -> return java.lang.Enum.valueOf(comp, value)
is List<*> -> return java.lang.Enum.valueOf(comp, value[1] as String)
else -> error("Unknown enum variant $value for $comp")
}
}
when (value) {
is Type -> return Class.forName(value.className)
is AnnotationNode -> return createProxy(comp as Class<out Annotation>, value)
is String, is Boolean, is Byte, is Double, is Int, is Float, is Long, is Short, is Char -> return value
}
error("Unknown enum variant $value for $comp")
}
fun defaultFor(fullType: Class<*>): Any? {
if (fullType.isArray) return java.lang.reflect.Array.newInstance(fullType.componentType, 0)
if (fullType.isPrimitive) {
return Defaults.defaultValue(fullType)
}
if (fullType == String::class.java)
return ""
return null
}
override fun invoke(
proxy: Any,
method: Method,
args: Array<out Any?>?
): Any? {
val name = method.name
val ret = method.returnType
val retU = generateSequence(ret) { if (it.isArray) it.componentType else null }
.toList()
val arrayDepth = retU.size - 1
val componentType = retU.last()
val off = offsets[name]
if (off == null) {
return defaultFor(ret)
}
return unmap(annotationNode.values[off], componentType, arrayDepth)
}
}
fun <T : Annotation> createProxy(
annotationClass: Class<T>,
annotationNode: AnnotationNode
): T {
require(Type.getType(annotationClass) == Type.getType(annotationNode.desc))
return Proxy.newProxyInstance(javaClass.classLoader,
arrayOf(annotationClass),
AnnotationProxy(annotationClass, annotationNode)) as T
}
}