fix : sort troop setting app info , for QQ9.0.0+

This commit is contained in:
suzhelan
2024-01-18 01:57:42 +08:00
parent 3cdbceac4d
commit c9b1fe9bf1
7 changed files with 145 additions and 89 deletions

View File

@@ -40,7 +40,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import top.linl.util.reflect.ClassUtils;
import top.linl.util.reflect.FieIdUtils;
import top.linl.util.reflect.FieldUtils;
import top.linl.util.reflect.MethodTool;
import xyz.nextalone.hook.CleanRecentChat;
@@ -102,7 +102,7 @@ public class FixCleanRecentChat {
//不hook onCreate方法了 那样需要重启才能生效 hook onResume可在界面重新渲染到屏幕时会调用生效
Method onCreateMethod = MethodTool.find("com.tencent.mobileqq.activity.home.Conversation").name("onResume").params(boolean.class).get();
HookUtils.hookAfterIfEnabled(cleanRecentChat, onCreateMethod, param -> {
ImageView imageView = FieIdUtils.getFirstField(param.thisObject, ImageView.class);
ImageView imageView = FieldUtils.getFirstField(param.thisObject, ImageView.class);
activity = (Activity) imageView.getContext();
imageView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
@@ -267,7 +267,7 @@ public class FixCleanRecentChat {
continue;
}
//delete util
Object util = FieIdUtils.getFirstField(recentContactItemHolder, findUtilClassType(recentContactItemHolder));//util run time obj
Object util = FieldUtils.getFirstField(recentContactItemHolder, findUtilClassType(recentContactItemHolder));//util run time obj
int adapterIndex = viewHolderEntry.getValue();//call param 1
/*
* { uid=0000,
@@ -296,7 +296,7 @@ public class FixCleanRecentChat {
continue;
}
}
Object itemBinder = FieIdUtils.getFirstField(recentContactItemHolder,
Object itemBinder = FieldUtils.getFirstField(recentContactItemHolder,
ClassUtils.getClass("com.tencent.qqnt.chats.core.adapter.holder.RecentContactItemBinding"));//call param 3
int viewId = deleteTextViewId;//call param 4
getDeleteMethod(recentContactItemHolder).invoke(util, adapterIndex, itemInfo, itemBinder, viewId);

View File

@@ -11,7 +11,7 @@ import io.github.qauxv.dsl.FunctionEntryRouter;
import io.github.qauxv.hook.CommonSwitchFunctionHook;
import io.github.qauxv.util.Initiator;
import java.lang.reflect.Method;
import top.linl.util.reflect.FieIdUtils;
import top.linl.util.reflect.FieldUtils;
import top.linl.util.reflect.MethodTool;
@@ -29,7 +29,7 @@ public class OffRelationshipIdentification extends CommonSwitchFunctionHook {
"onDataUpdate",
Initiator.loadClass("com.tencent.mobileqq.profilecard.data.ProfileCardInfo"));
HookUtils.hookAfterIfEnabled(this, method, param -> {
Object recyclerView = FieIdUtils.getFirstField(param.thisObject,
Object recyclerView = FieldUtils.getFirstField(param.thisObject,
Initiator.loadClass("com.tencent.biz.richframework.widget.listview.card.RFWCardListView"));
if (recyclerView == null) {
return;

View File

@@ -37,8 +37,11 @@ import io.github.qauxv.base.annotation.UiItemAgentEntry;
import io.github.qauxv.dsl.FunctionEntryRouter;
import io.github.qauxv.hook.CommonSwitchFunctionHook;
import java.lang.reflect.Method;
import java.util.List;
import java.util.ListIterator;
import top.linl.util.ScreenParamUtils;
import top.linl.util.reflect.FieIdUtils;
import top.linl.util.reflect.ClassUtils;
import top.linl.util.reflect.FieldUtils;
import top.linl.util.reflect.MethodTool;
@FunctionHookEntry
@@ -61,6 +64,44 @@ public class SortTroopSettingAppListView extends CommonSwitchFunctionHook {
@Override
protected boolean initOnce() throws Exception {
try {
//由于我记得是qq是在8996重构了群设置页面 但是网上并无此版本安装包记录 所以决定采用异常捕获的方法来适配已经重构了群设置页的QQ
Class<?> troopSettingFragmentV2 = ClassUtils.getClass("com.tencent.mobileqq.troop.troopsetting.activity.TroopSettingFragmentV2");
Method onViewCreatedAfterPartInitMethod = MethodTool.find(troopSettingFragmentV2)
.name("onViewCreatedAfterPartInit")
.params(android.view.View.class, android.os.Bundle.class)
.returnType(void.class)
.get();
HookUtils.hookBeforeIfEnabled(this,onViewCreatedAfterPartInitMethod,param -> {
List<Object> partList = FieldUtils.getFirstField(param.thisObject, List.class);
Class<?> troopAppClass = ClassUtils.getClass("com.tencent.mobileqq.troop.troopsetting.part.TroopSettingAppPart");
Class<?> memberInfoPartClass = ClassUtils.getClass("com.tencent.mobileqq.troop.troopsetting.part.TroopSettingMemberInfoPart");
Object troopAppPart = null;
ListIterator<Object> iterator = partList.listIterator();
int memberInfoPartIndex = 0;
boolean isStopSelfIncrementing = false;
while (iterator.hasNext()) {
if (!isStopSelfIncrementing) memberInfoPartIndex++;
Object nextPart = iterator.next();
//定位群成员卡片(part)的位置索引
if (nextPart.getClass() == memberInfoPartClass) {
isStopSelfIncrementing = true;
}
//获取群应用的part
if (nextPart.getClass() == troopAppClass) {
troopAppPart = nextPart;
iterator.remove();
break;
}
}
if (troopAppPart == null) throw new RuntimeException("troop app list is null");
partList.add(memberInfoPartIndex, troopAppPart);
});
return true;
} catch (Exception e) {
//此处可能会在9.0.0前捕获到异常ReflectException : 没有找到类: com.tencent.mobileqq.troop.troopsetting.activity.TroopSettingFragmentV2
}
//如果抛出异常则会进行下面的代码 以保持对旧版本NT的适配
Method doOnCreateMethod = MethodTool.find("com.tencent.mobileqq.troop.troopsetting.activity.TroopSettingActivity")
.returnType(boolean.class)
.params(Bundle.class)
@@ -69,7 +110,7 @@ public class SortTroopSettingAppListView extends CommonSwitchFunctionHook {
HookUtils.hookAfterIfEnabled(this, doOnCreateMethod, new HookUtils.AfterHookedMethod() {
@Override
public void afterHookedMethod(XC_MethodHook.MethodHookParam param) throws Throwable {
LinearLayout rootView = FieIdUtils.getFirstField(param.thisObject, LinearLayout.class);
LinearLayout rootView = FieldUtils.getFirstField(param.thisObject, LinearLayout.class);
int troopInfoTextIndex = 0;
View troopAppListView = null;
// View[] views = FieIdUtils.getFirstField(param.thisObject, View[].class);//过于复杂 不如不用

View File

@@ -30,7 +30,7 @@ import io.github.qauxv.dsl.FunctionEntryRouter;
import io.github.qauxv.hook.CommonSwitchFunctionHook;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import top.linl.util.reflect.FieIdUtils;
import top.linl.util.reflect.FieldUtils;
import top.linl.util.reflect.MethodTool;
@FunctionHookEntry
@@ -52,7 +52,7 @@ public class TurnOffFriendInteractionLogoView extends CommonSwitchFunctionHook {
.returnType(void.class)
.get();
HookUtils.hookBeforeIfEnabled(this, initViewMethod, param -> {
Field mViewContainerField = FieIdUtils.findUnknownTypeField(param.thisObject.getClass(), "mViewContainer");
Field mViewContainerField = FieldUtils.findUnknownTypeField(param.thisObject.getClass(), "mViewContainer");
mViewContainerField.set(param.thisObject, 1);
param.setResult(null);
});

View File

@@ -9,6 +9,7 @@ import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
public class ClassUtils {
@@ -19,12 +20,17 @@ public class ClassUtils {
static {
setHostClassLoader(Initiator.getHostClassLoader());
setModuleLoader(Initiator.getPluginClassLoader());
}
/**
* 获取基本类型
*/
private static Class<?> getBaseTypeClass(String baseTypeName) {
if (baseTypeName.length() == 1) {
return findSimpleType(baseTypeName.charAt(0));
}
for (Object[] baseType : baseTypes) {
if (baseTypeName.equals(baseType[0])) {
return (Class<?>) baseType[1];
@@ -33,6 +39,35 @@ public class ClassUtils {
throw new ReflectException(baseTypeName + " <-不是基本的数据类型");
}
/**
* conversion base type
*
* @param simpleType Smali Base Type V,Z,B,I...
*/
public static Class<?> findSimpleType(char simpleType) {
switch (simpleType) {
case 'V':
return void.class;
case 'Z':
return boolean.class;
case 'B':
return byte.class;
case 'S':
return short.class;
case 'C':
return char.class;
case 'I':
return int.class;
case 'J':
return long.class;
case 'F':
return float.class;
case 'D':
return double.class;
}
throw new RuntimeException("Not an underlying type");
}
/**
* 排除常用类
*/
@@ -83,7 +118,6 @@ public class ClassUtils {
this.oldClassLoader = classLoader;
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
Class<?> clazz = CLASS_CACHE.get(name);
@@ -100,6 +134,7 @@ public class ClassUtils {
} catch (Exception e) {
clazz = oldClassLoader.loadClass(name.substring(index + 1));
}
//转换数组类型
for (int i = 0; i < name.length(); i++) {
char ch = name.charAt(i);
if (ch == '[') {
@@ -135,7 +170,6 @@ public class ClassUtils {
return oldClassLoader.getResources(name);
}
@Override
public InputStream getResourceAsStream(String name) {
return oldClassLoader.getResourceAsStream(name);

View File

@@ -31,7 +31,7 @@ import java.util.List;
/**
* 反射字段工具
*/
public class FieIdUtils {
public class FieldUtils {
private static final HashMap<String, Field> FIELD_CACHE = new HashMap<>();

View File

@@ -1,53 +1,37 @@
/*
* QAuxiliary - An Xposed module for QQ/TIM
* Copyright (C) 2019-2023 QAuxiliary developers
* https://github.com/cinit/QAuxiliary
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version and our eula as published
* by QAuxiliary contributors.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
* <https://github.com/cinit/QAuxiliary/blob/master/LICENSE.md>.
*/
package top.linl.util.reflect;
import io.github.qauxv.util.Initiator;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class MethodTool {
private static final Map<String, Method> METHOD_CACHE = new HashMap<>();
private TargetMethodInfo targetMethod;
private TargetMethodInfo targetMethodInfo;
private MethodTool() {
}
/**
* @param findClassName 要查找的类名
*/
public static MethodTool find(String findClassName) {
MethodTool methodTool = new MethodTool();
methodTool.targetMethod = new TargetMethodInfo();
methodTool.targetMethod.findClassName = findClassName;
methodTool.targetMethodInfo = new TargetMethodInfo();
methodTool.targetMethodInfo.findClassName = findClassName;
return methodTool;
}
/**
* @param findClass 要查找的类
*/
public static MethodTool find(Class<?> findClass) {
MethodTool methodTool = new MethodTool();
methodTool.targetMethod = new TargetMethodInfo();
methodTool.targetMethod.findClass = findClass;
methodTool.targetMethodInfo = new TargetMethodInfo();
methodTool.targetMethodInfo.findClass = findClass;
methodTool.targetMethodInfo.findClassName = findClass.getName();
return methodTool;
}
@@ -59,95 +43,92 @@ public class MethodTool {
private static StringBuilder buildMethodSignature(String findClass, String methodName, Class<?>[] paramTypes, Class<?> returnType) {
StringBuilder sb = new StringBuilder();
sb.append(findClass).append(".").append(methodName).append("(");
for (Class<?> type : paramTypes) {
sb.append(type.getName()).append(",");
}
/*char splitChar = '\0';
for (Class<?> type : paramTypes) {
sb.append(splitChar);
splitChar = ',';
sb.append(type.getName());
}*/
if (sb.charAt(sb.length() - 1) == ',') {
sb.delete(sb.length() - 1, sb.length());
}
if (sb.charAt(sb.length() - 1) == ',') sb.delete(sb.length() - 1, sb.length());
sb.append(")");
if (returnType != null) {
sb.append(returnType.getName());
}
if (returnType != null) sb.append(returnType.getName());
return sb;
}
public MethodTool name(String name) {
this.targetMethod.methodName = name;
this.targetMethodInfo.methodName = name;
return this;
}
public MethodTool returnType(Class<?> returnType) {
this.targetMethod.returnType = returnType;
this.targetMethodInfo.returnType = returnType;
return this;
}
public MethodTool params(Class<?>... methodParamsType) {
this.targetMethod.methodParams = methodParamsType;
this.targetMethodInfo.methodParams = methodParamsType;
return this;
}
public <T> T call(Object target, Object... params) {
public <T> T call(Object runtimeObject, Object... params) {
try {
Method method = get();
return (T) method.invoke(target, params);
return (T) method.invoke(runtimeObject, params);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public <T> T callStatic(Object... params) {
try {
Method method = get();
return (T) method.invoke(null, params);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public Method get() {
TargetMethodInfo target = this.targetMethod;
TargetMethodInfo target = this.targetMethodInfo;
//构造方法签名
String signature = buildMethodSignature(target.findClassName, targetMethod.methodName, target.methodParams, target.returnType).toString();
String signature = buildMethodSignature(target.findClassName, targetMethodInfo.methodName, target.methodParams, target.returnType).toString();
if (METHOD_CACHE.containsKey(signature)) {
return METHOD_CACHE.get(signature);
}
try {
for (Class<?> currentFindClass = target.findClass == null ? Initiator.loadClass(target.findClassName) : target.findClass;
currentFindClass != Object.class; currentFindClass = currentFindClass.getSuperclass()) {
MethodFor:
for (Method method : currentFindClass.getDeclaredMethods()) {
if ((method.getName().equals(target.methodName) || target.methodName == null) && (method.getReturnType().equals(target.returnType)
|| target.returnType == null)) {
Class<?>[] methodParams = method.getParameterTypes();
if (methodParams.length == target.methodParams.length) {
for (int i = 0; i < methodParams.length; i++) {
if (target.methodParams[i] == Object.class) {
continue;
}
if (!Objects.equals(methodParams[i], target.methodParams[i])) {
continue MethodFor;
}
if (!CheckClassType.CheckClass(methodParams[i], target.methodParams[i])) {
continue MethodFor;
}
}
method.setAccessible(true);
METHOD_CACHE.put(signature, method);
return method;
for (Class<?> currentFindClass = target.findClass == null ? ClassUtils.getClass(target.findClassName) : target.findClass; currentFindClass != Object.class; currentFindClass = currentFindClass.getSuperclass()) {
MethodFor:
for (Method method : currentFindClass.getDeclaredMethods()) {
if ((target.methodName == null || method.getName().equals(target.methodName))
&& (target.returnType == null || method.getReturnType().equals(target.returnType))) {
Class<?>[] methodParams = method.getParameterTypes();
if (methodParams.length == target.methodParams.length) {
for (int i = 0; i < methodParams.length; i++) {
//如果是obj则直接视该类型为正确的
if (target.methodParams[i] == Object.class) continue;
if (!Objects.equals(methodParams[i], target.methodParams[i]))
continue MethodFor;
if (!CheckClassType.CheckClass(methodParams[i], target.methodParams[i]))
continue MethodFor;
}
method.setAccessible(true);
METHOD_CACHE.put(signature, method);
return method;
}
}
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
throw new ReflectException("没有查找到方法 : " + signature);
}
private static class TargetMethodInfo {
public class TypeMatcher {
private String startString;
public TypeMatcher(String startWithACharacter) {
this.startString = startWithACharacter;
}
}
private static class TargetMethodInfo {
public Class<?> findClass;
public String findClassName;
public String methodName;