Merge remote-tracking branch 'origin/main'

# Conflicts:
#	app/src/main/java/me/singleneuron/hook/AppCenterHook.kt
#	gradle/wrapper/gradle-wrapper.properties
This commit is contained in:
2024-07-25 21:00:19 +08:00
243 changed files with 11529 additions and 1212 deletions

View File

@@ -60,7 +60,7 @@ jobs:
echo "sdk.dir=${ANDROID_HOME}" > local.properties
- name: Setup Gradle
uses: gradle/gradle-build-action@v3.4.1
uses: gradle/gradle-build-action@v3.5.0
- name: Build with Gradle
run: |

View File

@@ -60,7 +60,7 @@ jobs:
restore-keys: native-cache-
- name: Setup Gradle
uses: gradle/gradle-build-action@v3.4.1
uses: gradle/gradle-build-action@v3.5.0
- name: Build with Gradle
run: |

3
.gitmodules vendored
View File

@@ -37,3 +37,6 @@
path = libs/LSPlant
url = https://github.com/LSPosed/LSPlant
shallow = true
[submodule "libs/libxposed/api"]
path = libs/libxposed/api
url = https://github.com/libxposed/api

View File

@@ -43,10 +43,17 @@ plugins {
id("build-logic.android.application")
alias(libs.plugins.changelog)
alias(libs.plugins.ksp)
alias(libs.plugins.protobuf)
alias(libs.plugins.serialization)
alias(libs.plugins.aboutlibraries)
}
// ------ buildscript config ------
val buildAllAbiForDebug = false
val isNewXposedApiEnabled = true
val fullNativeDebugMode = false
val currentBuildUuid = UUID.randomUUID().toString()
println("Current build ID is $currentBuildUuid")
@@ -58,8 +65,6 @@ if (ccacheExecutablePath != null) {
println("No ccache found.")
}
val fullNativeDebugMode = false
fun getSignatureKeyDigest(signConfig: SigningConfig?): String? {
var key1: String? = if (signConfig != null && signConfig.storeFile != null) {
// extract certificate digest
@@ -173,8 +178,10 @@ android {
if (fullNativeDebugMode) {
isJniDebuggable = true
} else {
@Suppress("ChromeOsAbiSupport")
abiFilters += arrayOf("arm64-v8a", "armeabi-v7a")
if (!buildAllAbiForDebug) {
@Suppress("ChromeOsAbiSupport")
abiFilters += arrayOf("arm64-v8a", "armeabi-v7a")
}
}
}
isCrunchPngs = false
@@ -205,12 +212,17 @@ android {
)
}
packaging {
resources.excludes.addAll(arrayOf(
"META-INF/**",
"kotlin/**",
"**.bin",
"kotlin-tooling-metadata.json"
))
// libxposed API uses META-INF/xposed
resources.excludes.addAll(
arrayOf(
"kotlin/**",
"**.bin",
"kotlin-tooling-metadata.json"
)
)
if (!isNewXposedApiEnabled) {
resources.excludes.add("META-INF/xposed/**")
}
}
buildFeatures {
@@ -247,15 +259,24 @@ kotlin {
sourceSets.configureEach {
kotlin.srcDir("$buildDir/generated/ksp/$name/kotlin/")
}
sourceSets.main {
kotlin.srcDir(File(rootDir, "libs/ezxhelper/src/main/java"))
}
}
dependencies {
// loader
compileOnly(projects.loader.hookapi)
runtimeOnly(projects.loader.sbl)
implementation(projects.loader.startup)
// ksp
ksp(projects.libs.ksp)
// host stub
compileOnly(projects.libs.stub)
// libraries
implementation(projects.libs.mmkv)
implementation(projects.libs.dexkit)
implementation(projects.libs.xView)
ksp(projects.libs.ksp)
compileOnly(libs.xposed)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.browser)
@@ -269,7 +290,6 @@ dependencies {
implementation(libs.colorpicker)
implementation(libs.material.dialogs.core)
implementation(libs.material.dialogs.input)
implementation(libs.ezXHelper)
// festival title
implementation(libs.confetti)
implementation(libs.weatherView)
@@ -281,7 +301,7 @@ dependencies {
implementation(libs.byte.buddy)
implementation(libs.dalvik.dx)
ksp(libs.sealedEnum.ksp)
implementation(libs.google.protobuf)
implementation(libs.google.protobuf.java)
}
val adb: String = androidComponents.sdkComponents.adb.get().asFile.absolutePath
@@ -474,3 +494,21 @@ val generateEulaAndPrivacy by tasks.registering {
}
}
}
// see https://github.com/google/protobuf-gradle-plugin/issues/518
protobuf {
protoc {
artifact = libs.google.protobuf.protoc.get().toString()
}
plugins {
generateProtoTasks {
all().forEach {
it.builtins {
create("java") {
option("lite")
}
}
}
}
}
}

View File

@@ -75,6 +75,11 @@
-dontwarn com.sun.jna.**
-dontwarn edu.umd.cs.findbugs.annotations.**
-dontwarn java.lang.instrument.**
# Xposed API
-dontwarn de.robv.android.xposed.**
-dontwarn io.github.libxposed.api.**
-keep class com.android.dx.** {
*;
}

View File

@@ -22,7 +22,7 @@
package hook
import android.content.Intent
import de.robv.android.xposed.XC_MethodHook
import io.github.qauxv.util.xpcompat.XC_MethodHook
import io.github.qauxv.base.annotation.FunctionHookEntry
import io.github.qauxv.base.annotation.UiItemAgentEntry
import io.github.qauxv.config.ConfigManager

View File

@@ -21,6 +21,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<permission
android:name="${applicationId}.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
android:protectionLevel="signature"
@@ -36,6 +37,7 @@
android:multiArch="true"
android:name=".util.hookstatus.ModuleAppImpl"
android:resizeableActivity="true"
android:description="@string/xposeddescription"
android:theme="@style/AppTheme.Def"
tools:ignore="AllowBackup,GoogleAppIndexingWarning,UnusedAttribute">
<activity

View File

@@ -1 +1 @@
io.github.qauxv.startup.HookEntry
io.github.qauxv.loader.sbl.xp51.Xp51HookEntry

View File

@@ -63,6 +63,7 @@ add_library(qauxv SHARED
qauxv_core/SilkCodec.cc
qauxv_core/HostInfo.cc
qauxv_core/NativeCoreBridge.cc
qauxv_core/linker_utils.cc
utils/shared_memory.cpp
utils/auto_close_fd.cc

View File

@@ -75,7 +75,7 @@ namespace teble::v2sign {
const uint32_t v2Id = 0x7109871a;
std::string getModulePath(JNIEnv *env) {
jclass cMainHook = env->FindClass("io/github/qauxv/startup/HookEntry");
jclass cMainHook = env->FindClass("io/github/qauxv/core/MainHook");
jclass cClass = env->FindClass("java/lang/Class");
jmethodID mGetClassLoader = env->GetMethodID(cClass, "getClassLoader",
"()Ljava/lang/ClassLoader;");

View File

@@ -16,6 +16,7 @@
#include <optional>
#include <sys/mman.h>
#include <ucontext.h>
#include <dlfcn.h>
#include <type_traits>
//#include <chrono>
#include <unordered_map>
@@ -32,7 +33,9 @@
#include "utils/AobScanUtils.h"
#include "utils/MemoryUtils.h"
#include "utils/arch_utils.h"
#include "utils/endian.h"
#include "qauxv_core/natives_utils.h"
#include "qauxv_core/linker_utils.h"
#ifndef STACK_GUARD
// for debug purpose only
@@ -52,251 +55,10 @@ jclass klassRevokeMsgHook = nullptr;
jobject gInstanceRevokeMsgHook = nullptr;
jmethodID handleRecallSysMsgFromNtKernel = nullptr;
uintptr_t gOffsetGetDecoderSp = 0;
uintptr_t gOffsetForTmpRev5048 = 0;
NOINLINE
uint64_t ThunkGetInt64Property(const void* thiz, int property) {
// vtable
// 4160. [[this+8]+0x58]
void* thisp8 = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(thiz) + 8);
uintptr_t vtable = *reinterpret_cast<uintptr_t*>(thisp8);
void* func = *reinterpret_cast<void**>(vtable + 0x58);
return reinterpret_cast<decltype(ThunkGetInt64Property)*>(func)(thisp8, property);
}
NOINLINE
uint32_t ThunkGetInt32Property(const void* thiz, int property) {
// vtable
// 4160. [[this+8]+0x38]
void* thisp8 = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(thiz) + 8);
uintptr_t vtable = *reinterpret_cast<uintptr_t*>(thisp8);
void* func = *reinterpret_cast<void**>(vtable + 0x38);
return reinterpret_cast<decltype(ThunkGetInt32Property)*>(func)(thisp8, property);
}
NOINLINE
std::string ThunkGetStringProperty(void* thiz, int property) {
// vtable
// 4160. [[this+8]+0x70]
void* thisp8 = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(thiz) + 8);
uintptr_t vtable = *reinterpret_cast<uintptr_t*>(thisp8);
void* func = *reinterpret_cast<void**>(vtable + 0x70);
return reinterpret_cast<decltype(ThunkGetStringProperty)*>(func)(thisp8, property);
}
template<typename ReturnType, uintptr_t vtableOffset, uintptr_t thizOffset, typename... ArgTypes>
requires((std::is_same_v<ReturnType, void> || std::is_integral_v<ReturnType> || std::is_pointer_v<ReturnType>)
&& ((std::is_integral_v<ArgTypes> || std::is_pointer_v<ArgTypes>) && ...))
NOINLINE
ReturnType vcall(void* thiz, ArgTypes... args) {
// vtable
// [[this+thizOff]+offsetVT]
void* thisp8 = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(thiz) + thizOffset);
uintptr_t vtable = *reinterpret_cast<uintptr_t*>(thisp8);
void* func = *reinterpret_cast<void**>(vtable + vtableOffset);
if constexpr (std::is_same_v<ReturnType, void>) {
reinterpret_cast<ReturnType(*)(void*, ArgTypes...)>(func)(thisp8, args...);
return;
} else {
return reinterpret_cast<ReturnType(*)(void*, ArgTypes...)>(func)(thisp8, args...);
}
}
template<typename... ArgTypes>
requires(((std::is_integral_v<ArgTypes> || std::is_pointer_v<ArgTypes>) && ...))
NOINLINE
void vcall_x8_v2(void* thiz, uintptr_t vtableOffset, uintptr_t thizOffset, void* x8, ArgTypes... args) {
// vtable
// [[this+thizOff]+offsetVT]
void* thisp8 = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(thiz) + thizOffset);
uintptr_t vtable = *reinterpret_cast<uintptr_t*>(thisp8);
void* func = *reinterpret_cast<void**>(vtable + vtableOffset);
static_assert(sizeof...(args) <= 3);
std::array<void*, 3> argArray = {reinterpret_cast<void*>(args)...};
call_func_with_x8(func, x8, thisp8, argArray[0], argArray[1], argArray[2]);
}
// helper for uintptr_t as this
template<typename ReturnType, uintptr_t vtableOffset, uintptr_t thizOffset, typename... ArgTypes>
requires((std::is_same_v<ReturnType, void> || std::is_integral_v<ReturnType> || std::is_pointer_v<ReturnType>)
&& ((std::is_integral_v<ArgTypes> || std::is_pointer_v<ArgTypes>) && ...))
static inline ReturnType vcall(uintptr_t thiz, ArgTypes... args) {
if constexpr (std::is_same_v<ReturnType, void>) {
vcall<vtableOffset, thizOffset, ArgTypes...>(reinterpret_cast<void*>(thiz), args...);
return;
} else {
return vcall<vtableOffset, thizOffset, ArgTypes...>(reinterpret_cast<void*>(thiz), args...);
}
}
template<typename... ArgTypes> requires((std::is_integral_v<ArgTypes> || std::is_pointer_v<ArgTypes>) && ...)
static inline void vcall_x8_v2(uintptr_t thiz, uintptr_t vtableOffset, uintptr_t thizOffset, void* x8, ArgTypes... args) {
vcall_x8_v2<ArgTypes...>(reinterpret_cast<void*>(thiz), vtableOffset, thizOffset, x8, args...);
}
//void ThunkCallAPI(void* x0, uintptr_t api_caller_id, int x2, int x3, int& x4, std::string& x5) {
// // 4160. 0x00cc0750
// // "CallAPI"
// // "!!! RegisterAPIHandler Error crash: api_caller_id is empty can not use You can use GlobalAPI or set other value to api_caller_id !!!"
// auto func = reinterpret_cast<decltype(ThunkCallAPI)*>((uintptr_t) gLibkernelBaseAddress + 0x00cc0750);
// func(x0, api_caller_id, x2, x3, x4, x5);
//}
class RevokeMsgInfoAccess {
public:
struct UnknownObjectStub16 {
void* _unk0_8;
void* _unk8_8;
};
};
void NotifyRecallSysMsgEvent(int chatType, const std::string& peerUid, const std::string& recallOpUid, const std::string& msgAuthorUid,
const std::string& toUid, uint64_t random64, uint64_t timeSeconds, uint64_t msgUid, uint64_t msgSeq, uint32_t msgClientSeq) {
JavaVM* vm = HostInfo::GetJavaVM();
if (vm == nullptr) {
LOGE("NotifyRecallSysMsgEvent fatal vm == null");
return;
}
if (klassRevokeMsgHook == nullptr) {
LOGE("NotifyRecallSysMsgEvent fatal klassRevokeMsgHook == null");
return;
}
// check if current thread is attached to jvm
JNIEnv* env = nullptr;
bool isAttachedManually = false;
jint err = vm->GetEnv((void**) &env, JNI_VERSION_1_6);
if (err == JNI_EDETACHED) {
if (vm->AttachCurrentThread(&env, nullptr) != JNI_OK) {
LOGE("NotifyRecallSysMsgEvent fatal AttachCurrentThread failed");
return;
}
isAttachedManually = true;
} else if (env == nullptr) {
LOGE("NotifyRecallSysMsgEvent fatal GetEnv failed, err = {}", err);
return;
}
// call java method
env->CallStaticVoidMethod(klassRevokeMsgHook, handleRecallSysMsgFromNtKernel,
jint(chatType), env->NewStringUTF(peerUid.c_str()), env->NewStringUTF(recallOpUid.c_str()),
env->NewStringUTF(msgAuthorUid.c_str()), env->NewStringUTF(toUid.c_str()),
jlong(random64), jlong(timeSeconds), jlong(msgUid), jlong(msgSeq), jint(msgClientSeq));
// check if exception occurred
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
env->ExceptionClear();
}
// detach thread if attached manually
if (isAttachedManually) {
vm->DetachCurrentThread();
}
}
void NotifyRecallMsgEventForC2c(const std::string& fromUid, const std::string& toUid,
uint64_t random64, uint64_t timeSeconds,
uint64_t msgUid, uint64_t msgSeq, uint32_t msgClientSeq) {
NotifyRecallSysMsgEvent(1, fromUid, fromUid, fromUid, toUid, random64, timeSeconds, msgUid, msgSeq, msgClientSeq);
}
void NotifyRecallMsgEventForGroup(const std::string& peerUid, const std::string& recallOpUid, const std::string& msgAuthorUid,
uint64_t random64, uint64_t timeSeconds, uint64_t msgSeq) {
NotifyRecallSysMsgEvent(2, peerUid, recallOpUid, msgAuthorUid, peerUid, random64, timeSeconds, 0, msgSeq, 0);
}
void (* sOriginHandleGroupRecallSysMsgCallback)(void*, void*, void*) = nullptr;
void HandleGroupRecallSysMsgCallback([[maybe_unused]] void* x0, void* x1, [[maybe_unused]] void* x2, [[maybe_unused]] int x3) {
// LOGD("HandleGroupRecallSysMsgCallback start p1={:p}, p2={:p}, p3={:p}", x0, x1, x2);
// we can still do it... hitherto p3 == null... we need to decode the message manually...
uintptr_t base = (uintptr_t) gLibkernelBaseAddress;
void* pVar1 = *(void**) x1;
if ((vcall<int, 0x118, 8, int>(pVar1, 3) & 1) == 0) {
LOGE("msg_recall: HandleRecallSysMsg: on recall group sys msg! hasn't msg_common::Msg::kBody");
return;
}
void* pVar2 = *(void**) x1;
STACK_GUARD;
std::array<uint8_t, 0x100> objVar1 = {}; // actual size unknown, maybe 0x90
STACK_GUARD;
vcall_x8_v2<int>(pVar2, gOffsetForTmpRev5048, 0, &objVar1, 3);
auto pVar3 = *(void**) (objVar1.data());
if (pVar3 == nullptr) {
LOGE("msg_recall: HandleRecallSysMsg: on recall group sys msg! msg_common::Msg::kBody = null");
return;
}
if ((vcall<int, 0x118, 8, int>(pVar3, 2) & 1) == 0) {
LOGE("msg_recall: HandleRecallSysMsg: on recall group sys msg! hasn't im_msg_body::MsgBody::kBytesMsgContent");
return;
}
STACK_GUARD;
std::vector<uint8_t> msgContentBytes;
STACK_GUARD;
vcall_x8_v2<int>(pVar3, 0x78, 8, &msgContentBytes, 2);
if (msgContentBytes.size() < 8) {
LOGE("msg_recall: HandleRecallSysMsg: on recall group sys msg! im_msg_body::MsgBody::kBytesMsgContent is error");
return;
}
std::vector<uint8_t> content = {msgContentBytes.begin() + 7, msgContentBytes.end()};
STACK_GUARD;
std::array<void*, 2> objVar3 = {}; // actual size 0x10, maybe shared_ptr, but we don't have dtor
STACK_GUARD;
call_func_with_x8((void*) (base + gOffsetGetDecoderSp), &objVar3, 0, 0, 0, 0);
void* notifyMsgBody = objVar3[0];
if ((vcall<int, 0x100, 8, std::vector<uint8_t>*>(notifyMsgBody, &content) & 1) == 0) {
LOGE("on recall group sys msg! decode kBytesMsgContent fail");
return;
}
uint64_t groupCode = vcall<uint64_t, 0x58, 8, int>(notifyMsgBody, 4);
if (groupCode == 0) {
LOGE("on recall group sys msg! group code is 0");
return;
}
uint64_t opType = vcall<uint64_t, 0x58, 8, int>(notifyMsgBody, 1);
if (opType != 7) {
LOGW("HandleGroupRecallSysMsgCallback: on recall group sys msg! no Prompt_MsgRecallReminder op_type:{}", opType);
return;
}
if ((vcall<uint64_t, 0x118, 8, int>(notifyMsgBody, 0xb) & 1) == 0) {
LOGE("HandleGroupRecallSysMsgCallback: on recall group sys msg! no NotifyMsgBody::opt_msg_recall");
return;
}
STACK_GUARD;
std::array<void*, 2> optMsgRecall = {}; // actual size 0x10, maybe shared_ptr, but we don't have dtor
STACK_GUARD;
vcall_x8_v2<int>(notifyMsgBody, gOffsetForTmpRev5048, 0, &optMsgRecall, 0xb);
if (optMsgRecall[0] == nullptr) {
LOGE("HandleGroupRecallSysMsgCallback: on recall group sys msg! NotifyMsgBody::opt_msg_recall == null");
return;
}
if ((vcall<int, 0x118, 8, int>(optMsgRecall[0], 3) & 1) == 0) {
LOGE("HandleGroupRecallSysMsgCallback: on recall group sys msg! on recall group sys msg! no msg_infos");
return;
}
std::array<void*, 3> vectorResultStub = {nullptr, nullptr, nullptr};
vcall_x8_v2<int>(optMsgRecall[0], 0xf0, 8, &vectorResultStub, 3);
std::string recallOpUid = ThunkGetStringProperty(optMsgRecall[0], 1);
const auto& msgInfoList = *reinterpret_cast<const std::vector<RevokeMsgInfoAccess::UnknownObjectStub16>*>(&vectorResultStub);
if (msgInfoList.empty()) {
LOGE("HandleGroupRecallSysMsgCallback: on recall group sys msg! no any msg info");
return;
}
std::string peerUid = fmt::format("{}", groupCode);
for (const auto& msgInfo: msgInfoList) {
uint32_t msgSeq = ThunkGetInt32Property(msgInfo._unk0_8, 1);
uint32_t random = ThunkGetInt32Property(msgInfo._unk0_8, 3);
uint64_t time = ThunkGetInt64Property(msgInfo._unk0_8, 2);
std::string msgAuthorUid = ThunkGetStringProperty(msgInfo._unk0_8, 6);
// Unfortunately, I didn't find a way to find the origMsgSenderUid.
// The only thing we can do is to get message by msgSeq, and get senderUid from it, iff we have the message.
NotifyRecallMsgEventForGroup(peerUid, recallOpUid, msgAuthorUid, random, time, msgSeq);
}
}
void (* sOriginHandleC2cRecallSysMsgCallback)(void*, void*, void*) = nullptr;
@@ -306,55 +68,85 @@ void HandleC2cRecallSysMsgCallback([[maybe_unused]] void* p1, [[maybe_unused]] v
LOGE("HandleC2cGroupSysMsgCallback BUG !!! *p3 = null, this should not happen!!!");
return;
}
std::array<void*, 3> vectorResultStub = {nullptr, nullptr, nullptr};
void* v1 = *(void**) p3;
void** v2 = (void**) ((uintptr_t) v1 + 8);
// 4160. 0xf0
auto func = reinterpret_cast<std::array<void*, 3>(*)(void*, int)>(*(void**) ((uintptr_t) *v2 + 0xf0));
// XXX: memory leak, no dtor available
vectorResultStub = func(v2, 1);
static_assert(sizeof(std::vector<int>) == sizeof(std::array<void*, 3>), "libcxx vector size not match");
const auto& objects = *reinterpret_cast<const std::vector<RevokeMsgInfoAccess::UnknownObjectStub16>*>(&vectorResultStub);
if (!objects.empty()) {
// void* x0 = nullptr;
// uintptr_t x1 = 0;
// {
// uint8_t* param_1 = static_cast<uint8_t*>(p1) + 8;
// uint8_t* pbVar1;
// uint64_t uVar2;
// uVar2 = *(uint64_t*) (param_1 + 8);
// pbVar1 = *(uint8_t**) (param_1 + 0x10);
// if ((*param_1 & 1) == 0) {
// pbVar1 = param_1 + 1;
// uVar2 = (uint64_t) (*param_1 >> 1);
// }
// x0 = pbVar1;
// x1 = uVar2;
// }
// int tmpInt = 0x138b;
// std::string tmpString;
// ThunkCallAPI(x0, x1, 0x10, 1, tmpInt, tmpString);
for (const auto& obj: objects) {
auto fromUid = ThunkGetStringProperty(obj._unk0_8, 1);
auto toUid = ThunkGetStringProperty(obj._unk0_8, 2);
auto randomId = ThunkGetInt64Property(obj._unk0_8, 6);
auto timeSeconds = ThunkGetInt64Property(obj._unk0_8, 5);
auto msgUid = ThunkGetInt64Property(obj._unk0_8, 4);
auto msgSeq = ThunkGetInt64Property(obj._unk0_8, 0x14);
auto msgClientSeq = ThunkGetInt32Property(obj._unk0_8, 3);
NotifyRecallMsgEventForC2c(fromUid, toUid, randomId, timeSeconds, msgUid, msgSeq, msgClientSeq);
}
}
// LOGD("HandleC2cRecallSysMsgCallback start p1={:p}, p2={:p}, p3={:p}", p1, p2, p3);
}
// Nobody uses PaiYiPai, right?
bool PerformNtRecallMsgHook(uint64_t baseAddress) {
if (sIsHooked) {
return false;
}
sIsHooked = true;
gLibkernelBaseAddress = reinterpret_cast<void*>(baseAddress);
//@formatter:off
// RecallC2cSysMsg 09 8d 40 f8 f5 03 00 aa 21 00 80 52 f3 03 02 aa 29 ?? 40 f9
auto targetRecallC2cSysMsg = AobScanTarget()
.WithName("RecallC2cSysMsg")
.WithSequence({0x09, 0x8d, 0x40, 0xf8, 0xf5, 0x03, 0x00, 0xaa, 0x21, 0x00, 0x80, 0x52, 0xf3, 0x03, 0x02, 0xaa, 0x29, 0x00, 0x40, 0xf9})
.WithMask( {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff})
.WithStep(4)
.WithExecMemOnly(true)
.WithOffsetsForResult({-0x20, -0x24, -0x28})
.WithResultValidator(CommonAobScanValidator::kArm64StpX29X30SpImm);
// RecallGroupSysMsg 28 00 40 f9 61 00 80 52 09 8d 40 f8 29 !! 40 f9
auto targetRecallGroupSysMsg = AobScanTarget()
.WithName("RecallGroupSysMsg")
.WithSequence({0x28, 0x00, 0x40, 0xf9, 0x61, 0x00, 0x80, 0x52, 0x09, 0x8d, 0x40, 0xf8, 0x29, 0x00, 0x40, 0xf9})
.WithMask( {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff})
.WithStep(4)
.WithExecMemOnly(true)
.WithOffsetsForResult({-0x18, -0x24, -0x28})
.WithResultValidator(CommonAobScanValidator::kArm64StpX29X30SpImm);
//@formatter:on
std::vector<std::string> errorMsgList;
// auto start = std::chrono::steady_clock::now();
if (!SearchForAllAobScanTargets({&targetRecallC2cSysMsg, &targetRecallGroupSysMsg}, gLibkernelBaseAddress, true, errorMsgList)) {
LOGE("InitInitNtKernelRecallMsgHook SearchForAllAobScanTargets failed");
// sth went wrong
for (const auto& msg: errorMsgList) {
// report error to UI somehow
TraceError(nullptr, gInstanceRevokeMsgHook, msg);
}
return false;
}
// auto end = std::chrono::steady_clock::now();
// LOGD("InitInitNtKernelRecallMsgHook AobScan elapsed: {}us", std::chrono::duration_cast<std::chrono::microseconds>(end - start).count());
uint64_t offsetC2c = targetRecallC2cSysMsg.GetResultOffset();
uint64_t offsetGroup = targetRecallGroupSysMsg.GetResultOffset();
LOGD("offsetC2c={:x}, offsetGroup={:x}", offsetC2c, offsetGroup);
if (offsetC2c != 0) {
void* c2c = (void*) (baseAddress + offsetC2c);
if (CreateInlineHook(c2c, (void*) &HandleC2cRecallSysMsgCallback, (void**) &sOriginHandleC2cRecallSysMsgCallback) != 0) {
TraceErrorF(nullptr, gInstanceRevokeMsgHook,
"InitInitNtKernelRecallMsgHook failed, DobbyHook c2c failed, c2c={:p}({:x}+{:x})",
c2c, baseAddress, offsetC2c);
return false;
}
} else {
TraceErrorF(nullptr, gInstanceRevokeMsgHook, "InitInitNtKernelRecallMsgHook failed, offsetC2c == 0");
}
if (offsetGroup != 0) {
void* group = (void*) (baseAddress + offsetGroup);
if (CreateInlineHook(group, (void*) &HandleGroupRecallSysMsgCallback, (void**) &sOriginHandleGroupRecallSysMsgCallback) != 0) {
TraceErrorF(nullptr, gInstanceRevokeMsgHook,
"InitInitNtKernelRecallMsgHook failed, DobbyHook group failed, group={:p}({:x}+{:x})",
group, baseAddress, offsetGroup);
return false;
}
} else {
TraceErrorF(nullptr, gInstanceRevokeMsgHook, "InitInitNtKernelRecallMsgHook failed, offsetGroup == 0");
}
return true;
}
bool InitInitNtKernelRecallMsgHook() {
using namespace utils;
@@ -362,137 +154,7 @@ bool InitInitNtKernelRecallMsgHook() {
LOGW("InitInitNtKernelRecallMsgHook failed, already hooked");
return false;
}
auto fnHookProc = [](uint64_t baseAddress) {
if (sIsHooked) {
return false;
}
sIsHooked = true;
gLibkernelBaseAddress = reinterpret_cast<void*>(baseAddress);
// RecallC2cSysMsg 09 8d 40 f8 f5 03 00 aa 21 00 80 52 f3 03 02 aa 29 8d 40 f9
auto targetRecallC2cSysMsg = AobScanTarget()
.WithName("RecallC2cSysMsg")
.WithSequence({0x09, 0x8d, 0x40, 0xf8, 0xf5, 0x03, 0x00, 0xaa, 0x21, 0x00, 0x80, 0x52, 0xf3, 0x03, 0x02, 0xaa, 0x29, 0x8d, 0x40, 0xf9})
.WithStep(4)
.WithExecMemOnly(true)
.WithOffsetsForResult({-0x20, -0x24, -0x28})
.WithResultValidator(CommonAobScanValidator::kArm64StpX29X30SpImm);
// RecallGroupSysMsg 28 00 40 f9 61 00 80 52 09 8d 40 f8 29 8d 40 f9
auto targetRecallGroupSysMsg = AobScanTarget()
.WithName("RecallGroupSysMsg")
.WithSequence({0x28, 0x00, 0x40, 0xf9, 0x61, 0x00, 0x80, 0x52, 0x09, 0x8d, 0x40, 0xf8, 0x29, 0x8d, 0x40, 0xf9})
.WithStep(4)
.WithExecMemOnly(true)
.WithOffsetsForResult({-0x18, -0x24, -0x28})
.WithResultValidator(CommonAobScanValidator::kArm64StpX29X30SpImm);
// GetDecoder 3f 8d 01 f8 f4 03 00 aa 1f 10 00 f9
auto targetGetDecoder = AobScanTarget()
.WithName("GetDecoder")
.WithSequence({0x3f, 0x8d, 0x01, 0xf8, 0xf4, 0x03, 0x00, 0xaa, 0x1f, 0x10, 0x00, 0xf9})
.WithStep(4)
.WithExecMemOnly(true)
.WithOffsetsForResult({-0x78})
.WithResultValidator(CommonAobScanValidator::kArm64StpX29X30SpImm);
//@formatter:off
//OffsetForTmpRev5048
//61 01 80 52 mov w1,#0xb
//e0 03 ?? aa mov x0,x?
//?? 10 00 94 bl FUN_?
//?? ?? 00 36 tbz w0,#0x0,LAB_?
//?? 02 40 f9 ldr x8,[x??]
//61 01 80 52 mov w1,#0xb
//e0 03 ?? aa mov x0,x??
//09 !! 40 f9 ldr x9,[x8, #0x!!] <-- we need to find this
//e8 ?? ?? 91 add x8,sp,#0x??
//20 01 3f d6 blr x9
auto targetInstructionOffsetForTmpRev5048 = AobScanTarget()
.WithName("InstructionOffsetForTmpRev5048")
// 0x61 0x01 0x80 0x52 0xe0 0x03 0x?? 0xaa
// 0x?? 0x10 0x00 0x94 0x?? 0x?? 0x00 0x36
// 0x?? 0x02 0x40 0xf9 0x61 0x01 0x80 0x52
// 0xe0 0x03 0x?? 0xaa 0x09 0x?? 0x40 0xf9
// 0xe8 0x?? 0x?? 0x91 0x20 0x01 0x3f 0xd6
.WithSequence({0x61, 0x01, 0x80, 0x52, 0xe0, 0x03, 0x00, 0xaa,
0x00, 0x10, 0x00, 0x94, 0x00, 0x00, 0x00, 0x36,
0x00, 0x02, 0x40, 0xf9, 0x61, 0x01, 0x80, 0x52,
0xe0, 0x03, 0x00, 0xaa, 0x09, 0x00, 0x40, 0xf9,
0xe8, 0x00, 0x00, 0x91, 0x20, 0x01, 0x3f, 0xd6})
.WithMask({ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff,
0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x00, 0xff, 0xff, 0x00, 0xff, 0xff,
0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff})
.WithStep(4)
.WithExecMemOnly(true)
.WithOffsetsForResult({7 * 4});
//@formatter:on
std::vector<std::string> errorMsgList;
// auto start = std::chrono::steady_clock::now();
if (!SearchForAllAobScanTargets({&targetRecallC2cSysMsg, &targetRecallGroupSysMsg, &targetGetDecoder,
&targetInstructionOffsetForTmpRev5048},
gLibkernelBaseAddress, true, errorMsgList)) {
LOGE("InitInitNtKernelRecallMsgHook SearchForAllAobScanTargets failed");
// sth went wrong
for (const auto& msg: errorMsgList) {
// report error to UI somehow
TraceError(nullptr, gInstanceRevokeMsgHook, msg);
}
return false;
}
// auto end = std::chrono::steady_clock::now();
// LOGD("InitInitNtKernelRecallMsgHook AobScan elapsed: {}us", std::chrono::duration_cast<std::chrono::microseconds>(end - start).count());
if (auto pkg = HostInfo::GetPackageName(); pkg != "com.tencent.mobileqq") {
TraceErrorF(nullptr, gInstanceRevokeMsgHook, "InitInitNtKernelRecallMsgHook failed, unexpected package name: {}", pkg);
return false;
}
uint64_t offsetC2c = targetRecallC2cSysMsg.GetResultOffset();
uint64_t offsetGroup = targetRecallGroupSysMsg.GetResultOffset();
uint64_t offsetGetDecoder = targetGetDecoder.GetResultOffset();
uint64_t offsetInstForTmpRev5048 = targetInstructionOffsetForTmpRev5048.GetResultOffset();
uint32_t instructionForTmpRev5048 = *reinterpret_cast<uint32_t*>(
reinterpret_cast<uintptr_t>(gLibkernelBaseAddress) + offsetInstForTmpRev5048);
{
// LOGD("instructionForTmpRev5048={:08x}", instructionForTmpRev5048);
// 09 !! 40 f9 ldr x9,[x8, #0x!!]
uint32_t imm12 = ((instructionForTmpRev5048 >> 10u) & 0xfffu) << 3u;
// LOGD("imm12={:x}", imm12);
gOffsetForTmpRev5048 = imm12;
}
LOGD("offsetC2c={:x}, offsetGroup={:x}, offsetGetDecoder={:x}, offsetInstForTmpRev5048={:x}, gOffsetForTmpRev5048={:x}",
offsetC2c, offsetGroup, offsetGetDecoder, offsetInstForTmpRev5048, gOffsetForTmpRev5048);
gOffsetGetDecoderSp = offsetGetDecoder;
if (offsetC2c != 0) {
void* c2c = (void*) (baseAddress + offsetC2c);
if (CreateInlineHook(c2c, (void*) &HandleC2cRecallSysMsgCallback, (void**) &sOriginHandleC2cRecallSysMsgCallback) != 0) {
TraceErrorF(nullptr, gInstanceRevokeMsgHook,
"InitInitNtKernelRecallMsgHook failed, DobbyHook c2c failed, c2c={:p}({:x}+{:x})",
c2c, baseAddress, offsetC2c);
return false;
}
} else {
TraceErrorF(nullptr, gInstanceRevokeMsgHook, "InitInitNtKernelRecallMsgHook failed, offsetC2c == 0");
}
if (offsetGroup != 0) {
void* group = (void*) (baseAddress + offsetGroup);
if (CreateInlineHook(group, (void*) &HandleGroupRecallSysMsgCallback, (void**) &sOriginHandleGroupRecallSysMsgCallback) != 0) {
TraceErrorF(nullptr, gInstanceRevokeMsgHook,
"InitInitNtKernelRecallMsgHook failed, DobbyHook group failed, group={:p}({:x}+{:x})",
group, baseAddress, offsetGroup);
return false;
}
} else {
TraceErrorF(nullptr, gInstanceRevokeMsgHook, "InitInitNtKernelRecallMsgHook failed, offsetGroup == 0");
}
return true;
};
auto fnHookProc = &PerformNtRecallMsgHook;
ProcessView self;
if (int err;(err = self.readProcess(getpid())) != 0) {
TraceErrorF(nullptr, gInstanceRevokeMsgHook, "InitInitNtKernelRecallMsgHook failed, readProcess failed: {}", err);
@@ -561,7 +223,7 @@ bool InitInitNtKernelRecallMsgHook() {
} // ntqq::hook
extern "C" JNIEXPORT jboolean JNICALL
Java_cc_ioctl_hook_msg_RevokeMsgHook_nativeInitNtKernelRecallMsgHook(JNIEnv* env, jobject thiz) {
Java_cc_ioctl_hook_msg_RevokeMsgHook_nativeInitNtKernelRecallMsgHookV1p2(JNIEnv* env, jobject thiz) {
using ntqq::hook::klassRevokeMsgHook;
using ntqq::hook::gInstanceRevokeMsgHook;
using ntqq::hook::handleRecallSysMsgFromNtKernel;

View File

@@ -0,0 +1,119 @@
//
// Created by sulfate on 2024-07-11.
//
#include "linker_utils.h"
#include <cstdint>
#include <atomic>
#include <optional>
#include <mutex>
#include <dlfcn.h>
#include <android/dlext.h>
#include "utils/Log.h"
#include "qauxv_core/HostInfo.h"
#include "utils/ProcessView.h"
#include "utils/ElfScan.h"
#include "utils/ElfView.h"
#include "utils/FileMemMap.h"
#include "utils/ThreadUtils.h"
namespace qauxv {
void* loader_android_dlopen_ext(const char* filename,
int flag,
const android_dlextinfo* extinfo,
const void* caller_addr) {
int sdk = qauxv::HostInfo::GetSdkInt();
// there is no linker namespace pre-N
if (sdk < 24) {
return android_dlopen_ext(filename, flag, extinfo);
}
static std::mutex sMutex;
std::scoped_lock lock_(sMutex);
// 1. get ld-android.so
static void* handleLoader = nullptr;
if (handleLoader == nullptr) {
handleLoader = dlopen("libdl.so", RTLD_NOW | RTLD_NOLOAD);
}
if (handleLoader == nullptr) {
// this should not happen
LOGE("loader_android_dlopen_ext: failed to find libdl.so");
return nullptr;
}
// for 8.0/SDK26+, we have the famous __loader_android_dlopen_ext
static std::optional<void*> p_loader_android_dlopen_ext = std::nullopt;
if (!p_loader_android_dlopen_ext.has_value()) {
p_loader_android_dlopen_ext = dlsym(handleLoader, "__loader_android_dlopen_ext");
}
// for 7.0/7.1, use static symbol __dl__ZL10dlopen_extPKciPK17android_dlextinfoPv
if (p_loader_android_dlopen_ext.value() == nullptr) {
using utils::ElfView;
const char* soname;
// it's actually ld-android.so, not linker(64)
if constexpr (sizeof(void*) == 8) {
soname = "linker64";
} else {
soname = "linker";
}
utils::ProcessView processView;
int rc;
if ((rc = processView.readProcess(getpid())) != 0) {
LOGE("HookLoadLibrary: failed to read process, rc = {}", rc);
return nullptr;
}
const void* linkerBaseAddress = nullptr;
std::string linkerPath;
for (const auto& m: processView.getModules()) {
if (m.name == soname || m.name == "ld.so" || m.name == "ld-android.so") {
linkerBaseAddress = reinterpret_cast<void*>(m.baseAddress);
linkerPath = m.path;
break;
}
}
if (linkerBaseAddress == nullptr || linkerPath.empty()) {
LOGE("HookLoadLibrary: failed to find linker module");
return nullptr;
}
FileMemMap linkerFileMap;
if ((rc = linkerFileMap.mapFilePath(linkerPath.c_str())) != 0) {
LOGE("HookLoadLibrary: failed to map linker file, rc = {}", rc);
return nullptr;
}
ElfView linkerElfView;
linkerElfView.AttachFileMemMapping(linkerFileMap.getAddress(), linkerFileMap.getLength());
if (!linkerElfView.IsValid()) {
LOGE("HookLoadLibrary: failed to attach linker file");
return nullptr;
}
auto linkerSymbolResolver = [&](const char* symbol) -> void* {
auto offset = linkerElfView.GetSymbolOffset(symbol);
if (offset == 0) {
return nullptr;
}
return reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(linkerBaseAddress) + static_cast<uintptr_t>(offset));
};
const char* sym_dlopen_ext = "__dl__ZL10dlopen_extPKciPK17android_dlextinfoPv";
p_loader_android_dlopen_ext = linkerSymbolResolver(sym_dlopen_ext);
if (p_loader_android_dlopen_ext.value() == nullptr) {
LOGE("loader_android_dlopen_ext: failed to find symbol {}", sym_dlopen_ext);
return nullptr;
}
}
using loader_android_dlopen_ext_t = void* (*)(const char*, int, const android_dlextinfo*, const void*);
auto fn_loader_android_dlopen_ext = reinterpret_cast<loader_android_dlopen_ext_t>(p_loader_android_dlopen_ext.value());
if (fn_loader_android_dlopen_ext == nullptr) {
LOGE("loader_android_dlopen_ext: failed to find symbol");
return nullptr;
}
// invoke the function
return fn_loader_android_dlopen_ext(filename, flag, extinfo, caller_addr);
}
void* loader_dlopen(const char* filename, int flag, const void* caller_addr) {
return loader_android_dlopen_ext(filename, flag, nullptr, caller_addr);
}
}

View File

@@ -0,0 +1,21 @@
//
// Created by sulfate on 2024-07-11.
//
#ifndef QAUXV_LINKER_UTILS_H
#define QAUXV_LINKER_UTILS_H
#include <android/dlext.h>
namespace qauxv {
void* loader_android_dlopen_ext(const char* filename,
int flag,
const android_dlextinfo* extinfo,
const void* caller_addr);
void* loader_dlopen(const char* filename, int flag, const void* caller_addr);
}
#endif //QAUXV_LINKER_UTILS_H

View File

@@ -0,0 +1,113 @@
#include <cstdint>
#include <cstddef>
#include <type_traits>
namespace platform::arch::endian {
#if __BYTE_ORDER == __LITTLE_ENDIAN
static constexpr bool kIsLittleEndian = true;
#elif __BYTE_ORDER == __BIG_ENDIAN
static constexpr bool kIsLittleEndian = false;
#else
#error "Unknown endianness"
#endif
static constexpr bool kIsBigEndian = !kIsLittleEndian;
template<typename T>
requires(std::is_integral_v<T> && std::is_unsigned_v<T> && sizeof(T) == 2)
static constexpr T SwapEndian16(T value) {
return static_cast<T>((value >> 8) | (value << 8));
}
template<typename T>
requires(std::is_integral_v<T> && std::is_unsigned_v<T> && sizeof(T) == 4)
static constexpr T SwapEndian32(T value) {
return static_cast<T>((value >> 24) | ((value & 0x00ff0000u) >> 8) |
((value & 0x0000ff00u) << 8) | (value << 24));
}
template<typename T>
requires(std::is_integral_v<T> && std::is_unsigned_v<T> && sizeof(T) == 8)
static constexpr T SwapEndian64(T value) {
return static_cast<T>((value >> 56) | ((value & 0x00ff000000000000ULL) >> 40) |
((value & 0x0000ff0000000000ULL) >> 24) |
((value & 0x000000ff00000000ULL) >> 8) |
((value & 0x00000000ff000000ULL) << 8) |
((value & 0x0000000000ff0000ULL) << 24) |
((value & 0x000000000000ff00ULL) << 40) | (value << 56));
}
static constexpr uint16_t ltoh16(uint16_t value) {
return kIsLittleEndian ? value : SwapEndian16(value);
}
static constexpr uint32_t ltoh32(uint32_t value) {
return kIsLittleEndian ? value : SwapEndian32(value);
}
static constexpr uint64_t ltoh64(uint64_t value) {
return kIsLittleEndian ? value : SwapEndian64(value);
}
static constexpr uint16_t htol16(uint16_t value) {
return kIsLittleEndian ? value : SwapEndian16(value);
}
static constexpr uint32_t htol32(uint32_t value) {
return kIsLittleEndian ? value : SwapEndian32(value);
}
static constexpr uint64_t htol64(uint64_t value) {
return kIsLittleEndian ? value : SwapEndian64(value);
}
static constexpr uint16_t btoh16(uint16_t value) {
return kIsBigEndian ? value : SwapEndian16(value);
}
static constexpr uint32_t btoh32(uint32_t value) {
return kIsBigEndian ? value : SwapEndian32(value);
}
static constexpr uint64_t btoh64(uint64_t value) {
return kIsBigEndian ? value : SwapEndian64(value);
}
static constexpr uint16_t htob16(uint16_t value) {
return kIsBigEndian ? value : SwapEndian16(value);
}
static constexpr uint32_t htob32(uint32_t value) {
return kIsBigEndian ? value : SwapEndian32(value);
}
static constexpr uint64_t htob64(uint64_t value) {
return kIsBigEndian ? value : SwapEndian64(value);
}
static constexpr uint16_t ntoh16(uint16_t value) {
return btoh16(value);
}
static constexpr uint32_t ntoh32(uint32_t value) {
return btoh32(value);
}
static constexpr uint64_t ntoh64(uint64_t value) {
return btoh64(value);
}
static constexpr uint16_t hton16(uint16_t value) {
return htob16(value);
}
static constexpr uint32_t hton32(uint32_t value) {
return htob32(value);
}
static constexpr uint64_t hton64(uint64_t value) {
return htob64(value);
}
}

View File

@@ -9,7 +9,7 @@ import android.util.Log;
import android.widget.LinearLayout;
import android.widget.TextView;
import cc.hicore.Env;
import de.robv.android.xposed.XposedBridge;
import io.github.qauxv.util.xpcompat.XposedBridge;
import io.github.qauxv.ui.CommonContextWrapper;
import io.github.qauxv.util.Toasts;
import java.io.File;

View File

@@ -39,7 +39,7 @@ import cc.hicore.ReflectUtil.XField;
import cc.hicore.ReflectUtil.XMethod;
import cc.hicore.dialog.RepeaterPlusIconSettingDialog;
import cc.ioctl.util.LayoutHelper;
import de.robv.android.xposed.XC_MethodHook;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.R;
import io.github.qauxv.util.CustomMenu;
import io.github.qauxv.util.LicenseStatus;

View File

@@ -51,9 +51,9 @@ import com.tencent.qqnt.kernel.nativeinterface.MsgAttributeInfo;
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord;
import com.xiaoniu.dispatcher.OnMenuBuilder;
import com.xiaoniu.util.ContextUtils;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.util.xpcompat.XposedBridge;
import io.github.qauxv.util.xpcompat.XposedHelpers;
import io.github.qauxv.R;
import io.github.qauxv.base.ISwitchCellAgent;
import io.github.qauxv.base.IUiItemAgent;

View File

@@ -33,7 +33,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import cc.ioctl.util.HookUtils;
import cc.ioctl.util.Reflex;
import de.robv.android.xposed.XC_MethodHook;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.base.annotation.FunctionHookEntry;
import io.github.qauxv.base.annotation.UiItemAgentEntry;
import io.github.qauxv.bridge.AppRuntimeHelper;

View File

@@ -25,8 +25,8 @@ import androidx.annotation.NonNull;
import cc.hicore.ReflectUtil.XMethod;
import cc.ioctl.util.HookUtils;
import cc.ioctl.util.HostInfo;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedHelpers;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.util.xpcompat.XposedHelpers;
import io.github.qauxv.base.annotation.FunctionHookEntry;
import io.github.qauxv.base.annotation.UiItemAgentEntry;
import io.github.qauxv.dsl.FunctionEntryRouter;

View File

@@ -26,8 +26,8 @@ import androidx.annotation.NonNull;
import cc.ioctl.util.HookUtils;
import cc.ioctl.util.HostInfo;
import cc.ioctl.util.Reflex;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedHelpers;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.util.xpcompat.XposedHelpers;
import io.github.qauxv.base.annotation.FunctionHookEntry;
import io.github.qauxv.base.annotation.UiItemAgentEntry;
import io.github.qauxv.dsl.FunctionEntryRouter;

View File

@@ -48,7 +48,7 @@ import com.tencent.qqnt.kernel.nativeinterface.MsgRecord;
import com.tencent.qqnt.kernel.nativeinterface.PicElement;
import com.xiaoniu.dispatcher.OnMenuBuilder;
import com.xiaoniu.util.ContextUtils;
import de.robv.android.xposed.XC_MethodHook;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.R;
import io.github.qauxv.base.annotation.FunctionHookEntry;
import io.github.qauxv.base.annotation.UiItemAgentEntry;

View File

@@ -26,8 +26,8 @@ import cc.hicore.QApp.QAppUtils;
import cc.hicore.ReflectUtil.XField;
import cc.hicore.hook.RepeaterPlus;
import cc.hicore.hook.stickerPanel.Hooker.StickerPanelEntryHooker;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.util.xpcompat.XposedBridge;
import io.github.qauxv.base.IDynamicHook;
import io.github.qauxv.base.annotation.FunctionHookEntry;
import io.github.qauxv.hook.BaseHookDispatcher;

View File

@@ -25,7 +25,7 @@ package cc.ioctl.hook;
import static android.widget.LinearLayout.LayoutParams.MATCH_PARENT;
import static android.widget.LinearLayout.LayoutParams.WRAP_CONTENT;
import static cc.ioctl.util.LayoutHelper.dip2px;
import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
import static io.github.qauxv.util.xpcompat.XposedHelpers.findAndHookMethod;
import static io.github.qauxv.util.Initiator.load;
import android.app.Activity;
@@ -42,8 +42,8 @@ import cc.ioctl.hook.friend.ShowDeletedFriendListEntry;
import cc.ioctl.util.ExfriendManager;
import cc.ioctl.util.LayoutHelper;
import cc.ioctl.util.Reflex;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedHelpers;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.util.xpcompat.XposedHelpers;
import io.github.qauxv.activity.SettingsUiFragmentHostActivity;
import io.github.qauxv.base.annotation.FunctionHookEntry;
import io.github.qauxv.bridge.AppRuntimeHelper;

View File

@@ -40,9 +40,9 @@ import cc.ioctl.util.HookUtils;
import cc.ioctl.util.HostInfo;
import cc.ioctl.util.LayoutHelper;
import cc.ioctl.util.Reflex;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.util.xpcompat.XposedBridge;
import io.github.qauxv.util.xpcompat.XposedHelpers;
import io.github.qauxv.BuildConfig;
import io.github.qauxv.R;
import io.github.qauxv.activity.SettingsUiFragmentHostActivity;

View File

@@ -28,8 +28,8 @@ import static io.github.qauxv.util.Initiator.load;
import cc.ioctl.util.ExfriendManager;
import cc.ioctl.util.HookUtils;
import cc.ioctl.util.Reflex;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedHelpers;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.util.xpcompat.XposedHelpers;
import io.github.qauxv.util.SyncUtils;
import io.github.qauxv.config.ConfigItems;
import io.github.qauxv.hook.BasePersistBackgroundHook;

View File

@@ -30,8 +30,8 @@ import cc.ioctl.util.HookUtils;
import cc.ioctl.util.HostInfo;
import cc.ioctl.util.Reflex;
import com.tencent.qqnt.kernel.nativeinterface.VASMsgBubble;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.util.xpcompat.XposedBridge;
import io.github.qauxv.base.annotation.FunctionHookEntry;
import io.github.qauxv.base.annotation.UiItemAgentEntry;
import io.github.qauxv.dsl.FunctionEntryRouter.Locations.Simplify;

View File

@@ -29,8 +29,8 @@ import androidx.annotation.Nullable;
import cc.hicore.QApp.QAppUtils;
import cc.ioctl.util.HookUtils;
import com.tencent.qqnt.kernel.nativeinterface.VASMsgFont;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.util.xpcompat.XposedBridge;
import io.github.qauxv.base.annotation.FunctionHookEntry;
import io.github.qauxv.base.annotation.UiItemAgentEntry;
import io.github.qauxv.dsl.FunctionEntryRouter.Locations.Simplify;

View File

@@ -29,8 +29,8 @@ import androidx.annotation.NonNull;
import cc.hicore.QApp.QAppUtils;
import cc.hicore.ReflectUtil.XMethod;
import cc.ioctl.util.HookUtils;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedHelpers;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.util.xpcompat.XposedHelpers;
import io.github.qauxv.base.annotation.FunctionHookEntry;
import io.github.qauxv.base.annotation.UiItemAgentEntry;
import io.github.qauxv.bridge.AIOUtilsImpl;

View File

@@ -24,8 +24,8 @@ package cc.ioctl.hook.chat;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import cc.ioctl.util.HookUtils;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.util.xpcompat.XposedBridge;
import io.github.qauxv.base.annotation.FunctionHookEntry;
import io.github.qauxv.base.annotation.UiItemAgentEntry;
import io.github.qauxv.dsl.FunctionEntryRouter.Locations.Auxiliary;

View File

@@ -29,8 +29,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import cc.ioctl.util.HostInfo;
import cc.ioctl.util.Reflex;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedHelpers;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.util.xpcompat.XposedHelpers;
import io.github.qauxv.base.annotation.FunctionHookEntry;
import io.github.qauxv.base.annotation.UiItemAgentEntry;
import io.github.qauxv.dsl.FunctionEntryRouter.Locations.Auxiliary;

View File

@@ -131,7 +131,8 @@ object GagInfoDisclosure : CommonSwitchFunctionHook(
if (vMsg[4].toInt() == 12) {
val selfUin = AppRuntimeHelper.getAccount()
val troopUin = getLongData(vMsg, 0).toString()
val opUin = getLongData(vMsg, 6).toString()
val opUinTmp = getLongData(vMsg, 6)
val opUin = (opUinTmp.takeIf { it > 0 } ?: (opUinTmp and 0xFFFFFFFFL)).toString()
val victimUinTmp = getLongData(vMsg, 16)
val victimUin = (victimUinTmp.takeIf { it > 0 } ?: (victimUinTmp and 0xFFFFFFFFL)).toString()
val victimTime = getLongData(vMsg, 20)

View File

@@ -26,8 +26,8 @@ import static io.github.qauxv.util.QQVersion.QQ_8_8_11;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import cc.ioctl.util.HostInfo;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.util.xpcompat.XposedBridge;
import io.github.qauxv.base.annotation.FunctionHookEntry;
import io.github.qauxv.base.annotation.UiItemAgentEntry;
import io.github.qauxv.dsl.FunctionEntryRouter.Locations.Auxiliary;

View File

@@ -36,9 +36,9 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import cc.ioctl.fragment.FakeBatteryConfigFragment;
import cc.ioctl.util.HostInfo;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.util.xpcompat.XposedBridge;
import io.github.qauxv.util.xpcompat.XposedHelpers;
import io.github.qauxv.util.SyncUtils;
import io.github.qauxv.activity.SettingsUiFragmentHostActivity;
import io.github.qauxv.base.ISwitchCellAgent;

View File

@@ -38,7 +38,7 @@ import cc.ioctl.util.HostInfo
import cc.ioctl.util.hookAfterIfEnabled
import com.github.kyuubiran.ezxhelper.utils.Log
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import de.robv.android.xposed.XposedHelpers
import io.github.qauxv.util.xpcompat.XposedHelpers
import io.github.duzhaokun123.util.FilePicker
import io.github.qauxv.base.IUiItemAgent
import io.github.qauxv.base.annotation.FunctionHookEntry

View File

@@ -28,8 +28,8 @@ import android.view.View
import cc.hicore.QApp.QAppUtils
import cc.ioctl.util.hookBeforeIfEnabled
import com.github.kyuubiran.ezxhelper.utils.isPrivate
import de.robv.android.xposed.XC_MethodHook
import de.robv.android.xposed.XposedBridge
import io.github.qauxv.util.xpcompat.XC_MethodHook
import io.github.qauxv.util.xpcompat.XposedBridge
import io.github.qauxv.base.annotation.FunctionHookEntry
import io.github.qauxv.base.annotation.UiItemAgentEntry
import io.github.qauxv.dsl.FunctionEntryRouter

View File

@@ -35,8 +35,8 @@ import androidx.annotation.Nullable;
import cc.ioctl.dialog.RikkaBaseApkFormatDialog;
import cc.ioctl.util.HookUtils;
import cc.ioctl.util.HostInfo;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedHelpers;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.util.xpcompat.XposedHelpers;
import io.github.qauxv.base.IUiItemAgent;
import io.github.qauxv.base.annotation.FunctionHookEntry;
import io.github.qauxv.base.annotation.UiItemAgentEntry;

View File

@@ -29,9 +29,9 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import cc.ioctl.dialog.RikkaCustomDeviceModelDialog;
import cc.ioctl.util.HostInfo;
import de.robv.android.xposed.XC_MethodReplacement;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import io.github.qauxv.util.xpcompat.XC_MethodReplacement;
import io.github.qauxv.util.xpcompat.XposedBridge;
import io.github.qauxv.util.xpcompat.XposedHelpers;
import io.github.qauxv.base.IUiItemAgent;
import io.github.qauxv.base.annotation.FunctionHookEntry;
import io.github.qauxv.base.annotation.UiItemAgentEntry;

View File

@@ -22,7 +22,7 @@
package cc.ioctl.hook.misc
import android.content.Intent
import de.robv.android.xposed.XC_MethodHook
import io.github.qauxv.util.xpcompat.XC_MethodHook
import io.github.qauxv.base.IDynamicHook
import io.github.qauxv.base.annotation.FunctionHookEntry
import io.github.qauxv.base.annotation.UiItemAgentEntry
@@ -60,7 +60,7 @@ object NoAskContinueRecvShortVideoOrNot : BaseSwitchFunctionDecorator(), IStartA
}
}
}
return true
return false
}
}

View File

@@ -29,7 +29,7 @@ import android.view.View
import androidx.appcompat.app.AlertDialog
import cc.ioctl.util.HookUtils
import cc.ioctl.util.Reflex
import de.robv.android.xposed.XposedBridge
import io.github.qauxv.util.xpcompat.XposedBridge
import io.github.qauxv.R
import io.github.qauxv.base.annotation.ComponentHookEntry
import io.github.qauxv.hook.BaseComponentHook

View File

@@ -47,8 +47,8 @@ import androidx.core.view.inputmethod.InputConnectionCompat;
import cc.hicore.message.bridge.Chat_facade_bridge;
import cc.ioctl.util.SendCacheUtils;
import cc.ioctl.util.ui.FaultyDialog;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.util.xpcompat.XposedBridge;
import io.github.qauxv.R;
import io.github.qauxv.base.annotation.FunctionHookEntry;
import io.github.qauxv.base.annotation.UiItemAgentEntry;

View File

@@ -31,9 +31,9 @@ import cc.ioctl.util.beforeHookIfEnabled
import com.tencent.qqnt.kernel.nativeinterface.MsgElement
import com.xiaoniu.dispatcher.OnMenuBuilder
import com.xiaoniu.util.ContextUtils
import de.robv.android.xposed.XC_MethodHook.MethodHookParam
import de.robv.android.xposed.XposedBridge
import de.robv.android.xposed.XposedHelpers
import io.github.qauxv.util.xpcompat.XC_MethodHook.MethodHookParam
import io.github.qauxv.util.xpcompat.XposedBridge
import io.github.qauxv.util.xpcompat.XposedHelpers
import io.github.qauxv.R
import io.github.qauxv.base.annotation.FunctionHookEntry
import io.github.qauxv.base.annotation.UiItemAgentEntry

View File

@@ -37,8 +37,6 @@ import cc.ioctl.util.HookUtils;
import cc.ioctl.util.HostInfo;
import cc.ioctl.util.Reflex;
import cc.ioctl.util.ui.FaultyDialog;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import io.github.qauxv.R;
import io.github.qauxv.activity.ShadowShareFileAgentActivity;
import io.github.qauxv.base.annotation.FunctionHookEntry;
@@ -56,6 +54,8 @@ import io.github.qauxv.util.dexkit.DefaultFileModel;
import io.github.qauxv.util.dexkit.DexKit;
import io.github.qauxv.util.dexkit.DexKitTarget;
import io.github.qauxv.util.dexkit.FileBrowserActivity_InnerClass_onItemClick;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.util.xpcompat.XposedBridge;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
@@ -112,7 +112,9 @@ public class FileShareExtHook extends CommonSwitchFunctionHook {
Class<?> kFileBrowserModelBase = Initiator.loadClass("com.tencent.mobileqq.filemanager.fileviewer.model.FileBrowserModelBase");
Class<?> kDefaultFileModel = DexKit.requireClassFromCache(DefaultFileModel.INSTANCE);
String fileViewerAdapterClassName;
if (requireMinQQVersion(QQVersion.QQ_9_0_15)) {
if (requireMinQQVersion(QQVersion.QQ_9_0_80)) {
fileViewerAdapterClassName = "com.tencent.mobileqq.filemanager.fileviewer.h";
} else if (requireMinQQVersion(QQVersion.QQ_9_0_15)) {
fileViewerAdapterClassName = "com.tencent.mobileqq.filemanager.fileviewer.g";
} else if (requireMinQQVersion(QQVersion.QQ_8_9_0)) {
fileViewerAdapterClassName = "com.tencent.mobileqq.filemanager.fileviewer.h";

View File

@@ -34,7 +34,7 @@ import cc.hicore.QApp.QAppUtils;
import cc.ioctl.util.HookUtils;
import cc.ioctl.util.Reflex;
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord;
import de.robv.android.xposed.XposedHelpers;
import io.github.qauxv.util.xpcompat.XposedHelpers;
import io.github.qauxv.base.annotation.FunctionHookEntry;
import io.github.qauxv.base.annotation.UiItemAgentEntry;
import io.github.qauxv.dsl.FunctionEntryRouter.Locations.Auxiliary;

View File

@@ -44,7 +44,7 @@ import cc.ioctl.util.hookBeforeIfEnabled
import cc.ioctl.util.ui.FaultyDialog
import com.github.kyuubiran.ezxhelper.utils.hookBefore
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord
import de.robv.android.xposed.XC_MethodHook.MethodHookParam
import io.github.qauxv.util.xpcompat.XC_MethodHook.MethodHookParam
import io.github.qauxv.R
import io.github.qauxv.base.annotation.DexDeobfs
import io.github.qauxv.base.annotation.FunctionHookEntry

View File

@@ -39,9 +39,9 @@ import com.tencent.qphone.base.remote.FromServiceMsg;
import com.tencent.qqnt.kernel.nativeinterface.PicElement;
import com.xiaoniu.dispatcher.OnMenuBuilder;
import com.xiaoniu.util.ContextUtils;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.util.xpcompat.XposedBridge;
import io.github.qauxv.util.xpcompat.XposedHelpers;
import io.github.qauxv.R;
import io.github.qauxv.base.annotation.FunctionHookEntry;
import io.github.qauxv.base.annotation.UiItemAgentEntry;

View File

@@ -28,7 +28,7 @@ import static cc.ioctl.util.LayoutHelper.dip2sp;
import static cc.ioctl.util.LayoutHelper.newLinearLayoutParams;
import static cc.ioctl.util.Reflex.findField;
import static cc.ioctl.util.Reflex.getFirstByType;
import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
import static io.github.qauxv.util.xpcompat.XposedHelpers.findAndHookMethod;
import static io.github.qauxv.bridge.AppRuntimeHelper.getQQAppInterface;
import static io.github.qauxv.util.Initiator._PttItemBuilder;
import static io.github.qauxv.util.Initiator.load;
@@ -60,8 +60,8 @@ import cc.ioctl.util.Reflex;
import com.tencent.qqnt.kernel.nativeinterface.PttElement;
import com.xiaoniu.dispatcher.OnMenuBuilder;
import com.xiaoniu.util.ContextUtils;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.util.xpcompat.XposedBridge;
import io.github.qauxv.R;
import io.github.qauxv.base.annotation.FunctionHookEntry;
import io.github.qauxv.base.annotation.UiItemAgentEntry;

View File

@@ -21,8 +21,8 @@
*/
package cc.ioctl.hook.msg;
import static de.robv.android.xposed.XposedHelpers.callMethod;
import static de.robv.android.xposed.XposedHelpers.setObjectField;
import static io.github.qauxv.util.xpcompat.XposedHelpers.callMethod;
import static io.github.qauxv.util.xpcompat.XposedHelpers.setObjectField;
import static io.github.qauxv.util.Initiator._C2CMessageProcessor;
import static io.github.qauxv.util.Initiator._QQMessageFacade;
@@ -33,12 +33,16 @@ import android.text.TextUtils;
import android.view.View;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import cc.hicore.QApp.QAppUtils;
import cc.ioctl.fragment.RevokeMsgConfigFragment;
import cc.ioctl.util.HookUtils;
import cc.ioctl.util.HostInfo;
import cc.ioctl.util.Reflex;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import com.tencent.qqnt.kernel.nativeinterface.MsgRecord;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.activity.SettingsUiFragmentHostActivity;
import io.github.qauxv.base.IUiItemAgent;
import io.github.qauxv.base.annotation.FunctionHookEntry;
@@ -56,6 +60,14 @@ import io.github.qauxv.bridge.ntapi.RelationNTUinAndUidApi;
import io.github.qauxv.config.ConfigManager;
import io.github.qauxv.dsl.FunctionEntryRouter.Locations.Auxiliary;
import io.github.qauxv.hook.CommonConfigFunctionHook;
import io.github.qauxv.proto.trpc.msg.C2CMsgRecallOuterClass;
import io.github.qauxv.proto.trpc.msg.ContentHeadOuterClass;
import io.github.qauxv.proto.trpc.msg.GroupMsgRecallOuterClass;
import io.github.qauxv.proto.trpc.msg.InfoSyncPushOuterClass;
import io.github.qauxv.proto.trpc.msg.MessageBodyOuterClass;
import io.github.qauxv.proto.trpc.msg.MessageOuterClass;
import io.github.qauxv.proto.trpc.msg.MsgPushOuterClass;
import io.github.qauxv.util.Initiator;
import io.github.qauxv.util.Log;
import io.github.qauxv.util.QQVersion;
import io.github.qauxv.util.SyncUtils;
@@ -64,13 +76,19 @@ import io.github.qauxv.util.dexkit.DexKit;
import io.github.qauxv.util.dexkit.DexKitTarget;
import io.github.qauxv.util.dexkit.NContactUtils_getBuddyName;
import io.github.qauxv.util.dexkit.NContactUtils_getDiscussionMemberShowName;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.Set;
import kotlin.Unit;
import kotlin.collections.ArraysKt;
import kotlin.jvm.functions.Function3;
import kotlin.jvm.internal.Intrinsics;
import kotlinx.coroutines.flow.MutableStateFlow;
@@ -89,6 +107,8 @@ import mqq.app.AppRuntime;
* 2023-06-16 Fri.12:40 Initial support for NT kernel.
* <p>
* 2023-07-12 Mon.23:12 Basic support for NT kernel.
* <p>
* 2024-07-17 Wed.22:03 Use ProtoBuf to get recall info.
*/
@FunctionHookEntry
@UiItemAgentEntry
@@ -101,6 +121,8 @@ public class RevokeMsgHook extends CommonConfigFunctionHook {
private static final String KEY_KEEP_SELF_REVOKE_MSG = "RevokeMsgHook.KEY_KEEP_SELF_REVOKE_MSG";
private static final String KEY_SHOW_SHMSGSEQ = "RevokeMsgHook.KEY_SHOW_SHMSGSEQ";
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
private RevokeMsgHook() {
//FIXME: is MSF really necessary?
super(SyncUtils.PROC_MAIN | SyncUtils.PROC_MSF, new DexKitTarget[]{
@@ -182,7 +204,9 @@ public class RevokeMsgHook extends CommonConfigFunctionHook {
public boolean initOnce() throws Exception {
boolean isSuccess = true;
if (QAppUtils.isQQnt()) {
isSuccess = nativeInitNtKernelRecallMsgHook();
boolean p1 = nativeInitNtKernelRecallMsgHookV1p2();
boolean p2 = initNtRecallMsgHookV2();
isSuccess = p1 && p2;
}
// The method is still there, even on NT.
// I decided to hook them as long as they are there.
@@ -216,7 +240,225 @@ public class RevokeMsgHook extends CommonConfigFunctionHook {
return isSuccess;
}
private native boolean nativeInitNtKernelRecallMsgHook();
private boolean initNtRecallMsgHookV2() throws ReflectiveOperationException {
Class<?> kIQQNTWrapperSessionProxy = Initiator.loadClass("com.tencent.qqnt.kernel.nativeinterface.IQQNTWrapperSession$CppProxy");
Method onMsfPushMethod = ArraysKt.single(kIQQNTWrapperSessionProxy.getDeclaredMethods(), method -> {
return "onMsfPush".equals(method.getName()) && (method.getParameterCount() == 3 || method.getParameterCount() == 2);
});
Class<?> kPushExtraInfo = null;
final Field transInfoMapField;
if (onMsfPushMethod.getParameterCount() == 3) {
kPushExtraInfo = onMsfPushMethod.getParameterTypes()[2];
transInfoMapField = kPushExtraInfo.getDeclaredField("transInfoMap");
transInfoMapField.setAccessible(true);
} else {
transInfoMapField = null;
}
HookUtils.hookBeforeIfEnabled(this, onMsfPushMethod, -51, param -> {
String cmd = (String) param.args[0];
if (cmd == null) {
return;
}
byte[] protoBuf = (byte[]) param.args[1];
if (protoBuf == null) {
protoBuf = EMPTY_BYTE_ARRAY;
}
HashMap<String, byte[]> transInfoMap = null;
if (transInfoMapField != null) {
Object pushExtraInfo = param.args[2];
if (pushExtraInfo != null) {
transInfoMap = (HashMap<String, byte[]>) transInfoMapField.get(pushExtraInfo);
}
}
onMsfPushNtMethodCallback(param, cmd, protoBuf, transInfoMap);
});
return true;
}
private void onMsfPushNtMethodCallback(@NonNull XC_MethodHook.MethodHookParam param, @NonNull String cmd,
@NonNull byte[] protoBuf, @Nullable HashMap<String, byte[]> transInfoMap) {
if ("trpc.msg.register_proxy.RegisterProxy.InfoSyncPush".equals(cmd)) {
handleRegisterProxyInfoSyncPush(param, protoBuf, transInfoMap);
} else if ("trpc.msg.olpush.OlPushService.MsgPush".equals(cmd)) {
handleOlPushServiceMsgPush(param, protoBuf, transInfoMap);
}
}
// trpc.msg.register_proxy.RegisterProxy.InfoSyncPush
private void handleRegisterProxyInfoSyncPush(
@NonNull XC_MethodHook.MethodHookParam param,
@NonNull byte[] protoBuf,
@Nullable HashMap<String, byte[]> transInfoMap
) {
try {
InfoSyncPushOuterClass.InfoSyncPush infoSyncPush = InfoSyncPushOuterClass.InfoSyncPush.parseFrom(protoBuf);
if (!infoSyncPush.hasSyncMsgRecall()) {
return;
}
InfoSyncPushOuterClass.InfoSyncPush.SyncMsgRecall syncMsgRecall = infoSyncPush.getSyncMsgRecall();
List<InfoSyncPushOuterClass.InfoSyncPush.SyncMsgRecall.SyncInfoBody> msgRecalls = syncMsgRecall.getBodyList();
for (InfoSyncPushOuterClass.InfoSyncPush.SyncMsgRecall.SyncInfoBody msgRecall : msgRecalls) {
String peerUid = msgRecall.getPeerUid();
long groupCode;
try {
groupCode = Long.parseLong(peerUid);
} catch (NumberFormatException e) {
// not a group
continue;
}
if (groupCode == 0) {
continue;
}
List<MessageOuterClass.Message> msgs = msgRecall.getMsgList();
for (MessageOuterClass.Message msg : msgs) {
handleCommonGroupMessageRecall(msg);
}
}
} catch (InvalidProtocolBufferException | ReflectiveOperationException e) {
traceError(e);
}
}
// trpc.msg.olpush.OlPushService.MsgPush
private void handleOlPushServiceMsgPush(@NonNull XC_MethodHook.MethodHookParam param,
@NonNull byte[] protoBuf, @Nullable HashMap<String, byte[]> transInfoMap) {
try {
MsgPushOuterClass.MsgPush msgPush = MsgPushOuterClass.MsgPush.parseFrom(protoBuf);
if (!msgPush.hasMessage()) {
return;
}
MessageOuterClass.Message message = msgPush.getMessage();
if (!message.hasBody() || !message.hasContentHead()) {
return;
}
ContentHeadOuterClass.ContentHead contentHead = message.getContentHead();
int type = contentHead.getType();
int subType = contentHead.getSubType();
// c2c recall: 528, 138
// group recall: 732, 17
if (type == 528 && subType == 138) {
handleMsgPushForC2cRecall(param, msgPush, message);
} else if (type == 732 && subType == 17) {
handleMsgPushForGroupRecall(param, msgPush, message);
}
} catch (InvalidProtocolBufferException | ReflectiveOperationException e) {
traceError(e);
}
}
private void handleMsgPushForC2cRecall(
@NonNull XC_MethodHook.MethodHookParam param,
@NonNull MsgPushOuterClass.MsgPush msgPush,
@NonNull MessageOuterClass.Message message
) throws InvalidProtocolBufferException, ReflectiveOperationException {
MessageBodyOuterClass.MessageBody body = message.getBody();
if (!body.hasMsgContent()) {
return;
}
ByteString msgContent = body.getMsgContent();
C2CMsgRecallOuterClass.C2CMsgRecall msgRecall = C2CMsgRecallOuterClass.C2CMsgRecall.parseFrom(msgContent);
List<C2CMsgRecallOuterClass.C2CMsgRecall.MsgInfo> msgInfoList = msgRecall.getMsgInfosList();
String selfUid = RelationNTUinAndUidApi.getUidFromUin(AppRuntimeHelper.getAccount());
if (TextUtils.isEmpty(selfUid)) {
Log.e("handleMsgPushForC2cRecall fatal: selfUid is empty");
return;
}
for (C2CMsgRecallOuterClass.C2CMsgRecall.MsgInfo msgInfo : msgInfoList) {
long msgUid = msgInfo.getMsgUid();
long msgSeq = msgInfo.getMsgSeq();
long msgClientSeq = msgInfo.getMsgClientSeq();
long timeSeconds = msgInfo.getTimestamp();
String fromUid = msgInfo.getFromUid();
String toUid = msgInfo.getToUid();
long random64 = msgInfo.getRandomId();
// from uid is always operator
String peerUid;
if (selfUid.equals(fromUid)) {
// msg is revoked by myself
peerUid = toUid;
} else {
peerUid = fromUid;
}
// invoke the handler
onRecallSysMsgForNT(ChatTypeConstants.C2C, peerUid, fromUid, fromUid, toUid, random64, timeSeconds, msgUid, msgSeq, (int) msgClientSeq);
}
}
private void handleMsgPushForGroupRecall(
@NonNull XC_MethodHook.MethodHookParam param,
@NonNull MsgPushOuterClass.MsgPush msgPush,
@NonNull MessageOuterClass.Message message
) throws InvalidProtocolBufferException, ReflectiveOperationException {
handleCommonGroupMessageRecall(message);
}
private void handleCommonGroupMessageRecall(
@NonNull MessageOuterClass.Message message
) throws InvalidProtocolBufferException, ReflectiveOperationException {
String selfUid = RelationNTUinAndUidApi.getUidFromUin(AppRuntimeHelper.getAccount());
if (TextUtils.isEmpty(selfUid)) {
Log.e("handleMsgPushForC2cRecall fatal: selfUid is empty");
return;
}
// verify the message type
ContentHeadOuterClass.ContentHead contentHead = message.getContentHead();
int type = contentHead.getType();
int subType = contentHead.getSubType();
// group recall: 732, 17
if (!(type == 732 && subType == 17)) {
// not our business
return;
}
MessageBodyOuterClass.MessageBody body = message.getBody();
if (!body.hasMsgContent()) {
return;
}
ByteString msgContent = body.getMsgContent();
if (msgContent.size() < 8) {
Log.e("handleMsgPushForGroupRecall: msgContent size is less than 8");
return;
}
// skip first 7 bytes
byte[] msgContentBytes = msgContent.toByteArray();
byte[] msgContentBytesTrimmed = new byte[msgContentBytes.length - 7];
System.arraycopy(msgContentBytes, 7, msgContentBytesTrimmed, 0, msgContentBytesTrimmed.length);
GroupMsgRecallOuterClass.GroupMsgRecall groupMsgRecall = GroupMsgRecallOuterClass.GroupMsgRecall.parseFrom(msgContentBytesTrimmed);
if (groupMsgRecall.getOpType() != 7) {
Log.w("handleMsgPushForGroupRecall: on recall group sys msg! no Prompt_MsgRecallReminder op_type: " + groupMsgRecall.getOpType());
return;
}
if (groupMsgRecall.getGroupCode() == 0) {
Log.e("handleMsgPushForGroupRecall: groupMsgRecall has no groupCode");
return;
}
String groupCodeText = String.valueOf(groupMsgRecall.getGroupCode());
if (!groupMsgRecall.hasMsgRecall()) {
Log.e("handleMsgPushForGroupRecall: groupMsgRecall has no MsgRecall");
return;
}
GroupMsgRecallOuterClass.GroupMsgRecall.MsgRecallInfo groupMsgRecallInfo = groupMsgRecall.getMsgRecall();
String operatorUid = groupMsgRecallInfo.getOperatorUid();
List<GroupMsgRecallOuterClass.GroupMsgRecall.MsgRecallInfo.MsgInfo> msgs = groupMsgRecallInfo.getMsgInfosList();
for (GroupMsgRecallOuterClass.GroupMsgRecall.MsgRecallInfo.MsgInfo msgInfo : msgs) {
long msgSeq = msgInfo.getMsgSeq();
long timeSeconds = msgInfo.getTimestamp();
String authorUid = msgInfo.getMsgAuthorUid();
long random64 = msgInfo.getRandomId();
// Log.d("handleMsgPushForGroupRecall: groupCode=" + groupCodeText + ", operatorUid=" + operatorUid
// + ", authorUid=" + authorUid + ", random64=" + random64 + ", timeSeconds=" + timeSeconds
// + ", msgSeq=" + msgSeq);
// invoke the handler
onRecallSysMsgForNT(ChatTypeConstants.GROUP, groupCodeText, operatorUid, authorUid, groupCodeText, random64, timeSeconds, 0, msgSeq, 0);
}
}
private static String b64encode(byte[] data) {
return android.util.Base64.encodeToString(data, android.util.Base64.NO_WRAP);
}
private native boolean nativeInitNtKernelRecallMsgHookV1p2();
private void onRevokeMsgLegacy(Object revokeMsgInfo) throws Exception {
RevokeMsgInfoImpl info = new RevokeMsgInfoImpl((Parcelable) revokeMsgInfo);

View File

@@ -37,8 +37,8 @@ import cc.ioctl.util.HookUtils;
import cc.ioctl.util.HostInfo;
import cc.ioctl.util.Reflex;
import cc.ioctl.util.ui.FaultyDialog;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.util.xpcompat.XposedBridge;
import io.github.qauxv.R;
import io.github.qauxv.activity.ShadowShareFileAgentActivity;
import io.github.qauxv.base.annotation.FunctionHookEntry;

View File

@@ -28,17 +28,20 @@ import cc.ioctl.util.HookUtils.BeforeAndAfterHookedMethod
import cc.ioctl.util.HookUtils.hookBeforeAndAfterIfEnabled
import cc.ioctl.util.LayoutHelper
import cc.ioctl.util.hookBeforeIfEnabled
import de.robv.android.xposed.XC_MethodHook.MethodHookParam
import com.github.kyuubiran.ezxhelper.utils.hookAfter
import io.github.qauxv.base.annotation.FunctionHookEntry
import io.github.qauxv.base.annotation.UiItemAgentEntry
import io.github.qauxv.dsl.FunctionEntryRouter
import io.github.qauxv.hook.CommonSwitchFunctionHook
import io.github.qauxv.util.Initiator
import io.github.qauxv.util.QQVersion
import io.github.qauxv.util.dexkit.AIOTitleVB_updateLeftTopBack_NT
import io.github.qauxv.util.dexkit.CCustomWidgetUtil_updateCustomNoteTxt_NT
import io.github.qauxv.util.dexkit.DexKit
import io.github.qauxv.util.dexkit.NCustomWidgetUtil_updateCustomNoteTxt
import io.github.qauxv.util.requireMinQQVersion
import io.github.qauxv.util.xpcompat.XC_MethodHook.MethodHookParam
import xyz.nextalone.util.get
import xyz.nextalone.util.throwOrTrue
/**
@@ -50,8 +53,9 @@ import xyz.nextalone.util.throwOrTrue
@UiItemAgentEntry
object ShowMsgCount : CommonSwitchFunctionHook(
targets = arrayOf(
NCustomWidgetUtil_updateCustomNoteTxt,
CCustomWidgetUtil_updateCustomNoteTxt_NT,
AIOTitleVB_updateLeftTopBack_NT,
NCustomWidgetUtil_updateCustomNoteTxt,
)
) {
@@ -60,8 +64,8 @@ object ShowMsgCount : CommonSwitchFunctionHook(
override fun initOnce() = throwOrTrue {
// 群消息数量
if (requireMinQQVersion(QQVersion.QQ_9_0_8)) {
// 群消息数量 + 群聊左上角返回消息数量
val clz = Initiator.loadClass("com.tencent.mobileqq.quibadge.QUIBadge")
val (updateNumName, mNumName, mTextName) = if (requireMinQQVersion(QQVersion.QQ_9_0_15)) {
Triple("updateNum", "mNum", "mText")
@@ -78,32 +82,53 @@ object ShowMsgCount : CommonSwitchFunctionHook(
param.result = null
}
} else {
val clz = DexKit.requireClassFromCache(CCustomWidgetUtil_updateCustomNoteTxt_NT)
val updateNum = clz.declaredMethods.single { method ->
val params = method.parameterTypes
params.size == 6 && params[0] == TextView::class.java
&& params[1] == Int::class.java && params[2] == Int::class.java
&& params[3] == Int::class.java && params[4] == Int::class.java
&& params[5] == String::class.java
}
hookBeforeAndAfterIfEnabled(this, updateNum, 50, object : BeforeAndAfterHookedMethod {
override fun beforeHookedMethod(param: MethodHookParam) {
param.args[4] = Int.MAX_VALUE
if (requireMinQQVersion(QQVersion.QQ_8_9_63)) {
// 群消息数量
val clz = DexKit.requireClassFromCache(CCustomWidgetUtil_updateCustomNoteTxt_NT)
val updateNum = clz.declaredMethods.single { method ->
val params = method.parameterTypes
params.size == 6 && params[0] == TextView::class.java
&& params[1] == Int::class.java && params[2] == Int::class.java
&& params[3] == Int::class.java && params[4] == Int::class.java
&& params[5] == String::class.java
}
hookBeforeAndAfterIfEnabled(this, updateNum, 50, object : BeforeAndAfterHookedMethod {
override fun beforeHookedMethod(param: MethodHookParam) {
param.args[4] = Int.MAX_VALUE
}
override fun afterHookedMethod(param: MethodHookParam) {
val tv = param.args[0] as TextView
val count = param.args[2] as Int
val str = count.toString()
val lp = tv.layoutParams
lp.width = LayoutHelper.dip2px(tv.context, (9 + 7 * str.length).toFloat())
tv.layoutParams = lp
override fun afterHookedMethod(param: MethodHookParam) {
val tv = param.args[0] as TextView
val count = param.args[2] as Int
val str = count.toString()
val lp = tv.layoutParams
lp.width = LayoutHelper.dip2px(tv.context, (9 + 7 * str.length).toFloat())
tv.layoutParams = lp
}
})
// 群聊左上角返回消息数量
DexKit.requireMethodFromCache(AIOTitleVB_updateLeftTopBack_NT).hookAfter {
if (it.args[0] is Int) {
val count = it.args[0] as Int
if (count > 0) {
val (mTitleBinding, unreadTv) = when {
requireMinQQVersion(QQVersion.QQ_9_0_0) -> Pair("e", "v")
requireMinQQVersion(QQVersion.QQ_8_9_80) -> Pair("e", "s")
requireMinQQVersion(QQVersion.QQ_8_9_70) -> Pair("e", "t")
requireMinQQVersion(QQVersion.QQ_8_9_63) -> Pair("e", "s")
else -> Pair("", "")
}
if (mTitleBinding.isNotEmpty() && unreadTv.isNotEmpty()) {
(it.thisObject.get(mTitleBinding).get(unreadTv) as TextView).text = count.toString()
}
}
}
}
})
}
}
// 总消息数量
if (requireMinQQVersion(QQVersion.QQ_9_0_8)) {
// 总消息数量
val clz = DexKit.requireClassFromCache(NCustomWidgetUtil_updateCustomNoteTxt)
val method = clz.declaredMethods.single { method ->
val params = method.parameterTypes
@@ -115,6 +140,7 @@ object ShowMsgCount : CommonSwitchFunctionHook(
param.args[3] = Int.MAX_VALUE
}
} else {
// 总消息数量(QQ[9.0.8]之前) + 群消息数量(QQNT[8.9.63]之前)
val clz = DexKit.requireClassFromCache(NCustomWidgetUtil_updateCustomNoteTxt)
val method = clz.declaredMethods.single { method ->
val params = method.parameterTypes
@@ -136,11 +162,11 @@ object ShowMsgCount : CommonSwitchFunctionHook(
}
override fun afterHookedMethod(param: MethodHookParam) {
val tv = param.args[0] as TextView
tv.maxWidth = Int.MAX_VALUE
val lp = tv.layoutParams
lp.width = ViewGroup.LayoutParams.WRAP_CONTENT
tv.layoutParams = lp
(param.args[0] as TextView).apply {
maxWidth = Int.MAX_VALUE
layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT
setPadding(0, 0, 0, 0)
}
}
})
}

View File

@@ -24,7 +24,7 @@ package cc.ioctl.hook.notification
import cc.ioctl.util.Reflex
import cc.ioctl.util.msg.MessageManager
import de.robv.android.xposed.XC_MethodHook
import io.github.qauxv.util.xpcompat.XC_MethodHook
import io.github.qauxv.base.annotation.FunctionHookEntry
import io.github.qauxv.hook.BasePersistBackgroundHook
import io.github.qauxv.util.Initiator

View File

@@ -29,7 +29,7 @@ import androidx.annotation.NonNull;
import cc.ioctl.util.HookUtils;
import cc.ioctl.util.HostInfo;
import cc.ioctl.util.Reflex;
import de.robv.android.xposed.XposedBridge;
import io.github.qauxv.util.xpcompat.XposedBridge;
import io.github.qauxv.base.annotation.FunctionHookEntry;
import io.github.qauxv.base.annotation.UiItemAgentEntry;
import io.github.qauxv.dsl.FunctionEntryRouter.Locations.Auxiliary;

View File

@@ -0,0 +1,78 @@
/*
* QAuxiliary - An Xposed module for QQ/TIM
* Copyright (C) 2019-2024 QAuxiliary developers
* https://github.com/cinit/QAuxiliary
*
* This software is an opensource software: you can redistribute it
* and/or modify it under the terms of the General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version 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 General Public License for more details.
*
* You should have received a copy of the General Public License
* along with this software.
* If not, see
* <https://github.com/cinit/QAuxiliary/blob/master/LICENSE.md>.
*/
package cc.ioctl.hook.profile
import android.app.Activity
import cc.hicore.QApp.QAppUtils
import com.github.kyuubiran.ezxhelper.utils.ArgTypes
import com.github.kyuubiran.ezxhelper.utils.Args
import com.github.kyuubiran.ezxhelper.utils.hookBefore
import com.github.kyuubiran.ezxhelper.utils.newInstance
import com.xiaoniu.util.ContextUtils
import io.github.qauxv.R
import io.github.qauxv.base.annotation.FunctionHookEntry
import io.github.qauxv.base.annotation.UiItemAgentEntry
import io.github.qauxv.dsl.FunctionEntryRouter
import io.github.qauxv.hook.CommonSwitchFunctionHook
import io.github.qauxv.ui.CommonContextWrapper
import io.github.qauxv.util.Initiator
import io.github.qauxv.util.dexkit.DexKit
import io.github.qauxv.util.dexkit.RecentPopup_onClickAction
import xyz.nextalone.util.get
import xyz.nextalone.util.throwOrTrue
@FunctionHookEntry
@UiItemAgentEntry
object OpenProfileCardMenu : CommonSwitchFunctionHook(
targets = arrayOf(RecentPopup_onClickAction)
) {
override val name = "打开资料卡"
override val description = "在首页加号菜单中添加打开资料卡功能"
override val uiItemLocation = FunctionEntryRouter.Locations.Auxiliary.PROFILE_CATEGORY
override val isAvailable = QAppUtils.isQQnt()
private fun context() = CommonContextWrapper.createMaterialDesignContext(ContextUtils.getCurrentActivity())
override fun initOnce() = throwOrTrue {
val menuItemId = 414414
val entryMenuItem = Initiator.loadClass("com.tencent.widget.PopupMenuDialog\$MenuItem").newInstance(
Args(arrayOf(menuItemId, "打开资料卡", "打开资料卡", R.drawable.ic_item_tool_72dp)),
ArgTypes(arrayOf(Int::class.java, String::class.java, String::class.java, Int::class.java))
)!!
Initiator.loadClass("com.tencent.widget.PopupMenuDialog").getDeclaredMethod(
"conversationPlusBuild",
Activity::class.java,
List::class.java,
Initiator.loadClass("com.tencent.widget.PopupMenuDialog\$OnClickActionListener"),
Initiator.loadClass("com.tencent.widget.PopupMenuDialog\$OnDismissListener")
).hookBefore {
it.args[1] = it.args[1] as List<*> + listOf(entryMenuItem)
}
DexKit.requireMethodFromCache(RecentPopup_onClickAction).hookBefore {
if (it.args[0].get("id") == menuItemId) {
OpenProfileCard.onClick(context())
it.result = null
}
}
}
}

View File

@@ -30,7 +30,7 @@ import cc.ioctl.util.Reflex
import cc.ioctl.util.hookAfterIfEnabled
import cc.ioctl.util.hookBeforeIfEnabled
import com.github.kyuubiran.ezxhelper.utils.hookBefore
import de.robv.android.xposed.XC_MethodHook.MethodHookParam
import io.github.qauxv.util.xpcompat.XC_MethodHook.MethodHookParam
import io.github.qauxv.base.annotation.FunctionHookEntry
import io.github.qauxv.base.annotation.UiItemAgentEntry
import io.github.qauxv.dsl.FunctionEntryRouter

View File

@@ -26,6 +26,7 @@ import android.view.ViewGroup
import android.view.ViewTreeObserver
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.view.children
import androidx.core.view.forEach
import androidx.core.view.forEachIndexed
import androidx.core.view.get
@@ -38,8 +39,6 @@ import com.github.kyuubiran.ezxhelper.utils.hookAfter
import com.github.kyuubiran.ezxhelper.utils.hookReturnConstant
import com.github.kyuubiran.ezxhelper.utils.paramCount
import com.github.kyuubiran.ezxhelper.utils.setViewZeroSize
import de.robv.android.xposed.XC_MethodReplacement
import de.robv.android.xposed.XposedBridge
import io.github.qauxv.base.annotation.FunctionHookEntry
import io.github.qauxv.base.annotation.UiItemAgentEntry
import io.github.qauxv.dsl.FunctionEntryRouter
@@ -59,6 +58,8 @@ import io.github.qauxv.util.dexkit.QQSettingMeABTestHelper_isZplanExpGroup_Old
import io.github.qauxv.util.dexkit.QQ_SETTING_ME_CONFIG_CLASS
import io.github.qauxv.util.hostInfo
import io.github.qauxv.util.requireMinQQVersion
import io.github.qauxv.util.xpcompat.XC_MethodReplacement
import io.github.qauxv.util.xpcompat.XposedBridge
import xyz.nextalone.base.MultiItemDelayableHook
import xyz.nextalone.util.*
import java.lang.reflect.Array
@@ -284,6 +285,19 @@ object SimplifyQQSettingMe :
}
}
// QQ 9.0.80 在xml布局中固定了14个item这里将上面步骤完成后还未初始化的item隐藏
if (requireMinQQVersion(QQVersion.QQ_9_0_80)) {
"com.tencent.mobileqq.bizParts.QQSettingMeMenuPanelPart".clazz!!.method("onInitView")!!.hookAfter { param ->
// val parent=param.thisObject.javaClass.declaredFields.first {
// it.javaClass==ViewGroup::class.java && (it.get(param.thisObject) as ViewGroup).childCount>=14
// }.get(param.thisObject) as ViewGroup
val parent = param.thisObject.get("h") as ViewGroup
parent.children.forEach {
if (!it.isClickable) it.setViewZeroSize()
}
}
}
if (requireMinQQVersion(QQVersion.QQ_9_0_0)) {
// View层隐藏超级QQ秀 (到这里应该只需要处理 超级QQ秀 了
// 9.0.20起此段代码无效果且该版本起超级QQ秀可以在上面移除所以不再执行

View File

@@ -24,7 +24,7 @@ package cc.ioctl.hook.troop
import android.view.View
import cc.ioctl.util.Reflex
import de.robv.android.xposed.XC_MethodHook
import io.github.qauxv.util.xpcompat.XC_MethodHook
import io.github.qauxv.base.annotation.FunctionHookEntry
import io.github.qauxv.base.annotation.UiItemAgentEntry
import io.github.qauxv.dsl.FunctionEntryRouter

View File

@@ -24,8 +24,8 @@ package cc.ioctl.hook.ui.chat;
import static io.github.qauxv.util.Initiator.load;
import androidx.annotation.NonNull;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedHelpers;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.util.xpcompat.XposedHelpers;
import io.github.qauxv.base.annotation.FunctionHookEntry;
import io.github.qauxv.base.annotation.UiItemAgentEntry;
import io.github.qauxv.dsl.FunctionEntryRouter.Locations.Simplify;

View File

@@ -52,7 +52,7 @@ import cc.ioctl.util.HookUtils.BeforeAndAfterHookedMethod;
import cc.ioctl.util.HostInfo;
import cc.ioctl.util.LayoutHelper;
import cc.ioctl.util.Reflex;
import de.robv.android.xposed.XC_MethodHook;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.base.ISwitchCellAgent;
import io.github.qauxv.base.IUiItemAgent;
import io.github.qauxv.base.annotation.FunctionHookEntry;

View File

@@ -25,8 +25,8 @@ import com.github.kyuubiran.ezxhelper.utils.findMethod
import com.github.kyuubiran.ezxhelper.utils.hookAfter
import com.github.kyuubiran.ezxhelper.utils.hookAllConstructorAfter
import com.github.kyuubiran.ezxhelper.utils.paramCount
import de.robv.android.xposed.XC_MethodReplacement
import de.robv.android.xposed.XposedHelpers
import io.github.qauxv.util.xpcompat.XC_MethodReplacement
import io.github.qauxv.util.xpcompat.XposedHelpers
import io.github.qauxv.base.annotation.FunctionHookEntry
import io.github.qauxv.base.annotation.UiItemAgentEntry
import io.github.qauxv.config.ConfigItems

View File

@@ -29,7 +29,7 @@ import android.view.View
import androidx.appcompat.widget.AppCompatCheckBox
import androidx.appcompat.widget.AppCompatTextView
import cc.ioctl.util.HookUtils
import de.robv.android.xposed.XposedBridge
import io.github.qauxv.util.xpcompat.XposedBridge
import io.github.qauxv.base.IUiItemAgent
import io.github.qauxv.base.annotation.FunctionHookEntry
import io.github.qauxv.base.annotation.UiItemAgentEntry

View File

@@ -36,8 +36,8 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import cc.ioctl.util.HostInfo;
import cc.ioctl.util.LayoutHelper;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.util.xpcompat.XposedBridge;
import io.github.qauxv.base.IUiItemAgent;
import io.github.qauxv.base.annotation.FunctionHookEntry;
import io.github.qauxv.base.annotation.UiItemAgentEntry;

View File

@@ -28,8 +28,8 @@ import androidx.annotation.NonNull;
import cc.hicore.QApp.QAppUtils;
import cc.ioctl.util.HookUtils;
import com.tencent.qqnt.kernel.nativeinterface.VASMsgAvatarPendant;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.util.xpcompat.XposedBridge;
import io.github.qauxv.base.annotation.FunctionHookEntry;
import io.github.qauxv.base.annotation.UiItemAgentEntry;
import io.github.qauxv.dsl.FunctionEntryRouter.Locations.Simplify;

View File

@@ -29,7 +29,7 @@ import com.github.kyuubiran.ezxhelper.utils.findMethod
import com.github.kyuubiran.ezxhelper.utils.getObjectAs
import com.github.kyuubiran.ezxhelper.utils.hookAfter
import com.github.kyuubiran.ezxhelper.utils.setViewZeroSize
import de.robv.android.xposed.XposedBridge
import io.github.qauxv.util.xpcompat.XposedBridge
import io.github.qauxv.base.annotation.FunctionHookEntry
import io.github.qauxv.base.annotation.UiItemAgentEntry
import io.github.qauxv.dsl.FunctionEntryRouter

View File

@@ -24,8 +24,8 @@ package cc.ioctl.hook.ui.title;
import android.view.View;
import androidx.annotation.NonNull;
import cc.ioctl.util.HookUtils;
import de.robv.android.xposed.XC_MethodReplacement;
import de.robv.android.xposed.XposedBridge;
import io.github.qauxv.util.xpcompat.XC_MethodReplacement;
import io.github.qauxv.util.xpcompat.XposedBridge;
import io.github.qauxv.base.annotation.FunctionHookEntry;
import io.github.qauxv.base.annotation.UiItemAgentEntry;
import io.github.qauxv.dsl.FunctionEntryRouter.Locations.Simplify;

View File

@@ -23,8 +23,8 @@ package cc.ioctl.util;
import android.view.View;
import android.view.ViewGroup;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XC_MethodHook.MethodHookParam;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.util.xpcompat.XC_MethodHook.MethodHookParam;
import io.github.qauxv.util.Log;
import io.github.qauxv.util.dexkit.DexMethodDescriptor;
import java.io.File;

View File

@@ -93,13 +93,46 @@ public class ExfriendManager {
private boolean dirtySerializedFlag = true;
private static final long[] ROBOT_ENTERPRISE_UIN_ARRAY = new long[]{
66600000L, // BabyQ
2854196306L, // 小冰
// 查询ROBOT信息 https://qun.qq.com/qunpro/robot/qunshare?robot_uin=66600000
66600000L, // babyQ
2854196925L, // QQ小店助手
2854202683L, // 游戏助手
2854204259L, // 赞噢机器人
2854196925L, // 小店助手
2854211892L,
2854202683L,
2854209338L, // 频道管理助手
2854211892L, // 饭团团
// 热门
3889031420L, // 庆余年 | 庆帝
3889019833L, // 鹅探长
2854208500L, // 修仙之路
2854202509L, // 饲养小猫
2854197266L, // AL_1S
2854214035L, // 心情复杂
2854196310L, // Q群管家
2854203763L, // 小YOYO
// 游戏
3889011373L, // 武林侠影
3889017942L, // 快说喜欢我
2854211478L, // 小念同学
2854198976L, // 小德娱乐菌
3889009909L, // 钓鱼达人
2854196306L, // 小冰
3889001741L, // 小小
// 娱乐
2854197548L, // 开心农场
3889000472L, // 麦麦子MaiBot
3889001044L, // 益智扫雷
2854207033L, // 房东人生
2854202692L, // 元梦甜橙喵
// 工具
2854203783L, // 阿罗娜小助手
2854213832L, // 小虾米
3889001607L, // 海兰德小助手
3889000871L, // 战地1小电视
2854196311L, // 王者荣耀小狐狸
2854207085L, // DNF手游-赛丽亚
2854196316L, // 和平精英-小几
2854212997L, // 机器人66
2854203945L, // 暗区突围老皮
};
private ExfriendManager(long uin) {
@@ -149,14 +182,14 @@ public class ExfriendManager {
}
public static ConcurrentHashMap getFriendsConcurrentHashMap(Object friendsManager)
throws IllegalAccessException, NoSuchFieldException {
throws IllegalAccessException, NoSuchFieldException {
for (Field field : Initiator.load("com.tencent.mobileqq.app.FriendsManager").getDeclaredFields()) {
if (ConcurrentHashMap.class == field.getType()) {
field.setAccessible(true);
ConcurrentHashMap concurrentHashMap = (ConcurrentHashMap) field.get(friendsManager);
if (concurrentHashMap != null && concurrentHashMap.size() > 0) {
if (concurrentHashMap.get(concurrentHashMap.keySet().toArray()[0]).getClass()
== Initiator.load("com.tencent.mobileqq.data.Friends")) {
== Initiator.load("com.tencent.mobileqq.data.Friends")) {
return concurrentHashMap;
}
}
@@ -477,7 +510,7 @@ public class ExfriendManager {
t.addField("_friendStatus", Table.TYPE_INT);
// 2. fill table
Iterator<Map.Entry<Integer, EventRecord>> it =/*(Iterator<Map.Entry<Long, FriendRecord>>)*/events
.entrySet().iterator();
.entrySet().iterator();
Map.Entry<Integer, EventRecord> ent;
EventRecord ev;
int k;
@@ -651,7 +684,7 @@ public class ExfriendManager {
mConfig.putInt("unread", 0);
try {
NotificationManager nm = (NotificationManager) HostInfo.getApplication()
.getSystemService(Context.NOTIFICATION_SERVICE);
.getSystemService(Context.NOTIFICATION_SERVICE);
nm.cancel(ID_EX_NOTIFY);
} catch (Exception e) {
Log.e(e);
@@ -791,13 +824,13 @@ public class ExfriendManager {
}
public Notification createNotiComp(NotificationManager nm, String ticker, String title,
String content, long[] vibration, PendingIntent pi) {
String content, long[] vibration, PendingIntent pi) {
Application app = HostInfo.getApplication();
//Do not use NotificationCompat, NotificationCompat does NOT support setSmallIcon with Bitmap.
Notification.Builder builder;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel("qn_del_notify", "删好友通知",
NotificationManager.IMPORTANCE_DEFAULT);
NotificationManager.IMPORTANCE_DEFAULT);
channel.setSound(null, null);
channel.setVibrationPattern(vibration);
nm.createNotificationChannel(channel);
@@ -825,7 +858,7 @@ public class ExfriendManager {
}
try {
Reflex.invokeVirtualAny(ManagerHelper.getFriendListHandler(), true, true, boolean.class,
boolean.class, void.class);
boolean.class, void.class);
} catch (Exception e) {
Log.e(e);
}

View File

@@ -23,8 +23,8 @@
package cc.ioctl.util;
import androidx.annotation.NonNull;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.util.xpcompat.XposedBridge;
import io.github.qauxv.base.ITraceableDynamicHook;
import io.github.qauxv.hook.BaseFunctionHook;
import io.github.qauxv.hook.BaseHookDispatcher;

View File

@@ -23,8 +23,8 @@
package cc.ioctl.util
import dalvik.system.BaseDexClassLoader
import de.robv.android.xposed.XC_MethodHook
import de.robv.android.xposed.XposedBridge
import io.github.qauxv.util.xpcompat.XC_MethodHook
import io.github.qauxv.util.xpcompat.XposedBridge
import io.github.qauxv.hook.BaseFunctionHook
import io.github.qauxv.util.LicenseStatus
import java.lang.reflect.Constructor

View File

@@ -24,7 +24,7 @@ package cc.ioctl.util;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import de.robv.android.xposed.XposedBridge;
import io.github.qauxv.util.xpcompat.XposedBridge;
import io.github.qauxv.util.Natives;
import io.github.qauxv.util.dexkit.DexMethodDescriptor;
import java.lang.reflect.Constructor;

View File

@@ -110,7 +110,7 @@ object ImageCustomSummary : CommonConfigFunctionHook("ImageCustomSummary", array
@SuppressLint("SetTextI18n")
private fun showConfigDialog(ctx: Context) {
val switchEnable = SwitchCompat(ctx).apply {
isChecked = isEnabled
isChecked = this@ImageCustomSummary.isEnabled
textSize = 16f
text = "功能总开关"
}
@@ -179,6 +179,9 @@ object ImageCustomSummary : CommonConfigFunctionHook("ImageCustomSummary", array
setView(rootLayout)
setPositiveButton("确定") { _, _ ->
isEnabled = switchEnable.isChecked
typePic0 = checkBoxTypePic0.isChecked
typePic1247 = checkBoxTypePic1247.isChecked
typeMarketFace = checkBoxTypeMarketFace.isChecked
summaryText = summaryTextEdit.text.toString()
valueState.update { if (isEnabled) "已开启" else "禁用" }
}
@@ -191,7 +194,6 @@ object ImageCustomSummary : CommonConfigFunctionHook("ImageCustomSummary", array
val sendMsgMethod = Initiator.loadClass("com.tencent.qqnt.kernel.nativeinterface.IKernelMsgService\$CppProxy").method("sendMsg")!!
hookBeforeIfEnabled(sendMsgMethod) { param ->
val contact = ContactCompat.fromKernelObject(param.args[1] as Serializable)
val chatType = contact.chatType
val elements = param.args[2] as ArrayList<*>
for (element in elements) {
val msgElement = (element as MsgElement)
@@ -202,9 +204,7 @@ object ImageCustomSummary : CommonConfigFunctionHook("ImageCustomSummary", array
}
if (typePic1247 && picSubType != 0) {
picElement.summary = summaryText
if (chatType != 4) { // 不是频道才修改类型, 不然会发送不出去
picElement.picSubType = 7
}
if (contact.chatType != 4) picElement.picSubType = 7
}
}
msgElement.marketFaceElement?.let { marketFaceElement ->

View File

@@ -23,7 +23,7 @@ package cn.lliiooll.hook
import cc.ioctl.hook.notification.MessageInterception
import cc.ioctl.util.msg.MessageReceiver
import de.robv.android.xposed.XposedHelpers
import io.github.qauxv.util.xpcompat.XposedHelpers
import io.github.qauxv.base.annotation.FunctionHookEntry
import io.github.qauxv.base.annotation.UiItemAgentEntry
import io.github.qauxv.bridge.AppRuntimeHelper

View File

@@ -5,8 +5,8 @@ import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import cc.ioctl.util.HookUtils;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.util.xpcompat.XposedBridge;
import io.github.qauxv.base.annotation.FunctionHookEntry;
import io.github.qauxv.base.annotation.UiItemAgentEntry;
import io.github.qauxv.dsl.FunctionEntryRouter;

View File

@@ -4,8 +4,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import cc.ioctl.util.HookUtils;
import cc.ioctl.util.Reflex;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.util.xpcompat.XposedBridge;
import io.github.qauxv.base.annotation.FunctionHookEntry;
import io.github.qauxv.base.annotation.UiItemAgentEntry;
import io.github.qauxv.dsl.FunctionEntryRouter;

View File

@@ -30,14 +30,18 @@ import cc.ioctl.hook.msg.PicMd5Hook
import cc.ioctl.hook.msg.PttForwardHook
import cc.ioctl.util.HookUtils
import com.github.kyuubiran.ezxhelper.utils.isAbstract
import de.robv.android.xposed.XC_MethodHook
import io.github.qauxv.util.xpcompat.XC_MethodHook
import io.github.duzhaokun123.hook.MessageCopyHook
import io.github.duzhaokun123.hook.MessageTTSHook
import io.github.qauxv.base.annotation.FunctionHookEntry
import io.github.qauxv.hook.BasePersistBackgroundHook
import io.github.qauxv.util.Initiator
import me.hd.hook.CopyMarkdown
import me.hd.hook.menu.CopyMarkdown
import me.hd.hook.menu.EditTextContent
import me.hd.hook.menu.RecallMsgRecord
import me.hd.hook.menu.RepeatToImg
import me.ketal.hook.PicCopyToClipboard
import me.qcuncle.hook.TranslateTextMsg
import top.xunflash.hook.MiniAppDirectJump
import java.lang.reflect.Method
@@ -55,6 +59,10 @@ object MenuBuilderHook : BasePersistBackgroundHook() {
MiniAppDirectJump,
CopyMarkdown,
MessageTTSHook,
EditTextContent,
TranslateTextMsg,
RecallMsgRecord,
RepeatToImg,
)
override fun initOnce(): Boolean {

View File

@@ -32,23 +32,21 @@ import io.github.qauxv.hook.CommonSwitchFunctionHook
import io.github.qauxv.util.QQVersion
import io.github.qauxv.util.dexkit.DexKit
import io.github.qauxv.util.dexkit.TroopInfoCardPageABConfig
import io.github.qauxv.util.requireMinQQVersion
import io.github.qauxv.util.hostInfo
import xyz.nextalone.util.throwOrTrue
@FunctionHookEntry
@UiItemAgentEntry
object DisableNewTroopInfoPage : CommonSwitchFunctionHook(arrayOf(TroopInfoCardPageABConfig)) {
override val name = "禁用新版群资料页"
override val description = "新版群资料页功能缺失,中看不中用,遂禁用之"
override val isAvailable = requireMinQQVersion(QQVersion.QQ_8_9_78)
override val uiItemLocation = FunctionEntryRouter.Locations.Auxiliary.GROUP_CATEGORY
override val isAvailable = hostInfo.versionCode in QQVersion.QQ_8_9_78..QQVersion.QQ_9_0_71
override fun initOnce() = throwOrTrue {
DexKit.requireClassFromCache(TroopInfoCardPageABConfig).findMethod {
returnType == Boolean::class.java && paramCount == 0
}.hookReturnConstant(false)
}
override val uiItemLocation = FunctionEntryRouter.Locations.Auxiliary.GROUP_CATEGORY
}

View File

@@ -24,7 +24,7 @@ package com.xiaoniu.hook
import android.content.Context
import cc.ioctl.util.HostInfo.isQQ
import cc.ioctl.util.afterHookIfEnabled
import de.robv.android.xposed.XposedHelpers
import io.github.qauxv.util.xpcompat.XposedHelpers
import io.github.qauxv.base.annotation.FunctionHookEntry
import io.github.qauxv.base.annotation.UiItemAgentEntry
import io.github.qauxv.dsl.FunctionEntryRouter.Locations.Auxiliary

View File

@@ -339,7 +339,7 @@ object TroopGroupHook : CommonSwitchFunctionHook(arrayOf(RecentPopup_onClickActi
override fun initOnce() = throwOrTrue {
val itemClazz = "Lcom/tencent/widget/PopupMenuDialog\$MenuItem;".clazz!!
val entryItem = itemClazz.newInstance(
Args(arrayOf(415411, "群聊分组", "群聊分组", R.drawable.ic_troop_group)),
Args(arrayOf(415411, "群聊分组", "群聊分组", R.drawable.ic_item_troop_group_72dp)),
ArgTypes(arrayOf(Int::class.java, String::class.java, String::class.java, Int::class.java))
)!!
"Lcom/tencent/widget/PopupMenuDialog;->conversationPlusBuild(Landroid/app/Activity;Ljava/util/List;Lcom/tencent/widget/PopupMenuDialog\$OnClickActionListener;Lcom/tencent/widget/PopupMenuDialog\$OnDismissListener;)Lcom/tencent/widget/PopupMenuDialog;".method.hookBefore {

View File

@@ -34,9 +34,9 @@ import cc.ioctl.util.Reflex
import cc.ioctl.util.afterHookIfEnabled
import com.xiaoniu.dispatcher.OnMenuBuilder
import com.xiaoniu.util.ContextUtils
import de.robv.android.xposed.XC_MethodHook
import de.robv.android.xposed.XposedBridge
import de.robv.android.xposed.XposedHelpers
import io.github.qauxv.util.xpcompat.XC_MethodHook
import io.github.qauxv.util.xpcompat.XposedBridge
import io.github.qauxv.util.xpcompat.XposedHelpers
import io.github.qauxv.R
import io.github.qauxv.base.annotation.FunctionHookEntry
import io.github.qauxv.base.annotation.UiItemAgentEntry

View File

@@ -32,9 +32,9 @@ import cc.ioctl.util.Reflex
import cc.ioctl.util.afterHookIfEnabled
import com.xiaoniu.dispatcher.OnMenuBuilder
import com.xiaoniu.util.ContextUtils
import de.robv.android.xposed.XC_MethodHook
import de.robv.android.xposed.XposedBridge
import de.robv.android.xposed.XposedHelpers
import io.github.qauxv.util.xpcompat.XC_MethodHook
import io.github.qauxv.util.xpcompat.XposedBridge
import io.github.qauxv.util.xpcompat.XposedHelpers
import io.github.duzhaokun123.hook.MessageCopyHook.TAG
import io.github.duzhaokun123.util.TTS
import io.github.qauxv.R
@@ -132,7 +132,7 @@ object MessageTTSHook : CommonSwitchFunctionHook(), OnMenuBuilder, DexKitFinder
override fun onGetMenuNt(msg: Any, componentType: String, param: XC_MethodHook.MethodHookParam) {
if (!isEnabled) return
val item = CustomMenu.createItemIconNt(msg, "TTS", 0, R.id.item_tts) {
val item = CustomMenu.createItemIconNt(msg, "TTS", R.drawable.ic_item_tts_72dp, R.id.item_tts) {
val ctx = ContextUtils.getCurrentActivity()
val wc = CommonContextWrapper.createAppCompatContext(ctx)
val text = try {
@@ -144,7 +144,7 @@ object MessageTTSHook : CommonSwitchFunctionHook(), OnMenuBuilder, DexKitFinder
}
TTS.speak(wc, text.toString())
}
val item2 = CustomMenu.createItemIconNt(msg, "TTS+", 0, R.id.item_tts2) {
val item2 = CustomMenu.createItemIconNt(msg, "TTS+", R.drawable.ic_item_tts_plus_72dp, R.id.item_tts2) {
val ctx = ContextUtils.getCurrentActivity()
val wc = CommonContextWrapper.createAppCompatContext(ctx)
val text = try {

View File

@@ -26,7 +26,7 @@ import android.view.View
import cc.ioctl.util.HookUtils.hookBeforeIfEnabled
import cc.ioctl.util.hookBeforeIfEnabled
import com.alphi.qhmk.module.HiddenVipIconForSe
import de.robv.android.xposed.XC_MethodHook.MethodHookParam
import io.github.qauxv.util.xpcompat.XC_MethodHook.MethodHookParam
import io.github.qauxv.base.annotation.FunctionHookEntry
import io.github.qauxv.base.annotation.UiItemAgentEntry
import io.github.qauxv.dsl.FunctionEntryRouter

View File

@@ -25,7 +25,7 @@ package io.github.fusumayuki.hook
import android.view.View
import android.view.ViewStub
import cc.ioctl.util.HookUtils.hookBeforeIfEnabled
import de.robv.android.xposed.XC_MethodHook.MethodHookParam
import io.github.qauxv.util.xpcompat.XC_MethodHook.MethodHookParam
import io.github.qauxv.base.annotation.FunctionHookEntry
import io.github.qauxv.base.annotation.UiItemAgentEntry
import io.github.qauxv.dsl.FunctionEntryRouter

View File

@@ -0,0 +1,56 @@
/*
* QAuxiliary - An Xposed module for QQ/TIM
* Copyright (C) 2019-2024 QAuxiliary developers
* https://github.com/cinit/QAuxiliary
*
* This software is an opensource software: you can redistribute it
* and/or modify it under the terms of the General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version 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 General Public License for more details.
*
* You should have received a copy of the General Public License
* along with this software.
* If not, see
* <https://github.com/cinit/QAuxiliary/blob/master/LICENSE.md>.
*/
package io.github.memory2314.hook
import cc.ioctl.util.hookBeforeIfEnabled
import io.github.qauxv.base.annotation.FunctionHookEntry
import io.github.qauxv.base.annotation.UiItemAgentEntry
import io.github.qauxv.dsl.FunctionEntryRouter
import io.github.qauxv.hook.CommonSwitchFunctionHook
import io.github.qauxv.util.QQVersion
import io.github.qauxv.util.hostInfo
import io.github.qauxv.util.requireMinQQVersion
import xyz.nextalone.util.invoke
import xyz.nextalone.util.method
import xyz.nextalone.util.throwOrTrue
@FunctionHookEntry
@UiItemAgentEntry
object GuildOldStyle : CommonSwitchFunctionHook() {
override val name = "频道旧版样式"
override val description = "仅支持QQ9.0.15~9.0.73版本"
override val uiItemLocation = FunctionEntryRouter.Locations.Auxiliary.GUILD_CATEGORY
override val isAvailable = hostInfo.versionCode in QQVersion.QQ_9_0_15..QQVersion.QQ_9_0_73
override fun initOnce() = throwOrTrue {
if (requireMinQQVersion(QQVersion.QQ_9_0_30)) { // 9.0.30~9.0.73(之后的版本获取布局方法不存在)
hookBeforeIfEnabled("Lcom/tencent/mobileqq/guild/mainframe/GuildFragmentDelegateFrame;->getCurrentMainFragment()Lcom/tencent/mobileqq/guild/mainframe/AbsGuildMainFragment;".method) {
it.result = it.thisObject.invoke("getMainFrameFragment")
}
} else { // (第一个新版频道)9.0.15~9.0.25
hookBeforeIfEnabled("Lcom/tencent/mobileqq/guild/mainframe/GuildFragmentDelegateFrame;->s()Lcom/tencent/mobileqq/guild/mainframe/AbsGuildMainFragment;".method) {
it.result = it.thisObject.invoke("u")
}
}
}
}

View File

@@ -0,0 +1,42 @@
/*
* QAuxiliary - An Xposed module for QQ/TIM
* Copyright (C) 2019-2024 QAuxiliary developers
* https://github.com/cinit/QAuxiliary
*
* This software is an opensource software: you can redistribute it
* and/or modify it under the terms of the General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version 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 General Public License for more details.
*
* You should have received a copy of the General Public License
* along with this software.
* If not, see
* <https://github.com/cinit/QAuxiliary/blob/master/LICENSE.md>.
*/
package io.github.memory2314.hook
import io.github.qauxv.base.annotation.FunctionHookEntry
import io.github.qauxv.base.annotation.UiItemAgentEntry
import io.github.qauxv.dsl.FunctionEntryRouter
import io.github.qauxv.hook.CommonSwitchFunctionHook
import xyz.nextalone.util.method
import xyz.nextalone.util.replace
import xyz.nextalone.util.throwOrTrue
@FunctionHookEntry
@UiItemAgentEntry
object RemoveShareLimit : CommonSwitchFunctionHook() {
override val name = "去除转发9名联系人限制"
override val uiItemLocation = FunctionEntryRouter.Locations.Auxiliary.CHAT_CATEGORY
override fun initOnce() = throwOrTrue {
"Lcom/tencent/mobileqq/activity/ForwardRecentActivity;->add2ForwardTargetList(Lcom/tencent/mobileqq/selectmember/ResultRecord;)Z".method.replace(this, true)
}
}

View File

@@ -56,7 +56,7 @@ import io.github.qauxv.fragment.AboutFragment;
import io.github.qauxv.fragment.CheckAbiVariantFragment;
import io.github.qauxv.fragment.CheckAbiVariantModel;
import io.github.qauxv.lifecycle.JumpActivityEntryHook;
import io.github.qauxv.startup.HookEntry;
import io.github.qauxv.util.PackageConstants;
import io.github.qauxv.util.SyncUtils;
import io.github.qauxv.util.Toasts;
import io.github.qauxv.util.UiThread;
@@ -165,11 +165,11 @@ public class ConfigV2Activity extends AppCompatTransferActivity {
String pkg = null;
var id = view.getId();
if (id == R.id.mainRelativeLayoutButtonOpenQQ) {
pkg = HookEntry.PACKAGE_NAME_QQ;
pkg = PackageConstants.PACKAGE_NAME_QQ;
} else if (id == R.id.mainRelativeLayoutButtonOpenTIM) {
pkg = HookEntry.PACKAGE_NAME_TIM;
pkg = PackageConstants.PACKAGE_NAME_TIM;
} else if (id == R.id.mainRelativeLayoutButtonOpenQQLite) {
pkg = HookEntry.PACKAGE_NAME_QQ_LITE;
pkg = PackageConstants.PACKAGE_NAME_QQ_LITE;
}
if (pkg != null) {
Intent intent = new Intent();
@@ -205,19 +205,19 @@ public class ConfigV2Activity extends AppCompatTransferActivity {
String pkg = null;
switch (which) {
case 0: {
pkg = HookEntry.PACKAGE_NAME_QQ;
pkg = PackageConstants.PACKAGE_NAME_QQ;
break;
}
case 1: {
pkg = HookEntry.PACKAGE_NAME_TIM;
pkg = PackageConstants.PACKAGE_NAME_TIM;
break;
}
case 2: {
pkg = HookEntry.PACKAGE_NAME_QQ_LITE;
pkg = PackageConstants.PACKAGE_NAME_QQ_LITE;
break;
}
case 3: {
pkg = HookEntry.PACKAGE_NAME_QQ_HD;
pkg = PackageConstants.PACKAGE_NAME_QQ_HD;
break;
}
default: {

View File

@@ -26,7 +26,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import cc.ioctl.util.Reflex;
import com.tencent.common.app.AppInterface;
import de.robv.android.xposed.XposedHelpers;
import io.github.qauxv.util.xpcompat.XposedHelpers;
import io.github.qauxv.base.annotation.DexDeobfs;
import io.github.qauxv.bridge.ntapi.RelationNTUinAndUidApi;
import io.github.qauxv.util.HostInfo;

View File

@@ -21,8 +21,8 @@
*/
package io.github.qauxv.bridge;
import static de.robv.android.xposed.XposedHelpers.callMethod;
import static de.robv.android.xposed.XposedHelpers.setObjectField;
import static io.github.qauxv.util.xpcompat.XposedHelpers.callMethod;
import static io.github.qauxv.util.xpcompat.XposedHelpers.setObjectField;
import android.os.Bundle;
import cc.ioctl.util.Reflex;

View File

@@ -22,7 +22,7 @@
package io.github.qauxv.config;
import android.os.Environment;
import io.github.qauxv.startup.HookEntry;
import io.github.qauxv.util.PackageConstants;
import io.github.qauxv.util.HostInfo;
import io.github.qauxv.util.Log;
import java.io.File;
@@ -44,7 +44,7 @@ public class SafeModeManager {
}
INSTANCE.mSafeModeEnableFile = new File(
Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/data/" +
HookEntry.sCurrentPackageName + "/" + SAFE_MODE_FILE_NAME
HostInfo.getHostInfo().getPackageName() + "/" + SAFE_MODE_FILE_NAME
);
return INSTANCE;
}
@@ -73,10 +73,6 @@ public class SafeModeManager {
if (!isAvailable()) {
return false;
}
if (HookEntry.sCurrentPackageName == null || HookEntry.sCurrentPackageName.isBlank()) {
Log.e("Failed to enable or disable safe mode, sCurrentPackageName is null or blank");
return false;
}
if (isEnable) {
try {
boolean isCreated = mSafeModeEnableFile.createNewFile();

View File

@@ -27,7 +27,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import cc.ioctl.hook.SettingEntryHook;
import cc.ioctl.util.HostInfo;
import de.robv.android.xposed.XposedBridge;
import io.github.qauxv.util.xpcompat.XposedBridge;
import io.github.qauxv.BuildConfig;
import io.github.qauxv.util.SyncUtils;
import io.github.qauxv.base.IDynamicHook;

View File

@@ -123,6 +123,9 @@ public class InjectDelayableHooks {
}
}
}
long start = System.currentTimeMillis();
final ArrayList<Step> steps = new ArrayList<>(todos);
// collect all dex-deobfs steps if backend supports
HashSet<String> deobfIndexList = new HashSet<>(16);
@@ -208,6 +211,8 @@ public class InjectDelayableHooks {
}
} finally {
DexDeobfsProvider.INSTANCE.exitDeobfsSection();
long end = System.currentTimeMillis();
Log.i("DexDeobfsProvider: cost " + (end - start) + "ms");
}
}
if (LicenseStatus.hasUserAcceptEula()) {

View File

@@ -44,8 +44,8 @@ import cc.ioctl.hook.ui.misc.OptXListViewScrollBar;
import cc.ioctl.hook.ui.title.RemoveCameraButton;
import cc.ioctl.util.HostInfo;
import cc.ioctl.util.Reflex;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.util.xpcompat.XposedBridge;
import io.github.qauxv.config.ConfigItems;
import io.github.qauxv.config.SafeModeManager;
import io.github.qauxv.lifecycle.ActProxyMgr;

View File

@@ -46,8 +46,17 @@ class HotUpdateConfigFragment : BaseRootLayoutFragment(), View.OnClickListener {
HotUpdateManager.CHANNEL_CANARY to R.id.hotUpdateConfig_channel_canary,
)
private val actionIdToViewId = mapOf(
HotUpdateManager.ACTION_DISABLE to R.id.hotUpdateConfig_action_disabled,
HotUpdateManager.ACTION_QUERY to R.id.hotUpdateConfig_action_query_before_update,
HotUpdateManager.ACTION_AUTO_UPDATE_WITH_NOTIFICATION to R.id.hotUpdateConfig_notice_after_update,
HotUpdateManager.ACTION_AUTO_UPDATE_WITHOUT_NOTIFICATION to R.id.hotUpdateConfig_auto_update_without_notice,
)
private fun viewIdToChannelId(viewId: Int) = channelIdToViewId.filterValues { it == viewId }.keys.first()
private fun viewIdToActionId(viewId: Int) = actionIdToViewId.filterValues { it == viewId }.keys.first()
override fun doOnCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
title = "热更新配置"
binding = FragmentHotUpdateConfigBinding.inflate(inflater, container, false).apply {
@@ -55,6 +64,12 @@ class HotUpdateConfigFragment : BaseRootLayoutFragment(), View.OnClickListener {
hotUpdateConfigChannelStable.setOnClickListener(this@HotUpdateConfigFragment)
hotUpdateConfigChannelBeta.setOnClickListener(this@HotUpdateConfigFragment)
hotUpdateConfigChannelCanary.setOnClickListener(this@HotUpdateConfigFragment)
hotUpdateConfigActionDisabled.setOnClickListener(this@HotUpdateConfigFragment)
hotUpdateConfigActionQueryBeforeUpdate.setOnClickListener(this@HotUpdateConfigFragment)
hotUpdateConfigNoticeAfterUpdate.setOnClickListener(this@HotUpdateConfigFragment)
hotUpdateConfigAutoUpdateWithoutNotice.setOnClickListener(this@HotUpdateConfigFragment)
updateViewStatus(this)
fun adjustTitleTextSize(v: AppCompatTextView) {
@@ -79,6 +94,11 @@ class HotUpdateConfigFragment : BaseRootLayoutFragment(), View.OnClickListener {
adjustTitleTextSize(hotUpdateConfigChannelStable)
adjustTitleTextSize(hotUpdateConfigChannelBeta)
adjustTitleTextSize(hotUpdateConfigChannelCanary)
adjustTitleTextSize(hotUpdateConfigActionDisabled)
adjustTitleTextSize(hotUpdateConfigActionQueryBeforeUpdate)
adjustTitleTextSize(hotUpdateConfigNoticeAfterUpdate)
adjustTitleTextSize(hotUpdateConfigAutoUpdateWithoutNotice)
}
rootLayoutView = binding!!.root
return binding!!.root
@@ -92,6 +112,7 @@ class HotUpdateConfigFragment : BaseRootLayoutFragment(), View.OnClickListener {
private fun updateViewStatus(binding: FragmentHotUpdateConfigBinding) {
val ctx = requireContext()
val currentChannel = HotUpdateManager.currentChannel
val currentAction = HotUpdateManager.currentAction
binding.hotUpdateConfigCurrentInfo.text = "别看了,这个功能还没做好,选什么都没用"
@@ -101,6 +122,12 @@ class HotUpdateConfigFragment : BaseRootLayoutFragment(), View.OnClickListener {
binding.hotUpdateConfigChannelBeta,
binding.hotUpdateConfigChannelCanary,
)
val actionButtons = arrayOf(
binding.hotUpdateConfigActionDisabled,
binding.hotUpdateConfigActionQueryBeforeUpdate,
binding.hotUpdateConfigNoticeAfterUpdate,
binding.hotUpdateConfigAutoUpdateWithoutNotice,
)
val accentColor = ThemeAttrUtils.resolveColorOrDefaultColorRes(
ctx,
androidx.appcompat.R.attr.colorAccent,
@@ -123,6 +150,22 @@ class HotUpdateConfigFragment : BaseRootLayoutFragment(), View.OnClickListener {
it.setTextColor(secondTextColor)
}
}
actionButtons.forEach {
val actionId = viewIdToActionId(it.id)
if (currentAction == actionId) {
it.setTextColor(accentColor)
if (it.compoundDrawables[2] == null) {
it.setCompoundDrawablesWithIntrinsicBounds(
null, null, ResourcesCompat.getDrawable(
ctx.resources, R.drawable.ic_check_24, ctx.theme
), null
)
}
} else {
it.setCompoundDrawables(null, null, null, null)
it.setTextColor(secondTextColor)
}
}
}
private fun onChannelClick(v: View, channelId: Int) {
@@ -159,12 +202,25 @@ class HotUpdateConfigFragment : BaseRootLayoutFragment(), View.OnClickListener {
}
}
private fun onActionClick(v: View, actionId: Int) {
if (HotUpdateManager.currentAction == actionId) {
return
}
HotUpdateManager.currentAction = actionId
updateViewStatus(binding!!)
}
override fun onClick(v: View) {
when (v.id) {
R.id.hotUpdateConfig_channel_disabled -> onChannelClick(v, HotUpdateManager.CHANNEL_DISABLED)
R.id.hotUpdateConfig_channel_stable -> onChannelClick(v, HotUpdateManager.CHANNEL_STABLE)
R.id.hotUpdateConfig_channel_beta -> onChannelClick(v, HotUpdateManager.CHANNEL_BETA)
R.id.hotUpdateConfig_channel_canary -> onChannelClick(v, HotUpdateManager.CHANNEL_CANARY)
R.id.hotUpdateConfig_action_disabled -> onActionClick(v, HotUpdateManager.ACTION_DISABLE)
R.id.hotUpdateConfig_action_query_before_update -> onActionClick(v, HotUpdateManager.ACTION_QUERY)
R.id.hotUpdateConfig_notice_after_update -> onActionClick(v, HotUpdateManager.ACTION_AUTO_UPDATE_WITH_NOTIFICATION)
R.id.hotUpdateConfig_auto_update_without_notice -> onActionClick(v, HotUpdateManager.ACTION_AUTO_UPDATE_WITHOUT_NOTIFICATION)
}
}

View File

@@ -39,9 +39,14 @@ import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CheckBox
import android.widget.EditText
import android.widget.FrameLayout
import android.widget.LinearLayout
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.AppCompatCheckBox
import androidx.appcompat.widget.AppCompatEditText
import androidx.appcompat.widget.AppCompatTextView
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.content.res.ResourcesCompat
import androidx.lifecycle.lifecycleScope
@@ -49,12 +54,13 @@ import cc.ioctl.fragment.ExfriendListFragment
import cc.ioctl.util.ExfriendManager
import cc.ioctl.util.HostInfo
import cc.ioctl.util.LayoutHelper
import cc.ioctl.util.LayoutHelper.dip2px
import cc.ioctl.util.Reflex
import cc.ioctl.util.data.EventRecord
import cc.ioctl.util.data.FriendRecord
import cc.ioctl.util.ui.ThemeAttrUtils
import cc.ioctl.util.ui.dsl.RecyclerListViewController
import de.robv.android.xposed.XposedBridge
import io.github.qauxv.util.xpcompat.XposedBridge
import io.github.qauxv.R
import io.github.qauxv.activity.SettingsUiFragmentHostActivity
import io.github.qauxv.activity.SettingsUiFragmentHostActivity.Companion.createStartActivityForFragmentIntent
@@ -66,11 +72,12 @@ import io.github.qauxv.dsl.item.CategoryItem
import io.github.qauxv.dsl.item.DslTMsgListItemInflatable
import io.github.qauxv.dsl.item.TextSwitchItem
import io.github.qauxv.lifecycle.ActProxyMgr
import io.github.qauxv.startup.HookEntry
import io.github.qauxv.poststartup.StartupInfo
import io.github.qauxv.startup.HybridClassLoader
import io.github.qauxv.tlb.ConfigTable.cacheMap
import io.github.qauxv.ui.CustomDialog
import io.github.qauxv.util.Initiator
import io.github.qauxv.util.LoaderExtensionHelper
import io.github.qauxv.util.Natives
import io.github.qauxv.util.Toasts
import io.github.qauxv.util.dexkit.DexKit
@@ -166,14 +173,7 @@ class TroubleshootFragment : BaseRootLayoutFragment() {
})
},
CategoryItem("调试信息") {
val statusInfo = "PID: " + android.os.Process.myPid() +
", UID: " + android.os.Process.myUid() +
", " + (if (android.os.Process.is64Bit()) "64 bit" else "32 bit") + "\n" +
"Xposed API version: " + XposedBridge.getXposedVersion() + "\n" +
HybridClassLoader.getXposedBridgeClassName() + "\n" +
"module: " + HookEntry.getModulePath() + "\n" +
"ctx.dataDir: " + hostInfo.application.dataDir
description(statusInfo, isTextSelectable = true)
description(generateStatusText(), isTextSelectable = true)
description(generateDebugInfo(), isTextSelectable = true)
}
)
@@ -222,8 +222,9 @@ class TroubleshootFragment : BaseRootLayoutFragment() {
exitProcess(0)
}
private val clickToResetDefaultConfig = confirmBeforeAction(
"此操作将删除该模块的所有配置信息,包括屏蔽通知的群列表,但不包括历史好友列表.点击确认后请等待3秒后手动重启" + hostInfo.hostName + ".\n此操作不可恢复"
private val clickToResetDefaultConfig = confirmTwiceBeforeAction(
"此操作将删除该模块的所有配置信息,包括屏蔽通知的群列表,但不包括历史好友列表.点击确认后请等待3秒后手动重启" + hostInfo.hostName + ".\n此操作不可恢复",
"删除所有配置信息"
) {
ConfigManager.getCache().apply {
clear()
@@ -237,12 +238,13 @@ class TroubleshootFragment : BaseRootLayoutFragment() {
exitProcess(0)
}
private val clickToClearRecoveredFriends = confirmBeforeAction(
private val clickToClearRecoveredFriends = confirmTwiceBeforeAction(
"""
此操作将删除当前账号(${getLongAccountUin()})下的 已恢复 的历史好友记录(记录可单独删除).
如果因 BUG 大量好友被标记为已删除, 请先刷新好友列表, 然后再点击此按钮.
此操作不可恢复
""".trimIndent()
""".trimIndent(),
"删除已恢复的好友记录"
) {
val exm = ExfriendManager.getCurrent()
val it: MutableIterator<*> = exm.events.entries.iterator()
@@ -258,11 +260,12 @@ class TroubleshootFragment : BaseRootLayoutFragment() {
Toasts.success(requireContext(), "操作成功")
}
private val clickToClearAllFriends = confirmBeforeAction(
private val clickToClearAllFriends = confirmTwiceBeforeAction(
"此操作将删除当前账号(" + getLongAccountUin()
+ ")下的 全部 的历史好友记录, 通常您不需要进行此操作. \n" +
"如果您的历史好友列表中因bug出现大量好友,请在联系人列表下拉刷新后点击 删除标记为已恢复的好友. \n" +
"此操作不可恢复"
"此操作不可恢复",
"删除所有好友记录"
) {
val uin = getLongAccountUin()
if (uin < 10000) {
@@ -303,6 +306,57 @@ class TroubleshootFragment : BaseRootLayoutFragment() {
dialog.show()
}
private fun confirmTwiceBeforeAction(
confirmMessage: String,
secondConfirmCheckBoxText: String,
action: () -> Unit
) = View.OnClickListener {
val ctx = requireContext()
val builder = AlertDialog.Builder(ctx)
builder.setPositiveButton(android.R.string.ok) { _, _ ->
try {
action()
} catch (e: Exception) {
CustomDialog.createFailsafe(ctx)
.setTitle(Reflex.getShortClassName(e))
.setCancelable(true)
.setMessage(e.toString())
.ok().show()
}
}
builder.setNegativeButton(android.R.string.cancel, null)
builder.setCancelable(true)
builder.setTitle("确认操作")
// create a linear layout to hold the message and checkbox
val layout = LinearLayout(ctx).apply {
orientation = LinearLayout.VERTICAL
val padding = ctx.resources.getDimension(androidx.appcompat.R.dimen.abc_dialog_padding_material).toInt()
setPadding(padding, padding / 3, padding, 0)
}
val message = AppCompatTextView(ctx).apply {
text = confirmMessage
setTextSize(TypedValue.COMPLEX_UNIT_SP, 16f)
setTextColor(ResourcesCompat.getColor(resources, R.color.firstTextColor, ctx.theme))
}
val checkBox = AppCompatCheckBox(ctx).apply {
text = secondConfirmCheckBoxText
setTextSize(TypedValue.COMPLEX_UNIT_SP, 16f)
setTextColor(ResourcesCompat.getColor(resources, R.color.firstTextColor, ctx.theme))
isClickable = true
isChecked = false
}
layout.addView(message)
layout.addView(checkBox)
builder.setView(layout)
val dialog = builder.show()
// get positive button and set listener
val positiveButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE)
checkBox.setOnCheckedChangeListener { _, isChecked ->
positiveButton.isEnabled = isChecked
}
positiveButton.isEnabled = false
}
private fun actionOrShowError(action: () -> Unit) = View.OnClickListener {
runOrShowError(action)
}
@@ -423,6 +477,26 @@ class TroubleshootFragment : BaseRootLayoutFragment() {
startActivity(intent)
}
private fun generateStatusText(): String {
val loader = StartupInfo.getLoaderInfo()
val hook = StartupInfo.requireHookBridge()
var statusInfo = "PID: " + android.os.Process.myPid() +
", UID: " + android.os.Process.myUid() +
", " + (if (android.os.Process.is64Bit()) "64 bit" else "32 bit") + "\n" +
"Xposed API version: " + hook.apiLevel + "\n" +
"module: " + StartupInfo.getModulePath() + "\n" +
"ctx.dataDir: " + hostInfo.application.dataDir + "\n"
statusInfo += "entry: " + loader.entryPointName + "\n"
var xp = loader.queryExtension("GetXposedBridgeClass") as Class<*>?
if (xp == null) {
xp = loader.queryExtension("GetXposedInterfaceClass") as Class<*>?
}
statusInfo += "XposedBridge: " + (if (xp != null) xp.name else "null") + "\n"
statusInfo += hook.frameworkName + " " + hook.frameworkVersion + " (" + hook.frameworkVersionCode + ")\n"
statusInfo += "Hook counter: " + LoaderExtensionHelper.getHookCounter();
return statusInfo
}
private fun generateDebugInfo(): CharSequence {
val ctx = requireContext()
val colorError: Int = ThemeAttrUtils.resolveColorOrDefaultColorInt(ctx, R.attr.unusableColor, Color.RED)

View File

@@ -28,8 +28,8 @@ import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.util.xpcompat.XposedBridge;
import io.github.qauxv.activity.SettingsUiFragmentHostActivity;
import io.github.qauxv.fragment.EulaFragment;
import io.github.qauxv.fragment.TroubleshootFragment;

View File

@@ -53,7 +53,8 @@ import androidx.annotation.RequiresApi;
import cc.ioctl.util.HostInfo;
import io.github.qauxv.R;
import io.github.qauxv.core.MainHook;
import io.github.qauxv.startup.HookEntry;
import io.github.qauxv.poststartup.StartupInfo;
import io.github.qauxv.util.PackageConstants;
import io.github.qauxv.ui.WindowIsTranslucent;
import io.github.qauxv.util.Initiator;
import io.github.qauxv.util.Log;
@@ -113,7 +114,7 @@ public class Parasitics {
return;
} catch (Resources.NotFoundException ignored) {
}
String sModulePath = HookEntry.getModulePath();
String sModulePath = StartupInfo.getModulePath();
if (sModulePath == null) {
throw new RuntimeException("get module path failed, loader=" + MainHook.class.getClassLoader());
}

View File

@@ -32,8 +32,8 @@ import android.provider.OpenableColumns;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import cc.ioctl.util.HostInfo;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.util.xpcompat.XposedBridge;
import io.github.qauxv.util.Initiator;
import io.github.qauxv.util.Log;
import io.github.qauxv.util.SyncUtils;

View File

@@ -25,8 +25,8 @@ package io.github.qauxv.omnifix.hw;
import android.content.Context;
import android.content.res.Resources;
import androidx.annotation.NonNull;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.util.xpcompat.XposedBridge;
import io.github.qauxv.BuildConfig;
import io.github.qauxv.util.Log;
import java.lang.reflect.Field;

View File

@@ -0,0 +1,138 @@
/*
* QAuxiliary - An Xposed module for QQ/TIM
* Copyright (C) 2019-2024 QAuxiliary developers
* https://github.com/cinit/QAuxiliary
*
* This software is an opensource software: you can redistribute it
* and/or modify it under the terms of the General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version 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 General Public License for more details.
*
* You should have received a copy of the General Public License
* along with this software.
* If not, see
* <https://github.com/cinit/QAuxiliary/blob/master/LICENSE.md>.
*/
package io.github.qauxv.poststartup;
import android.annotation.SuppressLint;
import android.app.Application;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.os.Build;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.github.qauxv.loader.hookapi.IHookBridge;
import io.github.qauxv.loader.hookapi.ILoaderInfo;
import io.github.qauxv.util.IoUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import org.lsposed.hiddenapibypass.HiddenApiBypass;
@Keep
public class StartupAgent {
private static boolean sInitialized = false;
private StartupAgent() {
throw new AssertionError("No instance for you!");
}
@Keep
public static void startup(
@NonNull String modulePath,
@NonNull ApplicationInfo appInfo,
@NonNull ILoaderInfo loaderInfo,
@NonNull ClassLoader hostClassLoader,
@Nullable IHookBridge hookBridge
) {
if (sInitialized) {
return;
}
sInitialized = true;
if (io.github.qauxv.R.string.res_inject_success >>> 24 == 0x7f) {
throw new AssertionError("package id must NOT be 0x7f, reject loading...");
}
if ("true".equals(System.getProperty(StartupAgent.class.getName()))) {
android.util.Log.e("QAuxv", "Error: QAuxiliary reloaded??");
// I don't know... What happened?
return;
}
System.setProperty(StartupAgent.class.getName(), "true");
StartupInfo.setModulePath(modulePath);
StartupInfo.setLoaderInfo(loaderInfo);
StartupInfo.setHookBridge(hookBridge);
StartupInfo.setInHostProcess(true);
// bypass hidden api
ensureHiddenApiAccess();
// we want context
Context ctx = getBaseApplicationImpl(hostClassLoader);
if (ctx == null) {
if (hookBridge == null) {
throw new UnsupportedOperationException("neither base application nor hook bridge found");
}
StartupHook.getInstance().initializeBeforeAppCreate(hostClassLoader);
} else {
StartupHook.getInstance().initializeAfterAppCreate(ctx);
}
}
public static Context getBaseApplicationImpl(@NonNull ClassLoader classLoader) {
Context app;
try {
Class<?> clz = classLoader.loadClass("com.tencent.common.app.BaseApplicationImpl");
Field fsApp = null;
for (Field f : clz.getDeclaredFields()) {
if (f.getType() == clz) {
fsApp = f;
break;
}
}
if (fsApp == null) {
throw new UnsupportedOperationException("field BaseApplicationImpl.sApplication not found");
}
app = (Context) fsApp.get(null);
return app;
} catch (ReflectiveOperationException e) {
android.util.Log.e("QAuxv", "getBaseApplicationImpl: failed", e);
throw IoUtils.unsafeThrow(e);
}
}
private static void ensureHiddenApiAccess() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !isHiddenApiAccessible()) {
android.util.Log.w("QAuxv", "Hidden API access not accessible, SDK_INT is " + Build.VERSION.SDK_INT);
HiddenApiBypass.setHiddenApiExemptions("L");
}
}
@SuppressLint({"BlockedPrivateApi", "PrivateApi"})
public static boolean isHiddenApiAccessible() {
Class<?> kContextImpl;
try {
kContextImpl = Class.forName("android.app.ContextImpl");
} catch (ClassNotFoundException e) {
return false;
}
Field mActivityToken = null;
Field mToken = null;
try {
mActivityToken = kContextImpl.getDeclaredField("mActivityToken");
} catch (NoSuchFieldException ignored) {
}
try {
mToken = kContextImpl.getDeclaredField("mToken");
} catch (NoSuchFieldException ignored) {
}
return mActivityToken != null || mToken != null;
}
}

View File

@@ -1,34 +1,36 @@
/*
* QAuxiliary - An Xposed module for QQ/TIM
* Copyright (C) 2019-2022 qwq233@qwq2333.top
* Copyright (C) 2019-2024 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
* This software is an opensource software: you can redistribute it
* and/or modify it under the terms of the 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
* version 3 of the License, or any later version 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.
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the 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/>
* You should have received a copy of the General Public License
* along with this software.
* If not, see
* <https://github.com/cinit/QAuxiliary/blob/master/LICENSE.md>.
*/
package io.github.qauxv.startup;
package io.github.qauxv.poststartup;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Environment;
import android.util.DisplayMetrics;
import android.util.Log;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import androidx.annotation.NonNull;
import io.github.qauxv.startup.HybridClassLoader;
import io.github.qauxv.util.IoUtils;
import io.github.qauxv.util.xpcompat.XC_MethodHook;
import io.github.qauxv.util.xpcompat.XposedBridge;
import io.github.qauxv.util.xpcompat.XposedHelpers;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@@ -38,9 +40,11 @@ import java.lang.reflect.Modifier;
import java.util.HashMap;
/**
* Startup hook for QQ/TIM They should act differently according to the process they belong to. I don't want to cope
* with them any more, enjoy it as long as possible. DO NOT INVOKE ANY METHOD THAT MAY GET IN TOUCH WITH KOTLIN HERE. DO
* NOT MODIFY ANY CODE HERE UNLESS NECESSARY.
* Startup hook for QQ/TIM They should act differently according to the process they belong to.
* <p>
* I don't want to cope with them anymore, enjoy it as long as possible.
* <p>
* DO NOT MODIFY ANY CODE HERE UNLESS NECESSARY.
*
* @author cinit
*/
@@ -48,7 +52,6 @@ public class StartupHook {
private static StartupHook sInstance;
private static boolean sSecondStageInit = false;
private boolean mFirstStageInit = false;
private StartupHook() {
}
@@ -65,44 +68,12 @@ public class StartupHook {
if (sSecondStageInit) {
return;
}
ClassLoader classLoader = ctx.getClassLoader();
if (classLoader == null) {
throw new AssertionError("ERROR: classLoader == null");
}
if ("true".equals(System.getProperty(StartupHook.class.getName()))) {
XposedBridge.log("Err:QAuxiliary reloaded??");
//I don't know... What happened?
return;
}
System.setProperty(StartupHook.class.getName(), "true");
injectClassLoader(classLoader);
HybridClassLoader.setHostClassLoader(ctx.getClassLoader());
StartupRoutine.execPostStartupInit(ctx, step, lpwReserved, bReserved);
sSecondStageInit = true;
deleteDirIfNecessaryNoThrow(ctx);
}
@SuppressWarnings("JavaReflectionMemberAccess")
@SuppressLint("DiscouragedPrivateApi")
private static void injectClassLoader(ClassLoader classLoader) {
if (classLoader == null) {
throw new NullPointerException("classLoader == null");
}
try {
Field fParent = ClassLoader.class.getDeclaredField("parent");
fParent.setAccessible(true);
ClassLoader mine = StartupHook.class.getClassLoader();
ClassLoader curr = (ClassLoader) fParent.get(mine);
if (curr == null) {
curr = XposedBridge.class.getClassLoader();
}
if (!curr.getClass().getName().equals(HybridClassLoader.class.getName())) {
fParent.set(mine, new HybridClassLoader(curr, classLoader));
}
} catch (Exception e) {
log_e(e);
}
}
static void deleteDirIfNecessaryNoThrow(Context ctx) {
try {
deleteFile(new File(ctx.getDataDir(), "app_qqprotect"));
@@ -147,58 +118,27 @@ public class StartupHook {
String msg = Log.getStackTraceString(th);
Log.e("QAuxv", msg);
try {
XposedBridge.log(th);
} catch (NoClassDefFoundError e) {
StartupInfo.getLoaderInfo().log(th);
} catch (NoClassDefFoundError | NullPointerException e) {
Log.e("Xposed", msg);
Log.e("EdXposed-Bridge", msg);
}
}
private static void checkClassLoaderIsolation() {
Class<?> stub;
try {
stub = Class.forName("com.tencent.common.app.BaseApplicationImpl");
} catch (ClassNotFoundException e) {
Log.d("QAuxv", "checkClassLoaderIsolation success");
return;
}
Log.e("QAuxv", "checkClassLoaderIsolation failure!");
Log.e("QAuxv", "HostApp: " + stub.getClassLoader());
Log.e("QAuxv", "Module: " + StartupHook.class.getClassLoader());
Log.e("QAuxv", "Module.parent: " + StartupHook.class.getClassLoader().getParent());
Log.e("QAuxv", "XposedBridge: " + XposedBridge.class.getClassLoader());
Log.e("QAuxv", "SystemClassLoader: " + ClassLoader.getSystemClassLoader());
Log.e("QAuxv", "Context.class: " + Context.class.getClassLoader());
public void initializeAfterAppCreate(@NonNull Context ctx) {
execStartupInit(ctx, null, null, false);
applyTargetDpiIfNecessary(ctx);
deleteDirIfNecessaryNoThrow(ctx);
}
public void initialize(ClassLoader rtLoader) throws Throwable {
if (mFirstStageInit) {
return;
}
public void initializeBeforeAppCreate(@NonNull ClassLoader rtLoader) {
try {
XC_MethodHook startup = new XC_MethodHook(51) {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
try {
Context app;
Class<?> clz = param.thisObject.getClass().getClassLoader()
.loadClass("com.tencent.common.app.BaseApplicationImpl");
Field fsApp = null;
for (Field f : clz.getDeclaredFields()) {
if (f.getType() == clz) {
fsApp = f;
break;
}
}
if (fsApp == null) {
throw new NoSuchFieldException("field BaseApplicationImpl.sApplication not found");
}
app = (Context) fsApp.get(null);
execStartupInit(app, param.thisObject, null, false);
} catch (Throwable e) {
log_e(e);
throw e;
}
ClassLoader cl = param.thisObject.getClass().getClassLoader();
Context app = StartupAgent.getBaseApplicationImpl(cl);
execStartupInit(app, param.thisObject, null, false);
}
};
Class<?> loadDex = findLoadDexTaskClass(rtLoader);
@@ -218,7 +158,6 @@ public class StartupHook {
}
}
XposedBridge.hookMethod(m, startup);
mFirstStageInit = true;
} catch (Throwable e) {
if ((e + "").contains("com.bug.zqq")) {
return;
@@ -227,7 +166,7 @@ public class StartupHook {
return;
}
log_e(e);
throw e;
throw IoUtils.unsafeThrow(e);
}
try {
XposedHelpers.findAndHookMethod(rtLoader.loadClass("com.tencent.mobileqq.qfix.QFixApplication"),

View File

@@ -0,0 +1,96 @@
/*
* QAuxiliary - An Xposed module for QQ/TIM
* Copyright (C) 2019-2024 QAuxiliary developers
* https://github.com/cinit/QAuxiliary
*
* This software is an opensource software: you can redistribute it
* and/or modify it under the terms of the General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version 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 General Public License for more details.
*
* You should have received a copy of the General Public License
* along with this software.
* If not, see
* <https://github.com/cinit/QAuxiliary/blob/master/LICENSE.md>.
*/
package io.github.qauxv.poststartup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.github.qauxv.loader.hookapi.IHookBridge;
import io.github.qauxv.loader.hookapi.ILoaderInfo;
import java.util.Objects;
public class StartupInfo {
private StartupInfo() {
throw new AssertionError("No instance for you!");
}
private static String modulePath;
private static ILoaderInfo loaderInfo;
private static IHookBridge hookBridge;
private static Boolean inHostProcess = null;
@NonNull
public static String getModulePath() {
return modulePath;
}
@NonNull
public static ILoaderInfo getLoaderInfo() {
return loaderInfo;
}
@Nullable
public static IHookBridge getHookBridge() {
return hookBridge;
}
@NonNull
public static IHookBridge requireHookBridge() {
if (hookBridge == null) {
throw new IllegalStateException("HookBridge is not initialized");
}
return hookBridge;
}
public static void setHookBridge(@Nullable IHookBridge hookBridge) {
StartupInfo.hookBridge = hookBridge;
}
public static void setLoaderInfo(@NonNull ILoaderInfo loaderInfo) {
Objects.requireNonNull(loaderInfo);
StartupInfo.loaderInfo = loaderInfo;
}
public static void setModulePath(@NonNull String modulePath) {
Objects.requireNonNull(modulePath);
StartupInfo.modulePath = modulePath;
}
public static boolean isInHostProcess() {
if (inHostProcess == null) {
throw new IllegalStateException("Host process status is not initialized");
}
return inHostProcess;
}
public static void setInHostProcess(boolean inHostProcess) {
if (StartupInfo.inHostProcess != null) {
throw new IllegalStateException("Host process status is already initialized");
}
StartupInfo.inHostProcess = inHostProcess;
}
}

View File

@@ -1,38 +1,37 @@
/*
* QAuxiliary - An Xposed module for QQ/TIM
* Copyright (C) 2019-2022 qwq233@qwq2333.top
* Copyright (C) 2019-2024 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
* This software is an opensource software: you can redistribute it
* and/or modify it under the terms of the 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
* version 3 of the License, or any later version 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.
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the 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/>
* You should have received a copy of the General Public License
* along with this software.
* If not, see
* <https://github.com/cinit/QAuxiliary/blob/master/LICENSE.md>.
*/
package io.github.qauxv.startup;
package io.github.qauxv.poststartup;
import android.annotation.SuppressLint;
import android.app.Application;
import android.content.Context;
import android.os.Build;
import com.github.kyuubiran.ezxhelper.init.EzXHelperInit;
import com.github.kyuubiran.ezxhelper.init.InitFields;
import com.github.kyuubiran.ezxhelper.utils.Log;
import io.github.qauxv.core.MainHook;
import io.github.qauxv.core.NativeCoreBridge;
import io.github.qauxv.util.HostInfo;
import io.github.qauxv.util.Initiator;
import io.github.qauxv.util.Natives;
import java.lang.reflect.Field;
import org.lsposed.hiddenapibypass.HiddenApiBypass;
public class StartupRoutine {
@@ -51,16 +50,13 @@ public class StartupRoutine {
* @param bReserved false, not used
*/
public static void execPostStartupInit(Context ctx, Object step, String lpwReserved, boolean bReserved) {
ensureHiddenApiAccess();
// init all kotlin utils here
EzXHelperInit.INSTANCE.initZygote(HookEntry.getInitZygoteStartupParam());
EzXHelperInit.INSTANCE.initHandleLoadPackage(HookEntry.getLoadPackageParam());
// resource injection is done somewhere else, do not init it here
EzXHelperInit.INSTANCE.initAppContext(ctx, false, false);
EzXHelperInit.INSTANCE.setLogTag("QAuxv");
HostInfo.init((Application) ctx);
Initiator.init(ctx.getClassLoader());
Natives.load(ctx);
InitFields.ezXClassLoader = ctx.getClassLoader();
// resource injection is done somewhere else, do not init it here
Log.INSTANCE.getCurrentLogger().setLogTag("QAuxv");
Natives.initialize(ctx);
overrideLSPatchModifiedVersionCodeIfNecessary(ctx);
NativeCoreBridge.initNativeCore(ctx.getPackageName(), Build.VERSION.SDK_INT,
HostInfo.getHostInfo().getVersionName(), HostInfo.getHostInfo().getVersionCode());
@@ -92,31 +88,4 @@ public class StartupRoutine {
}
}
private static void ensureHiddenApiAccess() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !isHiddenApiAccessible()) {
android.util.Log.w("QAuxv", "Hidden API access not accessible, SDK_INT is " + Build.VERSION.SDK_INT);
HiddenApiBypass.setHiddenApiExemptions("L");
}
}
@SuppressLint({"BlockedPrivateApi", "PrivateApi"})
public static boolean isHiddenApiAccessible() {
Class<?> kContextImpl;
try {
kContextImpl = Class.forName("android.app.ContextImpl");
} catch (ClassNotFoundException e) {
return false;
}
Field mActivityToken = null;
Field mToken = null;
try {
mActivityToken = kContextImpl.getDeclaredField("mActivityToken");
} catch (NoSuchFieldException ignored) {
}
try {
mToken = kContextImpl.getDeclaredField("mToken");
} catch (NoSuchFieldException ignored) {
}
return mActivityToken != null || mToken != null;
}
}

View File

@@ -21,7 +21,7 @@
*/
package io.github.qauxv.router.decorator
import de.robv.android.xposed.XC_MethodHook
import io.github.qauxv.util.xpcompat.XC_MethodHook
import io.github.qauxv.base.IDynamicHook
import io.github.qauxv.base.RuntimeErrorTracer

Some files were not shown because too many files have changed in this diff Show More