fix(YouTube - Client spoof): Spoof client to fix playback (#3199)

Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
This commit is contained in:
oSumAtrIX 2024-05-21 02:41:35 +02:00 committed by GitHub
parent 5990f119f1
commit bec1eef10f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 544 additions and 68 deletions

View file

@ -1631,12 +1631,10 @@ public final class app/revanced/patches/youtube/misc/dimensions/spoof/SpoofDevic
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
} }
public final class app/revanced/patches/youtube/misc/fix/playback/ClientSpoofPatch : app/revanced/patches/all/misc/transformation/BaseTransformInstructionsPatch { public final class app/revanced/patches/youtube/misc/fix/playback/SpoofClientPatch : app/revanced/patcher/patch/BytecodePatch {
public static final field INSTANCE Lapp/revanced/patches/youtube/misc/fix/playback/ClientSpoofPatch; public static final field INSTANCE Lapp/revanced/patches/youtube/misc/fix/playback/SpoofClientPatch;
public synthetic fun filterMap (Lcom/android/tools/smali/dexlib2/iface/ClassDef;Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/instruction/Instruction;I)Ljava/lang/Object; public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
public fun filterMap (Lcom/android/tools/smali/dexlib2/iface/ClassDef;Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/instruction/Instruction;I)Lkotlin/Triple; public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
public synthetic fun transform (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Ljava/lang/Object;)V
public fun transform (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Lkotlin/Triple;)V
} }
public final class app/revanced/patches/youtube/misc/fix/playback/SpoofSignaturePatch : app/revanced/patcher/patch/BytecodePatch { public final class app/revanced/patches/youtube/misc/fix/playback/SpoofSignaturePatch : app/revanced/patcher/patch/BytecodePatch {
@ -1651,6 +1649,14 @@ public final class app/revanced/patches/youtube/misc/fix/playback/SpoofSignature
public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
} }
public final class app/revanced/patches/youtube/misc/fix/playback/UserAgentClientSpoofPatch : app/revanced/patches/all/misc/transformation/BaseTransformInstructionsPatch {
public static final field INSTANCE Lapp/revanced/patches/youtube/misc/fix/playback/UserAgentClientSpoofPatch;
public synthetic fun filterMap (Lcom/android/tools/smali/dexlib2/iface/ClassDef;Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/instruction/Instruction;I)Ljava/lang/Object;
public fun filterMap (Lcom/android/tools/smali/dexlib2/iface/ClassDef;Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/instruction/Instruction;I)Lkotlin/Triple;
public synthetic fun transform (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Ljava/lang/Object;)V
public fun transform (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Lkotlin/Triple;)V
}
public final class app/revanced/patches/youtube/misc/gms/GmsCoreSupportPatch : app/revanced/patches/shared/misc/gms/BaseGmsCoreSupportPatch { public final class app/revanced/patches/youtube/misc/gms/GmsCoreSupportPatch : app/revanced/patches/shared/misc/gms/BaseGmsCoreSupportPatch {
public static final field INSTANCE Lapp/revanced/patches/youtube/misc/gms/GmsCoreSupportPatch; public static final field INSTANCE Lapp/revanced/patches/youtube/misc/gms/GmsCoreSupportPatch;
} }

View file

@ -0,0 +1,335 @@
package app.revanced.patches.youtube.misc.fix.playback
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.getInstructions
import app.revanced.patcher.extensions.or
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
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.settings.SettingsPatch
import app.revanced.patches.youtube.video.playerresponse.PlayerResponseMethodHookPatch
import app.revanced.util.getReference
import app.revanced.util.resultOrThrow
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
@Patch(
name = "Spoof client",
description = "Spoofs the client to allow video playback.",
dependencies = [
SpoofClientResourcePatch::class,
PlayerResponseMethodHookPatch::class,
SettingsPatch::class,
AddResourcesPatch::class,
UserAgentClientSpoofPatch::class,
PlayerResponseMethodHookPatch::class,
],
compatiblePackages = [
CompatiblePackage(
"com.google.android.youtube",
[
"18.37.36",
"18.38.44",
"18.43.45",
"18.44.41",
"18.45.43",
"18.48.39",
"18.49.37",
"19.01.34",
"19.02.39",
"19.03.36",
"19.04.38",
"19.05.36",
"19.06.39",
"19.07.40",
"19.08.36",
"19.09.38",
"19.10.39",
"19.11.43",
],
),
],
)
object SpoofClientPatch : BytecodePatch(
setOf(
// Client type spoof.
BuildInitPlaybackRequestFingerprint,
BuildPlayerRequestURIFingerprint,
SetPlayerRequestClientTypeFingerprint,
CreatePlayerRequestBodyFingerprint,
// Storyboard spoof.
StoryboardRendererSpecFingerprint,
PlayerResponseModelImplRecommendedLevelFingerprint,
StoryboardRendererDecoderRecommendedLevelFingerprint,
PlayerResponseModelImplGeneralFingerprint,
StoryboardRendererDecoderSpecFingerprint,
),
) {
private const val INTEGRATIONS_CLASS_DESCRIPTOR =
"Lapp/revanced/integrations/youtube/patches/spoof/SpoofClientPatch;"
private const val CLIENT_INFO_CLASS_DESCRIPTOR =
"Lcom/google/protos/youtube/api/innertube/InnertubeContext\$ClientInfo;"
override fun execute(context: BytecodeContext) {
AddResourcesPatch(this::class)
SettingsPatch.PreferenceScreen.MISC.addPreferences(
PreferenceScreen(
key = "revanced_spoof_client_screen",
sorting = Sorting.UNSORTED,
preferences = setOf(
SwitchPreference("revanced_spoof_client"),
SwitchPreference("revanced_spoof_client_use_ios"),
),
),
)
// region Block /initplayback requests to fall back to /get_watch requests.
BuildInitPlaybackRequestFingerprint.resultOrThrow().let {
val moveUriStringIndex = it.scanResult.patternScanResult!!.startIndex
it.mutableMethod.apply {
val targetRegister = getInstruction<OneRegisterInstruction>(moveUriStringIndex).registerA
addInstructions(
moveUriStringIndex + 1,
"""
invoke-static { v$targetRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->blockInitPlaybackRequest(Ljava/lang/String;)Ljava/lang/String;
move-result-object v$targetRegister
""",
)
}
}
// endregion
// region Block /get_watch requests to fall back to /player requests.
BuildPlayerRequestURIFingerprint.resultOrThrow().let {
val invokeToStringIndex = it.scanResult.patternScanResult!!.startIndex
it.mutableMethod.apply {
val uriRegister = getInstruction<FiveRegisterInstruction>(invokeToStringIndex).registerC
addInstructions(
invokeToStringIndex,
"""
invoke-static { v$uriRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->blockGetWatchRequest(Landroid/net/Uri;)Landroid/net/Uri;
move-result-object v$uriRegister
""",
)
}
}
// endregion
// region Get field references to be used below.
val (clientInfoField, clientInfoClientTypeField, clientInfoClientVersionField) =
SetPlayerRequestClientTypeFingerprint.resultOrThrow().let { result ->
// Field in the player request object that holds the client info object.
val clientInfoField = result.mutableMethod
.getInstructions().first { 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")
// Client info object's client type field.
val clientInfoClientTypeField = result.mutableMethod
.getInstruction(result.scanResult.patternScanResult!!.endIndex)
.getReference<FieldReference>() ?: throw PatchException("Could not find clientInfoClientTypeField")
// Client info object's client version field.
val clientInfoClientVersionField = result.mutableMethod
.getInstruction(result.scanResult.stringsScanResult!!.matches.first().index + 1)
.getReference<FieldReference>() ?: throw PatchException("Could not find clientInfoClientVersionField")
Triple(clientInfoField, clientInfoClientTypeField, clientInfoClientVersionField)
}
// endregion
// region Spoof client type for /player requests.
CreatePlayerRequestBodyFingerprint.resultOrThrow().let { result ->
val setClientInfoMethodName = "patch_setClientInfo"
val checkCastIndex = result.scanResult.patternScanResult!!.startIndex
var clientInfoContainerClassName: String
result.mutableMethod.apply {
val checkCastInstruction = getInstruction<OneRegisterInstruction>(checkCastIndex)
val requestMessageInstanceRegister = checkCastInstruction.registerA
clientInfoContainerClassName = checkCastInstruction.getReference<TypeReference>()!!.type
addInstruction(
checkCastIndex + 1,
"invoke-static { v$requestMessageInstanceRegister }," +
" ${result.classDef.type}->$setClientInfoMethodName($clientInfoContainerClassName)V",
)
}
// Change requestMessage.clientInfo.clientType and requestMessage.clientInfo.clientVersion to the spoofed values.
// Do this in a helper method, to remove the need of picking out multiple free registers from the hooked code.
result.mutableClass.methods.add(
ImmutableMethod(
result.mutableClass.type,
setClientInfoMethodName,
listOf(ImmutableMethodParameter(clientInfoContainerClassName, null, "clientInfoContainer")),
"V",
AccessFlags.PRIVATE or AccessFlags.STATIC,
null,
null,
MutableMethodImplementation(3),
).toMutable().apply {
addInstructions(
"""
invoke-static { }, $INTEGRATIONS_CLASS_DESCRIPTOR->isClientSpoofingEnabled()Z
move-result v0
if-eqz v0, :disabled
iget-object v0, p0, $clientInfoField
# Set client type to the spoofed value.
iget v1, v0, $clientInfoClientTypeField
invoke-static { v1 }, $INTEGRATIONS_CLASS_DESCRIPTOR->getClientTypeId(I)I
move-result v1
iput v1, v0, $clientInfoClientTypeField
# Set client version to the spoofed value.
iget-object v1, v0, $clientInfoClientVersionField
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
""",
)
},
)
}
// 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

@ -0,0 +1,18 @@
package app.revanced.patches.youtube.misc.fix.playback
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch
@Patch(dependencies = [ResourceMappingPatch::class])
internal object SpoofClientResourcePatch : ResourcePatch() {
internal var scrubbedPreviewThumbnailResourceId: Long = -1
override fun execute(context: ResourceContext) {
scrubbedPreviewThumbnailResourceId = ResourceMappingPatch[
"id",
"thumbnail",
]
}
}

View file

@ -68,7 +68,7 @@ object SpoofSignaturePatch : BytecodePatch(
// Hook the player parameters. // Hook the player parameters.
PlayerResponseMethodHookPatch += PlayerResponseMethodHookPatch.Hook.ProtoBufferParameter( PlayerResponseMethodHookPatch += PlayerResponseMethodHookPatch.Hook.ProtoBufferParameter(
"$INTEGRATIONS_CLASS_DESCRIPTOR->spoofParameter(Ljava/lang/String;Z)Ljava/lang/String;", "$INTEGRATIONS_CLASS_DESCRIPTOR->spoofParameter(Ljava/lang/String;Ljava/lang/String;Z)Ljava/lang/String;",
) )
// Force the seekbar time and chapters to always show up. // Force the seekbar time and chapters to always show up.
@ -104,7 +104,7 @@ object SpoofSignaturePatch : BytecodePatch(
} }
// If storyboard spoofing is turned off, then hide the empty seekbar thumbnail view. // If storyboard spoofing is turned off, then hide the empty seekbar thumbnail view.
ScrubbedPreviewLayoutFingerprint.result?.apply { SpoofSignaturePatchScrubbedPreviewLayoutFingerprint.result?.apply {
val endIndex = scanResult.patternScanResult!!.endIndex val endIndex = scanResult.patternScanResult!!.endIndex
mutableMethod.apply { mutableMethod.apply {
val imageViewFieldName = getInstruction<ReferenceInstruction>(endIndex).reference val imageViewFieldName = getInstruction<ReferenceInstruction>(endIndex).reference
@ -116,7 +116,7 @@ object SpoofSignaturePatch : BytecodePatch(
""", """,
) )
} }
} ?: throw ScrubbedPreviewLayoutFingerprint.exception } ?: throw SpoofSignaturePatchScrubbedPreviewLayoutFingerprint.exception
/** /**
* Hook StoryBoard renderer url * Hook StoryBoard renderer url

View file

@ -4,9 +4,9 @@ import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.ResourcePatch import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotation.Patch import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch
import app.revanced.patches.youtube.misc.settings.SettingsPatch
@Patch(dependencies = [SettingsPatch::class, ResourceMappingPatch::class]) @Patch(dependencies = [ResourceMappingPatch::class])
@Deprecated("This patch will be removed in the future.")
object SpoofSignatureResourcePatch : ResourcePatch() { object SpoofSignatureResourcePatch : ResourcePatch() {
internal var scrubbedPreviewThumbnailResourceId: Long = -1 internal var scrubbedPreviewThumbnailResourceId: Long = -1

View file

@ -2,8 +2,6 @@ package app.revanced.patches.youtube.misc.fix.playback
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.all.misc.transformation.BaseTransformInstructionsPatch import app.revanced.patches.all.misc.transformation.BaseTransformInstructionsPatch
import app.revanced.patches.all.misc.transformation.IMethodCall import app.revanced.patches.all.misc.transformation.IMethodCall
@ -16,14 +14,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.Instruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference import com.android.tools.smali.dexlib2.iface.reference.MethodReference
@Patch( object UserAgentClientSpoofPatch : BaseTransformInstructionsPatch<Instruction35cInfo>() {
name = "Client spoof",
description = "Spoofs the client to allow video playback.",
compatiblePackages = [
CompatiblePackage("com.google.android.youtube"),
],
)
object ClientSpoofPatch : BaseTransformInstructionsPatch<Instruction35cInfo>() {
private const val ORIGINAL_PACKAGE_NAME = "com.google.android.youtube" private const val ORIGINAL_PACKAGE_NAME = "com.google.android.youtube"
private const val USER_AGENT_STRING_BUILDER_APPEND_METHOD_REFERENCE = private const val USER_AGENT_STRING_BUILDER_APPEND_METHOD_REFERENCE =
"Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;" "Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;"

View file

@ -0,0 +1,16 @@
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.Opcode
internal object BuildInitPlaybackRequestFingerprint : MethodFingerprint(
returnType = "Lorg/chromium/net/UrlRequest\$Builder;",
opcodes = listOf(
Opcode.MOVE_RESULT_OBJECT,
Opcode.IGET_OBJECT, // Moves the request URI string to a register to build the request with.
),
strings = listOf(
"Content-Type",
"Range",
),
)

View file

@ -0,0 +1,21 @@
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.Opcode
internal object BuildPlayerRequestURIFingerprint : MethodFingerprint(
returnType = "Ljava/lang/String;",
opcodes = listOf(
Opcode.INVOKE_VIRTUAL, // Register holds player request URI.
Opcode.MOVE_RESULT_OBJECT,
Opcode.IPUT_OBJECT,
Opcode.IGET_OBJECT,
Opcode.MONITOR_EXIT,
Opcode.RETURN_OBJECT,
),
strings = listOf(
"youtubei/v1",
"key",
"asig",
),
)

View file

@ -0,0 +1,15 @@
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.Opcode
internal object CreatePlayerRequestBodyFingerprint : MethodFingerprint(
returnType = "V",
parameters = listOf("L"),
opcodes = listOf(
Opcode.CHECK_CAST,
Opcode.IGET,
Opcode.AND_INT_LIT16,
),
strings = listOf("ms"),
)

View file

@ -1,8 +1,8 @@
package app.revanced.patches.youtube.misc.fix.playback.fingerprints package app.revanced.patches.youtube.misc.fix.playback.fingerprints
import app.revanced.util.containsWideLiteralInstructionValue
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.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
@ -13,11 +13,11 @@ internal object PlayerResponseModelImplGeneralFingerprint : MethodFingerprint(
opcodes = listOf( opcodes = listOf(
Opcode.RETURN_OBJECT, Opcode.RETURN_OBJECT,
Opcode.CONST_4, Opcode.CONST_4,
Opcode.RETURN_OBJECT Opcode.RETURN_OBJECT,
), ),
customFingerprint = handler@{ methodDef, _ -> customFingerprint = handler@{ methodDef, _ ->
if (!methodDef.definingClass.endsWith("/PlayerResponseModelImpl;")) return@handler false if (!methodDef.definingClass.endsWith("/PlayerResponseModelImpl;")) return@handler false
methodDef.containsWideLiteralInstructionValue(55735497) methodDef.containsWideLiteralInstructionValue(55735497)
} },
) )

View file

@ -1,8 +1,8 @@
package app.revanced.patches.youtube.misc.fix.playback.fingerprints package app.revanced.patches.youtube.misc.fix.playback.fingerprints
import app.revanced.util.containsWideLiteralInstructionValue
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.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
@ -13,11 +13,11 @@ internal object PlayerResponseModelImplLiveStreamFingerprint : MethodFingerprint
opcodes = listOf( opcodes = listOf(
Opcode.RETURN_OBJECT, Opcode.RETURN_OBJECT,
Opcode.CONST_4, Opcode.CONST_4,
Opcode.RETURN_OBJECT Opcode.RETURN_OBJECT,
), ),
customFingerprint = handler@{ methodDef, _ -> customFingerprint = handler@{ methodDef, _ ->
if (!methodDef.definingClass.endsWith("/PlayerResponseModelImpl;")) return@handler false if (!methodDef.definingClass.endsWith("/PlayerResponseModelImpl;")) return@handler false
methodDef.containsWideLiteralInstructionValue(70276274) methodDef.containsWideLiteralInstructionValue(70276274)
} },
) )

View file

@ -1,8 +1,8 @@
package app.revanced.patches.youtube.misc.fix.playback.fingerprints package app.revanced.patches.youtube.misc.fix.playback.fingerprints
import app.revanced.util.containsWideLiteralInstructionValue
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.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
@ -13,11 +13,11 @@ internal object PlayerResponseModelImplRecommendedLevelFingerprint : MethodFinge
opcodes = listOf( opcodes = listOf(
Opcode.SGET_OBJECT, Opcode.SGET_OBJECT,
Opcode.IGET, Opcode.IGET,
Opcode.RETURN Opcode.RETURN,
), ),
customFingerprint = handler@{ methodDef, _ -> customFingerprint = handler@{ methodDef, _ ->
if (!methodDef.definingClass.endsWith("/PlayerResponseModelImpl;")) return@handler false if (!methodDef.definingClass.endsWith("/PlayerResponseModelImpl;")) return@handler false
methodDef.containsWideLiteralInstructionValue(55735497) methodDef.containsWideLiteralInstructionValue(55735497)
} },
) )

View file

@ -1,7 +1,7 @@
package app.revanced.patches.youtube.misc.fix.playback.fingerprints package app.revanced.patches.youtube.misc.fix.playback.fingerprints
import app.revanced.patcher.extensions.or import app.revanced.patcher.extensions.or
import app.revanced.patches.youtube.misc.fix.playback.SpoofSignatureResourcePatch import app.revanced.patches.youtube.misc.fix.playback.SpoofClientResourcePatch
import app.revanced.util.patch.LiteralValueFingerprint import app.revanced.util.patch.LiteralValueFingerprint
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
@ -23,5 +23,5 @@ internal object ScrubbedPreviewLayoutFingerprint : LiteralValueFingerprint(
Opcode.IPUT_OBJECT, // preview imageview Opcode.IPUT_OBJECT, // preview imageview
), ),
// This resource is used in ~ 40 different locations, but this method has a distinct list of parameters to match to. // This resource is used in ~ 40 different locations, but this method has a distinct list of parameters to match to.
literalSupplier = { SpoofSignatureResourcePatch.scrubbedPreviewThumbnailResourceId } literalSupplier = { SpoofClientResourcePatch.scrubbedPreviewThumbnailResourceId },
) )

View file

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

View file

@ -0,0 +1,27 @@
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patches.youtube.misc.fix.playback.SpoofSignatureResourcePatch
import app.revanced.util.patch.LiteralValueFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal object SpoofSignaturePatchScrubbedPreviewLayoutFingerprint : LiteralValueFingerprint(
accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL,
returnType = "V",
parameters = listOf("Landroid/content/Context;", "Landroid/util/AttributeSet;", "I", "I"),
opcodes = listOf(
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT,
Opcode.INVOKE_VIRTUAL,
Opcode.CONST,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.CHECK_CAST,
Opcode.IPUT_OBJECT, // preview imageview
),
// This resource is used in ~ 40 different locations, but this method has a distinct list of parameters to match to.
literalSupplier = { SpoofSignatureResourcePatch.scrubbedPreviewThumbnailResourceId },
)

View file

@ -17,7 +17,7 @@ internal object StoryboardRendererDecoderRecommendedLevelFingerprint : MethodFin
Opcode.MOVE_RESULT_OBJECT, Opcode.MOVE_RESULT_OBJECT,
Opcode.IPUT_OBJECT, Opcode.IPUT_OBJECT,
Opcode.INVOKE_INTERFACE, Opcode.INVOKE_INTERFACE,
Opcode.MOVE_RESULT Opcode.MOVE_RESULT,
), ),
strings = listOf("#-1#") strings = listOf("#-1#"),
) )

View file

@ -6,8 +6,8 @@ import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
/** /**
* Resolves to the same method as [StoryboardRendererDecoderRecommendedLevelFingerprint]. * Resolves to the same method as [StoryboardRendererDecoderRecommendedLevelFingerprint].
*/ */
internal object StoryboardRendererDecoderSpecFingerprint : MethodFingerprint( internal object StoryboardRendererDecoderSpecFingerprint : MethodFingerprint(
returnType = "V", returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
@ -19,5 +19,5 @@ internal object StoryboardRendererDecoderSpecFingerprint : MethodFingerprint(
Opcode.CONST_4, Opcode.CONST_4,
Opcode.IF_NEZ, Opcode.IF_NEZ,
), ),
strings = listOf("#-1#") strings = listOf("#-1#"),
) )

View file

@ -3,7 +3,7 @@ package app.revanced.patches.youtube.misc.gms
import app.revanced.patches.shared.fingerprints.CastContextFetchFingerprint import app.revanced.patches.shared.fingerprints.CastContextFetchFingerprint
import app.revanced.patches.shared.misc.gms.BaseGmsCoreSupportPatch import app.revanced.patches.shared.misc.gms.BaseGmsCoreSupportPatch
import app.revanced.patches.youtube.layout.buttons.cast.HideCastButtonPatch import app.revanced.patches.youtube.layout.buttons.cast.HideCastButtonPatch
import app.revanced.patches.youtube.misc.fix.playback.ClientSpoofPatch import app.revanced.patches.youtube.misc.fix.playback.SpoofClientPatch
import app.revanced.patches.youtube.misc.gms.Constants.REVANCED_YOUTUBE_PACKAGE_NAME import app.revanced.patches.youtube.misc.gms.Constants.REVANCED_YOUTUBE_PACKAGE_NAME
import app.revanced.patches.youtube.misc.gms.Constants.YOUTUBE_PACKAGE_NAME import app.revanced.patches.youtube.misc.gms.Constants.YOUTUBE_PACKAGE_NAME
import app.revanced.patches.youtube.misc.gms.GmsCoreSupportResourcePatch.gmsCoreVendorGroupIdOption import app.revanced.patches.youtube.misc.gms.GmsCoreSupportResourcePatch.gmsCoreVendorGroupIdOption
@ -27,13 +27,18 @@ object GmsCoreSupportPatch : BaseGmsCoreSupportPatch(
integrationsPatchDependency = IntegrationsPatch::class, integrationsPatchDependency = IntegrationsPatch::class,
dependencies = setOf( dependencies = setOf(
HideCastButtonPatch::class, HideCastButtonPatch::class,
ClientSpoofPatch::class, SpoofClientPatch::class,
), ),
gmsCoreSupportResourcePatch = GmsCoreSupportResourcePatch, gmsCoreSupportResourcePatch = GmsCoreSupportResourcePatch,
compatiblePackages = setOf( compatiblePackages = setOf(
CompatiblePackage( CompatiblePackage(
"com.google.android.youtube", "com.google.android.youtube",
setOf( setOf(
"18.37.36",
"18.38.44",
"18.43.45",
"18.44.41",
"18.45.43",
"18.48.39", "18.48.39",
"18.49.37", "18.49.37",
"19.01.34", "19.01.34",
@ -46,7 +51,7 @@ object GmsCoreSupportPatch : BaseGmsCoreSupportPatch(
"19.08.36", "19.08.36",
"19.09.38", "19.09.38",
"19.10.39", "19.10.39",
"19.11.43" "19.11.43",
), ),
), ),
), ),

View file

@ -119,7 +119,7 @@ object VideoInformationPatch : BytecodePatch(
// Call before any other video id hooks, // Call before any other video id hooks,
// so they can use VideoInformation and check if the video id is for a Short. // so they can use VideoInformation and check if the video id is for a Short.
PlayerResponseMethodHookPatch += PlayerResponseMethodHookPatch.Hook.ProtoBufferParameterBeforeVideoId( PlayerResponseMethodHookPatch += PlayerResponseMethodHookPatch.Hook.ProtoBufferParameterBeforeVideoId(
"$INTEGRATIONS_CLASS_DESCRIPTOR->newPlayerResponseSignature(Ljava/lang/String;Z)Ljava/lang/String;") "$INTEGRATIONS_CLASS_DESCRIPTOR->newPlayerResponseSignature(Ljava/lang/String;Ljava/lang/String;Z)Ljava/lang/String;")
/* /*
* Set the video time method * Set the video time method

View file

@ -24,24 +24,39 @@ object PlayerResponseMethodHookPatch :
private const val PARAMETER_PROTO_BUFFER = 3 private const val PARAMETER_PROTO_BUFFER = 3
private const val PARAMETER_IS_SHORT_AND_OPENING_OR_PLAYING = 11 private const val PARAMETER_IS_SHORT_AND_OPENING_OR_PLAYING = 11
// Temporary 4-bit registers used to pass the parameters to integrations. // Registers used to pass the parameters to integrations.
private const val REGISTER_VIDEO_ID = 0 private var playerResponseMethodCopyRegisters = false
private const val REGISTER_PROTO_BUFFER = 1 private lateinit var REGISTER_VIDEO_ID : String
private const val REGISTER_IS_SHORT_AND_OPENING_OR_PLAYING = 2 private lateinit var REGISTER_PROTO_BUFFER : String
private lateinit var REGISTER_IS_SHORT_AND_OPENING_OR_PLAYING : String
private lateinit var playerResponseMethod: MutableMethod private lateinit var playerResponseMethod: MutableMethod
private var numberOfInstructionsAdded = 0 private var numberOfInstructionsAdded = 0
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
playerResponseMethod = PlayerParameterBuilderFingerprint.result?.mutableMethod playerResponseMethod = PlayerParameterBuilderFingerprint.result?.mutableMethod
?: throw PlayerParameterBuilderFingerprint.exception ?: throw PlayerParameterBuilderFingerprint.exception
// On some app targets the method has too many registers pushing the parameters past v15.
// If needed, move the parameters to 4-bit registers so they can be passed to integrations.
playerResponseMethodCopyRegisters = playerResponseMethod.implementation!!.registerCount -
playerResponseMethod.parameterTypes.size + PARAMETER_IS_SHORT_AND_OPENING_OR_PLAYING > 15
if (playerResponseMethodCopyRegisters) {
REGISTER_VIDEO_ID = "v0"
REGISTER_PROTO_BUFFER = "v1"
REGISTER_IS_SHORT_AND_OPENING_OR_PLAYING = "v2"
} else {
REGISTER_VIDEO_ID = "p$PARAMETER_VIDEO_ID"
REGISTER_PROTO_BUFFER = "p$PARAMETER_PROTO_BUFFER"
REGISTER_IS_SHORT_AND_OPENING_OR_PLAYING = "p$PARAMETER_IS_SHORT_AND_OPENING_OR_PLAYING"
}
} }
override fun close() { override fun close() {
fun hookVideoId(hook: Hook) { fun hookVideoId(hook: Hook) {
playerResponseMethod.addInstruction( playerResponseMethod.addInstruction(
0, "invoke-static {v$REGISTER_VIDEO_ID, v$REGISTER_IS_SHORT_AND_OPENING_OR_PLAYING}, $hook" 0, "invoke-static {$REGISTER_VIDEO_ID, $REGISTER_IS_SHORT_AND_OPENING_OR_PLAYING}, $hook"
) )
numberOfInstructionsAdded++ numberOfInstructionsAdded++
} }
@ -50,8 +65,8 @@ object PlayerResponseMethodHookPatch :
playerResponseMethod.addInstructions( playerResponseMethod.addInstructions(
0, 0,
""" """
invoke-static {v$REGISTER_PROTO_BUFFER, v$REGISTER_IS_SHORT_AND_OPENING_OR_PLAYING}, $hook invoke-static {$REGISTER_PROTO_BUFFER, $REGISTER_VIDEO_ID, $REGISTER_IS_SHORT_AND_OPENING_OR_PLAYING}, $hook
move-result-object v$REGISTER_PROTO_BUFFER move-result-object $REGISTER_PROTO_BUFFER
""" """
) )
numberOfInstructionsAdded += 2 numberOfInstructionsAdded += 2
@ -67,13 +82,12 @@ object PlayerResponseMethodHookPatch :
videoIdHooks.forEach(::hookVideoId) videoIdHooks.forEach(::hookVideoId)
beforeVideoIdHooks.forEach(::hookProtoBufferParameter) beforeVideoIdHooks.forEach(::hookProtoBufferParameter)
// On some app targets the method has too many registers pushing the parameters past v15. if (playerResponseMethodCopyRegisters) {
// Move the parameters to 4-bit registers so they can be passed to integrations.
playerResponseMethod.addInstructions( playerResponseMethod.addInstructions(
0, """ 0, """
move-object/from16 v$REGISTER_VIDEO_ID, p$PARAMETER_VIDEO_ID move-object/from16 $REGISTER_VIDEO_ID, p$PARAMETER_VIDEO_ID
move-object/from16 v$REGISTER_PROTO_BUFFER, p$PARAMETER_PROTO_BUFFER move-object/from16 $REGISTER_PROTO_BUFFER, p$PARAMETER_PROTO_BUFFER
move/from16 v$REGISTER_IS_SHORT_AND_OPENING_OR_PLAYING, p$PARAMETER_IS_SHORT_AND_OPENING_OR_PLAYING move/from16 $REGISTER_IS_SHORT_AND_OPENING_OR_PLAYING, p$PARAMETER_IS_SHORT_AND_OPENING_OR_PLAYING
""", """,
) )
numberOfInstructionsAdded += 3 numberOfInstructionsAdded += 3
@ -81,9 +95,10 @@ object PlayerResponseMethodHookPatch :
// Move the modified register back. // Move the modified register back.
playerResponseMethod.addInstruction( playerResponseMethod.addInstruction(
numberOfInstructionsAdded, numberOfInstructionsAdded,
"move-object/from16 p$PARAMETER_PROTO_BUFFER, v$REGISTER_PROTO_BUFFER" "move-object/from16 p$PARAMETER_PROTO_BUFFER, $REGISTER_PROTO_BUFFER"
) )
} }
}
internal abstract class Hook(private val methodDescriptor: String) { internal abstract class Hook(private val methodDescriptor: String) {
internal class VideoId(methodDescriptor: String) : Hook(methodDescriptor) internal class VideoId(methodDescriptor: String) : Hook(methodDescriptor)

View file

@ -907,7 +907,7 @@
<string name="revanced_spoof_app_version_summary_on">Version spoofed</string> <string name="revanced_spoof_app_version_summary_on">Version spoofed</string>
<string name="revanced_spoof_app_version_summary_off">Version not spoofed</string> <string name="revanced_spoof_app_version_summary_off">Version not spoofed</string>
<string name="revanced_spoof_app_version_user_dialog_message">App version will be spoofed to an older version of YouTube.\n\nThis will change the appearance and features of the app, but unknown side effects may occur.\n\nIf later turned off, it is recommended to clear the app data to prevent UI bugs.</string> <string name="revanced_spoof_app_version_user_dialog_message">App version will be spoofed to an older version of YouTube.\n\nThis will change the appearance and features of the app, but unknown side effects may occur.\n\nIf later turned off, it is recommended to clear the app data to prevent UI bugs.</string>
<!-- It is ideal, but not required, if the text here appears alphabetically after the text used for 'revanced_spoof_app_version_title'. <!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch --> This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch -->
<string name="revanced_spoof_app_version_target_title">Spoof app version target</string> <string name="revanced_spoof_app_version_target_title">Spoof app version target</string>
<!-- 'RYD' is 'Return YouTube Dislike' --> <!-- 'RYD' is 'Return YouTube Dislike' -->
@ -1085,6 +1085,21 @@
<string name="revanced_slide_to_seek_summary_on">Slide to seek is enabled</string> <string name="revanced_slide_to_seek_summary_on">Slide to seek is enabled</string>
<string name="revanced_slide_to_seek_summary_off">Slide to seek is not enabled</string> <string name="revanced_slide_to_seek_summary_off">Slide to seek is not enabled</string>
</patch> </patch>
<patch id="misc.fix.playback.SpoofClientPatch">
<string name="revanced_spoof_client_screen_title">Spoof client</string>
<string name="revanced_spoof_client_screen_summary">Spoof the client to prevent playback issues</string>
<string name="revanced_spoof_client_title">Spoof client</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_user_dialog_message">Turning off this setting may cause video playback issues.</string>
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_client_spoof_title' -->
<string name="revanced_spoof_client_use_ios_title">Spoof client to iOS</string>
<string name="revanced_spoof_client_use_ios_summary">Spoof the client to iOS instead of an Android Testsuite</string>
<string name="revanced_spoof_client_use_ios_summary_on">Client is spoofed to an iOS client\n\nSide effects include:\n• 60 fps video may not be available\n• No HDR video\n• Some videos may not load\n• Higher video qualities may be missing</string>
<string name="revanced_spoof_client_use_ios_summary_off">Client is spoofed to an Android Testsuite client (iOS client is used for live streams)\n\nSide effects include:\n• Subtitles are missing\n• Player gestures may not work\n• Low quality Shorts seekbar thumbnails</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>
<!-- This patch is no longer used, these strings are not in use, and these strings will be deleted in the future. --> <!-- This patch is no longer used, these strings are not in use, and these strings will be deleted in the future. -->
<patch id="misc.fix.playback.SpoofSignaturePatch"> <patch id="misc.fix.playback.SpoofSignaturePatch">
<string name="revanced_spoof_signature_verification_screen_title">Spoof app signature</string> <string name="revanced_spoof_signature_verification_screen_title">Spoof app signature</string>