diff --git a/app/src/main/java/me/iacn/biliroaming/BiliBiliPackage.kt b/app/src/main/java/me/iacn/biliroaming/BiliBiliPackage.kt index f83bfe3b81..15f33545ee 100644 --- a/app/src/main/java/me/iacn/biliroaming/BiliBiliPackage.kt +++ b/app/src/main/java/me/iacn/biliroaming/BiliBiliPackage.kt @@ -190,6 +190,15 @@ class BiliBiliPackage constructor(private val mClassLoader: ClassLoader, mContex val tripleSpeedServiceClass by Weak { "com.bilibili.ship.theseus.united.player.TripleSpeedService\$runOldTripleSpeed\$1\$listener\$1\$onLongPress\$1" from mClassLoader } val storyPagerPlayerClass by Weak { mHookInfo.storyPagerPlayer.class_ from mClassLoader } + // Compose guard method,由 initHookInfo 中 DexHelper 查找并缓存到 hook info + val composeGuardMethod by Weak { + mHookInfo.composeGuard.orNull?.let { methodName -> + "androidx.compose.ui.platform.AbstractComposeView" + .findClassOrNull(mClassLoader) + ?.declaredMethods?.find { it.name == methodName } + } + } + // for v8.17.0+ val useNewMossFunc = instance.viewMossClass?.declaredMethods?.any { it.name == "executeRelatesFeed" @@ -402,6 +411,7 @@ class BiliBiliPackage constructor(private val mClassLoader: ClassLoader, mContex && getVersionCode(context.packageName) == info.clientVersionCode && BuildConfig.VERSION_CODE == info.moduleVersionCode && BuildConfig.VERSION_NAME == info.moduleVersionName + && info.generation >= getModuleGeneration(context) && info.biliAccounts.getAccessKey.orNull != null ) return info @@ -449,6 +459,13 @@ class BiliBiliPackage constructor(private val mClassLoader: ClassLoader, mContex } else classloader } + private fun getModuleGeneration(context: Context): Int = try { + context.createPackageContext(BuildConfig.APPLICATION_ID, + Context.CONTEXT_IGNORE_SECURITY) + .getSharedPreferences("biliroaming_hookinfo", Context.MODE_PRIVATE) + .getInt("generation", 0) + } catch (_: Exception) { 0 } + @JvmStatic fun initHookInfo(context: Context) = hookInfo { val classloader = context.classLoader @@ -464,6 +481,21 @@ class BiliBiliPackage constructor(private val mClassLoader: ClassLoader, mContex val dexHelper = DexHelper(classloader.findDexClassLoader(::findRealClassloader) ?: return@hookInfo) + + // 查找 AbstractComposeView 的 addView 守卫方法(供 SplashHook 使用) + val guardMethod = dexHelper.findMethodUsingString( + "Cannot add views to ", + true, + -1, -1, null, + -1, + null, null, null, true + ).firstOrNull()?.let { + dexHelper.decodeMethodIndex(it) + } + if (guardMethod != null) { + composeGuard = method { name = guardMethod.name } + } + lastUpdateTime = max( context.packageManager.getPackageInfo( AndroidAppHelper.currentPackageName(), @@ -479,6 +511,7 @@ class BiliBiliPackage constructor(private val mClassLoader: ClassLoader, mContex clientVersionCode = getVersionCode(context.packageName) moduleVersionCode = BuildConfig.VERSION_CODE moduleVersionName = BuildConfig.VERSION_NAME + generation = getModuleGeneration(context) mapIds = mapIds { val reg = Regex("^tv\\.danmaku\\.bili\\.[^.]*$") val mask = Modifier.STATIC or Modifier.PUBLIC or Modifier.FINAL diff --git a/app/src/main/java/me/iacn/biliroaming/MainActivity.kt b/app/src/main/java/me/iacn/biliroaming/MainActivity.kt index 0dcbe677b2..4bd37d8009 100644 --- a/app/src/main/java/me/iacn/biliroaming/MainActivity.kt +++ b/app/src/main/java/me/iacn/biliroaming/MainActivity.kt @@ -51,6 +51,7 @@ class MainActivity : Activity() { findPreference("version").summary = BuildConfig.VERSION_NAME findPreference("feature").onPreferenceClickListener = this findPreference("setting").onPreferenceClickListener = this + findPreference("clear_hook_cache").onPreferenceClickListener = this checkUpdate() } @@ -155,6 +156,7 @@ class MainActivity : Activity() { "update" -> onUpdateCheck() "feature" -> onFeatureClick() "setting" -> onSettingClick() + "clear_hook_cache" -> onClearHookCacheClick() else -> false } @@ -172,6 +174,14 @@ class MainActivity : Activity() { startActivity(this) } } + + private fun onClearHookCacheClick(): Boolean { + val prefs = activity.getSharedPreferences("biliroaming_hookinfo", Context.MODE_PRIVATE) + val nextGen = prefs.getInt("generation", 0) + 1 + prefs.edit().putInt("generation", nextGen).apply() + Toast.makeText(activity, "将在下次启动哔哩哔哩时重新扫描", Toast.LENGTH_SHORT).show() + return true + } } companion object { diff --git a/app/src/main/java/me/iacn/biliroaming/hook/KotlinxJsonHook.kt b/app/src/main/java/me/iacn/biliroaming/hook/KotlinxJsonHook.kt index 7d257f3b4e..15eb337e92 100644 --- a/app/src/main/java/me/iacn/biliroaming/hook/KotlinxJsonHook.kt +++ b/app/src/main/java/me/iacn/biliroaming/hook/KotlinxJsonHook.kt @@ -5,7 +5,7 @@ import me.iacn.biliroaming.hook.kotlinx.KotlinxProcessor import me.iacn.biliroaming.hook.kotlinx.KotlinxSplashListProcessor import me.iacn.biliroaming.hook.kotlinx.KotlinxSplashShowProcessor import me.iacn.biliroaming.utils.Log -import me.iacn.biliroaming.utils.getObjectField +import me.iacn.biliroaming.utils.callMethodOrNull import me.iacn.biliroaming.utils.getObjectFieldAs import me.iacn.biliroaming.utils.hookAfterAllMethods @@ -36,7 +36,7 @@ class KotlinxJsonHook(classLoader: ClassLoader) : BaseHook(classLoader) { if (deserializer == null || result == null) return val serialName = deserializer - .getObjectField("descriptor") + .callMethodOrNull("getDescriptor") ?.getObjectFieldAs("serialName") ?: return diff --git a/app/src/main/java/me/iacn/biliroaming/hook/SplashHook.kt b/app/src/main/java/me/iacn/biliroaming/hook/SplashHook.kt index bab1b03437..955623253c 100644 --- a/app/src/main/java/me/iacn/biliroaming/hook/SplashHook.kt +++ b/app/src/main/java/me/iacn/biliroaming/hook/SplashHook.kt @@ -4,8 +4,10 @@ import android.content.res.Configuration import android.graphics.Color import android.net.Uri import android.os.Bundle +import android.view.Gravity import android.view.View import android.view.ViewGroup +import android.widget.FrameLayout import android.widget.ImageView import me.iacn.biliroaming.BiliBiliPackage.Companion.instance import me.iacn.biliroaming.utils.* @@ -13,6 +15,14 @@ import java.io.File class SplashHook(classLoader: ClassLoader) : BaseHook(classLoader) { override fun startHook() { + // 解除 AbstractComposeView 的 addView 限制,使其恢复普通 ViewGroup 行为 + if (!composeAddViewPatched) { + try { + instance.composeGuardMethod?.replaceMethod { null } + } catch (_: Throwable) {} + composeAddViewPatched = true + } + val customSplash = sPrefs.getBoolean("custom_splash", false) val customSplashLogo = sPrefs.getBoolean("custom_splash_logo", false) val fullSplash = sPrefs.getBoolean("full_splash", false) @@ -51,8 +61,8 @@ class SplashHook(classLoader: ClassLoader) : BaseHook(classLoader) { if (isXmlPath) { applyXmlPath(view, brandId, customSplash, customSplashLogo) - } else if (view is ViewGroup) { - view.post { applyComposeOverlay(view, customSplash, customSplashLogo) } + } else { + applyComposeOverlay(view, customSplash, customSplashLogo) } } } @@ -84,47 +94,52 @@ class SplashHook(classLoader: ClassLoader) : BaseHook(classLoader) { } /** - * Compose 渲染路径:在 ComposeView 上叠加 ImageView。 - * ComposeView 是 ViewGroup 子类,addView 可添加覆盖层于 Compose 内容之上。 + * Compose 渲染路径:addView 限制已解除,直接用 FrameLayout 包裹 + * overlay 添加到 ComposeView 自身,随 ComposeView 自动销毁。 */ - private fun applyComposeOverlay(container: ViewGroup, customSplash: Boolean, customSplashLogo: Boolean) { - if (sPrefs.getBoolean("custom_splash", false)) { - val splashImage = File(currentContext.filesDir, SPLASH_IMAGE) - val iv = ImageView(container.context).apply { + private fun applyComposeOverlay(composeView: View, customSplash: Boolean, customSplashLogo: Boolean) { + val ctx = composeView.context + val logoH = composeView.resources.displayMetrics.heightPixels / 8 + val splashImage = File(currentContext.filesDir, SPLASH_IMAGE) + val logoImage = File(currentContext.filesDir, LOGO_IMAGE) + + val overlay = FrameLayout(ctx).apply { + layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + } + + if (customSplash) { + overlay.addView(ImageView(ctx).apply { scaleType = ImageView.ScaleType.CENTER_CROP - layoutParams = ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT + layoutParams = FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT ) - if (splashImage.exists()) { - setImageURI(Uri.fromFile(splashImage)) - } else { - visibility = View.GONE - } - } - container.addView(iv) + if (splashImage.exists()) setImageURI(Uri.fromFile(splashImage)) + else visibility = View.GONE + }) } - if (sPrefs.getBoolean("custom_splash_logo", false)) { - val logoH = container.resources.displayMetrics.heightPixels / 8 - val logoImage = File(currentContext.filesDir, LOGO_IMAGE) - val iv = ImageView(container.context).apply { + if (customSplashLogo) { + overlay.addView(ImageView(ctx).apply { scaleType = ImageView.ScaleType.FIT_CENTER - layoutParams = ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, logoH + layoutParams = FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + logoH, + Gravity.BOTTOM ) - y = (container.height - logoH).toFloat() - if (logoImage.exists()) { - setImageURI(Uri.fromFile(logoImage)) - } else { - visibility = View.GONE - } - } - container.addView(iv) + if (logoImage.exists()) setImageURI(Uri.fromFile(logoImage)) + else visibility = View.GONE + }) } + + (composeView as ViewGroup).addView(overlay) } companion object { const val SPLASH_IMAGE = "biliroaming_splash" const val LOGO_IMAGE = "biliroaming_logo" + private var composeAddViewPatched = false } } diff --git a/app/src/main/proto/me/iacn/biliroaming/configs.proto b/app/src/main/proto/me/iacn/biliroaming/configs.proto index cc82375280..75959b3716 100644 --- a/app/src/main/proto/me/iacn/biliroaming/configs.proto +++ b/app/src/main/proto/me/iacn/biliroaming/configs.proto @@ -415,4 +415,6 @@ message HookInfo { optional GCommonResolverParams gCommonResolverParams = 103; optional RewardAd rewardAd = 104; optional StoryPagerPlayer storyPagerPlayer = 105; + int32 generation = 106; + optional Method compose_guard = 107; } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e835ba2e42..ed298e9f1c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -135,6 +135,8 @@ 强制开启串台的泰区评论弹幕(仅调试使用) 分享日志 可能在旧版无效 + 清除hook缓存 + 下次打开哔哩哔哩时强制重新扫描hook信息 替换竖版视频 替换竖版视频为正常视频 自定义我的页面 diff --git a/app/src/main/res/xml/main_activity.xml b/app/src/main/res/xml/main_activity.xml index 019fbc72da..a13a361880 100644 --- a/app/src/main/res/xml/main_activity.xml +++ b/app/src/main/res/xml/main_activity.xml @@ -26,6 +26,11 @@ android:action="android.intent.action.VIEW" android:data="@string/help_url" /> + +