diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 66c6809..b908701 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -12,6 +12,9 @@ android { targetSdk = 33 versionCode = 1 versionName = "1.0" + ndk { + abiFilters += "arm64-v8a" + } } buildFeatures { buildConfig = true @@ -41,5 +44,6 @@ dependencies { implementation(libs.constraintlayout) compileOnly(libs.xposed.api) implementation(libs.ezxhelper) + implementation(libs.dexkit) compileOnly(project(":hidden-api")) } \ No newline at end of file diff --git a/app/src/main/java/five/ec1cff/myinjector/ZhihuXposedHandler.kt b/app/src/main/java/five/ec1cff/myinjector/ZhihuXposedHandler.kt index 37f8a3b..22d6f9c 100644 --- a/app/src/main/java/five/ec1cff/myinjector/ZhihuXposedHandler.kt +++ b/app/src/main/java/five/ec1cff/myinjector/ZhihuXposedHandler.kt @@ -7,36 +7,99 @@ import de.robv.android.xposed.IXposedHookLoadPackage import de.robv.android.xposed.XC_MethodHook import de.robv.android.xposed.XposedBridge import de.robv.android.xposed.XposedHelpers -import de.robv.android.xposed.callbacks.XC_LoadPackage +import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam +import org.luckypray.dexkit.DexKitBridge +import org.luckypray.dexkit.query.matchers.ClassMatcher +import java.io.File class ZhihuXposedHandler : IXposedHookLoadPackage { companion object { private const val TAG = "MyInjector-zhihu" } - override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) { + // Zhihu 10.2.0 (20214) sha256=0774c8c812232dd1d1c0a75e32f791f7171686a8c68ce280c6b3d9b82cdde5eb + // class f$r implements com.zhihu.android.app.feed.ui2.feed.a.a + // io.reactivex.subjects.ReplaySubject b + // public void a(com.zhihu.android.api.model.FeedList) + // use strings: "FeedRepository" "use cache" + private var localSourceClass = "com.zhihu.android.app.feed.ui2.feed.a.f\$r" + private var localSourceMethod = "a" + private var localSourceField = "b" + + private var cacheFile: File? = null + + private fun prepare(lpparam: LoadPackageParam) = kotlin.runCatching { + val f = File(lpparam.appInfo.dataDir, "dexkit.tmp") + cacheFile = f + if (f.isFile) { + try { + val lines = f.inputStream().use { f.readLines() } + val apkPath = lines[0] + if (apkPath == lpparam.appInfo.sourceDir) { + localSourceClass = lines[1] + localSourceMethod = lines[2] + localSourceField = lines[3] + Log.d(TAG, "prepare: use cached result") + return@runCatching + } else { + Log.d(TAG, "prepare: need invalidate cache!") + f.delete() + } + } catch (t: Throwable) { + Log.e(TAG, "prepare: failed to read", t) + f.delete() + } + } + Log.d(TAG, "prepare: start deobf") + System.loadLibrary("dexkit") + val bridge = DexKitBridge.create(lpparam.classLoader, true) + val method = bridge.findMethod { + searchPackages("com.zhihu.android.app.feed.ui2.feed") + matcher { + usingStrings("FeedRepository", "use cache") + } + }[0] + Log.d(TAG, "prepare: found method: $method") + val field = method.declaredClass!!.findField { + matcher { + type(ClassMatcher().className("io.reactivex.subjects.ReplaySubject")) + } + }[0] + Log.d(TAG, "prepare: found field: $field") + localSourceClass = method.className + localSourceMethod = method.methodName + localSourceField = field.fieldName + f.bufferedWriter().use { + it.write("${lpparam.appInfo.sourceDir}\n") + it.write("$localSourceClass\n") + it.write("$localSourceMethod\n") + it.write("$localSourceField\n") + } + }.onFailure { + Log.e(TAG, "prepare: failed", it) + } + + override fun handleLoadPackage(lpparam: LoadPackageParam) { if (lpparam.packageName != "com.zhihu.android") return + prepare(lpparam) hookDisableFeedAutoRefresh(lpparam) hookClipboard() } - private fun hookDisableFeedAutoRefresh(lpparam: XC_LoadPackage.LoadPackageParam) = + private fun hookDisableFeedAutoRefresh(lpparam: LoadPackageParam) = kotlin.runCatching { - // TODO: use dexkit - // Zhihu 10.2.0 (20214) sha256=0774c8c812232dd1d1c0a75e32f791f7171686a8c68ce280c6b3d9b82cdde5eb - // class f$r implements com.zhihu.android.app.feed.ui2.feed.a.a - // io.reactivex.subjects.ReplaySubject b - // public void a(com.zhihu.android.api.model.FeedList) - // use strings: "FeedRepository" "use cache" XposedBridge.hookAllMethods( XposedHelpers.findClass( - "com.zhihu.android.app.feed.ui2.feed.a.f\$r", + localSourceClass, lpparam.classLoader ), - "a", + localSourceMethod, object : XC_MethodHook() { override fun afterHookedMethod(param: MethodHookParam) { - val b = XposedHelpers.getObjectField(param.thisObject, "b") // ReplaySubject + val b = XposedHelpers.getObjectField( + param.thisObject, + localSourceField + ) // ReplaySubject XposedHelpers.callMethod( b, "onComplete" @@ -46,6 +109,7 @@ class ZhihuXposedHandler : IXposedHookLoadPackage { ) }.onFailure { Log.e(TAG, "hookDisableAutoRefresh: failed", it) + cacheFile?.delete() } private fun hookClipboard() = kotlin.runCatching { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 69e70cf..9889cc1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,4 +1,5 @@ [versions] +dexkit = "2.0.1" ezxhelper = "1.0.3" kotlin = "1.9.0" agp = "8.1.3" @@ -8,6 +9,7 @@ material = "1.9.0" constraintlayout = "2.1.4" [libraries] +dexkit = { module = "org.luckypray:dexkit", version.ref = "dexkit" } ezxhelper = { module = "com.github.kyuubiran:EzXHelper", version.ref = "ezxhelper" } xposed-api = { module = "de.robv.android.xposed:api", version.ref = "xposed" } appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }