mirror of
https://github.com/ReVanced/revanced-patches.git
synced 2024-11-10 01:01:56 +01:00
fix(YouTube): Fix issues related to playback by replace streaming data (#3582)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
This commit is contained in:
parent
17126df51c
commit
dfa94d70f6
30 changed files with 427 additions and 1126 deletions
|
@ -1916,6 +1916,12 @@ 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/SpoofVideoStreamsPatch : app/revanced/patcher/patch/BytecodePatch {
|
||||||
|
public static final field INSTANCE Lapp/revanced/patches/youtube/misc/fix/playback/SpoofVideoStreamsPatch;
|
||||||
|
public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
|
||||||
|
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
|
||||||
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/youtube/misc/fix/playback/UserAgentClientSpoofPatch : app/revanced/patches/all/misc/transformation/BaseTransformInstructionsPatch {
|
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 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 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;
|
||||||
|
|
|
@ -1,399 +1,11 @@
|
||||||
package app.revanced.patches.youtube.misc.fix.playback
|
package app.revanced.patches.youtube.misc.fix.playback
|
||||||
|
|
||||||
import app.revanced.patcher.data.BytecodeContext
|
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.InstructionExtensions.replaceInstruction
|
|
||||||
import app.revanced.patcher.extensions.or
|
|
||||||
import app.revanced.patcher.patch.BytecodePatch
|
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
|
|
||||||
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.ListPreference
|
|
||||||
import app.revanced.patches.shared.misc.settings.preference.NonInteractivePreference
|
|
||||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen
|
|
||||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
|
||||||
import app.revanced.patches.youtube.misc.backgroundplayback.BackgroundPlaybackPatch
|
|
||||||
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.*
|
|
||||||
import app.revanced.patches.youtube.misc.playertype.PlayerTypeHookPatch
|
|
||||||
import app.revanced.patches.youtube.misc.settings.SettingsPatch
|
|
||||||
import app.revanced.util.getReference
|
|
||||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
|
||||||
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(
|
@Deprecated("This patch is obsolete.", replaceWith = ReplaceWith("SpoofVideoStreamsPatch"))
|
||||||
name = "Spoof client",
|
|
||||||
description = "Spoofs the client to allow video playback.",
|
|
||||||
dependencies = [
|
|
||||||
SettingsPatch::class,
|
|
||||||
AddResourcesPatch::class,
|
|
||||||
UserAgentClientSpoofPatch::class,
|
|
||||||
// Required since iOS livestream fix partially enables background playback.
|
|
||||||
BackgroundPlaybackPatch::class,
|
|
||||||
PlayerTypeHookPatch::class,
|
|
||||||
],
|
|
||||||
compatiblePackages = [
|
|
||||||
CompatiblePackage(
|
|
||||||
"com.google.android.youtube",
|
|
||||||
[
|
|
||||||
// This patch works with these versions,
|
|
||||||
// but the dependent background playback patch does not.
|
|
||||||
// "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",
|
|
||||||
"19.12.41",
|
|
||||||
"19.13.37",
|
|
||||||
"19.14.43",
|
|
||||||
"19.15.36",
|
|
||||||
"19.16.39",
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
object SpoofClientPatch : BytecodePatch(
|
object SpoofClientPatch : BytecodePatch(
|
||||||
setOf(
|
dependencies = setOf(SpoofVideoStreamsPatch::class),
|
||||||
// Client type spoof.
|
|
||||||
BuildInitPlaybackRequestFingerprint,
|
|
||||||
BuildPlayerRequestURIFingerprint,
|
|
||||||
SetPlayerRequestClientTypeFingerprint,
|
|
||||||
CreatePlayerRequestBodyFingerprint,
|
|
||||||
CreatePlayerRequestBodyWithModelFingerprint,
|
|
||||||
CreatePlayerRequestBodyWithVersionReleaseFingerprint,
|
|
||||||
|
|
||||||
// Player gesture config.
|
|
||||||
PlayerGestureConfigSyntheticFingerprint,
|
|
||||||
|
|
||||||
// Player speed menu item.
|
|
||||||
CreatePlaybackSpeedMenuItemFingerprint,
|
|
||||||
|
|
||||||
// Video qualities missing.
|
|
||||||
BuildRequestFingerprint,
|
|
||||||
|
|
||||||
// Livestream audio only background playback.
|
|
||||||
PlayerResponseModelBackgroundAudioPlaybackFingerprint,
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
private const val INTEGRATIONS_CLASS_DESCRIPTOR =
|
override fun execute(context: BytecodeContext) {}
|
||||||
"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)
|
|
||||||
|
|
||||||
SettingsPatch.PreferenceScreen.MISC.addPreferences(
|
|
||||||
PreferenceScreen(
|
|
||||||
key = "revanced_spoof_client_screen",
|
|
||||||
sorting = PreferenceScreen.Sorting.UNSORTED,
|
|
||||||
preferences = setOf(
|
|
||||||
SwitchPreference("revanced_spoof_client"),
|
|
||||||
ListPreference("revanced_spoof_client_type",
|
|
||||||
summaryKey = null,
|
|
||||||
entriesKey = "revanced_spoof_client_type_entries",
|
|
||||||
entryValuesKey = "revanced_spoof_client_type_entry_values"
|
|
||||||
),
|
|
||||||
SwitchPreference("revanced_spoof_client_ios_force_avc"),
|
|
||||||
NonInteractivePreference("revanced_spoof_client_about_android_ios"),
|
|
||||||
NonInteractivePreference("revanced_spoof_client_about_android_vr")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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().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 = 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
val clientInfoClientModelField = CreatePlayerRequestBodyWithModelFingerprint.resultOrThrow().let {
|
|
||||||
val getClientModelIndex =
|
|
||||||
CreatePlayerRequestBodyWithModelFingerprint.indexOfBuildModelInstruction(it.method)
|
|
||||||
|
|
||||||
// The next IPUT_OBJECT instruction after getting the client model is setting the client model field.
|
|
||||||
val index = it.mutableMethod.indexOfFirstInstructionOrThrow(getClientModelIndex) {
|
|
||||||
opcode == Opcode.IPUT_OBJECT
|
|
||||||
}
|
|
||||||
|
|
||||||
it.mutableMethod.getInstruction(index).getReference<FieldReference>()
|
|
||||||
?: 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<FieldReference>()
|
|
||||||
?: throw PatchException("Could not find clientInfoOsVersionField")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 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.
|
|
||||||
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 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
|
|
||||||
|
|
||||||
# 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
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Fix player gesture if spoofing to iOS.
|
|
||||||
|
|
||||||
PlayerGestureConfigSyntheticFingerprint.resultOrThrow().let {
|
|
||||||
val endIndex = it.scanResult.patternScanResult!!.endIndex
|
|
||||||
val downAndOutLandscapeAllowedIndex = endIndex - 3
|
|
||||||
val downAndOutPortraitAllowedIndex = endIndex - 9
|
|
||||||
|
|
||||||
arrayOf(
|
|
||||||
downAndOutLandscapeAllowedIndex,
|
|
||||||
downAndOutPortraitAllowedIndex,
|
|
||||||
).forEach { index ->
|
|
||||||
val gestureAllowedMethod = context.toMethodWalker(it.mutableMethod)
|
|
||||||
.nextMethod(index, true)
|
|
||||||
.getMethod() as MutableMethod
|
|
||||||
|
|
||||||
gestureAllowedMethod.apply {
|
|
||||||
val isAllowedIndex = getInstructions().lastIndex
|
|
||||||
val isAllowed = getInstruction<OneRegisterInstruction>(isAllowedIndex).registerA
|
|
||||||
|
|
||||||
addInstructions(
|
|
||||||
isAllowedIndex,
|
|
||||||
"""
|
|
||||||
invoke-static { v$isAllowed }, $INTEGRATIONS_CLASS_DESCRIPTOR->enablePlayerGesture(Z)Z
|
|
||||||
move-result v$isAllowed
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Fix livestream audio only background play if spoofing to iOS.
|
|
||||||
// This force enables audio background playback.
|
|
||||||
|
|
||||||
PlayerResponseModelBackgroundAudioPlaybackFingerprint.resultOrThrow().mutableMethod.addInstructions(
|
|
||||||
0,
|
|
||||||
"""
|
|
||||||
invoke-static { }, $INTEGRATIONS_CLASS_DESCRIPTOR->overrideBackgroundAudioPlayback()Z
|
|
||||||
move-result v0
|
|
||||||
if-eqz v0, :do_not_override
|
|
||||||
return v0
|
|
||||||
:do_not_override
|
|
||||||
nop
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// Fix playback speed menu item if spoofing to iOS.
|
|
||||||
|
|
||||||
CreatePlaybackSpeedMenuItemFingerprint.resultOrThrow().let {
|
|
||||||
val scanResult = it.scanResult.patternScanResult!!
|
|
||||||
if (scanResult.startIndex != 0) throw PatchException("Unexpected start index: ${scanResult.startIndex}")
|
|
||||||
|
|
||||||
it.mutableMethod.apply {
|
|
||||||
// Find the conditional check if the playback speed menu item is not created.
|
|
||||||
val shouldCreateMenuIndex =
|
|
||||||
indexOfFirstInstructionOrThrow(scanResult.endIndex) { opcode == Opcode.IF_EQZ }
|
|
||||||
val shouldCreateMenuRegister = getInstruction<OneRegisterInstruction>(shouldCreateMenuIndex).registerA
|
|
||||||
|
|
||||||
addInstructions(
|
|
||||||
shouldCreateMenuIndex,
|
|
||||||
"""
|
|
||||||
invoke-static { v$shouldCreateMenuRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->forceCreatePlaybackSpeedMenu(Z)Z
|
|
||||||
move-result v$shouldCreateMenuRegister
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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<FiveRegisterInstruction>(buildRequestIndex).registerC
|
|
||||||
|
|
||||||
val newRequestBuilderIndex = result.scanResult.patternScanResult!!.endIndex
|
|
||||||
val urlRegister = getInstruction<FiveRegisterInstruction>(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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,239 +1,12 @@
|
||||||
package app.revanced.patches.youtube.misc.fix.playback
|
package app.revanced.patches.youtube.misc.fix.playback
|
||||||
|
|
||||||
import app.revanced.patcher.data.BytecodeContext
|
import app.revanced.patcher.data.BytecodeContext
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
|
||||||
import app.revanced.patcher.patch.BytecodePatch
|
import app.revanced.patcher.patch.BytecodePatch
|
||||||
import app.revanced.patcher.patch.annotation.Patch
|
|
||||||
import app.revanced.patcher.util.smali.ExternalLabel
|
|
||||||
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.playertype.PlayerTypeHookPatch
|
|
||||||
import app.revanced.patches.youtube.misc.settings.SettingsPatch
|
|
||||||
import app.revanced.patches.youtube.video.information.VideoInformationPatch
|
|
||||||
import app.revanced.patches.youtube.video.playerresponse.PlayerResponseMethodHookPatch
|
|
||||||
import app.revanced.util.exception
|
|
||||||
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.ReferenceInstruction
|
|
||||||
|
|
||||||
@Patch(
|
@Deprecated("This patch is obsolete.", replaceWith = ReplaceWith("SpoofVideoStreamsPatch"))
|
||||||
description = "Spoofs the signature to prevent playback issues.",
|
|
||||||
dependencies = [
|
|
||||||
SettingsPatch::class,
|
|
||||||
PlayerTypeHookPatch::class,
|
|
||||||
PlayerResponseMethodHookPatch::class,
|
|
||||||
VideoInformationPatch::class,
|
|
||||||
SpoofSignatureResourcePatch::class,
|
|
||||||
AddResourcesPatch::class,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
@Deprecated("This patch will be removed in the future.")
|
|
||||||
object SpoofSignaturePatch : BytecodePatch(
|
object SpoofSignaturePatch : BytecodePatch(
|
||||||
setOf(
|
dependencies = setOf(SpoofVideoStreamsPatch::class),
|
||||||
PlayerResponseModelImplGeneralFingerprint,
|
|
||||||
PlayerResponseModelImplLiveStreamFingerprint,
|
|
||||||
PlayerResponseModelImplRecommendedLevelFingerprint,
|
|
||||||
StoryboardRendererSpecFingerprint,
|
|
||||||
StoryboardRendererDecoderSpecFingerprint,
|
|
||||||
StoryboardRendererDecoderRecommendedLevelFingerprint,
|
|
||||||
StoryboardThumbnailParentFingerprint,
|
|
||||||
SpoofSignaturePatchScrubbedPreviewLayoutFingerprint,
|
|
||||||
StatsQueryParameterFingerprint,
|
|
||||||
ParamsMapPutFingerprint,
|
|
||||||
),
|
|
||||||
) {
|
) {
|
||||||
private const val INTEGRATIONS_CLASS_DESCRIPTOR =
|
override fun execute(context: BytecodeContext) {}
|
||||||
"Lapp/revanced/integrations/youtube/patches/spoof/SpoofSignaturePatch;"
|
|
||||||
|
|
||||||
override fun execute(context: BytecodeContext) {
|
|
||||||
AddResourcesPatch(this::class)
|
|
||||||
|
|
||||||
SettingsPatch.PreferenceScreen.MISC.addPreferences(
|
|
||||||
PreferenceScreen(
|
|
||||||
key = "revanced_spoof_signature_verification_screen",
|
|
||||||
sorting = Sorting.UNSORTED,
|
|
||||||
preferences = setOf(
|
|
||||||
SwitchPreference("revanced_spoof_signature_verification_enabled"),
|
|
||||||
SwitchPreference("revanced_spoof_signature_in_feed_enabled"),
|
|
||||||
SwitchPreference("revanced_spoof_storyboard"),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
// Hook the player parameters.
|
|
||||||
PlayerResponseMethodHookPatch += PlayerResponseMethodHookPatch.Hook.ProtoBufferParameter(
|
|
||||||
"$INTEGRATIONS_CLASS_DESCRIPTOR->spoofParameter(Ljava/lang/String;Ljava/lang/String;Z)Ljava/lang/String;",
|
|
||||||
)
|
|
||||||
|
|
||||||
// Force the seekbar time and chapters to always show up.
|
|
||||||
// This is used if the storyboard spec fetch fails, for viewing paid videos,
|
|
||||||
// or if storyboard spoofing is turned off.
|
|
||||||
StoryboardThumbnailParentFingerprint.result?.classDef?.let { classDef ->
|
|
||||||
StoryboardThumbnailFingerprint.also {
|
|
||||||
it.resolve(
|
|
||||||
context,
|
|
||||||
classDef,
|
|
||||||
)
|
|
||||||
}.result?.let {
|
|
||||||
val endIndex = it.scanResult.patternScanResult!!.endIndex
|
|
||||||
// Replace existing instruction to preserve control flow label.
|
|
||||||
// The replaced return instruction always returns false
|
|
||||||
// (it is the 'no thumbnails found' control path),
|
|
||||||
// so there is no need to pass the existing return value to integrations.
|
|
||||||
it.mutableMethod.replaceInstruction(
|
|
||||||
endIndex,
|
|
||||||
"""
|
|
||||||
invoke-static {}, $INTEGRATIONS_CLASS_DESCRIPTOR->getSeekbarThumbnailOverrideValue()Z
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
// Since this is end of the method must replace one line then add the rest.
|
|
||||||
it.mutableMethod.addInstructions(
|
|
||||||
endIndex + 1,
|
|
||||||
"""
|
|
||||||
move-result v0
|
|
||||||
return v0
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
} ?: throw StoryboardThumbnailFingerprint.exception
|
|
||||||
}
|
|
||||||
|
|
||||||
// If storyboard spoofing is turned off, then hide the empty seekbar thumbnail view.
|
|
||||||
SpoofSignaturePatchScrubbedPreviewLayoutFingerprint.result?.apply {
|
|
||||||
val endIndex = scanResult.patternScanResult!!.endIndex
|
|
||||||
mutableMethod.apply {
|
|
||||||
val imageViewFieldName = getInstruction<ReferenceInstruction>(endIndex).reference
|
|
||||||
addInstructions(
|
|
||||||
implementation!!.instructions.lastIndex,
|
|
||||||
"""
|
|
||||||
iget-object v0, p0, $imageViewFieldName # copy imageview field to a register
|
|
||||||
invoke-static {v0}, $INTEGRATIONS_CLASS_DESCRIPTOR->seekbarImageViewCreated(Landroid/widget/ImageView;)V
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} ?: throw SpoofSignaturePatchScrubbedPreviewLayoutFingerprint.exception
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hook StoryBoard renderer url
|
|
||||||
*/
|
|
||||||
arrayOf(
|
|
||||||
PlayerResponseModelImplGeneralFingerprint,
|
|
||||||
PlayerResponseModelImplLiveStreamFingerprint,
|
|
||||||
).forEach { fingerprint ->
|
|
||||||
fingerprint.result?.let {
|
|
||||||
it.mutableMethod.apply {
|
|
||||||
val getStoryBoardIndex = it.scanResult.patternScanResult!!.endIndex
|
|
||||||
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
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} ?: throw fingerprint.exception
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hook recommended seekbar thumbnails quality level.
|
|
||||||
StoryboardRendererDecoderRecommendedLevelFingerprint.result?.let {
|
|
||||||
val moveOriginalRecommendedValueIndex = it.scanResult.patternScanResult!!.endIndex
|
|
||||||
val originalValueRegister = it.mutableMethod
|
|
||||||
.getInstruction<OneRegisterInstruction>(moveOriginalRecommendedValueIndex).registerA
|
|
||||||
|
|
||||||
it.mutableMethod.addInstructions(
|
|
||||||
moveOriginalRecommendedValueIndex + 1,
|
|
||||||
"""
|
|
||||||
invoke-static { v$originalValueRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->getRecommendedLevel(I)I
|
|
||||||
move-result v$originalValueRegister
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
} ?: throw StoryboardRendererDecoderRecommendedLevelFingerprint.exception
|
|
||||||
|
|
||||||
// Hook the recommended precise seeking thumbnails quality level.
|
|
||||||
PlayerResponseModelImplRecommendedLevelFingerprint.result?.let {
|
|
||||||
it.mutableMethod.apply {
|
|
||||||
val moveOriginalRecommendedValueIndex = it.scanResult.patternScanResult!!.endIndex
|
|
||||||
val originalValueRegister =
|
|
||||||
getInstruction<OneRegisterInstruction>(moveOriginalRecommendedValueIndex).registerA
|
|
||||||
|
|
||||||
addInstructions(
|
|
||||||
moveOriginalRecommendedValueIndex,
|
|
||||||
"""
|
|
||||||
invoke-static { v$originalValueRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->getRecommendedLevel(I)I
|
|
||||||
move-result v$originalValueRegister
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} ?: throw PlayerResponseModelImplRecommendedLevelFingerprint.exception
|
|
||||||
|
|
||||||
StoryboardRendererSpecFingerprint.result?.let {
|
|
||||||
it.mutableMethod.apply {
|
|
||||||
val storyBoardUrlParams = 0
|
|
||||||
|
|
||||||
addInstructionsWithLabels(
|
|
||||||
0,
|
|
||||||
"""
|
|
||||||
if-nez p$storyBoardUrlParams, :ignore
|
|
||||||
invoke-static { p$storyBoardUrlParams }, $INTEGRATIONS_CLASS_DESCRIPTOR->getStoryboardRendererSpec(Ljava/lang/String;)Ljava/lang/String;
|
|
||||||
move-result-object p$storyBoardUrlParams
|
|
||||||
""",
|
|
||||||
ExternalLabel("ignore", getInstruction(0)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} ?: throw StoryboardRendererSpecFingerprint.exception
|
|
||||||
|
|
||||||
// Hook the seekbar thumbnail decoder and use a NULL spec for live streams.
|
|
||||||
StoryboardRendererDecoderSpecFingerprint.result?.let {
|
|
||||||
val storyBoardUrlIndex = it.scanResult.patternScanResult!!.startIndex + 1
|
|
||||||
val storyboardUrlRegister =
|
|
||||||
it.mutableMethod.getInstruction<OneRegisterInstruction>(storyBoardUrlIndex).registerA
|
|
||||||
|
|
||||||
it.mutableMethod.addInstructions(
|
|
||||||
storyBoardUrlIndex + 1,
|
|
||||||
"""
|
|
||||||
invoke-static { v$storyboardUrlRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->getStoryboardDecoderRendererSpec(Ljava/lang/String;)Ljava/lang/String;
|
|
||||||
move-result-object v$storyboardUrlRegister
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
} ?: throw StoryboardRendererDecoderSpecFingerprint.exception
|
|
||||||
|
|
||||||
// Fix stats not being tracked.
|
|
||||||
// Due to signature spoofing "adformat" is present in query parameters made for /stats requests,
|
|
||||||
// even though, for regular videos, it should not be.
|
|
||||||
// This breaks stats tracking.
|
|
||||||
// Replace the ad parameter with the video parameter in the query parameters.
|
|
||||||
StatsQueryParameterFingerprint.result?.let {
|
|
||||||
val putMethod = ParamsMapPutFingerprint.result?.method?.toString()
|
|
||||||
?: throw ParamsMapPutFingerprint.exception
|
|
||||||
|
|
||||||
it.mutableMethod.apply {
|
|
||||||
val adParamIndex = it.scanResult.stringsScanResult!!.matches.first().index
|
|
||||||
val videoParamIndex = adParamIndex + 3
|
|
||||||
|
|
||||||
// Replace the ad parameter with the video parameter.
|
|
||||||
replaceInstruction(adParamIndex, getInstruction(videoParamIndex))
|
|
||||||
|
|
||||||
// Call paramsMap.put instead of paramsMap.putIfNotExist
|
|
||||||
// because the key is already present in the map.
|
|
||||||
val putAdParamIndex = adParamIndex + 1
|
|
||||||
val putIfKeyNotExistsInstruction = getInstruction<FiveRegisterInstruction>(putAdParamIndex)
|
|
||||||
replaceInstruction(
|
|
||||||
putAdParamIndex,
|
|
||||||
"invoke-virtual { " +
|
|
||||||
"v${putIfKeyNotExistsInstruction.registerC}, " +
|
|
||||||
"v${putIfKeyNotExistsInstruction.registerD}, " +
|
|
||||||
"v${putIfKeyNotExistsInstruction.registerE} }, " +
|
|
||||||
putMethod,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} ?: throw StatsQueryParameterFingerprint.exception
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,18 +2,8 @@ package app.revanced.patches.youtube.misc.fix.playback
|
||||||
|
|
||||||
import app.revanced.patcher.data.ResourceContext
|
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.patches.shared.misc.mapping.ResourceMappingPatch
|
|
||||||
|
|
||||||
@Patch(dependencies = [ResourceMappingPatch::class])
|
|
||||||
@Deprecated("This patch will be removed in the future.")
|
@Deprecated("This patch will be removed in the future.")
|
||||||
object SpoofSignatureResourcePatch : ResourcePatch() {
|
object SpoofSignatureResourcePatch : ResourcePatch() {
|
||||||
internal var scrubbedPreviewThumbnailResourceId: Long = -1
|
override fun execute(context: ResourceContext) {}
|
||||||
|
|
||||||
override fun execute(context: ResourceContext) {
|
|
||||||
scrubbedPreviewThumbnailResourceId = ResourceMappingPatch[
|
|
||||||
"id",
|
|
||||||
"thumbnail",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,287 @@
|
||||||
|
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.addInstructionsWithLabels
|
||||||
|
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.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.ListPreference
|
||||||
|
import app.revanced.patches.shared.misc.settings.preference.NonInteractivePreference
|
||||||
|
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen
|
||||||
|
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||||
|
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.BuildInitPlaybackRequestFingerprint
|
||||||
|
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.BuildMediaDataSourceFingerprint
|
||||||
|
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.BuildPlayerRequestURIFingerprint
|
||||||
|
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.BuildRequestFingerprint
|
||||||
|
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.CreateStreamingDataFingerprint
|
||||||
|
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.ProtobufClassParseByteBufferFingerprint
|
||||||
|
import app.revanced.patches.youtube.misc.settings.SettingsPatch
|
||||||
|
import app.revanced.util.getReference
|
||||||
|
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||||
|
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.MethodReference
|
||||||
|
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||||
|
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
|
||||||
|
|
||||||
|
@Patch(
|
||||||
|
name = "Spoof video streams",
|
||||||
|
description = "Spoofs the client video streams to allow video playback.",
|
||||||
|
dependencies = [
|
||||||
|
SettingsPatch::class,
|
||||||
|
AddResourcesPatch::class,
|
||||||
|
UserAgentClientSpoofPatch::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",
|
||||||
|
"19.12.41",
|
||||||
|
"19.13.37",
|
||||||
|
"19.14.43",
|
||||||
|
"19.15.36",
|
||||||
|
"19.16.39",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
object SpoofVideoStreamsPatch : BytecodePatch(
|
||||||
|
setOf(
|
||||||
|
BuildInitPlaybackRequestFingerprint,
|
||||||
|
BuildPlayerRequestURIFingerprint,
|
||||||
|
CreateStreamingDataFingerprint,
|
||||||
|
BuildMediaDataSourceFingerprint,
|
||||||
|
BuildRequestFingerprint,
|
||||||
|
ProtobufClassParseByteBufferFingerprint,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
private const val INTEGRATIONS_CLASS_DESCRIPTOR =
|
||||||
|
"Lapp/revanced/integrations/youtube/patches/spoof/SpoofVideoStreamsPatch;"
|
||||||
|
|
||||||
|
override fun execute(context: BytecodeContext) {
|
||||||
|
AddResourcesPatch(this::class)
|
||||||
|
|
||||||
|
SettingsPatch.PreferenceScreen.MISC.addPreferences(
|
||||||
|
PreferenceScreen(
|
||||||
|
key = "revanced_spoof_video_streams_screen",
|
||||||
|
sorting = PreferenceScreen.Sorting.UNSORTED,
|
||||||
|
preferences = setOf(
|
||||||
|
SwitchPreference("revanced_spoof_video_streams"),
|
||||||
|
ListPreference(
|
||||||
|
"revanced_spoof_video_streams_client_type",
|
||||||
|
summaryKey = null,
|
||||||
|
entriesKey = "revanced_spoof_video_streams_client_type_entries",
|
||||||
|
entryValuesKey = "revanced_spoof_video_streams_client_type_entry_values",
|
||||||
|
),
|
||||||
|
SwitchPreference(
|
||||||
|
"revanced_spoof_video_streams_ios_force_avc",
|
||||||
|
tag = "app.revanced.integrations.youtube.settings.preference.ForceAVCSpoofingPreference",
|
||||||
|
),
|
||||||
|
NonInteractivePreference("revanced_spoof_video_streams_about_ios"),
|
||||||
|
NonInteractivePreference("revanced_spoof_video_streams_about_android_vr"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 replacement streams at player requests.
|
||||||
|
|
||||||
|
BuildRequestFingerprint.resultOrThrow().mutableMethod.apply {
|
||||||
|
val newRequestBuilderIndex = indexOfFirstInstructionOrThrow {
|
||||||
|
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||||
|
getReference<MethodReference>()?.name == "newUrlRequestBuilder"
|
||||||
|
}
|
||||||
|
val urlRegister = getInstruction<FiveRegisterInstruction>(newRequestBuilderIndex).registerD
|
||||||
|
val freeRegister = getInstruction<OneRegisterInstruction>(newRequestBuilderIndex + 1).registerA
|
||||||
|
|
||||||
|
addInstructions(
|
||||||
|
newRequestBuilderIndex,
|
||||||
|
"""
|
||||||
|
move-object v$freeRegister, p1
|
||||||
|
invoke-static { v$urlRegister, v$freeRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->fetchStreams(Ljava/lang/String;Ljava/util/Map;)V
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region Replace the streaming data with the replacement streams.
|
||||||
|
|
||||||
|
CreateStreamingDataFingerprint.resultOrThrow().let { result ->
|
||||||
|
result.mutableMethod.apply {
|
||||||
|
val setStreamDataMethodName = "patch_setStreamingData"
|
||||||
|
val resultMethodType = result.mutableClass.type
|
||||||
|
val videoDetailsIndex = result.scanResult.patternScanResult!!.endIndex
|
||||||
|
val videoDetailsRegister = getInstruction<TwoRegisterInstruction>(videoDetailsIndex).registerA
|
||||||
|
val videoDetailsClass = getInstruction(videoDetailsIndex).getReference<FieldReference>()!!.type
|
||||||
|
|
||||||
|
addInstruction(
|
||||||
|
videoDetailsIndex + 1,
|
||||||
|
"invoke-direct { p0, v$videoDetailsRegister }, " +
|
||||||
|
"$resultMethodType->$setStreamDataMethodName($videoDetailsClass)V",
|
||||||
|
)
|
||||||
|
|
||||||
|
val protobufClass = ProtobufClassParseByteBufferFingerprint.resultOrThrow().mutableMethod.definingClass
|
||||||
|
val setStreamingDataIndex = result.scanResult.patternScanResult!!.startIndex
|
||||||
|
|
||||||
|
val playerProtoClass = getInstruction(setStreamingDataIndex + 1)
|
||||||
|
.getReference<FieldReference>()!!.definingClass
|
||||||
|
|
||||||
|
val setStreamingDataField = getInstruction(setStreamingDataIndex).getReference<FieldReference>()
|
||||||
|
|
||||||
|
val getStreamingDataField = getInstruction(
|
||||||
|
indexOfFirstInstructionOrThrow {
|
||||||
|
opcode == Opcode.IGET_OBJECT && getReference<FieldReference>()?.definingClass == playerProtoClass
|
||||||
|
}
|
||||||
|
).getReference<FieldReference>()
|
||||||
|
|
||||||
|
// Use a helper method to avoid the need of picking out multiple free registers from the hooked code.
|
||||||
|
result.mutableClass.methods.add(
|
||||||
|
ImmutableMethod(
|
||||||
|
resultMethodType,
|
||||||
|
setStreamDataMethodName,
|
||||||
|
listOf(ImmutableMethodParameter(videoDetailsClass, null, "videoDetails")),
|
||||||
|
"V",
|
||||||
|
AccessFlags.PRIVATE or AccessFlags.FINAL,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
MutableMethodImplementation(9),
|
||||||
|
).toMutable().apply {
|
||||||
|
addInstructionsWithLabels(
|
||||||
|
0,
|
||||||
|
"""
|
||||||
|
invoke-static { }, $INTEGRATIONS_CLASS_DESCRIPTOR->isSpoofingEnabled()Z
|
||||||
|
move-result v0
|
||||||
|
if-eqz v0, :disabled
|
||||||
|
|
||||||
|
# Get video id.
|
||||||
|
iget-object v2, p1, $videoDetailsClass->c:Ljava/lang/String;
|
||||||
|
if-eqz v2, :disabled
|
||||||
|
|
||||||
|
# Get streaming data.
|
||||||
|
invoke-static { v2 }, $INTEGRATIONS_CLASS_DESCRIPTOR->getStreamingData(Ljava/lang/String;)Ljava/nio/ByteBuffer;
|
||||||
|
move-result-object v3
|
||||||
|
if-eqz v3, :disabled
|
||||||
|
|
||||||
|
# Parse streaming data.
|
||||||
|
sget-object v4, $playerProtoClass->a:$playerProtoClass
|
||||||
|
invoke-static { v4, v3 }, $protobufClass->parseFrom(${protobufClass}Ljava/nio/ByteBuffer;)$protobufClass
|
||||||
|
move-result-object v5
|
||||||
|
check-cast v5, $playerProtoClass
|
||||||
|
|
||||||
|
# Set streaming data.
|
||||||
|
iget-object v6, v5, $getStreamingDataField
|
||||||
|
if-eqz v6, :disabled
|
||||||
|
iput-object v6, p0, $setStreamingDataField
|
||||||
|
|
||||||
|
:disabled
|
||||||
|
return-void
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region Remove /videoplayback request body to fix playback.
|
||||||
|
// This is needed when using iOS client as streaming data source.
|
||||||
|
|
||||||
|
BuildMediaDataSourceFingerprint.resultOrThrow().let {
|
||||||
|
it.mutableMethod.apply {
|
||||||
|
val targetIndex = getInstructions().lastIndex
|
||||||
|
|
||||||
|
// Instructions are added just before the method returns,
|
||||||
|
// so there's no concern of clobbering in-use registers.
|
||||||
|
addInstructions(
|
||||||
|
targetIndex,
|
||||||
|
"""
|
||||||
|
# Field a: Stream uri.
|
||||||
|
# Field c: Http method.
|
||||||
|
# Field d: Post data.
|
||||||
|
move-object v0, p0 # method has over 15 registers and must copy p0 to a lower register.
|
||||||
|
iget-object v1, v0, $definingClass->a:Landroid/net/Uri;
|
||||||
|
iget v2, v0, $definingClass->c:I
|
||||||
|
iget-object v3, v0, $definingClass->d:[B
|
||||||
|
invoke-static { v1, v2, v3 }, $INTEGRATIONS_CLASS_DESCRIPTOR->removeVideoPlaybackPostBody(Landroid/net/Uri;I[B)[B
|
||||||
|
move-result-object v1
|
||||||
|
iput-object v1, v0, $definingClass->d:[B
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
internal object BuildMediaDataSourceFingerprint : MethodFingerprint(
|
||||||
|
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
|
||||||
|
returnType = "V",
|
||||||
|
parameters = listOf(
|
||||||
|
"Landroid/net/Uri;",
|
||||||
|
"J",
|
||||||
|
"I",
|
||||||
|
"[B",
|
||||||
|
"Ljava/util/Map;",
|
||||||
|
"J",
|
||||||
|
"J",
|
||||||
|
"Ljava/lang/String;",
|
||||||
|
"I",
|
||||||
|
"Ljava/lang/Object;"
|
||||||
|
)
|
||||||
|
)
|
|
@ -3,13 +3,34 @@ 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 com.android.tools.smali.dexlib2.AccessFlags
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
|
||||||
|
|
||||||
internal object BuildRequestFingerprint : MethodFingerprint(
|
internal object BuildRequestFingerprint : MethodFingerprint(
|
||||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
|
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
|
||||||
returnType = "Lorg/chromium/net/UrlRequest;",
|
returnType = "Lorg/chromium/net/UrlRequest;",
|
||||||
opcodes = listOf(
|
customFingerprint = { methodDef, _ ->
|
||||||
Opcode.INVOKE_DIRECT,
|
// Different targets have slightly different parameters
|
||||||
Opcode.INVOKE_VIRTUAL
|
|
||||||
)
|
// Earlier targets have parameters:
|
||||||
|
//L
|
||||||
|
//Ljava/util/Map;
|
||||||
|
//[B
|
||||||
|
//L
|
||||||
|
//L
|
||||||
|
//L
|
||||||
|
//Lorg/chromium/net/UrlRequest$Callback;
|
||||||
|
|
||||||
|
// Later targets have parameters:
|
||||||
|
//L
|
||||||
|
//Ljava/util/Map;
|
||||||
|
//[B
|
||||||
|
//L
|
||||||
|
//L
|
||||||
|
//L
|
||||||
|
//Lorg/chromium/net/UrlRequest\$Callback;
|
||||||
|
//L
|
||||||
|
|
||||||
|
val parameterTypes = methodDef.parameterTypes
|
||||||
|
(parameterTypes.size == 7 || parameterTypes.size == 8)
|
||||||
|
&& parameterTypes[1] == "Ljava/util/Map;" // URL headers.
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
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 CreatePlaybackSpeedMenuItemFingerprint : MethodFingerprint(
|
|
||||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
|
||||||
returnType = "V",
|
|
||||||
opcodes = listOf(
|
|
||||||
Opcode.IGET_OBJECT, // First instruction of the method
|
|
||||||
Opcode.IGET_OBJECT,
|
|
||||||
Opcode.IGET_OBJECT,
|
|
||||||
Opcode.CONST_4,
|
|
||||||
Opcode.IF_EQZ,
|
|
||||||
Opcode.INVOKE_INTERFACE,
|
|
||||||
null // MOVE_RESULT or MOVE_RESULT_OBJECT, Return value controls the creation of the playback speed menu item.
|
|
||||||
),
|
|
||||||
// 19.01 and earlier is missing the second parameter.
|
|
||||||
// Since this fingerprint is somewhat weak, work around by checking for both method parameter signatures.
|
|
||||||
customFingerprint = custom@{ methodDef, _ ->
|
|
||||||
// 19.01 and earlier parameters are: "[L"
|
|
||||||
// 19.02+ parameters are "[L", "F"
|
|
||||||
val parameterTypes = methodDef.parameterTypes
|
|
||||||
val firstParameter = parameterTypes.firstOrNull()
|
|
||||||
|
|
||||||
if (firstParameter == null || !firstParameter.startsWith("[L")) {
|
|
||||||
return@custom false
|
|
||||||
}
|
|
||||||
|
|
||||||
parameterTypes.size == 1 || (parameterTypes.size == 2 && parameterTypes[1] == "F")
|
|
||||||
}
|
|
||||||
)
|
|
|
@ -1,15 +0,0 @@
|
||||||
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"),
|
|
||||||
)
|
|
|
@ -1,31 +0,0 @@
|
||||||
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.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;"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
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<FieldReference>()
|
|
||||||
reference?.definingClass == "Landroid/os/Build\$VERSION;" &&
|
|
||||||
reference.name == "RELEASE" &&
|
|
||||||
reference.type == "Ljava/lang/String;"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
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 CreateStreamingDataFingerprint : MethodFingerprint(
|
||||||
|
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
|
||||||
|
returnType = "V",
|
||||||
|
parameters = listOf("L"),
|
||||||
|
opcodes = listOf(
|
||||||
|
Opcode.IPUT_OBJECT,
|
||||||
|
Opcode.IGET_OBJECT,
|
||||||
|
Opcode.IF_NEZ,
|
||||||
|
Opcode.SGET_OBJECT,
|
||||||
|
Opcode.IPUT_OBJECT
|
||||||
|
),
|
||||||
|
customFingerprint = { methodDef, classDef ->
|
||||||
|
classDef.fields.any { field ->
|
||||||
|
field.name == "a" && field.type.endsWith("/StreamingDataOuterClass\$StreamingData;")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
|
@ -1,25 +0,0 @@
|
||||||
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
|
|
||||||
|
|
||||||
@Deprecated("Fingerprint is obsolete and will be deleted soon")
|
|
||||||
internal object ParamsMapPutFingerprint : MethodFingerprint(
|
|
||||||
returnType = "V",
|
|
||||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
|
||||||
parameters = listOf(
|
|
||||||
"Ljava/lang/String;",
|
|
||||||
"Ljava/lang/String;",
|
|
||||||
),
|
|
||||||
opcodes = listOf(
|
|
||||||
Opcode.CONST_4,
|
|
||||||
Opcode.CONST_4,
|
|
||||||
Opcode.CONST_4,
|
|
||||||
Opcode.MOVE_OBJECT,
|
|
||||||
Opcode.MOVE_OBJECT,
|
|
||||||
Opcode.MOVE_OBJECT,
|
|
||||||
Opcode.INVOKE_DIRECT_RANGE,
|
|
||||||
),
|
|
||||||
)
|
|
|
@ -1,49 +0,0 @@
|
||||||
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
|
|
||||||
|
|
||||||
import app.revanced.patcher.extensions.or
|
|
||||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
|
||||||
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.MethodReference
|
|
||||||
|
|
||||||
internal object PlayerGestureConfigSyntheticFingerprint : MethodFingerprint(
|
|
||||||
returnType = "V",
|
|
||||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
|
||||||
parameters = listOf("Ljava/lang/Object;"),
|
|
||||||
opcodes = listOf(
|
|
||||||
Opcode.SGET_OBJECT,
|
|
||||||
Opcode.INVOKE_VIRTUAL,
|
|
||||||
Opcode.MOVE_RESULT,
|
|
||||||
Opcode.IF_EQZ,
|
|
||||||
Opcode.IF_EQZ,
|
|
||||||
Opcode.IGET_OBJECT,
|
|
||||||
Opcode.INVOKE_INTERFACE,
|
|
||||||
Opcode.MOVE_RESULT_OBJECT,
|
|
||||||
Opcode.INVOKE_VIRTUAL, // playerGestureConfig.downAndOutLandscapeAllowed.
|
|
||||||
Opcode.MOVE_RESULT,
|
|
||||||
Opcode.CHECK_CAST,
|
|
||||||
Opcode.IPUT_BOOLEAN,
|
|
||||||
Opcode.INVOKE_INTERFACE,
|
|
||||||
Opcode.MOVE_RESULT_OBJECT,
|
|
||||||
Opcode.INVOKE_VIRTUAL, // playerGestureConfig.downAndOutPortraitAllowed.
|
|
||||||
Opcode.MOVE_RESULT,
|
|
||||||
Opcode.IPUT_BOOLEAN,
|
|
||||||
Opcode.RETURN_VOID,
|
|
||||||
),
|
|
||||||
customFingerprint = { methodDef, classDef ->
|
|
||||||
fun indexOfDownAndOutAllowedInstruction(methodDef: Method) =
|
|
||||||
methodDef.indexOfFirstInstruction {
|
|
||||||
val reference = getReference<MethodReference>()
|
|
||||||
reference?.definingClass == "Lcom/google/android/libraries/youtube/innertube/model/media/PlayerConfigModel;" &&
|
|
||||||
reference.parameterTypes.isEmpty() &&
|
|
||||||
reference.returnType == "Z"
|
|
||||||
}
|
|
||||||
|
|
||||||
// This method is always called "a" because this kind of class always has a single method.
|
|
||||||
methodDef.name == "a" && classDef.methods.count() == 2 &&
|
|
||||||
indexOfDownAndOutAllowedInstruction(methodDef) >= 0
|
|
||||||
},
|
|
||||||
)
|
|
|
@ -1,25 +0,0 @@
|
||||||
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 PlayerResponseModelBackgroundAudioPlaybackFingerprint : MethodFingerprint(
|
|
||||||
returnType = "Z",
|
|
||||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
|
|
||||||
parameters = listOf("Lcom/google/android/libraries/youtube/innertube/model/player/PlayerResponseModel;"),
|
|
||||||
opcodes = listOf(
|
|
||||||
Opcode.CONST_4,
|
|
||||||
Opcode.IF_EQZ,
|
|
||||||
Opcode.INVOKE_INTERFACE,
|
|
||||||
Opcode.MOVE_RESULT_OBJECT,
|
|
||||||
Opcode.INVOKE_VIRTUAL,
|
|
||||||
Opcode.MOVE_RESULT,
|
|
||||||
Opcode.IF_NEZ,
|
|
||||||
Opcode.GOTO,
|
|
||||||
Opcode.RETURN,
|
|
||||||
null, // Opcode.CONST_4 or Opcode.MOVE
|
|
||||||
Opcode.RETURN,
|
|
||||||
)
|
|
||||||
)
|
|
|
@ -1,24 +0,0 @@
|
||||||
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
|
|
||||||
|
|
||||||
import app.revanced.patcher.extensions.or
|
|
||||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
|
||||||
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,
|
|
||||||
parameters = emptyList(),
|
|
||||||
opcodes = listOf(
|
|
||||||
Opcode.RETURN_OBJECT,
|
|
||||||
Opcode.CONST_4,
|
|
||||||
Opcode.RETURN_OBJECT,
|
|
||||||
),
|
|
||||||
customFingerprint = handler@{ methodDef, _ ->
|
|
||||||
if (!methodDef.definingClass.endsWith("/PlayerResponseModelImpl;")) return@handler false
|
|
||||||
|
|
||||||
methodDef.containsWideLiteralInstructionValue(55735497)
|
|
||||||
},
|
|
||||||
)
|
|
|
@ -1,24 +0,0 @@
|
||||||
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
|
|
||||||
|
|
||||||
import app.revanced.patcher.extensions.or
|
|
||||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
|
||||||
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 PlayerResponseModelImplLiveStreamFingerprint : MethodFingerprint(
|
|
||||||
returnType = "Ljava/lang/String;",
|
|
||||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
|
||||||
parameters = emptyList(),
|
|
||||||
opcodes = listOf(
|
|
||||||
Opcode.RETURN_OBJECT,
|
|
||||||
Opcode.CONST_4,
|
|
||||||
Opcode.RETURN_OBJECT,
|
|
||||||
),
|
|
||||||
customFingerprint = handler@{ methodDef, _ ->
|
|
||||||
if (!methodDef.definingClass.endsWith("/PlayerResponseModelImpl;")) return@handler false
|
|
||||||
|
|
||||||
methodDef.containsWideLiteralInstructionValue(70276274)
|
|
||||||
},
|
|
||||||
)
|
|
|
@ -1,24 +0,0 @@
|
||||||
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
|
|
||||||
|
|
||||||
import app.revanced.patcher.extensions.or
|
|
||||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
|
||||||
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,
|
|
||||||
parameters = emptyList(),
|
|
||||||
opcodes = listOf(
|
|
||||||
Opcode.SGET_OBJECT,
|
|
||||||
Opcode.IGET,
|
|
||||||
Opcode.RETURN,
|
|
||||||
),
|
|
||||||
customFingerprint = handler@{ methodDef, _ ->
|
|
||||||
if (!methodDef.definingClass.endsWith("/PlayerResponseModelImpl;")) return@handler false
|
|
||||||
|
|
||||||
methodDef.containsWideLiteralInstructionValue(55735497)
|
|
||||||
},
|
|
||||||
)
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
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 ProtobufClassParseByteBufferFingerprint : MethodFingerprint(
|
||||||
|
accessFlags = AccessFlags.PROTECTED or AccessFlags.STATIC,
|
||||||
|
parameters = listOf("L", "Ljava/nio/ByteBuffer;"),
|
||||||
|
returnType = "L",
|
||||||
|
opcodes = listOf(
|
||||||
|
Opcode.SGET_OBJECT,
|
||||||
|
Opcode.INVOKE_STATIC,
|
||||||
|
Opcode.MOVE_RESULT_OBJECT,
|
||||||
|
Opcode.RETURN_OBJECT,
|
||||||
|
),
|
||||||
|
customFingerprint = { methodDef, _ -> methodDef.name == "parseFrom" },
|
||||||
|
)
|
|
@ -1,13 +0,0 @@
|
||||||
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
|
|
||||||
|
|
||||||
import app.revanced.util.patch.LiteralValueFingerprint
|
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
|
||||||
|
|
||||||
internal object SetPlayerRequestClientTypeFingerprint : LiteralValueFingerprint(
|
|
||||||
opcodes = listOf(
|
|
||||||
Opcode.IGET,
|
|
||||||
Opcode.IPUT, // Sets ClientInfo.clientId.
|
|
||||||
),
|
|
||||||
strings = listOf("10.29"),
|
|
||||||
literalSupplier = { 134217728 }
|
|
||||||
)
|
|
|
@ -1,28 +0,0 @@
|
||||||
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
|
|
||||||
|
|
||||||
@Deprecated("Fingerprint is obsolete and will be deleted soon")
|
|
||||||
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 },
|
|
||||||
)
|
|
|
@ -1,8 +0,0 @@
|
||||||
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
|
|
||||||
|
|
||||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
|
||||||
|
|
||||||
@Deprecated("Fingerprint is obsolete and will be deleted soon")
|
|
||||||
internal object StatsQueryParameterFingerprint : MethodFingerprint(
|
|
||||||
strings = listOf("adunit"),
|
|
||||||
)
|
|
|
@ -1,24 +0,0 @@
|
||||||
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
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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,
|
|
||||||
parameters = listOf("Lcom/google/android/libraries/youtube/innertube/model/player/PlayerResponseModel;"),
|
|
||||||
opcodes = listOf(
|
|
||||||
Opcode.INVOKE_INTERFACE,
|
|
||||||
Opcode.MOVE_RESULT_OBJECT,
|
|
||||||
Opcode.IPUT_OBJECT,
|
|
||||||
Opcode.INVOKE_INTERFACE,
|
|
||||||
Opcode.MOVE_RESULT,
|
|
||||||
),
|
|
||||||
strings = listOf("#-1#"),
|
|
||||||
)
|
|
|
@ -1,24 +0,0 @@
|
||||||
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
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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,
|
|
||||||
parameters = listOf("Lcom/google/android/libraries/youtube/innertube/model/player/PlayerResponseModel;"),
|
|
||||||
opcodes = listOf(
|
|
||||||
Opcode.INVOKE_INTERFACE, // First instruction of the method.
|
|
||||||
Opcode.MOVE_RESULT_OBJECT,
|
|
||||||
Opcode.CONST_4,
|
|
||||||
Opcode.CONST_4,
|
|
||||||
Opcode.IF_NEZ,
|
|
||||||
),
|
|
||||||
strings = listOf("#-1#"),
|
|
||||||
)
|
|
|
@ -1,13 +0,0 @@
|
||||||
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
|
|
||||||
|
|
||||||
@Deprecated("Fingerprint is obsolete and will be deleted soon")
|
|
||||||
internal object StoryboardRendererSpecFingerprint : MethodFingerprint(
|
|
||||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
|
|
||||||
returnType = "L",
|
|
||||||
parameters = listOf("Ljava/lang/String;", "J"),
|
|
||||||
strings = listOf("\\|"),
|
|
||||||
)
|
|
|
@ -1,24 +0,0 @@
|
||||||
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
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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",
|
|
||||||
parameters = listOf(),
|
|
||||||
opcodes = listOf(
|
|
||||||
Opcode.MOVE_RESULT,
|
|
||||||
Opcode.IF_GTZ,
|
|
||||||
Opcode.GOTO,
|
|
||||||
Opcode.CONST_4,
|
|
||||||
Opcode.RETURN,
|
|
||||||
Opcode.RETURN, // Last instruction of method.
|
|
||||||
),
|
|
||||||
)
|
|
|
@ -1,18 +0,0 @@
|
||||||
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
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Here lies code that creates the seekbar thumbnails.
|
|
||||||
*
|
|
||||||
* An additional change here might force the thumbnails to be created,
|
|
||||||
* or possibly a change somewhere else (maybe involving YouTube 18.23.35 class `hte`)
|
|
||||||
*/
|
|
||||||
@Deprecated("Fingerprint is obsolete and will be deleted soon")
|
|
||||||
internal object StoryboardThumbnailParentFingerprint : MethodFingerprint(
|
|
||||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
|
||||||
returnType = "Landroid/graphics/Bitmap;",
|
|
||||||
strings = listOf("Storyboard regionDecoder.decodeRegion exception - "),
|
|
||||||
)
|
|
|
@ -32,12 +32,11 @@ object GmsCoreSupportPatch : BaseGmsCoreSupportPatch(
|
||||||
CompatiblePackage(
|
CompatiblePackage(
|
||||||
"com.google.android.youtube",
|
"com.google.android.youtube",
|
||||||
setOf(
|
setOf(
|
||||||
// Patch supports these versions but ClientSpoof does not.
|
"18.37.36",
|
||||||
// "18.37.36",
|
"18.38.44",
|
||||||
// "18.38.44",
|
"18.43.45",
|
||||||
// "18.43.45",
|
"18.44.41",
|
||||||
// "18.44.41",
|
"18.45.43",
|
||||||
// "18.45.43",
|
|
||||||
"18.48.39",
|
"18.48.39",
|
||||||
"18.49.37",
|
"18.49.37",
|
||||||
"19.01.34",
|
"19.01.34",
|
||||||
|
|
|
@ -1,5 +1,17 @@
|
||||||
<resources>
|
<resources>
|
||||||
<app id="youtube">
|
<app id="youtube">
|
||||||
|
<patch id="misc.fix.playback.SpoofVideoStreamsPatch">
|
||||||
|
<string-array name="revanced_spoof_video_streams_client_type_entries">
|
||||||
|
<!-- Device and operating systems names are not translatable, so no need to use strings.xml -->
|
||||||
|
<item>iOS</item>
|
||||||
|
<item>Android VR</item>
|
||||||
|
</string-array>
|
||||||
|
<string-array name="revanced_spoof_video_streams_client_type_entry_values">
|
||||||
|
<!-- Enum names from Integrations -->
|
||||||
|
<item>IOS</item>
|
||||||
|
<item>ANDROID_VR</item>
|
||||||
|
</string-array>
|
||||||
|
</patch>
|
||||||
<patch id="layout.spoofappversion.SpoofAppVersionPatch">
|
<patch id="layout.spoofappversion.SpoofAppVersionPatch">
|
||||||
<string-array name="revanced_spoof_app_version_target_entries">
|
<string-array name="revanced_spoof_app_version_target_entries">
|
||||||
<item>@string/revanced_spoof_app_version_target_entry_1</item>
|
<item>@string/revanced_spoof_app_version_target_entry_1</item>
|
||||||
|
@ -97,18 +109,6 @@
|
||||||
<item>END</item>
|
<item>END</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
</patch>
|
</patch>
|
||||||
<patch id="misc.fix.playback.SpoofClientPatch">
|
|
||||||
<string-array name="revanced_spoof_client_type_entries">
|
|
||||||
<!-- OS names are the same in all languages, so specify them here and not in Strings.xml -->
|
|
||||||
<item>iOS</item>
|
|
||||||
<item>Android VR</item>
|
|
||||||
</string-array>
|
|
||||||
<string-array name="revanced_spoof_client_type_entry_values">
|
|
||||||
<!-- Enum names from Integrations -->
|
|
||||||
<item>IOS</item>
|
|
||||||
<item>ANDROID_VR</item>
|
|
||||||
</string-array>
|
|
||||||
</patch>
|
|
||||||
<patch id="video.quality.RememberVideoQualityPatch">
|
<patch id="video.quality.RememberVideoQualityPatch">
|
||||||
<string-array name="revanced_video_quality_default_entries">
|
<string-array name="revanced_video_quality_default_entries">
|
||||||
<item>@string/revanced_video_quality_default_entry_1</item>
|
<item>@string/revanced_video_quality_default_entry_1</item>
|
||||||
|
|
|
@ -1148,41 +1148,23 @@ This is because Crowdin requires temporarily flattening this file and removing t
|
||||||
<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">
|
<patch id="misc.fix.playback.SpoofVideoStreamsPatch">
|
||||||
<string name="revanced_spoof_client_screen_title">Spoof client</string>
|
<string name="revanced_spoof_video_streams_screen_title">Spoof video streams</string>
|
||||||
<string name="revanced_spoof_client_screen_summary">Spoof the client to prevent playback issues</string>
|
<string name="revanced_spoof_video_streams_screen_summary">Spoof the client video streams to prevent playback issues</string>
|
||||||
<string name="revanced_spoof_client_title">Spoof client</string>
|
<string name="revanced_spoof_video_streams_title">Spoof video streams</string>
|
||||||
<string name="revanced_spoof_client_summary_on">Client is spoofed</string>
|
<string name="revanced_spoof_video_streams_summary_on">Video streams are spoofed</string>
|
||||||
<string name="revanced_spoof_client_summary_off">Client is not spoofed\n\nVideo playback may not work</string>
|
<string name="revanced_spoof_video_streams_summary_off">Video streams are 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_video_streams_user_dialog_message">Turning off this setting may cause video playback issues.</string>
|
||||||
<string name="revanced_spoof_client_type_title">Spoof client type</string>
|
<string name="revanced_spoof_video_streams_client_type_title">Default client</string>
|
||||||
<string name="revanced_spoof_client_ios_force_avc_title">Force iOS AVC (H.264)</string>
|
<string name="revanced_spoof_video_streams_ios_force_avc_title">Force AVC (H.264)</string>
|
||||||
<string name="revanced_spoof_client_ios_force_avc_summary_on">iOS video codec is AVC</string>
|
<string name="revanced_spoof_video_streams_ios_force_avc_summary_on">Video codec is AVC (H.264)</string>
|
||||||
<string name="revanced_spoof_client_ios_force_avc_summary_off">iOS video codec is AVC, VP9, or AV1</string>
|
<string name="revanced_spoof_video_streams_ios_force_avc_summary_off">Video codec is VP9 or AV1</string>
|
||||||
<string name="revanced_spoof_client_ios_force_avc_user_dialog_message">Enabling this might improve battery life and fix playback stuttering.\n\nAVC has a maximum resolution of 1080p, and video playback will use more internet data than VP9 or AV1.</string>
|
<string name="revanced_spoof_video_streams_ios_force_avc_no_hardware_vp9_summary_on">Your device does not have VP9 hardware decoding, and this setting is always on when Client spoofing is enabled</string>
|
||||||
<string name="revanced_spoof_client_about_android_ios_title">iOS spoofing side effects</string>
|
<string name="revanced_spoof_video_streams_ios_force_avc_user_dialog_message">Enabling this might improve battery life and fix playback stuttering.\n\nAVC has a maximum resolution of 1080p, and video playback will use more internet data than VP9 or AV1.</string>
|
||||||
<string name="revanced_spoof_client_about_android_ios_summary">• HDR is supported only with AV1 codec\n• Watch history does not work with a brand account</string>
|
<string name="revanced_spoof_video_streams_about_ios_title">iOS spoofing side effects</string>
|
||||||
<string name="revanced_spoof_client_about_android_vr_title">Android VR spoofing side effects</string>
|
<string name="revanced_spoof_video_streams_about_ios_summary">• Movies or paid videos may not play\n• Livestreams start from the beginning</string>
|
||||||
<string name="revanced_spoof_client_about_android_vr_summary">• 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</string>
|
<string name="revanced_spoof_video_streams_about_android_vr_title">Android VR spoofing side effects</string>
|
||||||
<string name="revanced_spoof_client_storyboard_timeout">Spoof client thumbnails not available (API timed out)</string>
|
<string name="revanced_spoof_video_streams_about_android_vr_summary">• Audio track menu is missing</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. -->
|
|
||||||
<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_summary">Spoof the app signature to prevent playback issues</string>
|
|
||||||
<string name="revanced_spoof_signature_verification_enabled_title">Spoof app signature</string>
|
|
||||||
<string name="revanced_spoof_signature_verification_enabled_summary_on">App signature spoofed\n\nSide effects include:\n• Enhanced bitrate is not available\n• Videos cannot be downloaded\n• No seekbar thumbnails for paid videos</string>
|
|
||||||
<string name="revanced_spoof_signature_verification_enabled_summary_off">App signature not spoofed\n\nVideo playback may not work</string>
|
|
||||||
<string name="revanced_spoof_signature_verification_enabled_user_dialog_message">Turning off this setting will cause video playback issues.</string>
|
|
||||||
<string name="revanced_spoof_signature_in_feed_enabled_title">Spoof app signature in feed</string>
|
|
||||||
<string name="revanced_spoof_signature_in_feed_enabled_summary_on">App signature spoofed\n\nSide effects include:\n• Feed videos are missing subtitles\n• Automatically played feed videos will show up in your watch history</string>
|
|
||||||
<string name="revanced_spoof_signature_in_feed_enabled_summary_off">App signature not spoofed for feed videos\n\nFeed videos will play for less than 1 minute before encountering playback issues</string>
|
|
||||||
<string name="revanced_spoof_storyboard_title">Spoof storyboard</string>
|
|
||||||
<string name="revanced_spoof_storyboard_summary_on">Storyboard spoofed</string>
|
|
||||||
<string name="revanced_spoof_storyboard_summary_off">Storyboard not spoofed\n\nSide effects include:\n• No ambient mode\n• Seekbar thumbnails are hidden</string>
|
|
||||||
<string name="revanced_spoof_storyboard_timeout">Spoof storyboard temporarily not available (API timed out)</string>
|
|
||||||
<string name="revanced_spoof_storyboard_io_exception">Spoof storyboard temporarily not available: %s</string>
|
|
||||||
</patch>
|
</patch>
|
||||||
<!-- This patch is no longer used and these strings will soon be deleted. -->
|
<!-- This patch is no longer used and these strings will soon be deleted. -->
|
||||||
<patch id="video.hdrbrightness.HDRBrightnessPatch">
|
<patch id="video.hdrbrightness.HDRBrightnessPatch">
|
||||||
|
|
Loading…
Reference in a new issue