Skip to content
Merged
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
183 changes: 118 additions & 65 deletions app/src/main/java/me/iacn/biliroaming/BiliBiliPackage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,9 @@ class BiliBiliPackage constructor(private val mClassLoader: ClassLoader, mContex
val rxGeneralResponseClass by Weak { "com.bilibili.okretro.call.rxjava.RxGeneralResponse" from mClassLoader }
val fastJsonClass by Weak { mHookInfo.fastJson.class_ from mClassLoader }
val bangumiUniformSeasonClass by Weak { mHookInfo.bangumiSeason from mClassLoader }
val sectionClass by Weak { mHookInfo.section.class_ from mClassLoader }
val viewHolderClass by Weak { mHookInfo.section.viewHolder from mClassLoader }
val kingPositionComponentClass by Weak { mHookInfo.autoLike.kingPositionComponent.class_ from mClassLoader }
val storyAbsControllerClass by Weak { mHookInfo.autoLike.storyAbsController.class_ from mClassLoader }
val storyDetailClass by Weak { "com.bilibili.video.story.StoryDetail" from mClassLoader }
val retrofitResponseClass by Weak { mHookInfo.retrofitResponse from mClassLoader }
val themeHelperClass by Weak { mHookInfo.themeHelper.class_ from mClassLoader }
val themeIdHelperClass by Weak { mHookInfo.themeIdHelper.class_ from mClassLoader }
Expand Down Expand Up @@ -179,6 +180,8 @@ class BiliBiliPackage constructor(private val mClassLoader: ClassLoader, mContex
val fastjsonFieldAnnotation by Weak { "com.alibaba.fastjson.annotation.JSONField" from mClassLoader }
val gsonFieldAnnotation by Weak { "com.google.gson.annotations.SerializedName" from mClassLoader }
val pegasusParserClass by Weak { mHookInfo.pegasusParser from mClassLoader }
val resolveClientCompanionClass by Weak { mHookInfo.resolveClientCompanion.class_ from mClassLoader }
val videoDownloadEntryClass by Weak { "com.bilibili.videodownloader.model.VideoDownloadEntry" from mClassLoader }

// for v8.17.0+
val useNewMossFunc = instance.viewMossClass?.declaredMethods?.any {
Expand Down Expand Up @@ -237,11 +240,11 @@ class BiliBiliPackage constructor(private val mClassLoader: ClassLoader, mContex

fun requestField() = mHookInfo.okHttp.response.request.orNull

fun likeMethod() = mHookInfo.section.like.orNull
fun componentMapField() = mHookInfo.autoLike.kingPositionComponent.componentMap.orNull

fun bindViewMethod() = mHookInfo.section.bindView.orNull
fun setMDataMethod() = mHookInfo.autoLike.storyAbsController.setMData.orNull

fun getRootMethod() = mHookInfo.section.getRoot.orNull
fun getMPlayerMethod() = mHookInfo.autoLike.storyAbsController.getMPlayer.orNull

fun themeName() = mHookInfo.themeName.field.orNull

Expand Down Expand Up @@ -358,6 +361,10 @@ class BiliBiliPackage constructor(private val mClassLoader: ClassLoader, mContex

fun getDataSPMethod() = mHookInfo.dataSP.get.orNull

fun buildCommonResolverParamsMethod() =
mHookInfo.resolveClientCompanion.buildCommonResolverParams.orNull

fun setExtraContentMethod() = mHookInfo.gCommonResolverParams.setExtraContent.orNull

private fun readHookInfo(context: Context): Configs.HookInfo {
try {
Expand Down Expand Up @@ -852,68 +859,74 @@ class BiliBiliPackage constructor(private val mClassLoader: ClassLoader, mContex
}?.name ?: return@field
}
}
section = section {
val kingPositionComponentClass = dexHelper.findMethodUsingString(
"LikeClicked(view=",
false,
-1,
-1,
null,
-1,
null,
null,
null,
true
).asSequence().firstNotNullOfOrNull {
dexHelper.decodeMethodIndex(it)?.declaringClass?.run {
var outerClass = this
while (outerClass.declaringClass != null) {
outerClass = outerClass.declaringClass!!
autoLike = autoLike {
kingPositionComponent = kingPositionComponent {
val kingPositionComponentClass = dexHelper.findMethodUsingString(
"LikeClicked(view=",
false,
-1,
-1,
null,
-1,
null,
null,
null,
true
).asSequence().firstNotNullOfOrNull {
dexHelper.decodeMethodIndex(it)?.declaringClass?.run {
var outerClass = this
while (outerClass.declaringClass != null) {
outerClass = outerClass.declaringClass!!
}
outerClass
}
outerClass
}
}
val genericInterface = kingPositionComponentClass?.genericInterfaces?.getOrNull(0)
val parameterType = (genericInterface as? ParameterizedType)?.actualTypeArguments?.getOrNull(0)
val viewHolderClass = if (parameterType is ParameterizedType) {
parameterType.rawType
} else {
parameterType
}?.let { it as? Class<*> }
val getRootMethod = viewHolderClass?.declaredMethods?.firstOrNull {
it.returnType == View::class.java && it.parameterCount == 0
}
val bindViewMethod = kingPositionComponentClass?.declaredMethods?.firstOrNull {
it.isPublic && it.parameterCount == 2
&& it.parameterTypes[0] == viewHolderClass
}
if (kingPositionComponentClass != null && viewHolderClass != null && bindViewMethod != null && getRootMethod != null) {
} ?: return@kingPositionComponent
val componentMapField = kingPositionComponentClass.declaredFields.firstOrNull {
it.type == Map::class.java
} ?: return@kingPositionComponent
class_ = class_ { name = kingPositionComponentClass.name }
viewHolder = class_ { name = viewHolderClass.name }
bindView = method { name = bindViewMethod.name }
getRoot = method { name = getRootMethod.name }
return@section
componentMap = field { name = componentMapField.name }
}
storyAbsController = storyAbsController {
val storyAbsControllerClass = dexHelper.findMethodUsingString(
"notify player state fail",
false,
-1,
-1,
null,
-1,
null,
null,
null,
true
).asSequence().mapNotNull {
dexHelper.decodeMethodIndex(it)?.declaringClass
}.firstOrNull() ?: return@storyAbsController
val storyPagerPlayerInterface = dexHelper.findMethodUsingString(
" play item but is inactivate",
false,
-1,
-1,
null,
-1,
null,
null,
null,
true
).asSequence().mapNotNull {
dexHelper.decodeMethodIndex(it)?.declaringClass?.interfaces?.getOrNull(0)
}.firstOrNull() ?: return@storyAbsController
val storyDetailClass = "com.bilibili.video.story.StoryDetail".from(classloader)
val setMDataMethod = storyAbsControllerClass.declaredMethods.firstOrNull {
it.returnType == Void.TYPE && it.parameterCount == 1 && it.parameterTypes[0] == storyDetailClass
} ?: return@storyAbsController
val getMPlayerMethod = storyAbsControllerClass.declaredMethods.firstOrNull {
storyPagerPlayerInterface.isAssignableFrom(it.returnType) && it.parameterCount == 0
} ?: return@storyAbsController
class_ = class_ { name = storyAbsControllerClass.name }
setMData = method { name = setMDataMethod.name }
getMPlayer = method { name = getMPlayerMethod.name }
}

val sectionClass = dexHelper.findMethodUsingString(
"ActionViewHolder",
false,
-1,
-1,
null,
-1,
null,
null,
null,
true
).firstOrNull()?.let {
dexHelper.decodeMethodIndex(it)
}?.declaringClass ?: return@section
val likeMethod = sectionClass?.superclass?.declaredMethods?.find {
it.parameterTypes.size == 1 && it.returnType == Void.TYPE && !it.isFinal
} ?: return@section
class_ = class_ { name = sectionClass.name }
like = method { name = likeMethod.name }
}
signQuery = signQuery {
val signedQueryClass =
Expand Down Expand Up @@ -1930,7 +1943,7 @@ class BiliBiliPackage constructor(private val mClassLoader: ClassLoader, mContex
}.let { playerQualityService.addAll(it.toList()) }
playerSettingHelper = playerSettingHelper {
val getDefaultQnMethod = dexHelper.findMethodUsingString(
"get free data failed",
"quality settings:",
false,
-1,
-1,
Expand Down Expand Up @@ -2329,6 +2342,46 @@ class BiliBiliPackage constructor(private val mClassLoader: ClassLoader, mContex
?: return@class_
name = getPegasusParser.declaringClass.name
}
resolveClientCompanion = resolveClientCompanion {
val resolveClientClass = dexHelper.findMethodUsingString(
"Invalid segment id: %s, segment list size:%s",
false,
-1,
-1,
null,
-1,
null,
null,
null,
true
).asSequence().firstNotNullOfOrNull {
dexHelper.decodeMethodIndex(it)?.declaringClass
} ?: return@resolveClientCompanion
val resolveClientCompanionClass = "${resolveClientClass.name}\$a".from(classloader)
?: return@resolveClientCompanion
val paramsClass =
"com.bilibili.app.gemini.base.player.GeminiCommonResolverParams".from(
classloader
)
val videoDownloadEntryClass =
"com.bilibili.videodownloader.model.VideoDownloadEntry".from(classloader)
val buildParamsMethod = resolveClientCompanionClass.declaredMethods.firstOrNull {
it.returnType == paramsClass && it.parameterCount == 1 && it.parameterTypes[0] == videoDownloadEntryClass
} ?: return@resolveClientCompanion
class_ = class_ { name = resolveClientCompanionClass.name }
buildCommonResolverParams = method { name = buildParamsMethod.name }
}
gCommonResolverParams = gCommonResolverParams {
val commonResolverParamsClass =
"com.bilibili.app.gemini.base.player.GeminiCommonResolverParams".from(
classloader
) ?: return@gCommonResolverParams
val setExtraContentMethod = commonResolverParamsClass.declaredMethods?.firstOrNull {
it.returnType == Void.TYPE && it.parameterCount == 1 && it.parameterTypes[0] == Map::class.java
} ?: return@gCommonResolverParams
class_ = class_ { name = commonResolverParamsClass.name }
setExtraContent = method { name = setExtraContentMethod.name }
}
dexHelper.close()
}

Expand Down
119 changes: 86 additions & 33 deletions app/src/main/java/me/iacn/biliroaming/hook/AutoLikeHook.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package me.iacn.biliroaming.hook

import android.view.View
import android.widget.LinearLayout
import de.robv.android.xposed.XC_MethodHook.Unhook
import me.iacn.biliroaming.BiliBiliPackage.Companion.instance
import me.iacn.biliroaming.utils.*

Expand All @@ -11,47 +13,98 @@ class AutoLikeHook(classLoader: ClassLoader) : BaseHook(classLoader) {
var detail: Pair<Long, Int>? = null
}

private val likeIds by lazy {
arrayOf(
"frame_like",
"like_layout"
).map { getId(it) }
}

override fun startHook() {
if (!sPrefs.getBoolean("auto_like", false)) return

Log.d("startHook: AutoLike")

val likeIds = arrayOf(
"frame_recommend",
"frame1",
"frame_like"
).map { getId(it) }
val unhookSet = arrayOf<Set<Unhook>?>(null)
unhookSet[0] = instance.kingPositionComponentClass?.hookAfterAllConstructors { param ->
val kingPositionComponent = param.thisObject
val componentMap =
kingPositionComponent.getObjectFieldAs<Map<*, *>>(instance.componentMapField())

val likeComponentClass = componentMap.keys.filterNotNull().firstNotNullOfOrNull {
val componentClass = it.javaClass
componentClass.declaredFields.firstOrNull {
it.type == Runnable::class.java
} ?: return@firstNotNullOfOrNull null
componentClass
} ?: return@hookAfterAllConstructors

val rebindView = likeComponentClass.declaredMethods.firstOrNull {
it.returnType == Void.TYPE && it.parameterCount == 5
&& it.parameterTypes[0] == likeComponentClass
&& it.parameterTypes[4] == LinearLayout::class.java
} ?: return@hookAfterAllConstructors

rebindView.hookAfterMethod { p ->
performLike(p.args[4] as View)
}

unhookSet[0]?.forEach { it.unhook() }
}

instance.likeMethod()?.let { likeMethod ->
instance.sectionClass?.hookAfterAllMethods(likeMethod) { param ->
val sec = param.thisObject ?: return@hookAfterAllMethods
if (!shouldClickLike()) {
return@hookAfterAllMethods
}
val likeView = sec.javaClass.declaredFields.filter {
View::class.java.isAssignableFrom(it.type)
}.firstNotNullOfOrNull {
sec.getObjectFieldOrNullAs<View>(it.name)?.takeIf { v ->
v.id in likeIds
}
}
likeView?.callOnClick()
// 番剧分集切换
instance.viewUniteMossClass?.hookBeforeMethod(
"arcRefresh",
"com.bapis.bilibili.app.viewunite.v1.ArcRefreshReq",
instance.mossResponseHandlerClass
) { param ->
val req = param.args[0]
val aid = req.callMethodAs("getAid")
?: req.callMethodAs<String?>("getBvid")?.let { bv2av(it) }
if (aid == null || aid <= 0L) {
return@hookBeforeMethod
}
param.args[1] = param.args[1].mossResponseHandlerReplaceProxy { reply ->
reply ?: return@mossResponseHandlerReplaceProxy null
val like = reply.callMethod("getReqUser")?.callMethodAs<Int?>("getLike")
?: return@mossResponseHandlerReplaceProxy null
detail = aid to like
null
}
}

// 竖屏模式
instance.storyAbsControllerClass?.hookAfterMethod(
instance.setMDataMethod(),
instance.storyDetailClass
) { param ->
if (param.thisObject.callMethod(instance.getMPlayerMethod()) == null) {
return@hookAfterMethod
}
val storyDetail = instance.fastJsonClass?.callStaticMethodAs<String>(
"toJSONString",
param.args[0]
).toJSONObject()
val playerArgs = storyDetail.optJSONObject("player_args")
val aid = playerArgs?.optLong("aid")
val reqUser = storyDetail.optJSONObject("req_user")
val like = reqUser?.optBoolean("like")
if (aid == null || like == null) {
return@hookAfterMethod
}
detail = aid to if (like) 1 else 0

performLike(param.thisObject as View)
}
}

private fun performLike(root: View) {
val likeView = likeIds.firstNotNullOfOrNull {
root.findViewById(it)
}
instance.bindViewMethod()?.let { bindViewMethod ->
instance.sectionClass?.hookAfterMethod(
bindViewMethod,
instance.viewHolderClass,
instance.continuationClass
) { param ->
if (!shouldClickLike()) {
return@hookAfterMethod
}
val root = param.args[0].callMethodAs<View>(instance.getRootMethod())
val likeView = likeIds.firstNotNullOfOrNull { id ->
root.findViewById(id)
}
likeView?.callOnClick()
likeView?.post {
if (shouldClickLike()) {
likeView.callOnClick()
}
}
}
Expand Down
Loading