fix : sort troop setting app info , for QQ9.0.0+
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);//过于复杂 不如不用
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -31,7 +31,7 @@ import java.util.List;
|
||||
/**
|
||||
* 反射字段工具
|
||||
*/
|
||||
public class FieIdUtils {
|
||||
public class FieldUtils {
|
||||
|
||||
private static final HashMap<String, Field> FIELD_CACHE = new HashMap<>();
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user