From a220cc51b1f03b983a12f352a8414a25287c3ee5 Mon Sep 17 00:00:00 2001 From: Matthew Olsson Date: Fri, 16 Jun 2023 19:27:13 -0700 Subject: [PATCH] (tmp) PreLaunch: Replace most uses of Asm's Type with custom Descriptor class --- build.gradle.kts | 2 +- .../ctjs/engine/langs/js/JSContextFactory.kt | 2 - .../ctjs/engine/langs/js/JSLoader.kt | 2 +- .../launch/CTJavaObjectMappingProvider.kt | 2 +- .../chattriggers/ctjs/launch/Descriptor.kt | 360 ++++++++++++++++++ .../ctjs/launch/DynamicMixinManager.kt | 2 +- .../com/chattriggers/ctjs/launch/Mappings.kt | 30 +- .../chattriggers/ctjs/launch/annotations.kt | 134 +------ .../generation/DynamicMixinGenerator.kt | 2 +- .../launch/generation/GenerationContext.kt | 10 +- .../ctjs/launch/generation/InjectGenerator.kt | 18 +- .../launch/generation/InjectorGenerator.kt | 111 +++--- .../launch/generation/InvokeDynamicSupport.kt | 3 - .../launch/generation/ModifyArgGenerator.kt | 55 +-- .../launch/generation/ModifyArgsGenerator.kt | 17 +- .../ModifyExpressionValueGenerator.kt | 28 +- .../generation/ModifyReceiverGenerator.kt | 26 +- .../launch/generation/RedirectGenerator.kt | 47 +-- .../ctjs/launch/generation/Utils.kt | 286 ++++---------- .../com/chattriggers/ctjs/utils/extensions.kt | 4 +- .../chattriggers/js/mixinProvidedLibs.js | 50 ++- 21 files changed, 678 insertions(+), 513 deletions(-) create mode 100644 src/main/kotlin/com/chattriggers/ctjs/launch/Descriptor.kt diff --git a/build.gradle.kts b/build.gradle.kts index 138c8169..e5ba46a7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -23,7 +23,7 @@ dependencies { include(modImplementation("net.fabricmc.fabric-api:fabric-api:${property("fabric_version")}")!!) include(modImplementation("net.fabricmc:fabric-language-kotlin:1.9.4+kotlin.1.8.21")!!) - include(modImplementation("com.github.ChatTriggers:rhino:8f320341a8")!!) + include(modImplementation("com.github.ChatTriggers:rhino:64e93fb497")!!) include(modImplementation("com.fasterxml.jackson.core:jackson-core:2.13.2")!!) include(modImplementation("com.fifesoft:rsyntaxtextarea:3.2.0")!!) include(modImplementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")!!) diff --git a/src/main/kotlin/com/chattriggers/ctjs/engine/langs/js/JSContextFactory.kt b/src/main/kotlin/com/chattriggers/ctjs/engine/langs/js/JSContextFactory.kt index 7456c366..bd6a7e72 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/engine/langs/js/JSContextFactory.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/engine/langs/js/JSContextFactory.kt @@ -2,8 +2,6 @@ package com.chattriggers.ctjs.engine.langs.js import com.chattriggers.ctjs.CTJS import com.chattriggers.ctjs.launch.CTJavaObjectMappingProvider -import com.chattriggers.ctjs.launch.Mappings -import net.fabricmc.loader.api.FabricLoader import org.mozilla.javascript.Context import org.mozilla.javascript.Context.EMIT_DEBUG_OUTPUT import org.mozilla.javascript.Context.FEATURE_LOCATION_INFORMATION_IN_ERROR diff --git a/src/main/kotlin/com/chattriggers/ctjs/engine/langs/js/JSLoader.kt b/src/main/kotlin/com/chattriggers/ctjs/engine/langs/js/JSLoader.kt index e604dbfe..062f2b23 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/engine/langs/js/JSLoader.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/engine/langs/js/JSLoader.kt @@ -235,7 +235,7 @@ object JSLoader : ILoader { } @JvmStatic - fun invokeMixin(func: Callable, args: Array): Any { + fun invokeMixin(func: Callable, args: Array): Any? { return wrapInContext { func.call(Context.getCurrentContext(), scope, scope, args) } diff --git a/src/main/kotlin/com/chattriggers/ctjs/launch/CTJavaObjectMappingProvider.kt b/src/main/kotlin/com/chattriggers/ctjs/launch/CTJavaObjectMappingProvider.kt index 41d862e0..2705e0eb 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/launch/CTJavaObjectMappingProvider.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/launch/CTJavaObjectMappingProvider.kt @@ -3,7 +3,7 @@ package com.chattriggers.ctjs.launch import org.mozilla.javascript.JavaObjectMappingProvider import java.lang.reflect.Modifier -object CTJavaObjectMappingProvider : JavaObjectMappingProvider { +internal object CTJavaObjectMappingProvider : JavaObjectMappingProvider { override fun findExtraMethods( clazz: Class<*>, map: MutableMap, diff --git a/src/main/kotlin/com/chattriggers/ctjs/launch/Descriptor.kt b/src/main/kotlin/com/chattriggers/ctjs/launch/Descriptor.kt new file mode 100644 index 00000000..508029b8 --- /dev/null +++ b/src/main/kotlin/com/chattriggers/ctjs/launch/Descriptor.kt @@ -0,0 +1,360 @@ +package com.chattriggers.ctjs.launch + +import com.chattriggers.ctjs.launch.generation.Utils +import org.objectweb.asm.Type + +sealed interface Descriptor { + val isType get() = true + + fun originalDescriptor(): String + fun mappedDescriptor(): String + fun toMappedType(): Type + + enum class Primitive(private val type: Type) : Descriptor { + VOID(Type.VOID_TYPE), + BOOLEAN(Type.BOOLEAN_TYPE), + CHAR(Type.CHAR_TYPE), + BYTE(Type.BYTE_TYPE), + SHORT(Type.SHORT_TYPE), + INT(Type.INT_TYPE), + FLOAT(Type.FLOAT_TYPE), + LONG(Type.LONG_TYPE), + DOUBLE(Type.DOUBLE_TYPE); + + override fun originalDescriptor(): String = type.descriptor + override fun mappedDescriptor() = originalDescriptor() + override fun toMappedType() = type + + override fun toString() = originalDescriptor() + + companion object { + val IDENTIFIERS = values().map { it.toString() } + } + } + + class Object(private val descriptor: String): Descriptor { + init { + require(descriptor !in Primitive.IDENTIFIERS) { + "Cannot pass a primitive type to Descriptor.Object" + } + require(!descriptor.startsWith('[')) { + "Cannot pass an array type to Descriptor.Object" + } + require('(' !in descriptor && ')' !in descriptor) { + "Cannot pass a method descriptor to Descriptor.Object" + } + require(':' !in descriptor) { + "Cannot pass a field descriptor to Descriptor.Object" + } + } + + override fun originalDescriptor() = descriptor + + override fun mappedDescriptor(): String { + // Do not map this class if it is not in a mapped package + for (mappedPackage in Mappings.mappedPackages) { + if (descriptor.startsWith(mappedPackage)) { + return Mappings.getMappedClassName(descriptor)?.let { + "L$it;" + } ?: error("Unknown class \"$descriptor\"") + } + } + + return descriptor + } + + override fun toMappedType(): Type = Type.getType(mappedDescriptor()) + + override fun toString() = originalDescriptor() + } + + data class Array(val base: Descriptor, val dimensions: Int) : Descriptor { + init { + require(base.isType) { + "Cannot pass a non-type object base to Descriptor.Array" + } + require(dimensions > 0) { + "Cannot pass a dimensions count less than 1 to Descritor.Array" + } + } + + override fun originalDescriptor() = "[".repeat(dimensions) + base.originalDescriptor() + + override fun mappedDescriptor() = "[".repeat(dimensions) + base.mappedDescriptor() + + override fun toMappedType(): Type = Type.getType(mappedDescriptor()) + + override fun toString() = originalDescriptor() + } + + data class Field(val owner: Object?, val name: String, val type: Descriptor?) : Descriptor { + override val isType get() = false + private val mappedName by lazy { + Mappings.getMappedClass(owner!!.originalDescriptor())?.fields?.get(name)?.name?.value + ?: error("Unknown field $name in class $owner") + } + + init { + require(type?.isType != false) { + "Cannot use non-type descriptor $type as the field type" + } + } + + override fun originalDescriptor() = buildString { + if (owner != null) + append(owner.originalDescriptor()) + append(name) + append(':') + if (type != null) + append(type.originalDescriptor()) + } + + override fun mappedDescriptor() = buildString { + require(owner != null) { + "Cannot build mapped field descriptor from incomplete field descriptor" + } + append(owner.mappedDescriptor()) + append(mappedName) + append(':') + if (type != null) + append(type.mappedDescriptor()) + } + + override fun toMappedType() = error("Cannot convert Field descriptor to Type") + + override fun toString() = originalDescriptor() + } + + data class Method( + val owner: Object?, + val name: String, + val parameters: List?, + val returnType: Descriptor?, + ) : Descriptor { + override val isType get() = false + private val mappedName by lazy { + Utils.findMethod(Mappings.getMappedClass(owner!!.originalDescriptor())!!, this).first.name.value + } + + init { + parameters?.forEach { + require(it.isType) { + "Cannot use non-type descriptor $it as a method parameter" + } + } + require(returnType?.isType != false) { + "Cannot use non-type descriptor $returnType as the method return type" + } + + require((parameters == null) == (returnType == null)) { + "Parameters and return type must both be specified or omitted in a method descriptor" + } + } + + override fun originalDescriptor() = buildString { + if (owner != null) + append(owner.originalDescriptor()) + append(name) + if (parameters != null) { + append('(') + parameters.forEach { append(it.originalDescriptor()) } + append(')') + append(returnType!!.originalDescriptor()) + } + } + + override fun mappedDescriptor() = buildString { + require(owner != null) { + "Cannot build mapped method descriptor from incomplete method descriptor" + } + append(owner.mappedDescriptor()) + append(mappedName) + if (parameters != null) { + append('(') + parameters.forEach { append(it.mappedDescriptor()) } + append(')') + append(returnType!!.mappedDescriptor()) + } + } + + override fun toMappedType(): Type = Type.getMethodType(mappedDescriptor()) + + override fun toString() = originalDescriptor() + } + + data class New(val type: Descriptor, val parameters: List?) : Descriptor { + override val isType get() = false + + init { + // TODO: What would `new int[]{...}` look like here? + require(type is Object) { + "New descriptor cannot use non-object descriptor as its return type" + } + parameters?.forEach { + require(it.isType) { + "Cannot use non-type descriptor $it as a new descriptor parameter" + } + } + } + + override fun originalDescriptor() = buildString { + if (parameters != null) { + append('(') + parameters.forEach { append(it.originalDescriptor()) } + append(')') + } + append(type.originalDescriptor()) + } + + override fun mappedDescriptor() = buildString { + if (parameters != null) { + append('(') + parameters.forEach { append(it.mappedDescriptor()) } + append(')') + } + append(type.mappedDescriptor()) + } + + override fun toMappedType() = error("Cannot convert New descriptor to Type") + + override fun toString() = originalDescriptor() + } + + class Parser(private val text: String) { + private var cursor = 0 + private val ch get() = text[cursor] + private val done get() = cursor > text.lastIndex + + init { + require(text.isNotEmpty()) { "Invalid descriptor \"$text\"" } + } + + private fun parseJavaIdentifier(): String? { + if (done || !ch.isJavaIdentifierStart()) + return null + + val start = cursor + cursor++ + while (!done && ch.isJavaIdentifierPart()) + cursor++ + return text.substring(start, cursor) + } + + fun parseType(full: Boolean = false): Descriptor { + if (full) + check(cursor == 0) + + val descriptor = if (ch == 'L') { + val semicolonIndex = text.indexOf(';', cursor) + if (semicolonIndex == -1) + error("Invalid descriptor \"$text\": expected ';' after position $cursor") + val objectType = text.substring(cursor, semicolonIndex + 1) + cursor += objectType.length + Object(objectType) + } else if (ch in "VZCBSIFJD") { + val primitive = Primitive.values().first { it.toString() == ch.toString() } + cursor++ + primitive + } else if (ch == '[') { + cursor++ + val base = parseType() + if (base is Array) { + Array(base.base, base.dimensions + 1) + } else Array(base, 1) + } else { + throw IllegalArgumentException("Invalid descriptor \"$text\": unexpected character at position $cursor") + } + + if (full) + require(done) { "Invalid descriptor: \"$text\""} + + return descriptor + } + + fun parseField(full: Boolean): Field { + check(cursor == 0) + + val owner = try { + val type = parseType() + require(type is Object) { + "Cannot create field descriptor with a non-object owner" + } + type + } catch (e: IllegalArgumentException) { + if (full) + throw e + null + } + + val name = parseJavaIdentifier() + requireNotNull(name) { "Invalid field descriptor: \"$text\"" } + + val type = if (!done && ch == ':') { + cursor++ + parseType() + } else null + + if (full) + requireNotNull(type) { "Invalid field descriptor: \"$text\"" } + + require(done) { "Invalid field descriptor: \"$text\"" } + return Field(owner, name, type) + } + + fun parseMethod(full: Boolean): Method { + check(cursor == 0) + + val owner = try { + val type = parseType() + require(type is Object) { + "Cannot create method descriptor with a non-object owner" + } + type + } catch (e: IllegalArgumentException) { + if (full) + throw e + null + } + + val name = parseJavaIdentifier() + requireNotNull(name) { "Invalid method descriptor: \"$text\"" } + + val parameters = parseParameters() + if (parameters == null && full) + throw IllegalArgumentException("Expected full method descriptor, found \"$text\"") + + val returnType = if (parameters != null) { + parseType() + } else null + + require(done) { "Invalid method descriptor: \"$text\"" } + + return Method(owner, name, parameters, returnType) + } + + fun parseNew(full: Boolean): New { + check(cursor == 0) + + val parameters = parseParameters() + if (parameters == null && full) + throw IllegalArgumentException("Expected full new descriptor, found \"$text\"") + + val type = parseType() + + require(done) { "Invalid new descriptor: \"$text\"" } + return New(type, parameters) + } + + private fun parseParameters(): List? { + return if (!done && ch == '(') { + cursor++ + val parameters = mutableListOf() + while (!done && ch != ')') + parameters.add(parseType()) + require(!done && ch == ')') { "Invalid method descriptor: \"$text\"" } + cursor++ + parameters + } else null + } + } +} diff --git a/src/main/kotlin/com/chattriggers/ctjs/launch/DynamicMixinManager.kt b/src/main/kotlin/com/chattriggers/ctjs/launch/DynamicMixinManager.kt index a3ff5e0b..fa66233d 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/launch/DynamicMixinManager.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/launch/DynamicMixinManager.kt @@ -12,7 +12,7 @@ import java.net.URL import java.net.URLConnection import java.net.URLStreamHandler -object DynamicMixinManager { +internal object DynamicMixinManager { internal const val GENERATED_PROTOCOL = "ct-generated" internal const val GENERATED_MIXIN = "ct-generated.mixins.json" internal const val GENERATED_PACKAGE = "com/chattriggers/ctjs/generated_mixins" diff --git a/src/main/kotlin/com/chattriggers/ctjs/launch/Mappings.kt b/src/main/kotlin/com/chattriggers/ctjs/launch/Mappings.kt index 9c49e4dd..4b139e22 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/launch/Mappings.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/launch/Mappings.kt @@ -14,9 +14,12 @@ import java.nio.charset.Charset import java.nio.file.Files import java.util.zip.ZipFile -object Mappings { +internal object Mappings { private const val YARN_MAPPINGS_URL_PREFIX = "https://maven.fabricmc.net/net/fabricmc/yarn/" + // If this is changed, also change the Java.type function in mixinProvidedLibs.js + val mappedPackages = setOf("Lnet/minecraft/", "Lcom/mojang/blaze3d/") + private val unmappedClasses = mutableMapOf() private val mappedToUnmappedClassNames = mutableMapOf() @@ -84,7 +87,11 @@ object Mappings { } fun getMappedClass(unmappedClassName: String): MappedClass? { - var name = unmappedClassName.normalizedForLookup() + var name = unmappedClassName.let { + (if (it.startsWith('L') && it.endsWith(';')) { + it.drop(1).dropLast(1) + } else it).replace('.', '/') + } mappedToUnmappedClassNames[name]?.also { name = it } return unmappedClasses[name] } @@ -112,7 +119,18 @@ object Mappings { val name: Mapping, val parameters: List, val returnType: Mapping, - ) + ) { + fun toDescriptor() = buildString { + append('(') + parameters.forEach { + append(it.type.value) + } + append(')') + append(returnType.value) + } + + fun toFullDescriptor() = name.value + toDescriptor() + } class MappedClass( val name: Mapping, @@ -141,10 +159,4 @@ object Mappings { private val Descriptored.mappedType: Type get() = Type.getType(getDescriptor("intermediary")) - - fun String.normalizedForLookup(): String { - return (if (startsWith('L') && endsWith(';')) { - drop(1).dropLast(1) - } else this).replace('.', '/') - } } diff --git a/src/main/kotlin/com/chattriggers/ctjs/launch/annotations.kt b/src/main/kotlin/com/chattriggers/ctjs/launch/annotations.kt index d2a4b776..a5b7f668 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/launch/annotations.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/launch/annotations.kt @@ -14,9 +14,7 @@ data class Mixin( val remap: Boolean?, ) -sealed interface IInjector - -class At( +data class At( val value: String, val id: String?, val slice: String?, @@ -28,36 +26,6 @@ class At( val opcode: Int?, val remap: Boolean?, ) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is At) return false - - if (value != other.value) return false - if (id != other.id) return false - if (slice != other.slice) return false - if (shift != other.shift) return false - if (by != other.by) return false - if (!args.listEq(other.args)) return false - if (target != other.target) return false - if (ordinal != other.ordinal) return false - if (opcode != other.opcode) return false - return remap == other.remap - } - - override fun hashCode(): Int { - var result = value.hashCode() - result = 31 * result + (id?.hashCode() ?: 0) - result = 31 * result + (slice?.hashCode() ?: 0) - result = 31 * result + (shift?.hashCode() ?: 0) - result = 31 * result + (by ?: 0) - result = 31 * result + args.listHash() - result = 31 * result + (target?.hashCode() ?: 0) - result = 31 * result + (ordinal ?: 0) - result = 31 * result + (opcode ?: 0) - result = 31 * result + (remap?.hashCode() ?: 0) - return result - } - enum class Shift { NONE, BEFORE, @@ -85,6 +53,8 @@ data class Local( val mutable: Boolean?, ) +sealed interface IInjector + data class Inject( val method: String, val id: String?, @@ -97,21 +67,9 @@ data class Inject( val expect: Int?, val allow: Int?, val constraints: String?, -) : IInjector { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is Inject) return false - - if (method != other.method) return false - return at.listEq(other.at) - } +) : IInjector - override fun hashCode(): Int { - return 31 * method.hashCode() + at.listHash() - } -} - -class Redirect( +data class Redirect( val method: String, val slice: Slice?, val at: At, @@ -120,22 +78,13 @@ class Redirect( val expect: Int?, val allow: Int?, val constraints: String?, -) : IInjector { - override fun equals(other: Any?): Boolean { - if (this === other) return true - return other is Redirect && method == other.method && at == other.at - } - - override fun hashCode(): Int { - return 31 * method.hashCode() + at.hashCode() - } -} +) : IInjector -class ModifyArg( +data class ModifyArg( val method: String, val slice: Slice?, val at: At, - val index: Int?, + val index: Int, val captureAllParams: Boolean?, val locals: List?, val remap: Boolean?, @@ -143,18 +92,9 @@ class ModifyArg( val expect: Int?, val allow: Int?, val constraints: String?, -) : IInjector { - override fun equals(other: Any?): Boolean { - if (this === other) return true - return other is ModifyArg && method == other.method && at == other.at - } - - override fun hashCode(): Int { - return 31 * method.hashCode() + at.hashCode() - } -} +) : IInjector -class ModifyArgs( +data class ModifyArgs( val method: String, val slice: Slice?, val at: At, @@ -164,18 +104,9 @@ class ModifyArgs( val expect: Int?, val allow: Int?, val constraints: String?, -) : IInjector { - override fun equals(other: Any?): Boolean { - if (this === other) return true - return other is ModifyArgs && method == other.method && at == other.at - } - - override fun hashCode(): Int { - return 31 * method.hashCode() + at.hashCode() - } -} +) : IInjector -class ModifyExpressionValue( +data class ModifyExpressionValue( val method: String, val at: At, val slice: List?, @@ -184,18 +115,9 @@ class ModifyExpressionValue( val require: Int?, val expect: Int?, val allow: Int?, -) : IInjector { - override fun equals(other: Any?): Boolean { - if (this === other) return true - return other is ModifyExpressionValue && method == other.method && at == other.at - } +) : IInjector - override fun hashCode(): Int { - return 31 * method.hashCode() + at.hashCode() - } -} - -class ModifyReceiver( +data class ModifyReceiver( val method: String, val at: At, val slice: List?, @@ -204,30 +126,4 @@ class ModifyReceiver( val require: Int?, val expect: Int?, val allow: Int?, -) : IInjector { - override fun equals(other: Any?): Boolean { - if (this === other) return true - return other is ModifyExpressionValue && method == other.method && at == other.at - } - - override fun hashCode(): Int { - return 31 * method.hashCode() + at.hashCode() - } -} - -private fun List?.listEq(other: List?): Boolean { - if ((this == null) != (other == null)) - return false - - if (this == null) - return true - - if (size != other!!.size) - return false - - return zip(other).all { it.first == it.second } -} - -private fun List?.listHash() = if (isNullOrEmpty()) 0 else { - fold(0) { acc, curr -> acc * 31 + curr.hashCode() } -} +) : IInjector 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 b3177633..f25c65c7 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/launch/generation/DynamicMixinGenerator.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/launch/generation/DynamicMixinGenerator.kt @@ -10,7 +10,7 @@ import org.objectweb.asm.Opcodes import java.io.File import org.spongepowered.asm.mixin.Mixin as SPMixin -class DynamicMixinGenerator(private val ctx: GenerationContext, private val details: ILoader.MixinDetails) { +internal class DynamicMixinGenerator(private val ctx: GenerationContext, private val details: ILoader.MixinDetails) { val generatedClassName = "CTMixin_\$${ctx.mixin.target.replace('.', '_')}\$_${mixinCounter++}" val generatedClassFullPath = "${DynamicMixinManager.GENERATED_PACKAGE}/$generatedClassName" diff --git a/src/main/kotlin/com/chattriggers/ctjs/launch/generation/GenerationContext.kt b/src/main/kotlin/com/chattriggers/ctjs/launch/generation/GenerationContext.kt index d93d199a..0d4d841e 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/launch/generation/GenerationContext.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/launch/generation/GenerationContext.kt @@ -1,16 +1,16 @@ package com.chattriggers.ctjs.launch.generation import com.chattriggers.ctjs.engine.ILoader +import com.chattriggers.ctjs.launch.Descriptor import com.chattriggers.ctjs.launch.Mappings -import com.chattriggers.ctjs.launch.Mappings.normalizedForLookup import com.chattriggers.ctjs.launch.Mixin -import org.objectweb.asm.Type import org.spongepowered.asm.mixin.transformer.ClassInfo -data class GenerationContext(val loader: ILoader, val mixin: Mixin) { +internal data class GenerationContext(val loader: ILoader, val mixin: Mixin) { val mappedClass = Mappings.getMappedClass(mixin.target) ?: error("Unknown class name ${mixin.target}") - fun findMethod(unmappedName: String, parameters: List?): Pair { - return Utils.findMethod(mappedClass, unmappedName, parameters) + fun findMethod(method: String): Pair { + val descriptor = Descriptor.Parser(method).parseMethod(full = false) + return Utils.findMethod(mappedClass, descriptor) } } diff --git a/src/main/kotlin/com/chattriggers/ctjs/launch/generation/InjectGenerator.kt b/src/main/kotlin/com/chattriggers/ctjs/launch/generation/InjectGenerator.kt index 2cc3b4f4..bc85e94b 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/launch/generation/InjectGenerator.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/launch/generation/InjectGenerator.kt @@ -1,13 +1,14 @@ package com.chattriggers.ctjs.launch.generation +import com.chattriggers.ctjs.launch.Descriptor import com.chattriggers.ctjs.launch.Inject -import org.objectweb.asm.Type +import com.chattriggers.ctjs.utils.descriptor import org.objectweb.asm.tree.MethodNode import org.spongepowered.asm.mixin.injection.callback.CallbackInfo import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable import org.spongepowered.asm.mixin.injection.Inject as SPInject -class InjectGenerator( +internal class InjectGenerator( ctx: GenerationContext, id: Int, private val inject: Inject, @@ -15,14 +16,13 @@ class InjectGenerator( override val type = "inject" override fun getInjectionSignature(): InjectionSignature { - val (_, name, possibleParameters) = Utils.splitMethodDescriptor(inject.method) - val (mappedMethod, method) = ctx.findMethod(name, possibleParameters) + val (mappedMethod, method) = ctx.findMethod(inject.method) val parameters = mutableListOf() if (mappedMethod.returnType.value == "V") { - parameters.add(Parameter(Type.getType(CallbackInfo::class.java), null)) + parameters.add(Parameter(CallbackInfo::class.descriptor(), null)) } else { - parameters.add(Parameter(Type.getType(CallbackInfoReturnable::class.java), null)) + parameters.add(Parameter(CallbackInfoReturnable::class.descriptor(), null)) } inject.locals?.forEach { @@ -30,9 +30,9 @@ class InjectGenerator( } return InjectionSignature( - name, + mappedMethod.name.original, parameters, - Type.VOID_TYPE, + Descriptor.Primitive.VOID, method.isStatic, mappedMethod, ) @@ -42,7 +42,7 @@ class InjectGenerator( node.visitAnnotation(SPInject::class.java.descriptorString(), true).apply { if (inject.id != null) visit("id", inject.id) - visit("method", signature.targetMethod.name.value) + visit("method", signature.targetMethod.toFullDescriptor()) if (inject.slice != null) visit("slice", inject.slice.map(Utils::createSliceAnnotation)) if (inject.at != null) diff --git a/src/main/kotlin/com/chattriggers/ctjs/launch/generation/InjectorGenerator.kt b/src/main/kotlin/com/chattriggers/ctjs/launch/generation/InjectorGenerator.kt index 7d4c45a8..43fd5540 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/launch/generation/InjectorGenerator.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/launch/generation/InjectorGenerator.kt @@ -3,17 +3,17 @@ package com.chattriggers.ctjs.launch.generation import codes.som.koffee.ClassAssembly import codes.som.koffee.insns.InstructionAssembly import codes.som.koffee.insns.jvm.* +import com.chattriggers.ctjs.launch.Descriptor import com.chattriggers.ctjs.launch.Local import com.chattriggers.ctjs.launch.Mappings import com.chattriggers.ctjs.utils.descriptorString -import com.chattriggers.ctjs.utils.type +import com.chattriggers.ctjs.utils.descriptor import com.llamalad7.mixinextras.sugar.ref.* import org.objectweb.asm.Handle import org.objectweb.asm.Opcodes -import org.objectweb.asm.Type import org.objectweb.asm.tree.MethodNode -abstract class InjectorGenerator(protected val ctx: GenerationContext, val id: Int) { +internal abstract class InjectorGenerator(protected val ctx: GenerationContext, val id: Int) { abstract val type: String abstract fun getInjectionSignature(): InjectionSignature @@ -32,26 +32,26 @@ abstract class InjectorGenerator(protected val ctx: GenerationContext, val id: I 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.type.internalName.startsWith("com/llamalad7/mixinextras/sugar/ref/")) { - when (it.type.sort) { - Type.BOOLEAN -> LocalBooleanRef::class.type() - Type.BYTE -> LocalByteRef::class.type() - Type.CHAR -> LocalCharRef::class.type() - Type.DOUBLE -> LocalDoubleRef::class.type() - Type.FLOAT -> LocalFloatRef::class.type() - Type.INT -> LocalIntRef::class.type() - Type.LONG -> LocalLongRef::class.type() - Type.SHORT -> LocalShortRef::class.type() - else -> LocalRef::class.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.type + } else it.descriptor } val methodNode = method( modifiers, "ctjs_${type}_$name", - returnType, - *parameterTypes.toTypedArray(), + returnType.toMappedType(), + *parameterTypes.map { it.toMappedType() }.toTypedArray(), ) { // Apply parameter annotations for (i in parameters.indices) { @@ -80,22 +80,30 @@ abstract class InjectorGenerator(protected val ctx: GenerationContext, val id: I aastore } - parameterTypes.forEachIndexed { index, type -> + parameterTypes.forEachIndexed { index, descriptor -> dup ldc(index + if (isStatic) 0 else 1) - getLoadInsn(type)(index + if (isStatic) 0 else 1) + getLoadInsn(descriptor)(index + if (isStatic) 0 else 1) // Box primitives if necessary - when (type.sort) { - Type.VOID -> TODO("is this possible?") - Type.BOOLEAN -> invokestatic(java.lang.Boolean::class, "valueOf", java.lang.Boolean::class, boolean) - Type.CHAR -> invokestatic(java.lang.Character::class, "valueOf", java.lang.Character::class, char) - Type.BYTE -> invokestatic(java.lang.Byte::class, "valueOf", java.lang.Byte::class, byte) - Type.SHORT -> invokestatic(java.lang.Short::class, "valueOf", java.lang.Short::class, short) - Type.INT -> invokestatic(java.lang.Integer::class, "valueOf", java.lang.Integer::class, int) - Type.FLOAT -> invokestatic(java.lang.Float::class, "valueOf", java.lang.Float::class, float) - Type.LONG -> invokestatic(java.lang.Long::class, "valueOf", java.lang.Long::class, long) - Type.DOUBLE -> invokestatic(java.lang.Double::class, "valueOf", java.lang.Double::class, double) + 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 -> {} } @@ -118,45 +126,45 @@ abstract class InjectorGenerator(protected val ctx: GenerationContext, val id: I arrayOf(loaderHandle, id), ) - when (returnType.sort) { - Type.VOID -> { + when (returnType) { + Descriptor.Primitive.VOID -> { pop _return } - Type.BOOLEAN -> { + Descriptor.Primitive.BOOLEAN -> { checkcast(java.lang.Boolean::class) invokevirtual(java.lang.Boolean::class, "booleanValue", boolean) ireturn } - in Type.CHAR..Type.DOUBLE -> { + is Descriptor.Primitive -> { checkcast(java.lang.Number::class) - when (returnType.sort) { - Type.CHAR -> { + when (returnType) { + Descriptor.Primitive.CHAR -> { invokevirtual(java.lang.Number::class, "charValue", char) ireturn } - Type.BYTE -> { + Descriptor.Primitive.BYTE -> { invokevirtual(java.lang.Number::class, "byteValue", byte) ireturn } - Type.SHORT -> { + Descriptor.Primitive.SHORT -> { invokevirtual(java.lang.Number::class, "shortValue", short) ireturn } - Type.INT -> { + Descriptor.Primitive.INT -> { invokevirtual(java.lang.Number::class, "intValue", int) ireturn } - Type.LONG -> { + Descriptor.Primitive.LONG -> { invokevirtual(java.lang.Number::class, "longValue", long) lreturn } - Type.FLOAT -> { + Descriptor.Primitive.FLOAT -> { invokevirtual(java.lang.Number::class, "floatValue", float) freturn } - Type.DOUBLE -> { + Descriptor.Primitive.DOUBLE -> { invokevirtual(java.lang.Number::class, "doubleValue", double) dreturn } @@ -164,7 +172,7 @@ abstract class InjectorGenerator(protected val ctx: GenerationContext, val id: I } } else -> { - checkcast(returnType) + checkcast(returnType.toMappedType()) areturn } } @@ -173,25 +181,28 @@ abstract class InjectorGenerator(protected val ctx: GenerationContext, val id: I attachAnnotation(methodNode, signature) } - private fun InstructionAssembly.getLoadInsn(type: Type): (Int) -> Unit { - return when (type.sort) { - Type.BOOLEAN, Type.BYTE, Type.SHORT, Type.INT -> ::iload - Type.LONG -> ::lload - Type.FLOAT -> ::fload - Type.DOUBLE -> ::dload + private fun InstructionAssembly.getLoadInsn(descriptor: Descriptor): (Int) -> Unit { + return when (descriptor) { + Descriptor.Primitive.BOOLEAN, + Descriptor.Primitive.BYTE, + Descriptor.Primitive.SHORT, + Descriptor.Primitive.INT -> ::iload + Descriptor.Primitive.LONG -> ::lload + Descriptor.Primitive.FLOAT -> ::fload + Descriptor.Primitive.DOUBLE -> ::dload else -> ::aload } } data class Parameter( - val type: Type, + val descriptor: Descriptor, val local: Local?, ) data class InjectionSignature( val name: String, val parameters: List, - val returnType: Type, + val returnType: Descriptor, val isStatic: Boolean, val targetMethod: Mappings.MappedMethod, ) diff --git a/src/main/kotlin/com/chattriggers/ctjs/launch/generation/InvokeDynamicSupport.kt b/src/main/kotlin/com/chattriggers/ctjs/launch/generation/InvokeDynamicSupport.kt index f67de951..0c1a5e89 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/launch/generation/InvokeDynamicSupport.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/launch/generation/InvokeDynamicSupport.kt @@ -1,8 +1,6 @@ package com.chattriggers.ctjs.launch.generation import com.chattriggers.ctjs.engine.ILoader -import com.chattriggers.ctjs.launch.MixinCallback -import com.chattriggers.ctjs.engine.module.ModuleManager import org.objectweb.asm.Handle import org.objectweb.asm.Opcodes import org.objectweb.asm.Type @@ -11,7 +9,6 @@ import java.lang.invoke.MethodHandle import java.lang.invoke.MethodHandles import java.lang.invoke.MethodType import java.lang.invoke.MutableCallSite -import java.lang.invoke.SwitchPoint import kotlin.reflect.jvm.javaMethod internal object InvokeDynamicSupport { diff --git a/src/main/kotlin/com/chattriggers/ctjs/launch/generation/ModifyArgGenerator.kt b/src/main/kotlin/com/chattriggers/ctjs/launch/generation/ModifyArgGenerator.kt index 4911f4e2..83629c55 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/launch/generation/ModifyArgGenerator.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/launch/generation/ModifyArgGenerator.kt @@ -1,13 +1,11 @@ package com.chattriggers.ctjs.launch.generation -import com.chattriggers.ctjs.launch.Mappings import com.chattriggers.ctjs.launch.ModifyArg import com.chattriggers.ctjs.utils.descriptorString -import org.objectweb.asm.Type import org.objectweb.asm.tree.MethodNode import org.spongepowered.asm.mixin.injection.ModifyArg as SPModifyArg -class ModifyArgGenerator( +internal class ModifyArgGenerator( ctx: GenerationContext, id: Int, private val modifyArg: ModifyArg, @@ -15,53 +13,31 @@ class ModifyArgGenerator( override val type = "modifyArg" override fun getInjectionSignature(): InjectionSignature { - // Resolve the injected method - val (_, name, possibleParameters) = Utils.splitMethodDescriptor(modifyArg.method) - val (mappedMethod, method) = ctx.findMethod(name, possibleParameters) + val (mappedMethod, method) = ctx.findMethod(modifyArg.method) // Resolve the target method - val rawTargetDescriptor = modifyArg.at.target - ?: error("ModifyArg injector expects its At.target to be non-null") - val targetDescriptor = Utils.splitMethodDescriptor(rawTargetDescriptor) - if (targetDescriptor.parameters == null || targetDescriptor.className == null) - error("ModifyArg injector expects its At.target to be a complete method target descriptor") - val targetMappedClass = Mappings.getMappedClass(targetDescriptor.className) - ?: error("Cannot find class ${targetDescriptor.className} specified as target in ModifyArg injector") + val atTarget = Utils.getAtTargetDescriptor(modifyArg.at) + check(atTarget is Utils.InvokeAtTarget) { "ModifyArg expects At.target to be INVOKE" } + val targetDescriptor = atTarget.descriptor + requireNotNull(targetDescriptor.parameters) - val (targetMappedMethod, _) = Utils.findMethod( - targetMappedClass, - targetDescriptor.methodName, - targetDescriptor.parameters, - ) - - if (modifyArg.index != null && modifyArg.index !in targetMappedMethod.parameters.indices) + if (modifyArg.index !in targetDescriptor.parameters.indices) error("ModifyArg received an out-of-bounds index ${modifyArg.index}") - val parameters = when { - modifyArg.captureAllParams == true -> targetMappedMethod.parameters.mapTo(mutableListOf()) { - Parameter(Type.getType(it.type.value), null) - } - modifyArg.index == null -> error("ModifyArg must capture all parameters or specify an index") - else -> { - mutableListOf( - Parameter(Type.getType(targetMappedMethod.parameters[modifyArg.index].type.value), null) - ) + val parameters = if (modifyArg.captureAllParams == true) { + targetDescriptor.parameters.mapTo(mutableListOf()) { + Parameter(it, null) } - } + } else mutableListOf(Parameter(targetDescriptor.parameters[modifyArg.index], null)) - val returnType = if (modifyArg.index != null) { - Type.getType(targetMappedMethod.parameters[modifyArg.index].type.value) - } else { - check(parameters.size == 1) // Should be true given the above checks - parameters[0].type - } + val returnType = targetDescriptor.parameters[modifyArg.index] modifyArg.locals?.forEach { parameters.add(Utils.getParameterFromLocal(it, mappedMethod)) } return InjectionSignature( - name, + mappedMethod.name.original, parameters, returnType, method.isStatic, @@ -71,12 +47,11 @@ class ModifyArgGenerator( override fun attachAnnotation(node: MethodNode, signature: InjectionSignature) { node.visitAnnotation(SPModifyArg::class.descriptorString(), true).apply { - visit("method", listOf(signature.targetMethod.name.value)) + visit("method", listOf(signature.targetMethod.toFullDescriptor())) if (modifyArg.slice != null) visit("slice", modifyArg.slice) visit("at", Utils.createAtAnnotation(modifyArg.at)) - if (modifyArg.index != null) - visit("index", modifyArg.index) + visit("index", modifyArg.index) if (modifyArg.remap != null) visit("remap", modifyArg.remap) if (modifyArg.require != null) diff --git a/src/main/kotlin/com/chattriggers/ctjs/launch/generation/ModifyArgsGenerator.kt b/src/main/kotlin/com/chattriggers/ctjs/launch/generation/ModifyArgsGenerator.kt index ea8d53f0..8ff1f1bf 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/launch/generation/ModifyArgsGenerator.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/launch/generation/ModifyArgsGenerator.kt @@ -1,13 +1,14 @@ package com.chattriggers.ctjs.launch.generation +import com.chattriggers.ctjs.launch.Descriptor import com.chattriggers.ctjs.launch.ModifyArgs +import com.chattriggers.ctjs.utils.descriptor import com.chattriggers.ctjs.utils.descriptorString -import org.objectweb.asm.Type import org.objectweb.asm.tree.MethodNode import org.spongepowered.asm.mixin.injection.invoke.arg.Args import org.spongepowered.asm.mixin.injection.ModifyArgs as SPModifyArgs -class ModifyArgsGenerator( +internal class ModifyArgsGenerator( ctx: GenerationContext, id: Int, private val modifyArgs: ModifyArgs, @@ -15,20 +16,18 @@ class ModifyArgsGenerator( override val type = "modifyArgs" override fun getInjectionSignature(): InjectionSignature { - // Resolve the injected method - val (_, name, possibleParameters) = Utils.splitMethodDescriptor(modifyArgs.method) - val (mappedMethod, method) = ctx.findMethod(name, possibleParameters) + val (mappedMethod, method) = ctx.findMethod(modifyArgs.method) val parameters = mutableListOf() - parameters.add(Parameter(Type.getType(Args::class.java), null)) + parameters.add(Parameter(Args::class.descriptor(), null)) modifyArgs.locals?.forEach { parameters.add(Utils.getParameterFromLocal(it, mappedMethod)) } return InjectionSignature( - name, + mappedMethod.name.original, parameters, - Type.VOID_TYPE, + Descriptor.Primitive.VOID, method.isStatic, mappedMethod, ) @@ -36,7 +35,7 @@ class ModifyArgsGenerator( override fun attachAnnotation(node: MethodNode, signature: InjectionSignature) { node.visitAnnotation(SPModifyArgs::class.descriptorString(), true).apply { - visit("method", listOf(signature.targetMethod.name.value)) + visit("method", listOf(signature.targetMethod.toFullDescriptor())) if (modifyArgs.slice != null) visit("slice", modifyArgs.slice) visit("at", Utils.createAtAnnotation(modifyArgs.at)) diff --git a/src/main/kotlin/com/chattriggers/ctjs/launch/generation/ModifyExpressionValueGenerator.kt b/src/main/kotlin/com/chattriggers/ctjs/launch/generation/ModifyExpressionValueGenerator.kt index 06d6fe9d..1b5f87d5 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/launch/generation/ModifyExpressionValueGenerator.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/launch/generation/ModifyExpressionValueGenerator.kt @@ -5,7 +5,7 @@ import com.chattriggers.ctjs.utils.descriptorString import org.objectweb.asm.tree.MethodNode import com.llamalad7.mixinextras.injector.ModifyExpressionValue as SPModifyExpressionValue -class ModifyExpressionValueGenerator( +internal class ModifyExpressionValueGenerator( ctx: GenerationContext, id: Int, private val modifyExpressionValue: ModifyExpressionValue, @@ -13,25 +13,25 @@ class ModifyExpressionValueGenerator( override val type = "modifyExpressionValue" override fun getInjectionSignature(): InjectionSignature { - // Resolve the injected method - val (_, name, possibleParameters) = Utils.splitMethodDescriptor(modifyExpressionValue.method) - val (mappedMethod, method) = ctx.findMethod(name, possibleParameters) - - val exprType = when (val atTarget = Utils.getAtTarget(modifyExpressionValue.at)) { - is Utils.InvokeAtTarget -> atTarget.returnType - is Utils.FieldAtTarget -> atTarget.type - is Utils.NewAtTarget -> atTarget.type - is Utils.ConstantAtTarget -> atTarget.type + 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 parameters = listOf(Parameter(exprType, null)) + modifyExpressionValue.locals?.map { + check(exprDescriptor != null && exprDescriptor.isType) + + val parameters = listOf(Parameter(exprDescriptor, null)) + modifyExpressionValue.locals?.map { Utils.getParameterFromLocal(it, mappedMethod) }.orEmpty() return InjectionSignature( - name, + mappedMethod.name.original, parameters, - exprType, + exprDescriptor, method.isStatic, mappedMethod, ) @@ -39,7 +39,7 @@ class ModifyExpressionValueGenerator( override fun attachAnnotation(node: MethodNode, signature: InjectionSignature) { node.visitAnnotation(SPModifyExpressionValue::class.descriptorString(), true).apply { - visit("method", listOf(signature.targetMethod.name.value)) + visit("method", listOf(signature.targetMethod.toFullDescriptor())) visit("at", Utils.createAtAnnotation(modifyExpressionValue.at)) if (modifyExpressionValue.slice != null) visit("slice", listOf(modifyExpressionValue.slice.map(Utils::createSliceAnnotation))) diff --git a/src/main/kotlin/com/chattriggers/ctjs/launch/generation/ModifyReceiverGenerator.kt b/src/main/kotlin/com/chattriggers/ctjs/launch/generation/ModifyReceiverGenerator.kt index 306f553f..82c5084c 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/launch/generation/ModifyReceiverGenerator.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/launch/generation/ModifyReceiverGenerator.kt @@ -5,7 +5,7 @@ import com.chattriggers.ctjs.utils.descriptorString import org.objectweb.asm.tree.MethodNode import com.llamalad7.mixinextras.injector.ModifyReceiver as SPModifyReceiver -class ModifyReceiverGenerator( +internal class ModifyReceiverGenerator( ctx: GenerationContext, id: Int, private val modifyReceiver: ModifyReceiver, @@ -13,28 +13,28 @@ class ModifyReceiverGenerator( override val type = "modifyReceiver" override fun getInjectionSignature(): InjectionSignature { - // Resolve the injected method - val (_, name, possibleParameters) = Utils.splitMethodDescriptor(modifyReceiver.method) - val (mappedMethod, method) = ctx.findMethod(name, possibleParameters) + val (mappedMethod, method) = ctx.findMethod(modifyReceiver.method) - val (owner, extraParams) = when (val atTarget = Utils.getAtTarget(modifyReceiver.at)) { - is Utils.InvokeAtTarget -> atTarget.owner to atTarget.parameters - is Utils.NewAtTarget -> atTarget.type to atTarget.parameters.orEmpty() + val (owner, extraParams) = when (val atTarget = Utils.getAtTargetDescriptor(modifyReceiver.at)) { + is Utils.InvokeAtTarget -> atTarget.descriptor.owner to atTarget.descriptor.parameters is Utils.FieldAtTarget -> { + check(atTarget.isStatic != null && atTarget.isGet != null) { + "ModifyReceiver targeting FIELD expects an opcode value" + } check(!atTarget.isStatic) { "ModifyReceiver targeting FIELD expects a non-static field access" } if (atTarget.isGet) { - atTarget.owner to emptyList() - } else atTarget.owner to listOf(atTarget.type) + atTarget.descriptor.owner to emptyList() + } else atTarget.descriptor.owner to listOf(atTarget.descriptor.type!!) } else -> error("Unsupported At.value for ModifyReceiver: ${atTarget.targetName}") } - val params = listOf(Parameter(owner, null)) + - extraParams.map { Parameter(it, null) } + + val params = listOf(Parameter(owner!!, null)) + + extraParams!!.map { Parameter(it, null) } + modifyReceiver.locals?.map { Utils.getParameterFromLocal(it, mappedMethod) }.orEmpty() return InjectionSignature( - name, + mappedMethod.name.original, params, owner, method.isStatic, @@ -44,7 +44,7 @@ class ModifyReceiverGenerator( override fun attachAnnotation(node: MethodNode, signature: InjectionSignature) { node.visitAnnotation(SPModifyReceiver::class.descriptorString(), true).apply { - visit("method", listOf(signature.targetMethod.name.value)) + visit("method", listOf(signature.targetMethod.toFullDescriptor())) visit("at", Utils.createAtAnnotation(modifyReceiver.at)) if (modifyReceiver.slice != null) visit("slice", listOf(modifyReceiver.slice.map(Utils::createSliceAnnotation))) diff --git a/src/main/kotlin/com/chattriggers/ctjs/launch/generation/RedirectGenerator.kt b/src/main/kotlin/com/chattriggers/ctjs/launch/generation/RedirectGenerator.kt index 5fa97477..78d90f92 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/launch/generation/RedirectGenerator.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/launch/generation/RedirectGenerator.kt @@ -1,14 +1,13 @@ package com.chattriggers.ctjs.launch.generation +import com.chattriggers.ctjs.launch.Descriptor import com.chattriggers.ctjs.launch.Mappings import com.chattriggers.ctjs.launch.Redirect import com.chattriggers.ctjs.utils.descriptorString -import org.objectweb.asm.Type import org.objectweb.asm.tree.MethodNode -import org.spongepowered.asm.mixin.transformer.ClassInfo import org.spongepowered.asm.mixin.injection.Redirect as SPRedirect -class RedirectGenerator( +internal class RedirectGenerator( ctx: GenerationContext, id: Int, private val redirect: Redirect, @@ -16,40 +15,42 @@ class RedirectGenerator( override val type = "redirect" override fun getInjectionSignature(): InjectionSignature { - val (_, name, possibleParameters) = Utils.splitMethodDescriptor(redirect.method) - val (mappedMethod, method) = ctx.findMethod(name, possibleParameters) + val (mappedMethod, method) = ctx.findMethod(redirect.method) val parameters = mutableListOf() - val returnType: Type + val returnType: Descriptor - when (val atTarget = Utils.getAtTarget(redirect.at)) { + when (val atTarget = Utils.getAtTargetDescriptor(redirect.at)) { is Utils.InvokeAtTarget -> { - val targetMappedClass = Mappings.getMappedClass(atTarget.owner.descriptor) - ?: error("Unknown class ${atTarget.owner.descriptor}") - val targetClass = ClassInfo.forName(targetMappedClass.name.value)!! - val targetMethod = targetClass.methods.find { it.originalName == atTarget.name } - ?: error("Unknown method ${atTarget.name} in class ${atTarget.owner.descriptor}") + val descriptor = atTarget.descriptor + + val targetClass = Mappings.getMappedClass(descriptor.owner!!.originalDescriptor()) + ?: error("Unknown class ${descriptor.owner}") + val targetMethod = Utils.findMethod(targetClass, descriptor).second if (!targetMethod.isStatic) - parameters.add(Parameter(atTarget.owner, null)) - atTarget.parameters.forEach { parameters.add(Parameter(it, null)) } - returnType = atTarget.returnType + parameters.add(Parameter(descriptor.owner, null)) + descriptor.parameters!!.forEach { parameters.add(Parameter(it, null)) } + returnType = descriptor.returnType!! } is Utils.FieldAtTarget -> { + check(atTarget.isStatic != null && atTarget.isGet != null) { + "Redirect targeting FIELD expects an opcode value" + } returnType = if (atTarget.isGet) { - atTarget.type - } else Type.VOID_TYPE + atTarget.descriptor.type!! + } else Descriptor.Primitive.VOID if (!atTarget.isStatic) - parameters.add(Parameter(atTarget.owner, null)) + parameters.add(Parameter(atTarget.descriptor.owner!!, null)) if (!atTarget.isGet) - parameters.add(Parameter(atTarget.type, null)) + parameters.add(Parameter(atTarget.descriptor.type!!, null)) } is Utils.NewAtTarget -> { - atTarget.parameters?.forEach { + atTarget.descriptor.parameters?.forEach { parameters.add(Parameter(it, null)) } - returnType = atTarget.type + returnType = atTarget.descriptor.type } else -> error("Invalid target type ${atTarget.targetName} for Redirect inject") } @@ -59,7 +60,7 @@ class RedirectGenerator( } return InjectionSignature( - name, + mappedMethod.name.original, parameters, returnType, method.isStatic, @@ -69,7 +70,7 @@ class RedirectGenerator( override fun attachAnnotation(node: MethodNode, signature: InjectionSignature) { node.visitAnnotation(SPRedirect::class.descriptorString(), true).apply { - visit("method", signature.targetMethod.name.value) + visit("method", signature.targetMethod.toFullDescriptor()) if (redirect.slice != null) visit("slice", Utils.createSliceAnnotation(redirect.slice)) visit("at", Utils.createAtAnnotation(redirect.at)) 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 39f06853..5d17f339 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/launch/generation/Utils.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/launch/generation/Utils.kt @@ -1,33 +1,32 @@ package com.chattriggers.ctjs.launch.generation -import com.chattriggers.ctjs.launch.At -import com.chattriggers.ctjs.launch.Local -import com.chattriggers.ctjs.launch.Mappings -import com.chattriggers.ctjs.launch.Slice +import com.chattriggers.ctjs.launch.* +import com.chattriggers.ctjs.utils.descriptor +import com.chattriggers.ctjs.utils.descriptorString import net.fabricmc.accesswidener.AccessWidenerReader import net.fabricmc.loader.impl.FabricLoaderImpl import org.objectweb.asm.Opcodes -import org.objectweb.asm.Type import org.objectweb.asm.tree.AnnotationNode -import org.spongepowered.asm.mixin.injection.struct.MemberInfo import org.spongepowered.asm.mixin.transformer.ClassInfo +import org.spongepowered.asm.mixin.injection.At as SPAt +import org.spongepowered.asm.mixin.injection.Slice as SPSlice internal object Utils { - private val argKVRegex = """^(\w+)=(.+)$""".toRegex() - fun createAtAnnotation(at: At): AnnotationNode { - return AnnotationNode(org.spongepowered.asm.mixin.injection.At::class.java.descriptorString()).apply { + return AnnotationNode(SPAt::class.descriptorString()).apply { if (at.id != null) visit("id", at.id) visit("value", at.value) if (at.slice != null) visit("slice", at.slice) if (at.shift != null) - visit("shift", arrayOf(org.spongepowered.asm.mixin.injection.At.Shift::class.java.descriptorString(), at.shift.name)) + visit("shift", arrayOf(SPAt.Shift::class.java.descriptorString(), at.shift.name)) if (at.by != null) visit("by", at.by) if (at.args != null) visit("args", at.args) + if (at.target != null) + visit("target", getAtTargetDescriptor(at).descriptor.mappedDescriptor()) if (at.ordinal != null) visit("ordinal", at.ordinal) if (at.opcode != null) @@ -35,17 +34,12 @@ internal object Utils { if (at.remap != null) visit("remap", at.remap) - if (at.target != null) { - // Use getAtTarget since it does the mapping for each component automatically - visit("target", getAtTarget(at).toString()) - } - visitEnd() } } fun createSliceAnnotation(slice: Slice): AnnotationNode { - return AnnotationNode(org.spongepowered.asm.mixin.injection.Slice::class.java.descriptorString()).apply { + return AnnotationNode(SPSlice::class.descriptorString()).apply { if (slice.id != null) visit("id", slice.id) if (slice.from != null) @@ -56,53 +50,6 @@ internal object Utils { } } - data class FullMethodDescriptor( - val className: String?, - val methodName: String, - val parameters: List?, - val returnType: String?, - ) - - // Accepts a method descriptor such as "foo" or "bar(I)V" and returns - // both the method name, and a list of parameters if they exist - fun splitMethodDescriptor(desc: String): FullMethodDescriptor { - var className: String? = null - var methodName: String - var parameters: List? = null - var returnType: String? = null - - if ('(' in desc) { - methodName = desc.substringBefore('(') - - val type = Type.getMethodType('(' + desc.substringAfter('(')) - parameters = type.argumentTypes.map { - if (it.sort > Type.DOUBLE) { - Mappings.getMappedClassName(it.descriptor) - ?.let { n -> return@map "L$n;" } - } - it.descriptor - } - returnType = type.returnType.let { - if (it.sort > Type.DOUBLE) { - Mappings.getMappedClassName(it.descriptor) - ?.also { n -> return@let "L$n;" } - } - it.descriptor - } - } else { - methodName = desc - } - - if (';' in methodName) { - methodName.split(';').let { - className = Mappings.getMappedClassName(it[0].drop(1)) - methodName = it[1] - } - } - - return FullMethodDescriptor(className, methodName, parameters, returnType) - } - fun widenField(mappedClass: Mappings.MappedClass, fieldName: String, isMutable: Boolean) { val field = mappedClass.fields[fieldName] ?: error("Unable to find field $fieldName in class ${mappedClass.name.original}") @@ -112,7 +59,7 @@ internal object Utils { field.name.value, field.type.value, AccessWidenerReader.AccessType.ACCESSIBLE, - false, // TODO: What is this? + false, ) if (isMutable) { @@ -121,7 +68,7 @@ internal object Utils { field.name.value, field.type.value, AccessWidenerReader.AccessType.MUTABLE, - false, // TODO: What is this? + false, ) } } @@ -131,97 +78,58 @@ internal object Utils { methodName: String, isMutable: Boolean, ) { - val (_, name, possibleParameters) = splitMethodDescriptor(methodName) - val mappedMethod = findMethod(mappedClass, name, possibleParameters).first - - val desc = buildString { - append('(') - append(mappedMethod.parameters.joinToString("") { it.type.value }) - append(')') - append(mappedMethod.returnType.value) - } + val descriptor = Descriptor.Parser(methodName).parseMethod(full = false) + val mappedMethod = findMethod(mappedClass, descriptor).first FabricLoaderImpl.INSTANCE.accessWidener.visitMethod( mappedClass.name.value, mappedMethod.name.value, - desc, + mappedMethod.toDescriptor(), AccessWidenerReader.AccessType.ACCESSIBLE, - false, // TODO: What is this? + false, ) if (isMutable) { FabricLoaderImpl.INSTANCE.accessWidener.visitMethod( mappedClass.name.value, mappedMethod.name.value, - desc, + mappedMethod.toDescriptor(), AccessWidenerReader.AccessType.MUTABLE, - false, // TODO: What is this? + false, ) } } fun findMethod( mappedClass: Mappings.MappedClass, - unmappedName: String, - parameters: List?, + descriptor: Descriptor.Method, ): Pair { - if (parameters == null) - return findMethod(mappedClass, unmappedName) - - val classInfo = ClassInfo.forName(mappedClass.name.value) - val mappedMethods = mappedClass.findMethods(unmappedName, classInfo) - ?: error("Cannot find method $unmappedName in class ${mappedClass.name.original}") - - for (method in mappedMethods) { - if (method.parameters.size != parameters.size) - continue - - if (method.parameters.zip(parameters).any { it.first.type.original != it.second }) - continue - - val desc = Type.getMethodDescriptor( - Type.getType(method.returnType.value), - *method.parameters.map { Type.getType(it.type.value) }.toTypedArray(), - ) - - val result = classInfo.findMethodInHierarchy( - method.name.value, - desc, - ClassInfo.SearchType.ALL_CLASSES, - ClassInfo.INCLUDE_ALL, - ) ?: continue + val parameters = descriptor.parameters - return method to result - } - - error("Unable to match descriptor $unmappedName(${parameters.joinToString()}) in class ${mappedClass.name.original}") - } - - fun findMethod( - mappedClass: Mappings.MappedClass, - unmappedName: String, - ): Pair { val classInfo = ClassInfo.forName(mappedClass.name.value) - val mappedMethods = mappedClass.findMethods(unmappedName, classInfo) - ?: error("Cannot find method $unmappedName in class ${mappedClass.name.original}") + val mappedMethods = mappedClass.findMethods(descriptor.name, classInfo) + ?: error("Cannot find method $${descriptor.name} in class ${mappedClass.name.original}") var value: Pair? = null for (method in mappedMethods) { - val desc = Type.getMethodDescriptor( - Type.getType(method.returnType.value), - *method.parameters.map { Type.getType(it.type.value) }.toTypedArray(), - ) + if (parameters != null) { + if (method.parameters.size != parameters.size) + continue + + if (method.parameters.zip(parameters).any { it.first.type.original != it.second.originalDescriptor() }) + continue + } val result = classInfo.findMethodInHierarchy( method.name.value, - desc, + method.toDescriptor(), ClassInfo.SearchType.ALL_CLASSES, ClassInfo.INCLUDE_ALL, ) ?: continue if (value != null) - error("Multiple methods match name $unmappedName in class ${mappedClass.name.original}, please " + + error("Multiple methods match name ${descriptor.name} in class ${mappedClass.name.original}, please " + "provide a method descriptor") value = method to result @@ -230,16 +138,16 @@ internal object Utils { if (value != null) return value - error("Unable to find method $unmappedName in class ${mappedClass.name.original}") + error("Unable to match method $descriptor in class ${mappedClass.name.original}") } fun getParameterFromLocal(local: Local, method: Mappings.MappedMethod): InjectorGenerator.Parameter { var modifiedLocal = local - val type = when { + val descriptor = when { local.print == true -> { // The type doesn't matter, it won't actually be applied - "I" + Descriptor.Primitive.INT } local.parameterName != null -> { require(local.type == null && local.index == null && local.ordinal == null) { @@ -249,7 +157,7 @@ internal object Utils { val parameter = method.parameters.find { p -> p.name.original == local.parameterName } ?: error("Could not find parameter \"${local.parameterName}\" in method ${method.name.original}") modifiedLocal = local.copy(index = parameter.lvtIndex) - parameter.type.value + Descriptor.Parser(parameter.type.value).parseType(full = true) } local.type != null -> { if (local.index != null) { @@ -261,119 +169,85 @@ internal object Utils { "Local that specifies a type must also specify an index or ordinal" } } - Mappings.getMappedClassName(local.type) ?: local.type + Descriptor.Object(local.type) } else -> error("Local must specify one of the following options: \"print\"; \"parameterName\"; " + "\"type\" and either \"ordinal\" or \"index\"") - }.let(Type::getType) + } - return InjectorGenerator.Parameter(type, modifiedLocal) - } + require(descriptor.isType) - sealed class AtTarget(val targetName: String) { - abstract override fun toString(): String + return InjectorGenerator.Parameter(descriptor, modifiedLocal) } - class InvokeAtTarget(val owner: Type, val name: String, val parameters: List, val returnType: Type) : AtTarget("INVOKE") { - override fun toString() = buildString { - append(owner.descriptor) - append(name) - append('(') - for (parameter in parameters) - append(parameter.descriptor) - append(')') - append(returnType.descriptor) - } + sealed class AtTarget(val descriptor: T, val targetName: String) + + class InvokeAtTarget(descriptor: Descriptor.Method) : AtTarget(descriptor, "INVOKE") { + override fun toString() = descriptor.originalDescriptor() } - class NewAtTarget(val type: Type, val parameters: List?) : AtTarget("NEW") { - override fun toString() = buildString { - append('(') - parameters?.forEach { append(it.descriptor) } - append(')') - append(type.descriptor) - } + class NewAtTarget(descriptor: Descriptor.New) : AtTarget(descriptor, "NEW") { + override fun toString() = descriptor.originalDescriptor() } - class FieldAtTarget(val owner: Type, val name: String, val type: Type, val isGet: Boolean, val isStatic: Boolean) : AtTarget("FIELD") { - override fun toString() = "${owner.descriptor}$name:${type.descriptor}" + class FieldAtTarget( + descriptor: Descriptor.Field, val isGet: Boolean?, val isStatic: Boolean?, + ) : AtTarget(descriptor, "FIELD") { + override fun toString() = descriptor.originalDescriptor() } - class ConstantAtTarget(val key: String, val type: Type) : AtTarget("CONSTANT") { - override fun toString() = "$key=${type.descriptor}" + class ConstantAtTarget(val key: String, descriptor: Descriptor) : AtTarget(descriptor, "CONSTANT") { + init { + require(descriptor.isType) + } + + override fun toString() = "$key=$descriptor" } - fun getAtTarget(at: At): AtTarget { + fun getAtTargetDescriptor(at: At): AtTarget<*> { return when (at.value) { "INVOKE" -> { - val info = MemberInfo.parse(at.target, null) - check(info.isFullyQualified) { "At targeting INVOKE expects its target to be a fully qualified method selector" } - val owner = (Mappings.getMappedClassName(info.owner) ?: info.owner).let { - Type.getType("L$it;") - } - val desc = Type.getMethodType(info.desc) - val parameters = desc.argumentTypes.map(Type::getDescriptor).map { - Type.getType(Mappings.getMappedClassName(it) ?: it) - } - val returnType = desc.returnType.descriptor.let { - Type.getType(Mappings.getMappedClassName(it) ?: it) - } - InvokeAtTarget(owner, info.name, parameters, returnType) + requireNotNull(at.target) { "At targeting INVOKE expects its target to be a method descriptor" } + InvokeAtTarget(Descriptor.Parser(at.target).parseMethod(full = true)) } "NEW" -> { - val args = parseAtArgs(at) - if (args != null && "class" in args) { - NewAtTarget(args["class"]!!, null) - } else { - val info = MemberInfo.parse(at.target, null) - check(info.desc.firstOrNull() == '(') { "At targeting NEW expects its target to be a constructor selector" } - val methodType = Type.getMethodType(info.desc) - NewAtTarget(methodType.returnType, methodType.argumentTypes.toList()) - } + requireNotNull(at.target) { "At targeting NEW expects its target to be a new invocation descriptor" } + NewAtTarget(Descriptor.Parser(at.target).parseNew(full = true)) } "FIELD" -> { - val info = MemberInfo.parse(at.target, null) - check('(' !in info.desc) { "At targeting FIELD expects its target to be a field selector" } - check(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 - val owner = (Mappings.getMappedClassName(info.owner) ?: info.owner).let { - Type.getType("L$it;") + 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) } - val type = Type.getType(Mappings.getMappedClassName(info.desc) ?: info.desc) - FieldAtTarget(owner, info.name, type, isGet, isStatic) } "CONSTANT" -> { - check(at.ordinal == null && at.args != null) { - "ModifyExpressionValue targeting CONSTANT requires args (ordinal is not supported)" + require(at.args != null) { + "At targeting CONSTANT requires args" } at.args.firstNotNullOfOrNull { val key = it.substringBefore('=') val type = when (key) { - "null" -> Type.getType("Ljava/lang/Object;") // Is this right? - "intValue=" -> Type.INT_TYPE - "floatValue=" -> Type.FLOAT_TYPE - "longValue=" -> Type.LONG_TYPE - "doubleValue=" -> Type.DOUBLE_TYPE - "stringValue=" -> Type.getType(String::class.java) - "classValue=" -> Type.getType("L${it.drop("classValue=".length)};") + "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("ModifyExpressionValue targeting CONSTANT expects a typeValue arg") + } ?: error("At targeting CONSTANT expects a typeValue arg") } else -> error("Invalid At.value for Utils.getAtTarget: ${at.value}") } } - - private fun parseAtArgs(at: At): Map? { - return at.args?.associate { - val match = argKVRegex.matchEntire(it) ?: error("Invalid injector arg: \"$it\"") - val type = match.groups[2]!!.value - match.groups[1]!!.value to Type.getType(Mappings.getMappedClassName(type) ?: type) - } - } } diff --git a/src/main/kotlin/com/chattriggers/ctjs/utils/extensions.kt b/src/main/kotlin/com/chattriggers/ctjs/utils/extensions.kt index 38e2ff12..a2484e03 100644 --- a/src/main/kotlin/com/chattriggers/ctjs/utils/extensions.kt +++ b/src/main/kotlin/com/chattriggers/ctjs/utils/extensions.kt @@ -1,10 +1,10 @@ package com.chattriggers.ctjs.utils +import com.chattriggers.ctjs.launch.Descriptor import com.fasterxml.jackson.core.Version import net.minecraft.util.Identifier import net.minecraft.util.math.MathHelper import org.mozilla.javascript.NativeObject -import org.objectweb.asm.Type import kotlin.reflect.KClass fun String.toVersion(): Version { @@ -36,4 +36,4 @@ fun Double.toDegrees() = this * MathHelper.DEGREES_PER_RADIAN fun Float.toDegrees() = this * MathHelper.DEGREES_PER_RADIAN fun KClass<*>.descriptorString(): String = java.descriptorString() -fun KClass<*>.type(): Type = Type.getType(java) +fun KClass<*>.descriptor() = Descriptor.Object(descriptorString()) diff --git a/src/main/resources/assets/chattriggers/js/mixinProvidedLibs.js b/src/main/resources/assets/chattriggers/js/mixinProvidedLibs.js index 89183e01..87fd1108 100644 --- a/src/main/resources/assets/chattriggers/js/mixinProvidedLibs.js +++ b/src/main/resources/assets/chattriggers/js/mixinProvidedLibs.js @@ -4,13 +4,12 @@ type(arg) { if (typeof arg !== 'string') throw new Error('Java.type expects a string as its only object'); - if (arg.startsWith('net.minecraft')) + if (arg.startsWith('net.minecraft') || arg.startsWith('com.mojang.blaze3d')) throw new Error(`Attempt to classload MC class ${arg} during Mixin application`); return Packages[arg] } } const JSLoader = Java.type('com.chattriggers.ctjs.engine.langs.js.JSLoader').INSTANCE; - const Class = Java.type('java.lang.Class'); global.Console = JSLoader.getConsole(); const MixinObj = Java.type('com.chattriggers.ctjs.launch.Mixin'); @@ -26,6 +25,43 @@ global.Opcodes = Java.type('org.objectweb.asm.Opcodes'); + // Descriptor helper + global.void_ = 'V'; + global.boolean = 'Z'; + global.char = 'C'; + global.byte = 'B'; + global.short = 'S'; + global.int = 'I'; + global.float = 'F'; + global.long = 'J'; + global.double = 'D'; + + global.desc = function desc(options) { + let str = options.owner ?? ''; + str += options.name ?? ''; + if (options.type) { + str += `:${options.type}`; + } else if (options.args) { + str += '('; + for (let arg of options.args) + str += arg; + str += ')'; + str += options.ret ?? ''; + } + return str; + } + + global.J = function(strings, exprs) { + // Transform 'a.b.c' into 'La/b/c;'; + let s = strings[0]; + for (let i = 0; exprs && i < exprs.length; i++) { + s += exprs[i]; + s += strings[i + 1]; + } + return `L${s.replaceAll('.', '/')};`; + } + + // Type assertions const checkType = (value, expectedType) => { if (typeof expectedType === 'string') { if (typeof value !== expectedType) @@ -70,7 +106,9 @@ const slice = obj.slice; const shift = obj.shift; const by = obj.by; - const args = obj.args; + let args = obj.args; + if (typeof args === 'string') + args = [args]; const target = obj.target; const ordinal = obj.ordinal; const opcode = obj.opcode; @@ -307,12 +345,13 @@ const method = obj.method ?? throw new Error('ModifyArg.method must be specified'); const slice = obj.slice; const at = obj.at ?? throw new Error('ModifyArg.at must be specified'); - const index = obj.index; + const index = obj.index ?? throw new Error('ModifyArg.index must be specified'); const captureAllParams = obj.captureAllParams; 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; @@ -324,6 +363,7 @@ assertType(captureAllParams, 'boolean', 'ModifyArg.captureAllParams'); assertArrayType(locals, Local, 'ModifyArg.locals'); assertType(remap, 'boolean', 'ModifyArg.remap'); + assertType(require, 'number', 'ModifyArg.require'); assertType(expect, 'number', 'ModifyArg.expect'); assertType(allow, 'number', 'ModifyArg.allow'); assertType(constraints, 'string', 'ModifyArg.constraints'); @@ -336,6 +376,7 @@ captureAllParams, locals?.map(l => l.localObj), remap, + require, expect, allow, constraints, @@ -451,6 +492,7 @@ expect, allow, ) + return JSLoader.registerInjector(this.mixinObj, modifyReceiverObj); } }