Skip to content

Commit

Permalink
fix(YouTube - Spoof client): Improve Android spoofing (#3230)
Browse files Browse the repository at this point in the history
  • Loading branch information
LisoUseInAIKyrios committed May 25, 2024
1 parent b72fe87 commit b688923
Show file tree
Hide file tree
Showing 10 changed files with 45 additions and 135 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.Sorting
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.video.playerresponse.PlayerResponseMethodHookPatch
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction
import app.revanced.util.resultOrThrow
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
Expand All @@ -35,11 +37,9 @@ import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
name = "Spoof client",
description = "Spoofs the client to allow video playback.",
dependencies = [
PlayerResponseMethodHookPatch::class,
SettingsPatch::class,
AddResourcesPatch::class,
UserAgentClientSpoofPatch::class,
PlayerResponseMethodHookPatch::class,
],
compatiblePackages = [
CompatiblePackage(
Expand Down Expand Up @@ -69,19 +69,11 @@ import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
)
object SpoofClientPatch : BytecodePatch(
setOf(
// Client type spoof.
BuildInitPlaybackRequestFingerprint,
BuildPlayerRequestURIFingerprint,
SetPlayerRequestClientTypeFingerprint,
CreatePlayerRequestBodyFingerprint,
CreatePlayerRequestBodyWithModelFingerprint,

// Storyboard spoof.
StoryboardRendererSpecFingerprint,
PlayerResponseModelImplRecommendedLevelFingerprint,
StoryboardRendererDecoderRecommendedLevelFingerprint,
PlayerResponseModelImplGeneralFingerprint,
StoryboardRendererDecoderSpecFingerprint,
),
) {
private const val INTEGRATIONS_CLASS_DESCRIPTOR =
Expand All @@ -98,7 +90,7 @@ object SpoofClientPatch : BytecodePatch(
sorting = Sorting.UNSORTED,
preferences = setOf(
SwitchPreference("revanced_spoof_client"),
SwitchPreference("revanced_spoof_client_use_testsuite"),
SwitchPreference("revanced_spoof_client_use_ios"),
),
),
)
Expand Down Expand Up @@ -149,11 +141,11 @@ object SpoofClientPatch : BytecodePatch(
SetPlayerRequestClientTypeFingerprint.resultOrThrow().let { result ->
// Field in the player request object that holds the client info object.
val clientInfoField = result.mutableMethod
.getInstructions().first { instruction ->
.getInstructions().find { instruction ->
// requestMessage.clientInfo = clientInfoBuilder.build();
instruction.opcode == Opcode.IPUT_OBJECT &&
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.
val clientInfoClientTypeField = result.mutableMethod
Expand All @@ -168,20 +160,17 @@ object SpoofClientPatch : BytecodePatch(
Triple(clientInfoField, clientInfoClientTypeField, clientInfoClientVersionField)
}

val clientInfoClientModelField = CreatePlayerRequestBodyWithModelFingerprint.resultOrThrow().mutableMethod.let {
val instructions = it.getInstructions()

val getClientModelIndex = it.indexOfFirstInstruction {
getReference<FieldReference>().toString() == "Landroid/os/Build;->MODEL:Ljava/lang/String;"
}
val clientInfoClientModelField = CreatePlayerRequestBodyWithModelFingerprint.resultOrThrow().let {
val getClientModelIndex = CreatePlayerRequestBodyWithModelFingerprint.indexOfBuildModelInstruction(it.method)
val instructions = it.mutableMethod.getInstructions()

// The next IPUT_OBJECT instruction after getting the client model is setting the client model field.
instructions.subList(
getClientModelIndex,
instructions.lastIndex,
).first { instruction ->
instructions.size,
).find { instruction ->
instruction.opcode == Opcode.IPUT_OBJECT
}.getReference<FieldReference>() ?: throw PatchException("Could not find clientInfoClientModelField")
}?.getReference<FieldReference>() ?: throw PatchException("Could not find clientInfoClientModelField")
}

// endregion
Expand Down Expand Up @@ -243,6 +232,7 @@ object SpoofClientPatch : BytecodePatch(
invoke-static { v1 }, $INTEGRATIONS_CLASS_DESCRIPTOR->getClientVersion(Ljava/lang/String;)Ljava/lang/String;
move-result-object v1
iput-object v1, v0, $clientInfoClientVersionField
:disabled
return-void
""",
Expand All @@ -253,104 +243,5 @@ object SpoofClientPatch : BytecodePatch(

// 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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,30 @@ package app.revanced.patches.youtube.misc.fix.playback.fingerprints

import app.revanced.patcher.extensions.or
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.indexOfFirstInstruction
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.reference.FieldReference

internal object CreatePlayerRequestBodyWithModelFingerprint : MethodFingerprint(
returnType = "L",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf(),
customFingerprint = { methodDef, _ ->
methodDef.implementation!!.instructions.any {
it.getReference<FieldReference>().toString() == "Landroid/os/Build;->MODEL:Ljava/lang/String;"
}
methodDef.containsWideLiteralInstructionValue(1073741824) &&
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;"
}
}


Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import app.revanced.util.containsWideLiteralInstructionValue
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode

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

@Deprecated("Fingerprint is obsolete and will be deleted soon")
internal object PlayerResponseModelImplRecommendedLevelFingerprint : MethodFingerprint(
returnType = "I",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
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

internal object SetPlayerRequestClientTypeFingerprint : MethodFingerprint(
strings = listOf("10.29"),
internal object SetPlayerRequestClientTypeFingerprint : LiteralValueFingerprint(
opcodes = listOf(
Opcode.IGET,
Opcode.IPUT, // Sets ClientInfo.clientId.
),
strings = listOf("10.29"),
literalSupplier = { 134217728 }
)
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.android.tools.smali.dexlib2.Opcode
/**
* Resolves to the same method as [StoryboardRendererDecoderSpecFingerprint].
*/
@Deprecated("Fingerprint is obsolete and will be deleted soon")
internal object StoryboardRendererDecoderRecommendedLevelFingerprint : MethodFingerprint(
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.android.tools.smali.dexlib2.Opcode
/**
* Resolves to the same method as [StoryboardRendererDecoderRecommendedLevelFingerprint].
*/
@Deprecated("Fingerprint is obsolete and will be deleted soon")
internal object StoryboardRendererDecoderSpecFingerprint : MethodFingerprint(
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags

@Deprecated("Fingerprint is obsolete and will be deleted soon")
internal object StoryboardRendererSpecFingerprint : MethodFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
returnType = "L",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.android.tools.smali.dexlib2.Opcode
/**
* Resolves using the class found in [StoryboardThumbnailParentFingerprint].
*/
@Deprecated("Fingerprint is obsolete and will be deleted soon")
internal object StoryboardThumbnailFingerprint : MethodFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
returnType = "Z",
Expand Down
7 changes: 3 additions & 4 deletions src/main/resources/addresources/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1092,10 +1092,9 @@
<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_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_testsuite_summary">Spoof the client to Android Testsuite</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_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_use_ios_title">Spoof client to iOS</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_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_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>
</patch>
Expand Down

0 comments on commit b688923

Please sign in to comment.