Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -1941,3 +1941,21 @@ Thumbs.db
/build-logic/convention/build/generated-sources/kotlin-dsl-plugins/kotlin/BuildLogic_android_basePlugin.kt
/build-logic/convention/build/generated-sources/kotlin-dsl-plugins/kotlin/BuildLogic_android_libraryPlugin.kt
/build-logic/convention/build/generated-sources/kotlin-dsl-plugins/kotlin/BuildLogic_rootProjectPlugin.kt

# Local tooling and crash artifacts
/.kotlin/
/.claude/
/%APPDATA%/
/202604*/
/hs_err_pid*.log
/crash_hit_flag
/env_info
/java_threads
/java_trace
/java_trace_analysis
/logcat
/maps
/native_trace
/status
/summary
/trace_module
44 changes: 42 additions & 2 deletions app/src/main/java/moe/ono/activity/OUOSettingActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.text.format.Formatter
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
Expand Down Expand Up @@ -49,6 +50,8 @@ import moe.ono.hooks.item.sigma.QQSurnamePredictor
import moe.ono.hostInfo
import moe.ono.isInHostProcess
import moe.ono.ui.ThemeAttrUtils
import moe.ono.util.LogUtils
import moe.ono.util.SystemServiceUtils
import moe.ono.ui.view.BgEffectPainter
import moe.ono.util.Utils.convertTimestampToDate
import moe.ono.util.Utils.jump
Expand Down Expand Up @@ -191,14 +194,18 @@ open class OUOSettingActivity : BaseActivity() {
val buildTime = findPreference<Preference>("build_time")
val buildUUID = findPreference<Preference>("build_uuid")
val enableLog = findPreference<MaterialSwitchPreference>("prek_enable_log")
val logDirectory = findPreference<Preference>("log_directory")
val clearLogs = findPreference<Preference>("clear_logs")
val hookPriority = findPreference<SimpleMenuPreference>("hook_priority")
version?.setSummary(BuildConfig.VERSION_NAME)
buildTime?.setSummary(convertTimestampToDate(BuildConfig.BUILD_TIMESTAMP))
buildUUID?.setSummary(BuildConfig.BUILD_UUID)
enableLog?.isChecked = ConfigManager.getDefaultConfig().getBooleanOrFalse(PrekEnableLog)
refreshLogPreferences(logDirectory, clearLogs)
enableLog?.setOnPreferenceChangeListener { _, newValue ->
val isEnabled = newValue as Boolean
ConfigManager.getDefaultConfig().edit().putBoolean(PrekEnableLog, isEnabled).apply()
refreshLogPreferences(logDirectory, clearLogs)
true
}

Expand All @@ -223,7 +230,33 @@ open class OUOSettingActivity : BaseActivity() {
jump(requireContext(), "https://github.com/cwuom/ono")
return super.onPreferenceTreeClick(preference)
}
"build_time", "build_uuid", "version","prek_enable_log", "hook_priority" -> {
"log_directory" -> {
val path = LogUtils.getLogRootDirectory()
SystemServiceUtils.copyToClipboard(requireContext(), path)
Toasts.success(requireContext(), "\u65E5\u5FD7\u76EE\u5F55\u5DF2\u590D\u5236")
return super.onPreferenceTreeClick(preference)
}
"clear_logs" -> {
MaterialAlertDialogBuilder(requireContext())
.setTitle("\u786E\u5B9A\u6E05\u7A7A\u65E5\u5FD7\u5417\uFF1F")
.setMessage("\u8FD9\u4F1A\u5220\u9664\u5F53\u524D\u5DF2\u4FDD\u5B58\u7684\u8FD0\u884C\u65E5\u5FD7\u548C\u9519\u8BEF\u65E5\u5FD7")
.setPositiveButton("确定") { _, _ ->
val cleared = LogUtils.clearLogs()
refreshLogPreferences(
findPreference<Preference>("log_directory"),
findPreference<Preference>("clear_logs")
)
if (cleared) {
Toasts.success(requireContext(), "\u65E5\u5FD7\u5DF2\u6E05\u7A7A")
} else {
Toasts.error(requireContext(), "\u65E5\u5FD7\u6E05\u7A7A\u5931\u8D25")
}
}
.setNegativeButton("取消", null)
.show()
return super.onPreferenceTreeClick(preference)
}
"build_time", "build_uuid", "version", "prek_enable_log", "hook_priority" -> {
return super.onPreferenceTreeClick(preference)
}
}
Expand All @@ -239,6 +272,13 @@ open class OUOSettingActivity : BaseActivity() {
}
return super.onPreferenceTreeClick(preference)
}

private fun refreshLogPreferences(logDirectory: Preference?, clearLogs: Preference?) {
val context = context ?: return
val size = Formatter.formatFileSize(context, LogUtils.getLogDirectorySize())
logDirectory?.summary = LogUtils.getLogRootDirectory() + "\n当前占用:" + size
clearLogs?.summary = "删除已保存的运行日志和错误日志(当前占用:" + size + ")"
}
}

override fun onCreateOptionsMenu(menu: Menu): Boolean {
Expand Down Expand Up @@ -475,4 +515,4 @@ open class OUOSettingActivity : BaseActivity() {
return intent
}
}
}
}
7 changes: 5 additions & 2 deletions app/src/main/java/moe/ono/core/QLauncher.java
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,12 @@ public static void injectLifecycleForProcess(Context ctx) {
Parasitics.injectModuleResources(CacheConfig.getSplashActivity().getResources());
Parasitics.initForStubActivity(ctx);
ServletPool.INSTANCE.injectServlet();
QQInterfaces.Companion.update();
try {
QQInterfaces.Companion.update();
} catch (Throwable e) {
Logger.e("QQInterfaces.update failed during startup", e);
}
});
}
}
}

143 changes: 104 additions & 39 deletions app/src/main/java/moe/ono/hooks/base/api/QQMsgViewAdapter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@ import moe.ono.hooks._base.ApiHookItem
import moe.ono.hooks._core.annotation.HookItem
import moe.ono.reflex.ClassUtils
import moe.ono.reflex.FieldUtils
import moe.ono.reflex.Ignore
import moe.ono.reflex.MethodUtils
import moe.ono.util.HostInfo
import moe.ono.util.Logger
import java.lang.reflect.Method
import java.lang.reflect.Modifier
import java.util.LinkedHashSet

@HookItem(path = "API/适配QQMsg内容ViewID")
class QQMsgViewAdapter : ApiHookItem() {


companion object {
private var contentViewId = 0

Expand All @@ -38,7 +40,7 @@ class QQMsgViewAdapter : ApiHookItem() {
}
}

private var unhook: XC_MethodHook.Unhook? = null
private val unhooks = ArrayList<XC_MethodHook.Unhook>()

private fun findContentViewId(): Int {
return cGetInt(
Expand All @@ -55,43 +57,99 @@ class QQMsgViewAdapter : ApiHookItem() {
}

override fun entry(loader: ClassLoader) {
if (findContentViewId() > 0) {
contentViewId = findContentViewId()
val cachedViewId = findContentViewId()
if (cachedViewId > 0) {
contentViewId = cachedViewId
return
}
val onMsgViewUpdate =
MethodUtils.create("com.tencent.mobileqq.aio.msglist.holder.AIOBubbleMsgItemVB")

val holderClass = ClassUtils.findClass("com.tencent.mobileqq.aio.msglist.holder.AIOBubbleMsgItemVB")
val candidateMethods = collectCandidateMethods(holderClass)
if (candidateMethods.isEmpty()) {
Logger.e("适配QQMsg内容ViewID", "No candidate update method found in ${holderClass.name}")
return
}

candidateMethods.forEach { method ->
unhooks.add(hookAfter(method) { param ->
handleItemViewUpdate(param.thisObject)
})
}
}

private fun collectCandidateMethods(holderClass: Class<*>): List<Method> {
val candidates = LinkedHashSet<Method>()
try {
MethodUtils.create(holderClass)
.methodName("handleUIState")
.returnType(Void.TYPE)
.params(Int::class.java, Ignore::class.java, List::class.java, Bundle::class.java)
.first()
unhook = hookAfter(onMsgViewUpdate) { param ->
val thisObject = param.thisObject
val msgView = FieldUtils.create(thisObject)
.fieldType(View::class.java)
.firstValue<View>(thisObject)

val aioMsgItem = FieldUtils.create(thisObject)
.fieldType(ClassUtils.findClass("com.tencent.mobileqq.aio.msg.AIOMsgItem"))
.firstValue<Any>(thisObject)

if (aioMsgItem == null || msgView == null) return@hookAfter

val msgRecord: Any = MethodUtils.create(aioMsgItem.javaClass).methodName("getMsgRecord")
.callFirst(aioMsgItem)

val elements: ArrayList<Any> = FieldUtils.getField(
msgRecord, "elements",
ArrayList::class.java
)

for (msgElement in elements) {
val type: Int =
FieldUtils.getField(msgElement, "elementType", Int::class.javaPrimitiveType)
//文本和图片类型的view 不解析其他类型的 否则解析不出来
if (type <= 2) {
findContentView(msgView as ViewGroup)
break
.getResult()
.forEach { candidates.add(it) }
} catch (_: Throwable) {
}

holderClass.declaredMethods
.filterTo(candidates) { isCandidateMethod(it) }
return candidates.toList()
}

private fun isCandidateMethod(method: Method): Boolean {
if (Modifier.isStatic(method.modifiers) || method.returnType != Void.TYPE) {
return false
}
val parameterTypes = method.parameterTypes
if (parameterTypes.size !in 3..5) {
return false
}
val containsList = parameterTypes.any { List::class.java.isAssignableFrom(it) }
val containsBundle = parameterTypes.any { Bundle::class.java.isAssignableFrom(it) }
val containsInt = parameterTypes.any {
it == Int::class.javaPrimitiveType || it == Int::class.javaObjectType
}
return containsList && containsBundle && containsInt
}

private fun handleItemViewUpdate(thisObject: Any) {
if (contentViewId > 0) {
clearHooks()
return
}

val msgView = FieldUtils.create(thisObject)
.fieldType(View::class.java)
.firstValue<View>(thisObject) ?: return

val aioMsgItem = FieldUtils.create(thisObject)
.fieldType(ClassUtils.findClass("com.tencent.mobileqq.aio.msg.AIOMsgItem"))
.firstValue<Any>(thisObject) ?: return

val msgRecord = try {
MethodUtils.create(aioMsgItem.javaClass)
.methodName("getMsgRecord")
.callFirst<Any>(aioMsgItem)
} catch (_: Throwable) {
return
}

val elements = try {
FieldUtils.getField<ArrayList<Any>>(msgRecord, "elements", ArrayList::class.java)
} catch (_: Throwable) {
return
}

val msgViewGroup = msgView as? ViewGroup ?: return
for (msgElement in elements) {
val type = try {
FieldUtils.getField<Int>(msgElement, "elementType", Int::class.javaPrimitiveType)
} catch (_: Throwable) {
continue
}
if (type <= 2) {
findContentView(msgViewGroup)
if (contentViewId > 0) {
clearHooks()
}
break
}
}
}
Expand All @@ -102,11 +160,18 @@ class QQMsgViewAdapter : ApiHookItem() {
if (child.javaClass.name == "com.tencent.qqnt.aio.holder.template.BubbleLayoutCompatPress") {
contentViewId = child.id
putContentViewId(child.id)
//解开hook
unhook?.unhook()
break
}
}
}

}
private fun clearHooks() {
unhooks.forEach {
try {
it.unhook()
} catch (_: Throwable) {
}
}
unhooks.clear()
}
}
Loading
Loading