fix: migration side effects

This commit is contained in:
particle-box
2026-03-03 23:37:30 +05:30
parent 1ccdaee879
commit d6e74cb207
8 changed files with 141 additions and 19 deletions

View File

@@ -1501,7 +1501,8 @@ class BulkMessagingAction : AbstractAction() {
val addFriendMethodName = addFriend14Method.get() ?: return@runCatching context.log.error("Could not find add friend method name")
val sourceTypeClass = sourceType.getAsClass() ?: return@runCatching context.log.error("Could not find source type class")
val pageTypeClass = pageType.getAsClass() ?: return@runCatching context.log.error("Could not find page type class")
val method = f9lClass.declaredMethods.firstOrNull { it.name == addFriendMethodName }
val method = f9lClass.methods.firstOrNull { it.name == addFriendMethodName }
?: f9lClass.declaredMethods.firstOrNull { it.name == addFriendMethodName }
?: return@runCatching context.log.error("Could not find $addFriendMethodName method")
fun findStaticField(clazz: Class<*>): Any? = clazz.findStaticObjectFieldByType(clazz)
val enumClass = method.parameterTypes[2]

View File

@@ -99,7 +99,8 @@ class ManageFriendList : AbstractAction() {
val sourceTypeClass = sourceType.getAsClass() ?: return@runCatching context.log.error("Could not find source type class")
val pageTypeClass = pageType.getAsClass() ?: return@runCatching context.log.error("Could not find page type class")
val method = f9lClass.declaredMethods.firstOrNull { it.name == addFriendMethodName }
val method = f9lClass.methods.firstOrNull { it.name == addFriendMethodName }
?: f9lClass.declaredMethods.firstOrNull { it.name == addFriendMethodName }
?: return@runCatching context.log.error("Could not find $addFriendMethodName method")
// Helper function to find static field by trying fallback

View File

@@ -530,11 +530,14 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
}
val operaLayerList = (param.thisObject() as Any).getObjectField(layerListField.get()!!) as ArrayList<*>
val mediaParamMap: ParamMap = operaLayerList.map { Layer(it) }.first().paramMap
if (!mediaParamMap.containsKey("image_media_info") && !mediaParamMap.containsKey("video_media_info_list")) {
return@onOperaViewStateCallback
}
val mediaParamMap: ParamMap = operaLayerList
.asSequence()
.mapNotNull { layerObj ->
layerObj?.let { runCatching { Layer(it).paramMap }.getOrNull() }
}
.firstOrNull {
it.containsKey("image_media_info") || it.containsKey("video_media_info_list")
} ?: return@onOperaViewStateCallback
val mediaInfoMap = mutableMapOf<SplitMediaAssetType, MediaInfo>()
val isVideo = mediaParamMap.containsKey("video_media_info_list")

View File

@@ -89,7 +89,15 @@ class MessageSender(
}
private fun internalSendMessage(conversations: List<SnapUUID>, localMessageContentTemplate: String, callback: Any) {
val sendMessageWithContentMethod = context.classCache.conversationManager.declaredMethods.first { it.name == "sendMessageWithContent" }
val sendMessageWithContentMethod = sequence {
var current: Class<*>? = context.classCache.conversationManager
while (current != null && current != Any::class.java && current != Object::class.java) {
yield(current)
current = current.superclass
}
}.flatMap { clazz -> clazz.declaredMethods.asSequence() }
.firstOrNull { it.name == "sendMessageWithContent" }
?: throw NoSuchMethodException("sendMessageWithContent")
val localMessageContent = context.gson.fromJson(localMessageContentTemplate, context.classCache.localMessageContent)
val messageDestinations = MessageDestinations(AbstractWrapper.newEmptyInstance(context.classCache.messageDestinations)).also {

View File

@@ -98,11 +98,13 @@ class CoreScriptHooker: AbstractBinding("hooker", BindingSide.CORE) {
}
fun findMethod(clazz: Class<*>, methodName: String): Member? {
return clazz.declaredMethods.find { it.name == methodName }
return collectMethods(clazz).firstOrNull { it.name == methodName }
}
fun findMethodWithParameters(clazz: Class<*>, methodName: String, vararg types: String): Member? {
return clazz.declaredMethods.find { method -> method.name == methodName && method.parameterTypes.map { it.name }.toTypedArray() contentEquals types }
return collectMethods(clazz).firstOrNull { method ->
method.name == methodName && method.parameterTypes.map { it.name }.toTypedArray() contentEquals types
}
}
fun findMethod(className: String, methodName: String): Member? {
@@ -159,10 +161,45 @@ class CoreScriptHooker: AbstractBinding("hooker", BindingSide.CORE) {
fun hookAllConstructors(className: String, stage: String, callback: HookCallback)
= findClassSafe(className)?.let { hookAllConstructors(it, stage, callback) }
private fun collectMethods(clazz: Class<*>): List<Method> {
val methods = LinkedHashMap<String, Method>()
val visited = HashSet<Class<*>>()
var currentClass: Class<*>? = clazz
while (currentClass != null && currentClass != Any::class.java && currentClass != Object::class.java) {
collectMethodsRecursive(currentClass, methods, visited)
currentClass = currentClass.superclass
}
return methods.values.toList()
}
private fun collectMethodsRecursive(
clazz: Class<*>,
methods: MutableMap<String, Method>,
visited: MutableSet<Class<*>>
) {
if (!visited.add(clazz)) return
clazz.declaredMethods.forEach { method ->
val signature = buildString {
append(method.name)
append("#")
method.parameterTypes.forEach {
append(it.name)
append(";")
}
}
methods.putIfAbsent(signature, method)
}
clazz.interfaces.forEach { collectMethodsRecursive(it, methods, visited) }
}
override fun onDispose() {
hooks.forEach { it() }
hooks.clear()
}
override fun getObject() = this
}
}

View File

@@ -46,9 +46,8 @@ object Hooker {
stage: HookStage,
crossinline filter: (HookAdapter) -> Boolean,
noinline consumer: (HookAdapter) -> Unit
): Set<HookHandle> = clazz.declaredMethods
): Set<HookHandle> = collectMethods(clazz, methodName)
.asSequence()
.filter { it.name == methodName }
.map { method ->
method.isAccessible = true
val hookResult = YukiHookCompat.hookMember(method, newMethodHook(stage, consumer, filter))
@@ -167,6 +166,46 @@ object Hooker {
unhooks.forEach{ it.unhook() }
}.also { unhooks.addAll(it) }
}
@PublishedApi
internal fun collectMethods(clazz: Class<*>, methodName: String): List<Method> {
val methods = LinkedHashMap<String, Method>()
val visited = HashSet<Class<*>>()
var currentClass: Class<*>? = clazz
while (currentClass != null && currentClass != Any::class.java && currentClass != Object::class.java) {
collectMethodsRecursive(currentClass, methodName, methods, visited)
currentClass = currentClass.superclass
}
return methods.values.toList()
}
private fun collectMethodsRecursive(
clazz: Class<*>,
methodName: String,
methods: MutableMap<String, Method>,
visited: MutableSet<Class<*>>
) {
if (!visited.add(clazz)) return
clazz.declaredMethods
.asSequence()
.filter { it.name == methodName }
.forEach { method ->
val signature = buildString {
append(method.name)
append("#")
method.parameterTypes.forEach {
append(it.name)
append(";")
}
}
methods.putIfAbsent(signature, method)
}
clazz.interfaces.forEach { collectMethodsRecursive(it, methodName, methods, visited) }
}
}
fun Class<*>.hookConstructor(

View File

@@ -1,7 +1,11 @@
package me.eternal.purrfectsnap.core.util.ktx
fun Any.getObjectField(fieldName: String): Any? {
return KavaRefFieldBridge.getField(this, fieldName)
return runCatching {
KavaRefFieldBridge.getField(this, fieldName)
}.getOrElse {
findFieldRecursive(this::class.java, fieldName).also { it.isAccessible = true }.get(this)
}
}
fun Any.findFieldNamesByType(type: Class<*>): List<String> {
@@ -13,7 +17,11 @@ fun Any.allFieldNames(): List<String> {
}
fun Class<*>.getStaticObjectField(fieldName: String): Any? {
return KavaRefFieldBridge.getStaticField(this, fieldName)
return runCatching {
KavaRefFieldBridge.getStaticField(this, fieldName)
}.getOrElse {
findFieldRecursive(this, fieldName).also { it.isAccessible = true }.get(null)
}
}
fun Class<*>.findStaticObjectFieldByType(type: Class<*>): Any? {
@@ -21,14 +29,22 @@ fun Class<*>.findStaticObjectFieldByType(type: Class<*>): Any? {
}
fun Any.setEnumField(fieldName: String, value: String) {
val enumType = KavaRefFieldBridge.getFieldType(this, fieldName)
val enumType = runCatching {
KavaRefFieldBridge.getFieldType(this, fieldName)
}.getOrElse {
findFieldRecursive(this::class.java, fieldName).type
}
enumType.enumConstants?.firstOrNull { it.toString() == value }?.let { enum ->
setObjectField(fieldName, enum)
}
}
fun Any.setObjectField(fieldName: String, value: Any?) {
KavaRefFieldBridge.setField(this, fieldName, value)
runCatching {
KavaRefFieldBridge.setField(this, fieldName, value)
}.getOrElse {
findFieldRecursive(this::class.java, fieldName).also { f -> f.isAccessible = true }.set(this, value)
}
}
fun Any.getObjectFieldOrNull(fieldName: String): Any? {
@@ -39,3 +55,12 @@ fun Any.getObjectFieldOrNull(fieldName: String): Any? {
}
}
private fun findFieldRecursive(clazz: Class<*>, fieldName: String): java.lang.reflect.Field {
var current: Class<*>? = clazz
while (current != null && current != Any::class.java && current != Object::class.java) {
runCatching { return current.getDeclaredField(fieldName) }
current = current.superclass
}
throw NoSuchFieldException("${clazz.name}#$fieldName")
}

View File

@@ -15,7 +15,15 @@ class ConversationManager(
val context: ModContext,
obj: Any
) : AbstractWrapper(obj) {
private fun findMethodByName(name: String) = context.classCache.conversationManager.declaredMethods.find { it.name == name } ?: throw RuntimeException("Could not find method $name")
private fun findMethodByName(name: String) = sequence {
var current: Class<*>? = context.classCache.conversationManager
while (current != null && current != Any::class.java && current != Object::class.java) {
yield(current)
current = current.superclass
}
}.flatMap { clazz -> clazz.declaredMethods.asSequence() }
.firstOrNull { it.name == name }
?: throw RuntimeException("Could not find method $name")
private val updateMessageMethod by lazy { findMethodByName("updateMessage") }
private val fetchConversationWithMessagesPaginatedMethod by lazy { findMethodByName("fetchConversationWithMessagesPaginated") }
@@ -204,4 +212,4 @@ class ConversationManager(
.override("onError") { onError(it.arg<Any>(0).toString()) }.build()
)
}
}
}