diff --git a/src/main/java/com/chattriggers/ctjs/internal/mixins/MinecraftClientMixin.java b/src/main/java/com/chattriggers/ctjs/internal/mixins/MinecraftClientMixin.java index e2fd550f..fb16fecc 100644 --- a/src/main/java/com/chattriggers/ctjs/internal/mixins/MinecraftClientMixin.java +++ b/src/main/java/com/chattriggers/ctjs/internal/mixins/MinecraftClientMixin.java @@ -2,9 +2,14 @@ import com.chattriggers.ctjs.internal.engine.CTEvents; import com.chattriggers.ctjs.api.triggers.TriggerType; +import com.chattriggers.ctjs.internal.engine.JSLoader; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import net.minecraft.client.MinecraftClient; +import net.minecraft.client.RunArgs; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.network.ServerInfo; +import net.minecraft.client.resource.server.ServerResourcePackLoader; import net.minecraft.client.world.ClientWorld; import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; @@ -13,6 +18,8 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import java.nio.file.Path; + @Mixin(MinecraftClient.class) public abstract class MinecraftClientMixin { @Shadow @Nullable public ClientWorld world; diff --git a/src/main/kotlin/com/chattriggers/ctjs/internal/engine/JSLoader.kt b/src/main/kotlin/com/chattriggers/ctjs/internal/engine/JSLoader.kt index de36b348..2db47b0e 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/internal/engine/JSLoader.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/internal/engine/JSLoader.kt @@ -207,6 +207,9 @@ object JSLoader { } } + @JvmStatic + fun mixinIsAttached(id: Int) = mixinIdMap[id]?.method != null + fun invokeMixinLookup(id: Int): MixinCallback { val callback = mixinIdMap[id] ?: error("Unknown mixin id $id for loader ${this::class.simpleName}") diff --git a/src/main/kotlin/com/chattriggers/ctjs/internal/launch/DynamicMixinManager.kt b/src/main/kotlin/com/chattriggers/ctjs/internal/launch/DynamicMixinManager.kt index a32339be..70937b0e 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/internal/launch/DynamicMixinManager.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/internal/launch/DynamicMixinManager.kt @@ -44,8 +44,8 @@ internal object DynamicMixinManager { for ((mixin, details) in mixins) { val ctx = GenerationContext(mixin) val generator = DynamicMixinGenerator(ctx, details) - ByteBasedStreamHandler[generator.generatedClassFullPath + ".class"] = generator.generate() - dynamicMixins += generator.generatedClassName + ByteBasedStreamHandler[ctx.generatedClassFullPath + ".class"] = generator.generate() + dynamicMixins += ctx.generatedClassName } ByteBasedStreamHandler[GENERATED_MIXIN] = createDynamicMixinsJson(dynamicMixins) diff --git a/src/main/kotlin/com/chattriggers/ctjs/internal/launch/InvokeDynamicSupport.kt b/src/main/kotlin/com/chattriggers/ctjs/internal/launch/InvokeDynamicSupport.kt index 7a79ebec..767dba1c 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/internal/launch/InvokeDynamicSupport.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/internal/launch/InvokeDynamicSupport.kt @@ -68,20 +68,8 @@ internal object InvokeDynamicSupport { // Make an initial lookup to the target function. This is where we want our mixin handler method to point to val mixinCallback = JSLoader.invokeMixinLookup(mixinId) - check((mixinCallback.handle == null) == (mixinCallback.method == null)) - - val targetHandle = if (mixinCallback.handle == null) { - // If we don't have a handle, that means the user hasn't called attach() on the callback, meaning this mixin - // is "unused"... - val (methodName, injectionType) = InjectorGenerator.disassembleIndyName(name) - - error( - "$injectionType mixin into method $methodName was called, but has no handler. Did you forget to " + - "call attach()?" - ) - } else { - mixinCallback.handle!! - } + checkNotNull(mixinCallback.handle) + checkNotNull(mixinCallback.method) // Until we /ct load, however. When we reload, we need to re-resolve all JS invocation targets since our old // engine context has been thrown away and recreated. It is also possible that the user has changed their code @@ -96,7 +84,7 @@ internal object InvokeDynamicSupport { // Note that the mechanism for flipping these switches is in MixinCallback. When the user calls attach(), the // invalidator gets invalidated, as the method has changed. This of course happens for all mixins when the user // /ct loads, since the scripts are re-run. - val guardedTarget = mixinCallback.invalidator.guardWithTest(targetHandle, initTarget) + val guardedTarget = mixinCallback.invalidator.guardWithTest(mixinCallback.handle, initTarget) // We now have a target that is very fast to call back into the target method, and can survive reloads or calls // to attach(), so we want our call site to now point to that target. @@ -104,6 +92,6 @@ internal object InvokeDynamicSupport { // This method invocation occurred because we actually tried to call the target method with the supplied mixin // arguments. So in addition to performing the call site rebinding, we also need to make the actual method call. - return targetHandle.invoke(args) + return mixinCallback.handle!!.invoke(args) } } diff --git a/src/main/kotlin/com/chattriggers/ctjs/internal/launch/annotations.kt b/src/main/kotlin/com/chattriggers/ctjs/internal/launch/annotations.kt index f2c278c6..10759c54 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/internal/launch/annotations.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/internal/launch/annotations.kt @@ -1,6 +1,7 @@ package com.chattriggers.ctjs.internal.launch import com.chattriggers.ctjs.internal.utils.descriptor +import org.objectweb.asm.Opcodes import org.spongepowered.asm.mixin.injection.Constant as SPConstant /* @@ -27,6 +28,86 @@ data class At( val opcode: Int?, val remap: Boolean?, ) { + internal val atTarget: AtTarget<*> by lazy(::getAtTarget) + + internal sealed class AtTarget(val descriptor: T, val targetName: String) + + internal class InvokeTarget(descriptor: Descriptor.Method) : AtTarget(descriptor, "INVOKE") { + override fun toString() = descriptor.originalDescriptor() + } + + internal class NewTarget(descriptor: Descriptor.New) : AtTarget(descriptor, "NEW") { + override fun toString() = descriptor.originalDescriptor() + } + + internal class FieldTarget( + descriptor: Descriptor.Field, val isGet: Boolean?, val isStatic: Boolean?, + ) : AtTarget(descriptor, "FIELD") { + override fun toString() = descriptor.originalDescriptor() + } + + internal class ConstantTarget(val key: String, descriptor: Descriptor) : AtTarget(descriptor, "CONSTANT") { + init { + require(descriptor.isType) + } + + override fun toString() = "$key=$descriptor" + } + + private fun getAtTarget(): AtTarget<*> { + return when (value) { + "INVOKE" -> { + requireNotNull(target) { "At targeting INVOKE expects its target to be a method descriptor" } + InvokeTarget(Descriptor.Parser(target).parseMethod(full = true)) + } + "NEW" -> { + requireNotNull(target) { "At targeting NEW expects its target to be a new invocation descriptor" } + NewTarget(Descriptor.Parser(target).parseNew(full = true)) + } + "FIELD" -> { + requireNotNull(target) { "At targeting FIELD expects its target to be a field descriptor" } + if (opcode != null) { + require( + opcode in setOf( + Opcodes.GETFIELD, + Opcodes.GETSTATIC, + Opcodes.PUTFIELD, + Opcodes.PUTSTATIC + ) + ) { + "At targeting FIELD expects its opcode to be one of: GETFIELD, GETSTATIC, PUTFIELD, PUTSTATIC" + } + val isGet = opcode == Opcodes.GETFIELD || opcode == Opcodes.GETSTATIC + val isStatic = opcode == Opcodes.GETSTATIC || opcode == Opcodes.PUTSTATIC + FieldTarget(Descriptor.Parser(target).parseField(full = true), isGet, isStatic) + } else { + FieldTarget(Descriptor.Parser(target).parseField(full = true), null, null) + } + } + "CONSTANT" -> { + require(args != null) { + "At targeting CONSTANT requires args" + } + args.firstNotNullOfOrNull { + val key = it.substringBefore('=') + val type = when (key) { + "null" -> Any::class.descriptor() // Is this right? + "intValue" -> Descriptor.Primitive.INT + "floatValue" -> Descriptor.Primitive.FLOAT + "longValue" -> Descriptor.Primitive.LONG + "doubleValue" -> Descriptor.Primitive.DOUBLE + "stringValue" -> String::class.descriptor() + "classValue" -> Descriptor.Object("L${it.substringAfter("=")};") + else -> return@firstNotNullOfOrNull null + } + + ConstantTarget(key, type) + } ?: error("At targeting CONSTANT expects a typeValue arg") + } + else -> error("Invalid At.value for Utils.getAtTarget: ${value}") + } + } + enum class Shift { NONE, BEFORE, diff --git a/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/DynamicMixinGenerator.kt b/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/DynamicMixinGenerator.kt index 6064f814..080a8464 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/DynamicMixinGenerator.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/DynamicMixinGenerator.kt @@ -10,11 +10,8 @@ import java.io.File import org.spongepowered.asm.mixin.Mixin as SPMixin internal class DynamicMixinGenerator(private val ctx: GenerationContext, private val details: MixinDetails) { - val generatedClassName = "CTMixin_\$${ctx.mixin.target.replace('.', '_')}\$_${mixinCounter++}" - val generatedClassFullPath = "${DynamicMixinManager.GENERATED_PACKAGE}/$generatedClassName" - fun generate(): ByteArray { - val mixinClassNode = assembleClass(public, generatedClassFullPath, version = Opcodes.V17) { + val mixinClassNode = assembleClass(public, ctx.generatedClassFullPath, version = Opcodes.V17) { for ((id, injector) in details.injectors) { when (injector) { is Inject -> InjectGenerator(ctx, id, injector).generate() @@ -48,13 +45,9 @@ internal class DynamicMixinGenerator(private val ctx: GenerationContext, private if (CTJS.isDevelopment) { val dir = File(CTJS.configLocation, "ChatTriggers/mixin-classes") dir.mkdirs() - File(dir, "$generatedClassName.class").writeBytes(bytes) + File(dir, "${ctx.generatedClassName}.class").writeBytes(bytes) } return bytes } - - companion object { - private var mixinCounter = 0 - } } diff --git a/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/GenerationContext.kt b/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/GenerationContext.kt index 00e2abb6..33f6d14d 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/GenerationContext.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/GenerationContext.kt @@ -2,14 +2,21 @@ package com.chattriggers.ctjs.internal.launch.generation import com.chattriggers.ctjs.api.Mappings import com.chattriggers.ctjs.internal.launch.Descriptor +import com.chattriggers.ctjs.internal.launch.DynamicMixinManager import com.chattriggers.ctjs.internal.launch.Mixin import org.spongepowered.asm.mixin.transformer.ClassInfo internal data class GenerationContext(val mixin: Mixin) { val mappedClass = Mappings.getMappedClass(mixin.target) ?: error("Unknown class name ${mixin.target}") + val generatedClassName = "CTMixin_\$${mixin.target.replace('.', '_')}\$_${mixinCounter++}" + val generatedClassFullPath = "${DynamicMixinManager.GENERATED_PACKAGE}/$generatedClassName" fun findMethod(method: String): Pair { val descriptor = Descriptor.Parser(method).parseMethod(full = false) return Utils.findMethod(mappedClass, descriptor) } + + companion object { + private var mixinCounter = 0 + } } diff --git a/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/InjectGenerator.kt b/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/InjectGenerator.kt index 872b9d3b..38969063 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/InjectGenerator.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/InjectGenerator.kt @@ -1,5 +1,9 @@ package com.chattriggers.ctjs.internal.launch.generation +import codes.som.koffee.MethodAssembly +import codes.som.koffee.insns.jvm.aconst_null +import codes.som.koffee.insns.jvm.areturn +import codes.som.koffee.insns.jvm.ldc import com.chattriggers.ctjs.internal.launch.Descriptor import com.chattriggers.ctjs.internal.launch.Inject import com.chattriggers.ctjs.internal.utils.descriptor @@ -61,4 +65,10 @@ internal class InjectGenerator( visitEnd() } } + + context(MethodAssembly) + override fun generateNotAttachedBehavior() { + // This method is expected to leave something on the stack + aconst_null + } } diff --git a/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/InjectorGenerator.kt b/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/InjectorGenerator.kt index 7263b6fb..71bee7e0 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/InjectorGenerator.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/InjectorGenerator.kt @@ -1,58 +1,66 @@ package com.chattriggers.ctjs.internal.launch.generation import codes.som.koffee.ClassAssembly +import codes.som.koffee.MethodAssembly import codes.som.koffee.insns.InstructionAssembly import codes.som.koffee.insns.jvm.* +import codes.som.koffee.insns.sugar.JumpCondition +import codes.som.koffee.insns.sugar.ifStatement import com.chattriggers.ctjs.api.Mappings +import com.chattriggers.ctjs.internal.engine.JSLoader import com.chattriggers.ctjs.internal.launch.Descriptor import com.chattriggers.ctjs.internal.launch.InvokeDynamicSupport import com.chattriggers.ctjs.internal.launch.Local import com.chattriggers.ctjs.internal.utils.descriptor import com.chattriggers.ctjs.internal.utils.descriptorString -import com.llamalad7.mixinextras.sugar.ref.* import org.objectweb.asm.tree.MethodNode internal abstract class InjectorGenerator(protected val ctx: GenerationContext, val id: Int) { abstract val type: String + protected val signature by lazy { getInjectionSignature() } + protected val parameterDescriptors by lazy { + signature.parameters.map { + // Check if the type needs to be wrapped in a ref. Also handle the case where + // the user provides an explicitly wrapped type + if (it.local?.mutable == true && !it.descriptor.originalDescriptor() + .startsWith("Lcom/llamalad7/mixinextras/sugar/ref/") + ) { + when (it.descriptor) { + com.chattriggers.ctjs.internal.launch.Descriptor.Primitive.BOOLEAN -> com.llamalad7.mixinextras.sugar.ref.LocalBooleanRef::class.descriptor() + com.chattriggers.ctjs.internal.launch.Descriptor.Primitive.BYTE -> com.llamalad7.mixinextras.sugar.ref.LocalByteRef::class.descriptor() + com.chattriggers.ctjs.internal.launch.Descriptor.Primitive.CHAR -> com.llamalad7.mixinextras.sugar.ref.LocalCharRef::class.descriptor() + com.chattriggers.ctjs.internal.launch.Descriptor.Primitive.DOUBLE -> com.llamalad7.mixinextras.sugar.ref.LocalDoubleRef::class.descriptor() + com.chattriggers.ctjs.internal.launch.Descriptor.Primitive.FLOAT -> com.llamalad7.mixinextras.sugar.ref.LocalFloatRef::class.descriptor() + com.chattriggers.ctjs.internal.launch.Descriptor.Primitive.INT -> com.llamalad7.mixinextras.sugar.ref.LocalIntRef::class.descriptor() + com.chattriggers.ctjs.internal.launch.Descriptor.Primitive.LONG -> com.llamalad7.mixinextras.sugar.ref.LocalLongRef::class.descriptor() + com.chattriggers.ctjs.internal.launch.Descriptor.Primitive.SHORT -> com.llamalad7.mixinextras.sugar.ref.LocalShortRef::class.descriptor() + else -> com.llamalad7.mixinextras.sugar.ref.LocalRef::class.descriptor() + } + } else it.descriptor + } + } abstract fun getInjectionSignature(): InjectionSignature abstract fun attachAnnotation(node: MethodNode, signature: InjectionSignature) + context(MethodAssembly) + abstract fun generateNotAttachedBehavior() + context(ClassAssembly) fun generate() { - val signature = getInjectionSignature() val (targetMethod, parameters, returnType, isStatic) = signature var modifiers = private if (isStatic) modifiers += static - val parameterTypes = parameters.map { - // Check if the type needs to be wrapped in a ref. Also handle the case where - // the user provides an explicitly wrapped type - if (it.local?.mutable == true && !it.descriptor.originalDescriptor() - .startsWith("Lcom/llamalad7/mixinextras/sugar/ref/") - ) { - when (it.descriptor) { - Descriptor.Primitive.BOOLEAN -> LocalBooleanRef::class.descriptor() - Descriptor.Primitive.BYTE -> LocalByteRef::class.descriptor() - Descriptor.Primitive.CHAR -> LocalCharRef::class.descriptor() - Descriptor.Primitive.DOUBLE -> LocalDoubleRef::class.descriptor() - Descriptor.Primitive.FLOAT -> LocalFloatRef::class.descriptor() - Descriptor.Primitive.INT -> LocalIntRef::class.descriptor() - Descriptor.Primitive.LONG -> LocalLongRef::class.descriptor() - Descriptor.Primitive.SHORT -> LocalShortRef::class.descriptor() - else -> LocalRef::class.descriptor() - } - } else it.descriptor - } - + val nameForInjection = targetMethod.name.original.replace('<', '$').replace('>', '$') val methodNode = method( modifiers, - "ctjs_${type}_${targetMethod.name.original}_${counter++}", + "ctjs_${type}_${nameForInjection}_${counter++}", returnType.toMappedType(), - *parameterTypes.map { it.toMappedType() }.toTypedArray(), + *parameterDescriptors.map { it.toMappedType() }.toTypedArray(), ) { // Apply parameter annotations for (i in parameters.indices) { @@ -65,6 +73,14 @@ internal abstract class InjectorGenerator(protected val ctx: GenerationContext, } } + // Check if we're attached + ldc(id) + invokestatic(JSLoader::class, "mixinIsAttached", Boolean::class, Int::class) + ifStatement(JumpCondition.False) { + generateNotAttachedBehavior() + generateReturn(returnType) + } + ldc(parameters.size + if (isStatic) 0 else 1) anewarray(Any::class) @@ -75,100 +91,109 @@ internal abstract class InjectorGenerator(protected val ctx: GenerationContext, aastore } - parameterTypes.forEachIndexed { index, descriptor -> + parameterDescriptors.forEachIndexed { index, descriptor -> dup ldc(index + if (isStatic) 0 else 1) - getLoadInsn(descriptor)(index + if (isStatic) 0 else 1) - - // Box primitives if necessary - when (descriptor) { - Descriptor.Primitive.VOID -> throw IllegalStateException("Cannot use Void as a parameter type") - Descriptor.Primitive.BOOLEAN -> - invokestatic(java.lang.Boolean::class, "valueOf", java.lang.Boolean::class, boolean) - Descriptor.Primitive.CHAR -> - invokestatic(java.lang.Character::class, "valueOf", java.lang.Character::class, char) - Descriptor.Primitive.BYTE -> - invokestatic(java.lang.Byte::class, "valueOf", java.lang.Byte::class, byte) - Descriptor.Primitive.SHORT -> - invokestatic(java.lang.Short::class, "valueOf", java.lang.Short::class, short) - Descriptor.Primitive.INT -> - invokestatic(java.lang.Integer::class, "valueOf", java.lang.Integer::class, int) - Descriptor.Primitive.FLOAT -> - invokestatic(java.lang.Float::class, "valueOf", java.lang.Float::class, float) - Descriptor.Primitive.LONG -> - invokestatic(java.lang.Long::class, "valueOf", java.lang.Long::class, long) - Descriptor.Primitive.DOUBLE -> - invokestatic(java.lang.Double::class, "valueOf", java.lang.Double::class, double) - else -> {} - } - + generateParameterLoad(index) + generateBoxIfNecessary(descriptor) aastore } invokedynamic( - assembleIndyName(targetMethod.name.original, type), + assembleIndyName(nameForInjection, type), "([Ljava/lang/Object;)Ljava/lang/Object;", InvokeDynamicSupport.BOOTSTRAP_HANDLE, arrayOf(id), ) - when (returnType) { - Descriptor.Primitive.VOID -> { - pop - _return - } - Descriptor.Primitive.BOOLEAN -> { - checkcast(java.lang.Boolean::class) - invokevirtual(java.lang.Boolean::class, "booleanValue", boolean) - ireturn - } - is Descriptor.Primitive -> { - checkcast(java.lang.Number::class) - - when (returnType) { - Descriptor.Primitive.CHAR -> { - invokevirtual(java.lang.Number::class, "charValue", char) - ireturn - } - Descriptor.Primitive.BYTE -> { - invokevirtual(java.lang.Number::class, "byteValue", byte) - ireturn - } - Descriptor.Primitive.SHORT -> { - invokevirtual(java.lang.Number::class, "shortValue", short) - ireturn - } - Descriptor.Primitive.INT -> { - invokevirtual(java.lang.Number::class, "intValue", int) - ireturn - } - Descriptor.Primitive.LONG -> { - invokevirtual(java.lang.Number::class, "longValue", long) - lreturn - } - Descriptor.Primitive.FLOAT -> { - invokevirtual(java.lang.Number::class, "floatValue", float) - freturn - } - Descriptor.Primitive.DOUBLE -> { - invokevirtual(java.lang.Number::class, "doubleValue", double) - dreturn - } - else -> throw IllegalStateException() - } - } - else -> { - checkcast(returnType.toMappedType()) - areturn + generateUnboxIfNecessary(returnType) + generateReturn(returnType) + } + + attachAnnotation(methodNode, signature) + } + + context(MethodAssembly) + private fun generateBoxIfNecessary(descriptor: Descriptor) { + when (descriptor) { + Descriptor.Primitive.VOID -> throw IllegalStateException("Cannot use Void as a parameter type") + Descriptor.Primitive.BOOLEAN -> + invokestatic(java.lang.Boolean::class, "valueOf", java.lang.Boolean::class, boolean) + Descriptor.Primitive.CHAR -> + invokestatic(java.lang.Character::class, "valueOf", java.lang.Character::class, char) + Descriptor.Primitive.BYTE -> + invokestatic(java.lang.Byte::class, "valueOf", java.lang.Byte::class, byte) + Descriptor.Primitive.SHORT -> + invokestatic(java.lang.Short::class, "valueOf", java.lang.Short::class, short) + Descriptor.Primitive.INT -> + invokestatic(java.lang.Integer::class, "valueOf", java.lang.Integer::class, int) + Descriptor.Primitive.FLOAT -> + invokestatic(java.lang.Float::class, "valueOf", java.lang.Float::class, float) + Descriptor.Primitive.LONG -> + invokestatic(java.lang.Long::class, "valueOf", java.lang.Long::class, long) + Descriptor.Primitive.DOUBLE -> + invokestatic(java.lang.Double::class, "valueOf", java.lang.Double::class, double) + else -> {} + } + } + + context(MethodAssembly) + private fun generateUnboxIfNecessary(descriptor: Descriptor) { + when (descriptor) { + Descriptor.Primitive.VOID -> {} + Descriptor.Primitive.BOOLEAN -> { + checkcast(java.lang.Boolean::class) + invokevirtual(java.lang.Boolean::class, "booleanValue", boolean) + } + is Descriptor.Primitive -> { + checkcast(java.lang.Number::class) + + when (descriptor) { + Descriptor.Primitive.CHAR -> invokevirtual(java.lang.Number::class, "charValue", char) + Descriptor.Primitive.BYTE -> invokevirtual(java.lang.Number::class, "byteValue", byte) + Descriptor.Primitive.SHORT -> invokevirtual(java.lang.Number::class, "shortValue", short) + Descriptor.Primitive.INT -> invokevirtual(java.lang.Number::class, "intValue", int) + Descriptor.Primitive.LONG -> invokevirtual(java.lang.Number::class, "longValue", long) + Descriptor.Primitive.FLOAT -> invokevirtual(java.lang.Number::class, "floatValue", float) + Descriptor.Primitive.DOUBLE -> invokevirtual(java.lang.Number::class, "doubleValue", double) + else -> throw IllegalStateException() } } + else -> checkcast(descriptor.toMappedType()) } + } - attachAnnotation(methodNode, signature) + context(MethodAssembly) + private fun generateReturn(returnType: Descriptor) { + when (returnType) { + Descriptor.Primitive.VOID -> { + pop + _return + } + Descriptor.Primitive.BOOLEAN -> ireturn + Descriptor.Primitive.LONG -> lreturn + Descriptor.Primitive.FLOAT -> freturn + Descriptor.Primitive.DOUBLE -> dreturn + is Descriptor.Primitive -> ireturn + else -> areturn + } } - private fun InstructionAssembly.getLoadInsn(descriptor: Descriptor): (Int) -> Unit { - return when (descriptor) { + protected fun InstructionAssembly.generateParameterLoad(parameterIndex: Int) { + val localIndex = (0 until parameterIndex).sumOf { + val descriptor = parameterDescriptors[it] + if (descriptor == Descriptor.Primitive.LONG || descriptor == Descriptor.Primitive.DOUBLE) { + // Compiler bug + @Suppress("USELESS_CAST") + 2 as Int + } else 1 + } + generateLoad(parameterDescriptors[parameterIndex], localIndex) + } + + protected fun InstructionAssembly.generateLoad(descriptor: Descriptor, index: Int) { + val modifiedIndex = if (signature.isStatic) index else index + 1 + when (descriptor) { Descriptor.Primitive.BOOLEAN, Descriptor.Primitive.BYTE, Descriptor.Primitive.SHORT, @@ -177,7 +202,7 @@ internal abstract class InjectorGenerator(protected val ctx: GenerationContext, Descriptor.Primitive.FLOAT -> ::fload Descriptor.Primitive.DOUBLE -> ::dload else -> ::aload - } + }(modifiedIndex) } data class Parameter( @@ -197,10 +222,5 @@ internal abstract class InjectorGenerator(protected val ctx: GenerationContext, fun assembleIndyName(methodName: String, injectionType: String) = "invokeDynamic_${methodName}_${injectionType}_${counter++}" - - fun disassembleIndyName(name: String): Pair = name.drop("invokeDynamic_".length).let { - val (methodName, injectionType) = it.split('_') - methodName to injectionType - } } } diff --git a/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyArgGenerator.kt b/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyArgGenerator.kt index 7f76841d..bafd1297 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyArgGenerator.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyArgGenerator.kt @@ -1,8 +1,11 @@ package com.chattriggers.ctjs.internal.launch.generation +import codes.som.koffee.MethodAssembly +import com.chattriggers.ctjs.internal.launch.At import com.chattriggers.ctjs.internal.launch.ModifyArg import com.chattriggers.ctjs.internal.utils.descriptorString import org.objectweb.asm.tree.MethodNode +import kotlin.math.sign import org.spongepowered.asm.mixin.injection.ModifyArg as SPModifyArg internal class ModifyArgGenerator( @@ -16,8 +19,8 @@ internal class ModifyArgGenerator( val (mappedMethod, method) = ctx.findMethod(modifyArg.method) // Resolve the target method - val atTarget = Utils.getAtTargetDescriptor(modifyArg.at) - check(atTarget is Utils.InvokeAtTarget) { "ModifyArg expects At.target to be INVOKE" } + val atTarget = modifyArg.at.atTarget + check(atTarget is At.InvokeTarget) { "ModifyArg expects At.target to be INVOKE" } val targetDescriptor = atTarget.descriptor requireNotNull(targetDescriptor.parameters) @@ -61,4 +64,9 @@ internal class ModifyArgGenerator( visit("constraints", modifyArg.constraints) } } + + context(MethodAssembly) + override fun generateNotAttachedBehavior() { + generateParameterLoad(0) + } } diff --git a/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyArgs.kt b/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyArgsGenerator.kt similarity index 88% rename from src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyArgs.kt rename to src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyArgsGenerator.kt index 17b9a103..156a35e4 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyArgs.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyArgsGenerator.kt @@ -1,5 +1,7 @@ package com.chattriggers.ctjs.internal.launch.generation +import codes.som.koffee.MethodAssembly +import codes.som.koffee.insns.jvm.aconst_null import com.chattriggers.ctjs.internal.launch.Descriptor import com.chattriggers.ctjs.internal.launch.ModifyArgs import com.chattriggers.ctjs.internal.utils.descriptor @@ -48,4 +50,10 @@ internal class ModifyArgsGenerator( visit("constraints", modifyArgs.constraints) } } + + context(MethodAssembly) + override fun generateNotAttachedBehavior() { + // This method is expected to leave something on the stack + aconst_null + } } diff --git a/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyConstantGenerator.kt b/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyConstantGenerator.kt index adc1e2b9..ae93f40c 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyConstantGenerator.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyConstantGenerator.kt @@ -1,5 +1,6 @@ package com.chattriggers.ctjs.internal.launch.generation +import codes.som.koffee.MethodAssembly import com.chattriggers.ctjs.internal.launch.ModifyConstant import com.chattriggers.ctjs.internal.utils.descriptorString import org.objectweb.asm.tree.MethodNode @@ -45,4 +46,9 @@ internal class ModifyConstantGenerator( visitEnd() } } + + context(MethodAssembly) + override fun generateNotAttachedBehavior() { + generateParameterLoad(0) + } } diff --git a/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyExpressionValueGenerator.kt b/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyExpressionValueGenerator.kt index b06eb926..80e6b785 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyExpressionValueGenerator.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyExpressionValueGenerator.kt @@ -1,5 +1,8 @@ package com.chattriggers.ctjs.internal.launch.generation +import codes.som.koffee.MethodAssembly +import com.chattriggers.ctjs.internal.launch.At +import com.chattriggers.ctjs.internal.launch.Descriptor import com.chattriggers.ctjs.internal.launch.ModifyExpressionValue import com.chattriggers.ctjs.internal.utils.descriptorString import org.objectweb.asm.tree.MethodNode @@ -15,14 +18,17 @@ internal class ModifyExpressionValueGenerator( override fun getInjectionSignature(): InjectionSignature { val (mappedMethod, method) = ctx.findMethod(modifyExpressionValue.method) - val exprDescriptor = when (val atTarget = Utils.getAtTargetDescriptor(modifyExpressionValue.at)) { - is Utils.InvokeAtTarget -> atTarget.descriptor.returnType - is Utils.FieldAtTarget -> atTarget.descriptor.type - is Utils.NewAtTarget -> atTarget.descriptor.type - is Utils.ConstantAtTarget -> atTarget.descriptor + val exprDescriptor = when (val atTarget = modifyExpressionValue.at.atTarget) { + is At.InvokeTarget -> atTarget.descriptor.returnType + is At.FieldTarget -> atTarget.descriptor.type + is At.NewTarget -> atTarget.descriptor.type + is At.ConstantTarget -> atTarget.descriptor } check(exprDescriptor != null && exprDescriptor.isType) + check(exprDescriptor != Descriptor.Primitive.VOID) { + "ModifyExpressionValue mixin cannot target a void method" + } val parameters = listOf(Parameter(exprDescriptor)) + modifyExpressionValue.locals ?.map(Utils::getParameterFromLocal) @@ -53,4 +59,9 @@ internal class ModifyExpressionValueGenerator( visitEnd() } } + + context(MethodAssembly) + override fun generateNotAttachedBehavior() { + generateParameterLoad(0) + } } diff --git a/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyReceiverGenerator.kt b/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyReceiverGenerator.kt index 580d3e09..8432e65d 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyReceiverGenerator.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyReceiverGenerator.kt @@ -1,5 +1,10 @@ package com.chattriggers.ctjs.internal.launch.generation +import codes.som.koffee.MethodAssembly +import codes.som.koffee.insns.jvm.getfield +import codes.som.koffee.insns.jvm.invokevirtual +import codes.som.koffee.insns.jvm.putfield +import com.chattriggers.ctjs.internal.launch.At import com.chattriggers.ctjs.internal.launch.ModifyReceiver import com.chattriggers.ctjs.internal.utils.descriptorString import org.objectweb.asm.tree.MethodNode @@ -15,9 +20,9 @@ internal class ModifyReceiverGenerator( override fun getInjectionSignature(): InjectionSignature { val (mappedMethod, method) = ctx.findMethod(modifyReceiver.method) - val (owner, extraParams) = when (val atTarget = Utils.getAtTargetDescriptor(modifyReceiver.at)) { - is Utils.InvokeAtTarget -> atTarget.descriptor.owner to atTarget.descriptor.parameters - is Utils.FieldAtTarget -> { + val (owner, extraParams) = when (val atTarget = modifyReceiver.at.atTarget) { + is At.InvokeTarget -> atTarget.descriptor.owner to atTarget.descriptor.parameters + is At.FieldTarget -> { check(atTarget.isStatic != null && atTarget.isGet != null) { "ModifyReceiver targeting FIELD expects an opcode value" } @@ -58,4 +63,9 @@ internal class ModifyReceiverGenerator( visitEnd() } } + + context(MethodAssembly) + override fun generateNotAttachedBehavior() { + generateParameterLoad(0) + } } diff --git a/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyReturnValueInjector.kt b/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyReturnValueInjector.kt index 518785d4..f80f4251 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyReturnValueInjector.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyReturnValueInjector.kt @@ -1,9 +1,11 @@ package com.chattriggers.ctjs.internal.launch.generation +import codes.som.koffee.MethodAssembly import com.chattriggers.ctjs.internal.launch.Descriptor import com.chattriggers.ctjs.internal.launch.ModifyReturnValue import com.chattriggers.ctjs.internal.utils.descriptorString import org.objectweb.asm.tree.MethodNode +import org.spongepowered.asm.mixin.injection.Desc import com.llamalad7.mixinextras.injector.ModifyReturnValue as SPModifyReturnValue internal class ModifyReturnValueInjector( @@ -16,6 +18,9 @@ internal class ModifyReturnValueInjector( override fun getInjectionSignature(): InjectionSignature { val (mappedMethod, method) = ctx.findMethod(modifyReturnValue.method) val returnType = Descriptor.Parser(mappedMethod.returnType.value).parseType(full = true) + check(returnType != Descriptor.Primitive.VOID) { + "ModifyReturnValue mixin cannot target a void method" + } val parameters = listOf(Parameter(returnType)) + modifyReturnValue.locals ?.map(Utils::getParameterFromLocal) @@ -46,4 +51,9 @@ internal class ModifyReturnValueInjector( visitEnd() } } + + context(MethodAssembly) + override fun generateNotAttachedBehavior() { + generateParameterLoad(0) + } } diff --git a/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyVariableGenerator.kt b/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyVariableGenerator.kt index b8c6dd57..b99755d1 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyVariableGenerator.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/ModifyVariableGenerator.kt @@ -1,5 +1,6 @@ package com.chattriggers.ctjs.internal.launch.generation +import codes.som.koffee.MethodAssembly import com.chattriggers.ctjs.internal.launch.Local import com.chattriggers.ctjs.internal.launch.ModifyVariable import com.chattriggers.ctjs.internal.utils.descriptorString @@ -65,4 +66,9 @@ internal class ModifyVariableGenerator( visitEnd() } } + + context(MethodAssembly) + override fun generateNotAttachedBehavior() { + generateParameterLoad(0) + } } diff --git a/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/RedirectGenerator.kt b/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/RedirectGenerator.kt index 705f74bf..a7ca6b78 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/RedirectGenerator.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/RedirectGenerator.kt @@ -1,6 +1,10 @@ package com.chattriggers.ctjs.internal.launch.generation +import codes.som.koffee.MethodAssembly +import codes.som.koffee.insns.jvm.* +import codes.som.koffee.insns.sugar.construct import com.chattriggers.ctjs.api.Mappings +import com.chattriggers.ctjs.internal.launch.At import com.chattriggers.ctjs.internal.launch.Descriptor import com.chattriggers.ctjs.internal.launch.Redirect import com.chattriggers.ctjs.internal.utils.descriptorString @@ -20,8 +24,8 @@ internal class RedirectGenerator( val parameters = mutableListOf() val returnType: Descriptor - when (val atTarget = Utils.getAtTargetDescriptor(redirect.at)) { - is Utils.InvokeAtTarget -> { + when (val atTarget = redirect.at.atTarget) { + is At.InvokeTarget -> { val descriptor = atTarget.descriptor val targetClass = Mappings.getMappedClass(descriptor.owner!!.originalDescriptor()) @@ -32,7 +36,7 @@ internal class RedirectGenerator( descriptor.parameters!!.forEach { parameters.add(Parameter(it)) } returnType = descriptor.returnType!! } - is Utils.FieldAtTarget -> { + is At.FieldTarget -> { require(atTarget.isStatic != null && atTarget.isGet != null) { "Redirect targeting FIELD expects an opcode value" } @@ -46,7 +50,7 @@ internal class RedirectGenerator( if (!atTarget.isGet) parameters.add(Parameter(atTarget.descriptor.type!!)) } - is Utils.NewAtTarget -> { + is At.NewTarget -> { atTarget.descriptor.parameters?.forEach { parameters.add(Parameter(it)) } @@ -86,4 +90,61 @@ internal class RedirectGenerator( visitEnd() } } + + context(MethodAssembly) + override fun generateNotAttachedBehavior() { + val parameters = signature.parameters.filter { it.local == null } + + when (val target = redirect.at.atTarget) { + is At.FieldTarget -> { + parameters.indices.forEach { generateParameterLoad(it) } + + val owner = target.descriptor.owner!!.toType() + val name = target.descriptor.name + val type = target.descriptor.type!!.toType() + + if (target.isGet!!) { + if (target.isStatic!!) { + getstatic(owner, name, type) + } else { + getfield(owner, name, type) + } + } else { + if (target.isStatic!!) { + putstatic(owner, name, type) + } else { + putfield(owner, name, type) + } + + // Must leave something on the stack to pop + aconst_null + } + } + is At.InvokeTarget -> { + parameters.indices.forEach { generateParameterLoad(it) } + + val owner = target.descriptor.owner!!.toType() + val name = target.descriptor.name + val returnType = target.descriptor.returnType!!.toType() + val parameterTypes = parameters.drop(1).map { + it.descriptor.toType() + }.toTypedArray() + + if (signature.isStatic) { + invokestatic(owner, name, returnType, *parameterTypes) + } else { + invokevirtual(owner, name, returnType, *parameterTypes) + } + } + is At.NewTarget -> { + construct( + target.descriptor.type.toType(), + *parameters.map { it.descriptor.toType() }.toTypedArray(), + ) { + parameters.indices.forEach { generateParameterLoad(it) } + } + } + else -> error("unreachable") + } + } } diff --git a/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/Utils.kt b/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/Utils.kt index 25707d71..d706959c 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/Utils.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/Utils.kt @@ -29,7 +29,7 @@ internal object Utils { if (at.args != null) visit("args", at.args) if (at.target != null) - visit("target", getAtTargetDescriptor(at).descriptor.mappedDescriptor()) + visit("target", at.atTarget.descriptor.mappedDescriptor()) if (at.ordinal != null) visit("ordinal", at.ordinal) if (at.opcode != null) @@ -158,7 +158,7 @@ internal object Utils { method.name.value, method.toDescriptor(), ClassInfo.SearchType.ALL_CLASSES, - ClassInfo.INCLUDE_ALL, + ClassInfo.INCLUDE_ALL or ClassInfo.INCLUDE_INITIALISERS, ) ?: continue if (value != null) @@ -201,82 +201,4 @@ internal object Utils { return InjectorGenerator.Parameter(descriptor, local) } - - sealed class AtTarget(val descriptor: T, val targetName: String) - - class InvokeAtTarget(descriptor: Descriptor.Method) : AtTarget(descriptor, "INVOKE") { - override fun toString() = descriptor.originalDescriptor() - } - - class NewAtTarget(descriptor: Descriptor.New) : AtTarget(descriptor, "NEW") { - override fun toString() = descriptor.originalDescriptor() - } - - class FieldAtTarget( - descriptor: Descriptor.Field, val isGet: Boolean?, val isStatic: Boolean?, - ) : AtTarget(descriptor, "FIELD") { - override fun toString() = descriptor.originalDescriptor() - } - - class ConstantAtTarget(val key: String, descriptor: Descriptor) : AtTarget(descriptor, "CONSTANT") { - init { - require(descriptor.isType) - } - - override fun toString() = "$key=$descriptor" - } - - fun getAtTargetDescriptor(at: At): AtTarget<*> { - return when (at.value) { - "INVOKE" -> { - requireNotNull(at.target) { "At targeting INVOKE expects its target to be a method descriptor" } - InvokeAtTarget(Descriptor.Parser(at.target).parseMethod(full = true)) - } - "NEW" -> { - requireNotNull(at.target) { "At targeting NEW expects its target to be a new invocation descriptor" } - NewAtTarget(Descriptor.Parser(at.target).parseNew(full = true)) - } - "FIELD" -> { - requireNotNull(at.target) { "At targeting FIELD expects its target to be a field descriptor" } - if (at.opcode != null) { - require( - at.opcode in setOf( - Opcodes.GETFIELD, - Opcodes.GETSTATIC, - Opcodes.PUTFIELD, - Opcodes.PUTSTATIC - ) - ) { - "At targeting FIELD expects its opcode to be one of: GETFIELD, GETSTATIC, PUTFIELD, PUTSTATIC" - } - val isGet = at.opcode == Opcodes.GETFIELD || at.opcode == Opcodes.GETSTATIC - val isStatic = at.opcode == Opcodes.GETSTATIC || at.opcode == Opcodes.PUTSTATIC - FieldAtTarget(Descriptor.Parser(at.target).parseField(full = true), isGet, isStatic) - } else { - FieldAtTarget(Descriptor.Parser(at.target).parseField(full = true), null, null) - } - } - "CONSTANT" -> { - require(at.args != null) { - "At targeting CONSTANT requires args" - } - at.args.firstNotNullOfOrNull { - val key = it.substringBefore('=') - val type = when (key) { - "null" -> Any::class.descriptor() // Is this right? - "intValue" -> Descriptor.Primitive.INT - "floatValue" -> Descriptor.Primitive.FLOAT - "longValue" -> Descriptor.Primitive.LONG - "doubleValue" -> Descriptor.Primitive.DOUBLE - "stringValue" -> String::class.descriptor() - "classValue" -> Descriptor.Object("L${it.substringAfter("=")};") - else -> return@firstNotNullOfOrNull null - } - - ConstantAtTarget(key, type) - } ?: error("At targeting CONSTANT expects a typeValue arg") - } - else -> error("Invalid At.value for Utils.getAtTarget: ${at.value}") - } - } } diff --git a/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/WrapOperationGenerator.kt b/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/WrapOperationGenerator.kt index c561b585..3026f771 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/WrapOperationGenerator.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/WrapOperationGenerator.kt @@ -1,11 +1,15 @@ package com.chattriggers.ctjs.internal.launch.generation +import codes.som.koffee.MethodAssembly +import codes.som.koffee.insns.jvm.* import com.chattriggers.ctjs.api.Mappings +import com.chattriggers.ctjs.internal.launch.At import com.chattriggers.ctjs.internal.launch.Descriptor import com.chattriggers.ctjs.internal.launch.WrapOperation import com.chattriggers.ctjs.internal.utils.descriptor import com.chattriggers.ctjs.internal.utils.descriptorString import com.llamalad7.mixinextras.injector.wrapoperation.Operation +import org.objectweb.asm.Type import org.objectweb.asm.tree.MethodNode import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation as SPWrapOperation @@ -34,8 +38,8 @@ internal class WrapOperationGenerator( parameters.add(Parameter(Operation::class.descriptor())) returnType = Descriptor.Primitive.BOOLEAN } else { - when (val atTarget = Utils.getAtTargetDescriptor(wrapOperation.at)) { - is Utils.InvokeAtTarget -> { + when (val atTarget = wrapOperation.at.atTarget) { + is At.InvokeTarget -> { val descriptor = atTarget.descriptor val targetClass = Mappings.getMappedClass(descriptor.owner!!.originalDescriptor()) @@ -51,7 +55,7 @@ internal class WrapOperationGenerator( parameters.add(Parameter(Operation::class.descriptor())) returnType = descriptor.returnType!! } - is Utils.FieldAtTarget -> { + is At.FieldTarget -> { require(atTarget.isStatic != null && atTarget.isGet != null) { "WrapOperation targeting FIELD expects an opcode value" } @@ -69,6 +73,13 @@ internal class WrapOperationGenerator( descriptor.type!! } else Descriptor.Primitive.VOID } + is At.NewTarget -> { + atTarget.descriptor.parameters!!.forEach { + parameters.add(Parameter(it)) + } + parameters.add(Parameter(Operation::class.descriptor())) + returnType = atTarget.descriptor.type + } else -> error("Unexpected At.target for WrapOperation: ${atTarget.targetName}") } } @@ -101,4 +112,26 @@ internal class WrapOperationGenerator( visitEnd() } } + + context(MethodAssembly) + override fun generateNotAttachedBehavior() { + val operationType = Type.getType(Operation::class.java) + val operationParameterIndex = signature.parameters.indexOfFirst { + it.descriptor.toType() == operationType + } + check(operationParameterIndex != -1) + + generateParameterLoad(operationParameterIndex) + ldc(operationParameterIndex) + anewarray() + + (0 until operationParameterIndex).map { + dup + ldc(it) + generateParameterLoad(it) + aastore + } + + invokeinterface(Operation::class, "call", Any::class, Array::class) + } } diff --git a/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/WrapWithConditionGenerator.kt b/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/WrapWithConditionGenerator.kt index 6cc297a5..75318b05 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/WrapWithConditionGenerator.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/internal/launch/generation/WrapWithConditionGenerator.kt @@ -1,6 +1,9 @@ package com.chattriggers.ctjs.internal.launch.generation +import codes.som.koffee.MethodAssembly +import codes.som.koffee.insns.jvm.ldc import com.chattriggers.ctjs.api.Mappings +import com.chattriggers.ctjs.internal.launch.At import com.chattriggers.ctjs.internal.launch.Descriptor import com.chattriggers.ctjs.internal.launch.WrapWithCondition import com.chattriggers.ctjs.internal.utils.descriptorString @@ -19,8 +22,8 @@ internal class WrapWithConditionGenerator( val parameters = mutableListOf() - when (val atTarget = Utils.getAtTargetDescriptor(wrapWithCondition.at)) { - is Utils.InvokeAtTarget -> { + when (val atTarget = wrapWithCondition.at.atTarget) { + is At.InvokeTarget -> { val descriptor = atTarget.descriptor val targetClass = Mappings.getMappedClass(descriptor.owner!!.originalDescriptor()) @@ -34,7 +37,7 @@ internal class WrapWithConditionGenerator( parameters.add(Parameter(it)) } } - is Utils.FieldAtTarget -> { + is At.FieldTarget -> { require(atTarget.isStatic != null && atTarget.isGet != null) { "WrapWithCondition targeting FIELD expects an opcode value" } @@ -76,4 +79,9 @@ internal class WrapWithConditionGenerator( visitEnd() } } + + context(MethodAssembly) + override fun generateNotAttachedBehavior() { + ldc(1) + } }