Skip to content

Commit

Permalink
feat(YouTube): Add Spoof client patch
Browse files Browse the repository at this point in the history
  • Loading branch information
anddea committed May 27, 2024
1 parent dee55a0 commit 5161192
Show file tree
Hide file tree
Showing 18 changed files with 390 additions and 57 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package app.revanced.patches.music.utils.fix.client

import app.revanced.patches.shared.spoofuseragent.BaseSpoofUserAgentPatch

object SpoofUserAgentPatch : BaseSpoofUserAgentPatch("com.google.android.apps.youtube.music")

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package app.revanced.patches.music.utils.gms

import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.fix.clientspoof.ClientSpoofPatch
import app.revanced.patches.music.utils.fix.client.SpoofUserAgentPatch
import app.revanced.patches.music.utils.fix.fileprovider.FileProviderPatch
import app.revanced.patches.music.utils.integrations.IntegrationsPatch
import app.revanced.patches.music.utils.mainactivity.fingerprints.MainActivityFingerprint
Expand All @@ -14,7 +14,7 @@ object GmsCoreSupportPatch : BaseGmsCoreSupportPatch(
fromPackageName = ORIGINAL_PACKAGE_NAME_YOUTUBE,
mainActivityOnCreateFingerprint = MainActivityFingerprint,
integrationsPatchDependency = IntegrationsPatch::class,
dependencies = setOf(ClientSpoofPatch::class, PackageNamePatch::class, FileProviderPatch::class),
dependencies = setOf(SpoofUserAgentPatch::class, PackageNamePatch::class, FileProviderPatch::class),
gmsCoreSupportResourcePatch = GmsCoreSupportResourcePatch,
compatiblePackages = COMPATIBLE_PACKAGE
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package app.revanced.patches.shared.fingerprints

import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint
import app.revanced.patches.shared.fingerprints.CreatePlayerRequestBodyWithModelFingerprint.indexOfModelInstruction
import app.revanced.patches.shared.fingerprints.CreatePlayerRequestBodyWithModelFingerprint.indexOfReleaseInstruction
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
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 = emptyList(),
opcodes = listOf(Opcode.OR_INT_LIT16),
customFingerprint = { methodDef, _ ->
indexOfModelInstruction(methodDef) >= 0
&& indexOfReleaseInstruction(methodDef) >= 0
}
) {
fun indexOfModelInstruction(methodDef: Method) =
methodDef.indexOfFirstInstruction {
getReference<FieldReference>().toString() == "Landroid/os/Build;->MODEL:Ljava/lang/String;"
}

fun indexOfReleaseInstruction(methodDef: Method) =
methodDef.indexOfFirstInstruction {
getReference<FieldReference>().toString() == "Landroid/os/Build${'$'}VERSION;->RELEASE:Ljava/lang/String;"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,31 @@ import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patches.shared.spoofappversion.fingerprints.ClientInfoFingerprint
import app.revanced.patches.shared.spoofappversion.fingerprints.ClientInfoParentFingerprint
import app.revanced.patches.shared.fingerprints.CreatePlayerRequestBodyWithModelFingerprint
import app.revanced.patches.shared.fingerprints.CreatePlayerRequestBodyWithModelFingerprint.indexOfReleaseInstruction
import app.revanced.util.getTargetIndexReversed
import app.revanced.util.getTargetIndexWithFieldReferenceName
import app.revanced.util.resultOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction

abstract class BaseSpoofAppVersionPatch(
private val descriptor: String
) : BytecodePatch(
setOf(ClientInfoParentFingerprint)
setOf(CreatePlayerRequestBodyWithModelFingerprint)
) {
override fun execute(context: BytecodeContext) {

ClientInfoParentFingerprint.resultOrThrow().let { parentResult ->
ClientInfoFingerprint.resolve(context, parentResult.classDef)
CreatePlayerRequestBodyWithModelFingerprint.resultOrThrow().mutableMethod.apply {
val versionIndex = indexOfReleaseInstruction(this) + 1
val insertIndex = getTargetIndexReversed(versionIndex, Opcode.IPUT_OBJECT)
val insertRegister = getInstruction<TwoRegisterInstruction>(insertIndex).registerA

ClientInfoFingerprint.resultOrThrow().let {
it.mutableMethod.apply {
val versionIndex = getTargetIndexWithFieldReferenceName("RELEASE") + 1
val insertIndex = getTargetIndexReversed(versionIndex, Opcode.IPUT_OBJECT)
val insertRegister = getInstruction<TwoRegisterInstruction>(insertIndex).registerA

addInstructions(
insertIndex, """
invoke-static {v$insertRegister}, $descriptor
move-result-object v$insertRegister
"""
)
}
}
addInstructions(
insertIndex, """
invoke-static {v$insertRegister}, $descriptor
move-result-object v$insertRegister
"""
)
}

}
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package app.revanced.patches.shared.clientspoof
package app.revanced.patches.shared.spoofuseragent

import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
Expand All @@ -16,7 +16,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.reference.MethodReference

abstract class BaseClientSpoofPatch(
abstract class BaseSpoofUserAgentPatch(
private val packageName: String
) : BaseTransformInstructionsPatch<Instruction35cInfo>() {
override fun filterMap(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
package app.revanced.patches.youtube.utils.fix.client

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.PatchException
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import app.revanced.patches.shared.fingerprints.CreatePlayerRequestBodyWithModelFingerprint
import app.revanced.patches.shared.fingerprints.CreatePlayerRequestBodyWithModelFingerprint.indexOfModelInstruction
import app.revanced.patches.youtube.utils.compatibility.Constants
import app.revanced.patches.youtube.utils.fix.client.fingerprints.BuildInitPlaybackRequestFingerprint
import app.revanced.patches.youtube.utils.fix.client.fingerprints.BuildPlayerRequestURIFingerprint
import app.revanced.patches.youtube.utils.fix.client.fingerprints.CreatePlayerRequestBodyFingerprint
import app.revanced.patches.youtube.utils.fix.client.fingerprints.SetPlayerRequestClientTypeFingerprint
import app.revanced.patches.youtube.utils.integrations.Constants.MISC_PATH
import app.revanced.patches.youtube.utils.playertype.PlayerTypeHookPatch
import app.revanced.patches.youtube.utils.settings.SettingsPatch
import app.revanced.patches.youtube.video.information.VideoInformationPatch
import app.revanced.patches.youtube.video.playerresponse.PlayerResponseMethodHookPatch
import app.revanced.util.getReference
import app.revanced.util.getStringInstructionIndex
import app.revanced.util.patch.BaseBytecodePatch
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.instruction.TwoRegisterInstruction
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

object SpoofClientPatch : BaseBytecodePatch(
name = "Spoof client",
description = "Adds options to spoofs the client to allow video playback.",
dependencies = setOf(
PlayerTypeHookPatch::class,
PlayerResponseMethodHookPatch::class,
SettingsPatch::class,
VideoInformationPatch::class,
SpoofUserAgentPatch::class,
),
compatiblePackages = Constants.COMPATIBLE_PACKAGE,
fingerprints = setOf(
BuildInitPlaybackRequestFingerprint,
BuildPlayerRequestURIFingerprint,
SetPlayerRequestClientTypeFingerprint,
CreatePlayerRequestBodyFingerprint,
CreatePlayerRequestBodyWithModelFingerprint,
)
) {
private const val INTEGRATIONS_CLASS_DESCRIPTOR =
"$MISC_PATH/SpoofClientPatch;"
private const val CLIENT_INFO_CLASS_DESCRIPTOR =
"Lcom/google/protos/youtube/api/innertube/InnertubeContext\$ClientInfo;"

override fun execute(context: BytecodeContext) {

// region Block /initplayback requests to fall back to /get_watch requests.

BuildInitPlaybackRequestFingerprint.resultOrThrow().let {
it.mutableMethod.apply {
val moveUriStringIndex = it.scanResult.patternScanResult!!.startIndex
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 {
it.mutableMethod.apply {
val invokeToStringIndex = it.scanResult.patternScanResult!!.startIndex
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 ->
with (result.mutableMethod) {
// Field in the player request object that holds the client info object.
val clientInfoField = 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")

// Client info object's client type field.
val clientInfoClientTypeField = getInstruction(result.scanResult.patternScanResult!!.endIndex)
.getReference<FieldReference>() ?: throw PatchException("Could not find clientInfoClientTypeField")

val clientInfoVersionIndex = getStringInstructionIndex("10.29")
val clientInfoVersionRegister = getInstruction<OneRegisterInstruction>(clientInfoVersionIndex).registerA
val clientInfoClientVersionFieldIndex = implementation!!.instructions.let {
clientInfoVersionIndex + it.subList(clientInfoVersionIndex, it.size - 1).indexOfFirst { instruction ->
instruction.opcode == Opcode.IPUT_OBJECT
&& (instruction as TwoRegisterInstruction).registerA == clientInfoVersionRegister
}
}

// Client info object's client version field.
val clientInfoClientVersionField = getInstruction(clientInfoClientVersionFieldIndex)
.getReference<FieldReference>() ?: throw PatchException("Could not find clientInfoClientVersionField")

Triple(clientInfoField, clientInfoClientTypeField, clientInfoClientVersionField)
}
}

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

// The next IPUT_OBJECT instruction after getting the client model is setting the client model field.
instructions.subList(
getClientModelIndex,
instructions.size,
).find { instruction ->
val reference = instruction.getReference<FieldReference>()
instruction.opcode == Opcode.IPUT_OBJECT
&& reference?.definingClass == CLIENT_INFO_CLASS_DESCRIPTOR
&& reference.type == "Ljava/lang/String;"
}?.getReference<FieldReference>() ?: throw PatchException("Could not find clientInfoClientModelField")
}

// endregion

// region Spoof client type for /player requests.

CreatePlayerRequestBodyFingerprint.resultOrThrow().let {
it.mutableMethod.apply {
val setClientInfoMethodName = "setClientInfo"
val checkCastIndex = it.scanResult.patternScanResult!!.startIndex

val checkCastInstruction = getInstruction<OneRegisterInstruction>(checkCastIndex)
val requestMessageInstanceRegister = checkCastInstruction.registerA
val clientInfoContainerClassName = checkCastInstruction.getReference<TypeReference>()!!.type

addInstruction(
checkCastIndex + 1,
"invoke-static { v$requestMessageInstanceRegister }," +
" $definingClass->$setClientInfoMethodName($clientInfoContainerClassName)V",
)

// Change client info to use the spoofed values.
// Do this in a helper method, to remove the need of picking out multiple free registers from the hooked code.
it.mutableClass.methods.add(
ImmutableMethod(
definingClass,
setClientInfoMethodName,
listOf(ImmutableMethodParameter(clientInfoContainerClassName, annotations, "clientInfoContainer")),
"V",
AccessFlags.PRIVATE or AccessFlags.STATIC,
annotations,
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 model to the spoofed value.
iget-object v1, v0, $clientInfoClientModelField
invoke-static { v1 }, $INTEGRATIONS_CLASS_DESCRIPTOR->getClientModel(Ljava/lang/String;)Ljava/lang/String;
move-result-object v1
iput-object v1, v0, $clientInfoClientModelField
# 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 check whether video is Shorts or Clips.

PlayerResponseMethodHookPatch += PlayerResponseMethodHookPatch.Hook.PlayerParameter(
"$INTEGRATIONS_CLASS_DESCRIPTOR->setPlayerResponseVideoId(" +
"Ljava/lang/String;Ljava/lang/String;Z)Ljava/lang/String;",
)

// endregion

/**
* Add settings
*/
SettingsPatch.addPreference(
arrayOf(
"PREFERENCE_CATEGORY: MISC_EXPERIMENTAL_FLAGS",
"SETTINGS: SPOOF_CLIENT"
)
)

SettingsPatch.updatePatchStatus(this)
}
}
Loading

0 comments on commit 5161192

Please sign in to comment.