diff --git a/src/main/kotlin/com/chattriggers/ctjs/launch/annotations.kt b/src/main/kotlin/com/chattriggers/ctjs/launch/annotations.kt index 8a4e094d..aa41405e 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/launch/annotations.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/launch/annotations.kt @@ -179,6 +179,23 @@ data class ModifyReturnValue( val allow: Int?, ) : IInjector +data class ModifyVariable( + val method: String, + val at: At, + val slice: Slice?, + val print: Boolean?, + val ordinal: Int?, + val index: Int?, + val type: String?, + val parameterName: String?, + val locals: List?, + val remap: Boolean?, + val require: Int?, + val expect: Int?, + val allow: Int?, + val constraints: String?, +) : IInjector + data class WrapOperation( val method: String, val at: At?, diff --git a/src/main/kotlin/com/chattriggers/ctjs/launch/generation/DynamicMixinGenerator.kt b/src/main/kotlin/com/chattriggers/ctjs/launch/generation/DynamicMixinGenerator.kt index 5f0d8e15..1989e9f9 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/launch/generation/DynamicMixinGenerator.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/launch/generation/DynamicMixinGenerator.kt @@ -26,6 +26,7 @@ internal class DynamicMixinGenerator(private val ctx: GenerationContext, private is ModifyExpressionValue -> ModifyExpressionValueGenerator(ctx, id, injector).generate() is ModifyReceiver -> ModifyReceiverGenerator(ctx, id, injector).generate() is ModifyReturnValue -> ModifyReturnValueInjector(ctx, id, injector).generate() + is ModifyVariable -> ModifyVariableGenerator(ctx, id, injector).generate() is WrapOperation -> WrapOperationGenerator(ctx, id, injector).generate() is WrapWithCondition -> WrapWithConditionGenerator(ctx, id, injector).generate() } diff --git a/src/main/kotlin/com/chattriggers/ctjs/launch/generation/ModifyVariableGenerator.kt b/src/main/kotlin/com/chattriggers/ctjs/launch/generation/ModifyVariableGenerator.kt new file mode 100644 index 00000000..1ac437f2 --- /dev/null +++ b/src/main/kotlin/com/chattriggers/ctjs/launch/generation/ModifyVariableGenerator.kt @@ -0,0 +1,69 @@ +package com.chattriggers.ctjs.launch.generation + +import com.chattriggers.ctjs.launch.Local +import com.chattriggers.ctjs.launch.ModifyVariable +import com.chattriggers.ctjs.utils.descriptorString +import org.objectweb.asm.tree.MethodNode +import org.spongepowered.asm.mixin.injection.ModifyVariable as SPModifyVariable + +internal class ModifyVariableGenerator( + ctx: GenerationContext, + id: Int, + private var modifyVariable: ModifyVariable, +) : InjectorGenerator(ctx, id) { + override val type = "modifyVariable" + + override fun getInjectionSignature(): InjectionSignature { + val (mappedMethod, method) = ctx.findMethod(modifyVariable.method) + + // Construct a temporary local so we can call Utils.getParameterFromLocal + val tempLocal = Local( + modifyVariable.print, + modifyVariable.parameterName, + modifyVariable.index, + modifyVariable.ordinal, + modifyVariable.type, + mutable = false, + ) + + val parameter = Utils.getParameterFromLocal(tempLocal, mappedMethod, name = "ModifyVariable") + + // Update our ModifyVariable annotation since we may have changed the index + modifyVariable = modifyVariable.copy( + index = parameter.local?.index ?: modifyVariable.index, + ) + + return InjectionSignature( + mappedMethod, + listOf(parameter.copy(local = null)), + parameter.descriptor, + method.isStatic, + ) + } + + override fun attachAnnotation(node: MethodNode, signature: InjectionSignature) { + node.visitAnnotation(SPModifyVariable::class.descriptorString(), true).apply { + visit("method", listOf(signature.targetMethod.toFullDescriptor())) + visit("at", Utils.createAtAnnotation(modifyVariable.at)) + if (modifyVariable.slice != null) + visit("slice", Utils.createSliceAnnotation(modifyVariable.slice!!)) + if (modifyVariable.print != null) + visit("print", modifyVariable.print) + if (modifyVariable.ordinal != null) + visit("ordinal", modifyVariable.ordinal) + if (modifyVariable.index != null) + visit("index", modifyVariable.index) + if (modifyVariable.remap != null) + visit("remap", modifyVariable.remap) + if (modifyVariable.require != null) + visit("require", modifyVariable.require) + if (modifyVariable.expect != null) + visit("expect", modifyVariable.expect) + if (modifyVariable.allow != null) + visit("allow", modifyVariable.allow) + if (modifyVariable.constraints != null) + visit("constraints", modifyVariable.constraints) + visitEnd() + } + } +} diff --git a/src/main/kotlin/com/chattriggers/ctjs/launch/generation/Utils.kt b/src/main/kotlin/com/chattriggers/ctjs/launch/generation/Utils.kt index f82b1745..a869eeec 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/launch/generation/Utils.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/launch/generation/Utils.kt @@ -173,7 +173,11 @@ internal object Utils { error("Unable to match method $descriptor in class ${mappedClass.name.original}") } - fun getParameterFromLocal(local: Local, method: Mappings.MappedMethod): InjectorGenerator.Parameter { + fun getParameterFromLocal( + local: Local, + method: Mappings.MappedMethod, + name: String = "Local", + ): InjectorGenerator.Parameter { var modifiedLocal = local val descriptor = when { @@ -183,7 +187,7 @@ internal object Utils { } local.parameterName != null -> { require(local.type == null && local.index == null && local.ordinal == null) { - "Local that specifies parameterName cannot specify type, index, or ordinal" + "$name that specifies parameterName cannot specify type, index, or ordinal" } val parameter = method.parameters.find { p -> p.name.original == local.parameterName } @@ -194,16 +198,16 @@ internal object Utils { local.type != null -> { if (local.index != null) { require(local.ordinal == null) { - "Local that specifies a type and index cannot specify an ordinal" + "$name that specifies a type and index cannot specify an ordinal" } } else { require(local.ordinal != null) { - "Local that specifies a type must also specify an index or ordinal" + "$name that specifies a type must also specify an index or ordinal" } } - Descriptor.Object(local.type) + Descriptor.Parser(local.type).parseType(full = true) } - else -> error("Local must specify one of the following options: \"print\"; \"parameterName\"; " + + else -> error("$name must specify one of the following options: \"print\"; \"parameterName\"; " + "\"type\" and either \"ordinal\" or \"index\"") } diff --git a/src/main/resources/assets/chattriggers/js/mixinProvidedLibs.js b/src/main/resources/assets/chattriggers/js/mixinProvidedLibs.js index f9d87262..d043d5b1 100644 --- a/src/main/resources/assets/chattriggers/js/mixinProvidedLibs.js +++ b/src/main/resources/assets/chattriggers/js/mixinProvidedLibs.js @@ -27,6 +27,7 @@ const ModifyExpressionValueObj = Java.type('com.chattriggers.ctjs.launch.ModifyExpressionValue'); const ModifyReceiverObj = Java.type('com.chattriggers.ctjs.launch.ModifyReceiver'); const ModifyReturnValueObj = Java.type('com.chattriggers.ctjs.launch.ModifyReturnValue'); + const ModifyVariableObj = Java.type('com.chattriggers.ctjs.launch.ModifyVariable'); const WrapOperationObj = Java.type('com.chattriggers.ctjs.launch.WrapOperation'); const WrapWithConditionObj = Java.type('com.chattriggers.ctjs.launch.WrapWithCondition'); @@ -297,6 +298,12 @@ return this._createModifyReturnValue(obj); } + modifyVariable(obj) { + if (typeof obj != 'object') + throw new Error('Mixin.modifyVariable() expects an object as its first argument'); + return this._createModifyVariable(obj); + } + wrapOperation(obj) { if (typeof obj != 'object') throw new Error('Mixin.wrapOperation() expects an object as its first argument'); @@ -641,11 +648,65 @@ require, expect, allow, - ) + ); return JSLoader.registerInjector(this.mixinObj, modifyReturnValueObj); } + _createModifyVariable(obj) { + const method = obj.method ?? throw new Error('ModifyVariable.method must be specified'); + const at = obj.at ?? throw new Error('ModifyVariable.at must be specified'); + const slice = obj.slice; + const print = obj.print; + const ordinal = obj.ordinal; + const index = obj.index; + const type = obj.type; + const parameterName = obj.parameterName; + let locals = obj.locals; + if (locals instanceof Local) + locals = [locals]; + const remap = obj.remap; + const require = obj.require; + const expect = obj.expect; + const allow = obj.allow; + const constraints = obj.constraints; + + assertType(method, 'string', 'ModifyVariable.method'); + assertType(at, At, 'ModifyVariable.at'); + assertType(slice, Slice, 'ModifyVariable.slice'); + assertType(remap, 'boolean', 'ModifyVariable.remap'); + assertType(print, 'boolean', 'ModifyVariable.print'); + assertType(ordinal, 'number', 'ModifyVariable.ordinal'); + assertType(index, 'number', 'ModifyVariable.index'); + assertType(type, 'string', 'ModifyVariable.type'); + assertType(parameterName, 'string', 'ModifyVariable.type'); + assertArrayType(locals, Local, 'ModifyVariable.locals'); + assertType(remap, 'number', 'ModifyVariable.remap'); + assertType(require, 'number', 'ModifyVariable.require'); + assertType(expect, 'number', 'ModifyVariable.expect'); + assertType(allow, 'number', 'ModifyVariable.allow'); + assertType(constraints, 'string', 'ModifyVariable.constraints'); + + const modifyVariableObj = new ModifyVariableObj( + method, + at?.atObj, + slice?.sliceObj, + print, + ordinal, + index, + type, + parameterName, + locals?.map(l => l.localObj), + remap, + require, + expect, + allow, + constraints, + ); + + return JSLoader.registerInjector(this.mixinObj, modifyVariableObj); + } + _createWrapOperation(obj) { const method = obj.method ?? throw new Error('WrapOperation.method must be specified'); const at = obj.at;