diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/SpoofClientPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/SpoofClientPatch.kt index f188a0a43a..4a9a1724cb 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/SpoofClientPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/SpoofClientPatch.kt @@ -5,6 +5,7 @@ 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.InstructionExtensions.replaceInstruction import app.revanced.patcher.extensions.or import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.PatchException @@ -77,6 +78,7 @@ object SpoofClientPatch : BytecodePatch( SetPlayerRequestClientTypeFingerprint, CreatePlayerRequestBodyFingerprint, CreatePlayerRequestBodyWithModelFingerprint, + CreatePlayerRequestBodyWithVersionReleaseFingerprint, // Player gesture config. PlayerGestureConfigSyntheticFingerprint, @@ -84,6 +86,9 @@ object SpoofClientPatch : BytecodePatch( // Player speed menu item. CreatePlaybackSpeedMenuItemFingerprint, + // Video qualities missing. + BuildRequestFingerprint, + // Watch history. GetTrackingUriFingerprint, ), @@ -92,6 +97,10 @@ object SpoofClientPatch : BytecodePatch( "Lapp/revanced/integrations/youtube/patches/spoof/SpoofClientPatch;" private const val CLIENT_INFO_CLASS_DESCRIPTOR = "Lcom/google/protos/youtube/api/innertube/InnertubeContext\$ClientInfo;" + private const val REQUEST_CLASS_DESCRIPTOR = + "Lorg/chromium/net/ExperimentalUrlRequest;" + private const val REQUEST_BUILDER_CLASS_DESCRIPTOR = + "Lorg/chromium/net/ExperimentalUrlRequest\$Builder;" override fun execute(context: BytecodeContext) { AddResourcesPatch(this::class) @@ -186,6 +195,19 @@ object SpoofClientPatch : BytecodePatch( ?: throw PatchException("Could not find clientInfoClientModelField") } + val clientInfoOsVersionField = CreatePlayerRequestBodyWithVersionReleaseFingerprint.resultOrThrow().let { + val getOsVersionIndex = + CreatePlayerRequestBodyWithVersionReleaseFingerprint.indexOfBuildVersionReleaseInstruction(it.method) + + // The next IPUT_OBJECT instruction after getting the client os version is setting the client os version field. + val index = it.mutableMethod.indexOfFirstInstructionOrThrow(getOsVersionIndex) { + opcode == Opcode.IPUT_OBJECT + } + + it.mutableMethod.getInstruction(index).getReference() + ?: throw PatchException("Could not find clientInfoOsVersionField") + } + // endregion // region Spoof client type for /player requests. @@ -245,6 +267,12 @@ 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 + + # Set client os version to the spoofed value. + iget-object v1, v0, $clientInfoOsVersionField + invoke-static { v1 }, $INTEGRATIONS_CLASS_DESCRIPTOR->getOsVersion(Ljava/lang/String;)Ljava/lang/String; + move-result-object v1 + iput-object v1, v0, $clientInfoOsVersionField :disabled return-void @@ -330,5 +358,28 @@ object SpoofClientPatch : BytecodePatch( } // endregion + + // region Fix video qualities missing, if spoofing to iOS by overriding the user agent. + + BuildRequestFingerprint.resultOrThrow().let { result -> + result.mutableMethod.apply { + val buildRequestIndex = getInstructions().lastIndex - 2 + val requestBuilderRegister = getInstruction(buildRequestIndex).registerC + + val newRequestBuilderIndex = result.scanResult.patternScanResult!!.endIndex + val urlRegister = getInstruction(newRequestBuilderIndex).registerD + + // Replace "requestBuilder.build(): Request" with "overrideUserAgent(requestBuilder, url): Request". + replaceInstruction( + buildRequestIndex, + "invoke-static { v$requestBuilderRegister, v$urlRegister }, " + + "$INTEGRATIONS_CLASS_DESCRIPTOR->" + + "overrideUserAgent(${REQUEST_BUILDER_CLASS_DESCRIPTOR}Ljava/lang/String;)" + + REQUEST_CLASS_DESCRIPTOR + ) + } + } + + // endregion } } diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/BuildRequestFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/BuildRequestFingerprint.kt new file mode 100644 index 0000000000..49c4a76ee1 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/BuildRequestFingerprint.kt @@ -0,0 +1,15 @@ +package app.revanced.patches.youtube.misc.fix.playback.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.MethodFingerprint +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode + +internal object BuildRequestFingerprint : MethodFingerprint( + accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC, + returnType = "Lorg/chromium/net/UrlRequest;", + opcodes = listOf( + Opcode.INVOKE_DIRECT, + Opcode.INVOKE_VIRTUAL + ) +) diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/CreatePlayerRequestBodyWithVersionReleaseFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/CreatePlayerRequestBodyWithVersionReleaseFingerprint.kt new file mode 100644 index 0000000000..1fba488bef --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/CreatePlayerRequestBodyWithVersionReleaseFingerprint.kt @@ -0,0 +1,31 @@ +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.CreatePlayerRequestBodyWithVersionReleaseFingerprint.indexOfBuildVersionReleaseInstruction +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 CreatePlayerRequestBodyWithVersionReleaseFingerprint : MethodFingerprint( + returnType = "L", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = listOf(), + customFingerprint = { methodDef, _ -> + methodDef.containsWideLiteralInstructionValue(1073741824) && + indexOfBuildVersionReleaseInstruction(methodDef) >= 0 + }, +) { + fun indexOfBuildVersionReleaseInstruction(methodDef: Method) = + methodDef.indexOfFirstInstruction { + val reference = getReference() + reference?.definingClass == "Landroid/os/Build\$VERSION;" && + reference.name == "RELEASE" && + reference.type == "Ljava/lang/String;" + } +} + + diff --git a/src/main/resources/addresources/values/strings.xml b/src/main/resources/addresources/values/strings.xml index fc2924bdc7..7f5ab9d0df 100644 --- a/src/main/resources/addresources/values/strings.xml +++ b/src/main/resources/addresources/values/strings.xml @@ -1132,8 +1132,8 @@ This is because Crowdin requires temporarily flattening this file and removing t Client is not spoofed\n\nVideo playback may not work Turning off this setting may cause video playback issues. Spoof client to iOS - Client is currently spoofed to iOS\n\nSide effects include:\n• No HDR video\n• Higher video qualities may be missing\n• Live streams cannot play as audio only\n• Live streams not available on Android 8.0 - Client is currently spoofed to Android VR\n\nSide effects include:\n• No HDR video\n• Kids videos do not playback\n• Paused videos can randomly resume\n• Low quality Shorts seekbar thumbnails\n• Download action button is always hidden\n• End screen cards are always hidden + Client is currently spoofed to iOS\n\nSide effects include:\n• No HDR video\n• Higher video qualities may be missing\n• Live streams cannot play as audio only + Client is currently spoofed to Android VR\n\nSide effects include:\n• No HDR video\n• Kids videos do not playback\n• Paused videos can randomly resume\n• Low quality Shorts seekbar thumbnails\n• Download action button is hidden\n• End screen cards are hidden Spoof client thumbnails not available (API timed out) Spoof client thumbnails temporarily not available: %s