fix(YouTube - Spoof client): Improve Android spoofing (#3230)

This commit is contained in:
LisoUseInAIKyrios 2024-05-26 02:10:21 +04:00 committed by GitHub
parent b72fe87cf9
commit b688923c7e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 45 additions and 135 deletions

View file

@ -15,11 +15,13 @@ import app.revanced.patches.all.misc.resources.AddResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen.Sorting import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen.Sorting
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.* import app.revanced.patches.youtube.misc.fix.playback.fingerprints.BuildInitPlaybackRequestFingerprint
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.BuildPlayerRequestURIFingerprint
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.CreatePlayerRequestBodyFingerprint
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.CreatePlayerRequestBodyWithModelFingerprint
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.SetPlayerRequestClientTypeFingerprint
import app.revanced.patches.youtube.misc.settings.SettingsPatch import app.revanced.patches.youtube.misc.settings.SettingsPatch
import app.revanced.patches.youtube.video.playerresponse.PlayerResponseMethodHookPatch
import app.revanced.util.getReference import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction
import app.revanced.util.resultOrThrow import app.revanced.util.resultOrThrow
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
@ -35,11 +37,9 @@ import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
name = "Spoof client", name = "Spoof client",
description = "Spoofs the client to allow video playback.", description = "Spoofs the client to allow video playback.",
dependencies = [ dependencies = [
PlayerResponseMethodHookPatch::class,
SettingsPatch::class, SettingsPatch::class,
AddResourcesPatch::class, AddResourcesPatch::class,
UserAgentClientSpoofPatch::class, UserAgentClientSpoofPatch::class,
PlayerResponseMethodHookPatch::class,
], ],
compatiblePackages = [ compatiblePackages = [
CompatiblePackage( CompatiblePackage(
@ -69,19 +69,11 @@ import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
) )
object SpoofClientPatch : BytecodePatch( object SpoofClientPatch : BytecodePatch(
setOf( setOf(
// Client type spoof.
BuildInitPlaybackRequestFingerprint, BuildInitPlaybackRequestFingerprint,
BuildPlayerRequestURIFingerprint, BuildPlayerRequestURIFingerprint,
SetPlayerRequestClientTypeFingerprint, SetPlayerRequestClientTypeFingerprint,
CreatePlayerRequestBodyFingerprint, CreatePlayerRequestBodyFingerprint,
CreatePlayerRequestBodyWithModelFingerprint, CreatePlayerRequestBodyWithModelFingerprint,
// Storyboard spoof.
StoryboardRendererSpecFingerprint,
PlayerResponseModelImplRecommendedLevelFingerprint,
StoryboardRendererDecoderRecommendedLevelFingerprint,
PlayerResponseModelImplGeneralFingerprint,
StoryboardRendererDecoderSpecFingerprint,
), ),
) { ) {
private const val INTEGRATIONS_CLASS_DESCRIPTOR = private const val INTEGRATIONS_CLASS_DESCRIPTOR =
@ -98,7 +90,7 @@ object SpoofClientPatch : BytecodePatch(
sorting = Sorting.UNSORTED, sorting = Sorting.UNSORTED,
preferences = setOf( preferences = setOf(
SwitchPreference("revanced_spoof_client"), SwitchPreference("revanced_spoof_client"),
SwitchPreference("revanced_spoof_client_use_testsuite"), SwitchPreference("revanced_spoof_client_use_ios"),
), ),
), ),
) )
@ -149,11 +141,11 @@ object SpoofClientPatch : BytecodePatch(
SetPlayerRequestClientTypeFingerprint.resultOrThrow().let { result -> SetPlayerRequestClientTypeFingerprint.resultOrThrow().let { result ->
// Field in the player request object that holds the client info object. // Field in the player request object that holds the client info object.
val clientInfoField = result.mutableMethod val clientInfoField = result.mutableMethod
.getInstructions().first { instruction -> .getInstructions().find { instruction ->
// requestMessage.clientInfo = clientInfoBuilder.build(); // requestMessage.clientInfo = clientInfoBuilder.build();
instruction.opcode == Opcode.IPUT_OBJECT && instruction.opcode == Opcode.IPUT_OBJECT &&
instruction.getReference<FieldReference>()?.type == CLIENT_INFO_CLASS_DESCRIPTOR instruction.getReference<FieldReference>()?.type == CLIENT_INFO_CLASS_DESCRIPTOR
}.getReference<FieldReference>() ?: throw PatchException("Could not find clientInfoField") }?.getReference<FieldReference>() ?: throw PatchException("Could not find clientInfoField")
// Client info object's client type field. // Client info object's client type field.
val clientInfoClientTypeField = result.mutableMethod val clientInfoClientTypeField = result.mutableMethod
@ -168,20 +160,17 @@ object SpoofClientPatch : BytecodePatch(
Triple(clientInfoField, clientInfoClientTypeField, clientInfoClientVersionField) Triple(clientInfoField, clientInfoClientTypeField, clientInfoClientVersionField)
} }
val clientInfoClientModelField = CreatePlayerRequestBodyWithModelFingerprint.resultOrThrow().mutableMethod.let { val clientInfoClientModelField = CreatePlayerRequestBodyWithModelFingerprint.resultOrThrow().let {
val instructions = it.getInstructions() val getClientModelIndex = CreatePlayerRequestBodyWithModelFingerprint.indexOfBuildModelInstruction(it.method)
val instructions = it.mutableMethod.getInstructions()
val getClientModelIndex = it.indexOfFirstInstruction {
getReference<FieldReference>().toString() == "Landroid/os/Build;->MODEL:Ljava/lang/String;"
}
// The next IPUT_OBJECT instruction after getting the client model is setting the client model field. // The next IPUT_OBJECT instruction after getting the client model is setting the client model field.
instructions.subList( instructions.subList(
getClientModelIndex, getClientModelIndex,
instructions.lastIndex, instructions.size,
).first { instruction -> ).find { instruction ->
instruction.opcode == Opcode.IPUT_OBJECT instruction.opcode == Opcode.IPUT_OBJECT
}.getReference<FieldReference>() ?: throw PatchException("Could not find clientInfoClientModelField") }?.getReference<FieldReference>() ?: throw PatchException("Could not find clientInfoClientModelField")
} }
// endregion // endregion
@ -243,6 +232,7 @@ object SpoofClientPatch : BytecodePatch(
invoke-static { v1 }, $INTEGRATIONS_CLASS_DESCRIPTOR->getClientVersion(Ljava/lang/String;)Ljava/lang/String; invoke-static { v1 }, $INTEGRATIONS_CLASS_DESCRIPTOR->getClientVersion(Ljava/lang/String;)Ljava/lang/String;
move-result-object v1 move-result-object v1
iput-object v1, v0, $clientInfoClientVersionField iput-object v1, v0, $clientInfoClientVersionField
:disabled :disabled
return-void return-void
""", """,
@ -253,104 +243,5 @@ object SpoofClientPatch : BytecodePatch(
// endregion // endregion
// region Fix storyboard if Android Testsuite is used.
PlayerResponseMethodHookPatch += PlayerResponseMethodHookPatch.Hook.ProtoBufferParameter(
"$INTEGRATIONS_CLASS_DESCRIPTOR->setPlayerResponseVideoId(" +
"Ljava/lang/String;Ljava/lang/String;Z)Ljava/lang/String;",
)
// Hook recommended seekbar thumbnails quality level for regular videos.
StoryboardRendererDecoderRecommendedLevelFingerprint.resultOrThrow().let {
val endIndex = it.scanResult.patternScanResult!!.endIndex
it.mutableMethod.apply {
val originalValueRegister =
getInstruction<OneRegisterInstruction>(endIndex).registerA
addInstructions(
endIndex + 1,
"""
invoke-static { v$originalValueRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->getRecommendedLevel(I)I
move-result v$originalValueRegister
""",
)
}
}
// Hook the recommended precise seeking thumbnails quality.
PlayerResponseModelImplRecommendedLevelFingerprint.resultOrThrow().let {
val endIndex = it.scanResult.patternScanResult!!.endIndex
it.mutableMethod.apply {
val originalValueRegister =
getInstruction<OneRegisterInstruction>(endIndex).registerA
addInstructions(
endIndex,
"""
invoke-static { v$originalValueRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->getRecommendedLevel(I)I
move-result v$originalValueRegister
""",
)
}
}
// TODO: Hook the seekbar recommended level for Shorts to fix Shorts low quality seekbar thumbnails.
/**
* Hook StoryBoard renderer url.
*/
PlayerResponseModelImplGeneralFingerprint.resultOrThrow().let {
val getStoryBoardIndex = it.scanResult.patternScanResult!!.endIndex
it.mutableMethod.apply {
val getStoryBoardRegister = getInstruction<OneRegisterInstruction>(getStoryBoardIndex).registerA
addInstructions(
getStoryBoardIndex,
"""
invoke-static { v$getStoryBoardRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->getStoryboardRendererSpec(Ljava/lang/String;)Ljava/lang/String;
move-result-object v$getStoryBoardRegister
""",
)
}
}
// Hook the seekbar thumbnail decoder, required for Shorts.
StoryboardRendererDecoderSpecFingerprint.resultOrThrow().let {
val storyBoardUrlIndex = it.scanResult.patternScanResult!!.startIndex + 1
it.mutableMethod.apply {
val getStoryBoardRegister = getInstruction<OneRegisterInstruction>(storyBoardUrlIndex).registerA
addInstructions(
storyBoardUrlIndex + 1,
"""
invoke-static { v$getStoryBoardRegister }, ${INTEGRATIONS_CLASS_DESCRIPTOR}->getStoryboardRendererSpec(Ljava/lang/String;)Ljava/lang/String;
move-result-object v$getStoryBoardRegister
""",
)
}
}
StoryboardRendererSpecFingerprint.resultOrThrow().let {
it.mutableMethod.apply {
val storyBoardUrlParams = "p0"
addInstructions(
0,
"""
if-nez $storyBoardUrlParams, :ignore
invoke-static { $storyBoardUrlParams }, $INTEGRATIONS_CLASS_DESCRIPTOR->getStoryboardRendererSpec(Ljava/lang/String;)Ljava/lang/String;
move-result-object $storyBoardUrlParams
:ignore
nop
""",
)
}
}
// endregion
} }
} }

View file

@ -2,8 +2,12 @@ package app.revanced.patches.youtube.misc.fix.playback.fingerprints
import app.revanced.patcher.extensions.or import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patcher.fingerprint.MethodFingerprint
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.CreatePlayerRequestBodyWithModelFingerprint.indexOfBuildModelInstruction
import app.revanced.util.containsWideLiteralInstructionValue
import app.revanced.util.getReference import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.reference.FieldReference import com.android.tools.smali.dexlib2.iface.reference.FieldReference
internal object CreatePlayerRequestBodyWithModelFingerprint : MethodFingerprint( internal object CreatePlayerRequestBodyWithModelFingerprint : MethodFingerprint(
@ -11,8 +15,17 @@ internal object CreatePlayerRequestBodyWithModelFingerprint : MethodFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf(), parameters = listOf(),
customFingerprint = { methodDef, _ -> customFingerprint = { methodDef, _ ->
methodDef.implementation!!.instructions.any { methodDef.containsWideLiteralInstructionValue(1073741824) &&
it.getReference<FieldReference>().toString() == "Landroid/os/Build;->MODEL:Ljava/lang/String;" indexOfBuildModelInstruction(methodDef) >= 0
}
}, },
) ) {
fun indexOfBuildModelInstruction(methodDef: Method) =
methodDef.indexOfFirstInstruction {
val reference = getReference<FieldReference>()
reference?.definingClass == "Landroid/os/Build;" &&
reference.name == "MODEL" &&
reference.type == "Ljava/lang/String;"
}
}

View file

@ -6,6 +6,7 @@ import app.revanced.util.containsWideLiteralInstructionValue
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
@Deprecated("Fingerprint is obsolete and will be deleted soon")
internal object PlayerResponseModelImplGeneralFingerprint : MethodFingerprint( internal object PlayerResponseModelImplGeneralFingerprint : MethodFingerprint(
returnType = "Ljava/lang/String;", returnType = "Ljava/lang/String;",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,

View file

@ -6,6 +6,7 @@ import app.revanced.util.containsWideLiteralInstructionValue
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
@Deprecated("Fingerprint is obsolete and will be deleted soon")
internal object PlayerResponseModelImplRecommendedLevelFingerprint : MethodFingerprint( internal object PlayerResponseModelImplRecommendedLevelFingerprint : MethodFingerprint(
returnType = "I", returnType = "I",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,

View file

@ -1,12 +1,13 @@
package app.revanced.patches.youtube.misc.fix.playback.fingerprints package app.revanced.patches.youtube.misc.fix.playback.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.util.patch.LiteralValueFingerprint
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
internal object SetPlayerRequestClientTypeFingerprint : MethodFingerprint( internal object SetPlayerRequestClientTypeFingerprint : LiteralValueFingerprint(
strings = listOf("10.29"),
opcodes = listOf( opcodes = listOf(
Opcode.IGET, Opcode.IGET,
Opcode.IPUT, // Sets ClientInfo.clientId. Opcode.IPUT, // Sets ClientInfo.clientId.
), ),
strings = listOf("10.29"),
literalSupplier = { 134217728 }
) )

View file

@ -8,6 +8,7 @@ import com.android.tools.smali.dexlib2.Opcode
/** /**
* Resolves to the same method as [StoryboardRendererDecoderSpecFingerprint]. * Resolves to the same method as [StoryboardRendererDecoderSpecFingerprint].
*/ */
@Deprecated("Fingerprint is obsolete and will be deleted soon")
internal object StoryboardRendererDecoderRecommendedLevelFingerprint : MethodFingerprint( internal object StoryboardRendererDecoderRecommendedLevelFingerprint : MethodFingerprint(
returnType = "V", returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,

View file

@ -8,6 +8,7 @@ import com.android.tools.smali.dexlib2.Opcode
/** /**
* Resolves to the same method as [StoryboardRendererDecoderRecommendedLevelFingerprint]. * Resolves to the same method as [StoryboardRendererDecoderRecommendedLevelFingerprint].
*/ */
@Deprecated("Fingerprint is obsolete and will be deleted soon")
internal object StoryboardRendererDecoderSpecFingerprint : MethodFingerprint( internal object StoryboardRendererDecoderSpecFingerprint : MethodFingerprint(
returnType = "V", returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,

View file

@ -4,6 +4,7 @@ import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
@Deprecated("Fingerprint is obsolete and will be deleted soon")
internal object StoryboardRendererSpecFingerprint : MethodFingerprint( internal object StoryboardRendererSpecFingerprint : MethodFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC, accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
returnType = "L", returnType = "L",

View file

@ -8,6 +8,7 @@ import com.android.tools.smali.dexlib2.Opcode
/** /**
* Resolves using the class found in [StoryboardThumbnailParentFingerprint]. * Resolves using the class found in [StoryboardThumbnailParentFingerprint].
*/ */
@Deprecated("Fingerprint is obsolete and will be deleted soon")
internal object StoryboardThumbnailFingerprint : MethodFingerprint( internal object StoryboardThumbnailFingerprint : MethodFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
returnType = "Z", returnType = "Z",

View file

@ -1092,10 +1092,9 @@
<string name="revanced_spoof_client_summary_on">Client is spoofed</string> <string name="revanced_spoof_client_summary_on">Client is spoofed</string>
<string name="revanced_spoof_client_summary_off">Client is not spoofed\n\nVideo playback may not work</string> <string name="revanced_spoof_client_summary_off">Client is not spoofed\n\nVideo playback may not work</string>
<string name="revanced_spoof_client_user_dialog_message">Turning off this setting may cause video playback issues.</string> <string name="revanced_spoof_client_user_dialog_message">Turning off this setting may cause video playback issues.</string>
<string name="revanced_spoof_client_use_testsuite_title">Spoof client to Android Testsuite</string> <string name="revanced_spoof_client_use_ios_title">Spoof client to iOS</string>
<string name="revanced_spoof_client_use_testsuite_summary">Spoof the client to Android Testsuite</string> <string name="revanced_spoof_client_use_ios_summary_on">Client is currently spoofed to iOS\n\nSide effects include:\n• No HDR video\n• Speed menu is missing\n• Watch history may not work\n• Live streams cannot play as audio only\n• Live streams not available on older devices</string>
<string name="revanced_spoof_client_use_testsuite_summary_on">Client is spoofed to an Android Testsuite client (iOS client is used for live streams)\n\nSide effects include, but are not limited to:\n• Speed flyout menu is missing\n• Captions are missing\n• Player swipe gestures may not work\n• Low quality Shorts seekbar thumbnails\n• Watch history may not work</string> <string name="revanced_spoof_client_use_ios_summary_off">Client is currently spoofed to Android VR\n\nSide effects include:\n• No HDR video\n• Player swipe gestures do not work\n• Kids videos do not playback\n• Paused videos can randomly resume</string>
<string name="revanced_spoof_client_use_testsuite_summary_off">Client is spoofed to an iOS client\n\nSide effects include:\n• No HDR video\n• Speed flyout menu is missing</string>
<string name="revanced_spoof_client_storyboard_timeout">Spoof client thumbnails not available (API timed out)</string> <string name="revanced_spoof_client_storyboard_timeout">Spoof client thumbnails not available (API timed out)</string>
<string name="revanced_spoof_client_storyboard_io_exception">Spoof client thumbnails temporarily not available: %s</string> <string name="revanced_spoof_client_storyboard_io_exception">Spoof client thumbnails temporarily not available: %s</string>
</patch> </patch>