fix(YouTube - Spoof client): Improve Android spoofing (#3230)
This commit is contained in:
parent
b72fe87cf9
commit
b688923c7e
|
@ -15,11 +15,13 @@ import app.revanced.patches.all.misc.resources.AddResourcesPatch
|
||||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen
|
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen
|
||||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen.Sorting
|
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen.Sorting
|
||||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||||
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.*
|
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.BuildInitPlaybackRequestFingerprint
|
||||||
|
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.BuildPlayerRequestURIFingerprint
|
||||||
|
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.CreatePlayerRequestBodyFingerprint
|
||||||
|
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.CreatePlayerRequestBodyWithModelFingerprint
|
||||||
|
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.SetPlayerRequestClientTypeFingerprint
|
||||||
import app.revanced.patches.youtube.misc.settings.SettingsPatch
|
import app.revanced.patches.youtube.misc.settings.SettingsPatch
|
||||||
import app.revanced.patches.youtube.video.playerresponse.PlayerResponseMethodHookPatch
|
|
||||||
import app.revanced.util.getReference
|
import app.revanced.util.getReference
|
||||||
import app.revanced.util.indexOfFirstInstruction
|
|
||||||
import app.revanced.util.resultOrThrow
|
import app.revanced.util.resultOrThrow
|
||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
|
@ -35,11 +37,9 @@ import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
|
||||||
name = "Spoof client",
|
name = "Spoof client",
|
||||||
description = "Spoofs the client to allow video playback.",
|
description = "Spoofs the client to allow video playback.",
|
||||||
dependencies = [
|
dependencies = [
|
||||||
PlayerResponseMethodHookPatch::class,
|
|
||||||
SettingsPatch::class,
|
SettingsPatch::class,
|
||||||
AddResourcesPatch::class,
|
AddResourcesPatch::class,
|
||||||
UserAgentClientSpoofPatch::class,
|
UserAgentClientSpoofPatch::class,
|
||||||
PlayerResponseMethodHookPatch::class,
|
|
||||||
],
|
],
|
||||||
compatiblePackages = [
|
compatiblePackages = [
|
||||||
CompatiblePackage(
|
CompatiblePackage(
|
||||||
|
@ -69,19 +69,11 @@ import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
|
||||||
)
|
)
|
||||||
object SpoofClientPatch : BytecodePatch(
|
object SpoofClientPatch : BytecodePatch(
|
||||||
setOf(
|
setOf(
|
||||||
// Client type spoof.
|
|
||||||
BuildInitPlaybackRequestFingerprint,
|
BuildInitPlaybackRequestFingerprint,
|
||||||
BuildPlayerRequestURIFingerprint,
|
BuildPlayerRequestURIFingerprint,
|
||||||
SetPlayerRequestClientTypeFingerprint,
|
SetPlayerRequestClientTypeFingerprint,
|
||||||
CreatePlayerRequestBodyFingerprint,
|
CreatePlayerRequestBodyFingerprint,
|
||||||
CreatePlayerRequestBodyWithModelFingerprint,
|
CreatePlayerRequestBodyWithModelFingerprint,
|
||||||
|
|
||||||
// Storyboard spoof.
|
|
||||||
StoryboardRendererSpecFingerprint,
|
|
||||||
PlayerResponseModelImplRecommendedLevelFingerprint,
|
|
||||||
StoryboardRendererDecoderRecommendedLevelFingerprint,
|
|
||||||
PlayerResponseModelImplGeneralFingerprint,
|
|
||||||
StoryboardRendererDecoderSpecFingerprint,
|
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
private const val INTEGRATIONS_CLASS_DESCRIPTOR =
|
private const val INTEGRATIONS_CLASS_DESCRIPTOR =
|
||||||
|
@ -98,7 +90,7 @@ object SpoofClientPatch : BytecodePatch(
|
||||||
sorting = Sorting.UNSORTED,
|
sorting = Sorting.UNSORTED,
|
||||||
preferences = setOf(
|
preferences = setOf(
|
||||||
SwitchPreference("revanced_spoof_client"),
|
SwitchPreference("revanced_spoof_client"),
|
||||||
SwitchPreference("revanced_spoof_client_use_testsuite"),
|
SwitchPreference("revanced_spoof_client_use_ios"),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -149,11 +141,11 @@ object SpoofClientPatch : BytecodePatch(
|
||||||
SetPlayerRequestClientTypeFingerprint.resultOrThrow().let { result ->
|
SetPlayerRequestClientTypeFingerprint.resultOrThrow().let { result ->
|
||||||
// Field in the player request object that holds the client info object.
|
// Field in the player request object that holds the client info object.
|
||||||
val clientInfoField = result.mutableMethod
|
val clientInfoField = result.mutableMethod
|
||||||
.getInstructions().first { instruction ->
|
.getInstructions().find { instruction ->
|
||||||
// requestMessage.clientInfo = clientInfoBuilder.build();
|
// requestMessage.clientInfo = clientInfoBuilder.build();
|
||||||
instruction.opcode == Opcode.IPUT_OBJECT &&
|
instruction.opcode == Opcode.IPUT_OBJECT &&
|
||||||
instruction.getReference<FieldReference>()?.type == CLIENT_INFO_CLASS_DESCRIPTOR
|
instruction.getReference<FieldReference>()?.type == CLIENT_INFO_CLASS_DESCRIPTOR
|
||||||
}.getReference<FieldReference>() ?: throw PatchException("Could not find clientInfoField")
|
}?.getReference<FieldReference>() ?: throw PatchException("Could not find clientInfoField")
|
||||||
|
|
||||||
// Client info object's client type field.
|
// Client info object's client type field.
|
||||||
val clientInfoClientTypeField = result.mutableMethod
|
val clientInfoClientTypeField = result.mutableMethod
|
||||||
|
@ -168,20 +160,17 @@ object SpoofClientPatch : BytecodePatch(
|
||||||
Triple(clientInfoField, clientInfoClientTypeField, clientInfoClientVersionField)
|
Triple(clientInfoField, clientInfoClientTypeField, clientInfoClientVersionField)
|
||||||
}
|
}
|
||||||
|
|
||||||
val clientInfoClientModelField = CreatePlayerRequestBodyWithModelFingerprint.resultOrThrow().mutableMethod.let {
|
val clientInfoClientModelField = CreatePlayerRequestBodyWithModelFingerprint.resultOrThrow().let {
|
||||||
val instructions = it.getInstructions()
|
val getClientModelIndex = CreatePlayerRequestBodyWithModelFingerprint.indexOfBuildModelInstruction(it.method)
|
||||||
|
val instructions = it.mutableMethod.getInstructions()
|
||||||
val getClientModelIndex = it.indexOfFirstInstruction {
|
|
||||||
getReference<FieldReference>().toString() == "Landroid/os/Build;->MODEL:Ljava/lang/String;"
|
|
||||||
}
|
|
||||||
|
|
||||||
// The next IPUT_OBJECT instruction after getting the client model is setting the client model field.
|
// The next IPUT_OBJECT instruction after getting the client model is setting the client model field.
|
||||||
instructions.subList(
|
instructions.subList(
|
||||||
getClientModelIndex,
|
getClientModelIndex,
|
||||||
instructions.lastIndex,
|
instructions.size,
|
||||||
).first { instruction ->
|
).find { instruction ->
|
||||||
instruction.opcode == Opcode.IPUT_OBJECT
|
instruction.opcode == Opcode.IPUT_OBJECT
|
||||||
}.getReference<FieldReference>() ?: throw PatchException("Could not find clientInfoClientModelField")
|
}?.getReference<FieldReference>() ?: throw PatchException("Could not find clientInfoClientModelField")
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
@ -243,6 +232,7 @@ object SpoofClientPatch : BytecodePatch(
|
||||||
invoke-static { v1 }, $INTEGRATIONS_CLASS_DESCRIPTOR->getClientVersion(Ljava/lang/String;)Ljava/lang/String;
|
invoke-static { v1 }, $INTEGRATIONS_CLASS_DESCRIPTOR->getClientVersion(Ljava/lang/String;)Ljava/lang/String;
|
||||||
move-result-object v1
|
move-result-object v1
|
||||||
iput-object v1, v0, $clientInfoClientVersionField
|
iput-object v1, v0, $clientInfoClientVersionField
|
||||||
|
|
||||||
:disabled
|
:disabled
|
||||||
return-void
|
return-void
|
||||||
""",
|
""",
|
||||||
|
@ -253,104 +243,5 @@ object SpoofClientPatch : BytecodePatch(
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region Fix storyboard if Android Testsuite is used.
|
|
||||||
|
|
||||||
PlayerResponseMethodHookPatch += PlayerResponseMethodHookPatch.Hook.ProtoBufferParameter(
|
|
||||||
"$INTEGRATIONS_CLASS_DESCRIPTOR->setPlayerResponseVideoId(" +
|
|
||||||
"Ljava/lang/String;Ljava/lang/String;Z)Ljava/lang/String;",
|
|
||||||
)
|
|
||||||
|
|
||||||
// Hook recommended seekbar thumbnails quality level for regular videos.
|
|
||||||
StoryboardRendererDecoderRecommendedLevelFingerprint.resultOrThrow().let {
|
|
||||||
val endIndex = it.scanResult.patternScanResult!!.endIndex
|
|
||||||
|
|
||||||
it.mutableMethod.apply {
|
|
||||||
val originalValueRegister =
|
|
||||||
getInstruction<OneRegisterInstruction>(endIndex).registerA
|
|
||||||
|
|
||||||
addInstructions(
|
|
||||||
endIndex + 1,
|
|
||||||
"""
|
|
||||||
invoke-static { v$originalValueRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->getRecommendedLevel(I)I
|
|
||||||
move-result v$originalValueRegister
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hook the recommended precise seeking thumbnails quality.
|
|
||||||
PlayerResponseModelImplRecommendedLevelFingerprint.resultOrThrow().let {
|
|
||||||
val endIndex = it.scanResult.patternScanResult!!.endIndex
|
|
||||||
|
|
||||||
it.mutableMethod.apply {
|
|
||||||
val originalValueRegister =
|
|
||||||
getInstruction<OneRegisterInstruction>(endIndex).registerA
|
|
||||||
|
|
||||||
addInstructions(
|
|
||||||
endIndex,
|
|
||||||
"""
|
|
||||||
invoke-static { v$originalValueRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->getRecommendedLevel(I)I
|
|
||||||
move-result v$originalValueRegister
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Hook the seekbar recommended level for Shorts to fix Shorts low quality seekbar thumbnails.
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hook StoryBoard renderer url.
|
|
||||||
*/
|
|
||||||
PlayerResponseModelImplGeneralFingerprint.resultOrThrow().let {
|
|
||||||
val getStoryBoardIndex = it.scanResult.patternScanResult!!.endIndex
|
|
||||||
|
|
||||||
it.mutableMethod.apply {
|
|
||||||
val getStoryBoardRegister = getInstruction<OneRegisterInstruction>(getStoryBoardIndex).registerA
|
|
||||||
|
|
||||||
addInstructions(
|
|
||||||
getStoryBoardIndex,
|
|
||||||
"""
|
|
||||||
invoke-static { v$getStoryBoardRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->getStoryboardRendererSpec(Ljava/lang/String;)Ljava/lang/String;
|
|
||||||
move-result-object v$getStoryBoardRegister
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hook the seekbar thumbnail decoder, required for Shorts.
|
|
||||||
StoryboardRendererDecoderSpecFingerprint.resultOrThrow().let {
|
|
||||||
val storyBoardUrlIndex = it.scanResult.patternScanResult!!.startIndex + 1
|
|
||||||
|
|
||||||
it.mutableMethod.apply {
|
|
||||||
val getStoryBoardRegister = getInstruction<OneRegisterInstruction>(storyBoardUrlIndex).registerA
|
|
||||||
|
|
||||||
addInstructions(
|
|
||||||
storyBoardUrlIndex + 1,
|
|
||||||
"""
|
|
||||||
invoke-static { v$getStoryBoardRegister }, ${INTEGRATIONS_CLASS_DESCRIPTOR}->getStoryboardRendererSpec(Ljava/lang/String;)Ljava/lang/String;
|
|
||||||
move-result-object v$getStoryBoardRegister
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StoryboardRendererSpecFingerprint.resultOrThrow().let {
|
|
||||||
it.mutableMethod.apply {
|
|
||||||
val storyBoardUrlParams = "p0"
|
|
||||||
|
|
||||||
addInstructions(
|
|
||||||
0,
|
|
||||||
"""
|
|
||||||
if-nez $storyBoardUrlParams, :ignore
|
|
||||||
invoke-static { $storyBoardUrlParams }, $INTEGRATIONS_CLASS_DESCRIPTOR->getStoryboardRendererSpec(Ljava/lang/String;)Ljava/lang/String;
|
|
||||||
move-result-object $storyBoardUrlParams
|
|
||||||
:ignore
|
|
||||||
nop
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,12 @@ 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 app.revanced.patches.youtube.misc.fix.playback.fingerprints.CreatePlayerRequestBodyWithModelFingerprint.indexOfBuildModelInstruction
|
||||||
|
import app.revanced.util.containsWideLiteralInstructionValue
|
||||||
import app.revanced.util.getReference
|
import app.revanced.util.getReference
|
||||||
|
import app.revanced.util.indexOfFirstInstruction
|
||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
|
import com.android.tools.smali.dexlib2.iface.Method
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||||
|
|
||||||
internal object CreatePlayerRequestBodyWithModelFingerprint : MethodFingerprint(
|
internal object CreatePlayerRequestBodyWithModelFingerprint : MethodFingerprint(
|
||||||
|
@ -11,8 +15,17 @@ internal object CreatePlayerRequestBodyWithModelFingerprint : MethodFingerprint(
|
||||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||||
parameters = listOf(),
|
parameters = listOf(),
|
||||||
customFingerprint = { methodDef, _ ->
|
customFingerprint = { methodDef, _ ->
|
||||||
methodDef.implementation!!.instructions.any {
|
methodDef.containsWideLiteralInstructionValue(1073741824) &&
|
||||||
it.getReference<FieldReference>().toString() == "Landroid/os/Build;->MODEL:Ljava/lang/String;"
|
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;"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import app.revanced.util.containsWideLiteralInstructionValue
|
||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
|
|
||||||
|
@Deprecated("Fingerprint is obsolete and will be deleted soon")
|
||||||
internal object PlayerResponseModelImplGeneralFingerprint : MethodFingerprint(
|
internal object PlayerResponseModelImplGeneralFingerprint : MethodFingerprint(
|
||||||
returnType = "Ljava/lang/String;",
|
returnType = "Ljava/lang/String;",
|
||||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||||
|
|
|
@ -6,6 +6,7 @@ import app.revanced.util.containsWideLiteralInstructionValue
|
||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
|
|
||||||
|
@Deprecated("Fingerprint is obsolete and will be deleted soon")
|
||||||
internal object PlayerResponseModelImplRecommendedLevelFingerprint : MethodFingerprint(
|
internal object PlayerResponseModelImplRecommendedLevelFingerprint : MethodFingerprint(
|
||||||
returnType = "I",
|
returnType = "I",
|
||||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
|
package app.revanced.patches.youtube.misc.fix.playback.fingerprints
|
||||||
|
|
||||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
import app.revanced.util.patch.LiteralValueFingerprint
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
|
|
||||||
internal object SetPlayerRequestClientTypeFingerprint : MethodFingerprint(
|
internal object SetPlayerRequestClientTypeFingerprint : LiteralValueFingerprint(
|
||||||
strings = listOf("10.29"),
|
|
||||||
opcodes = listOf(
|
opcodes = listOf(
|
||||||
Opcode.IGET,
|
Opcode.IGET,
|
||||||
Opcode.IPUT, // Sets ClientInfo.clientId.
|
Opcode.IPUT, // Sets ClientInfo.clientId.
|
||||||
),
|
),
|
||||||
|
strings = listOf("10.29"),
|
||||||
|
literalSupplier = { 134217728 }
|
||||||
)
|
)
|
||||||
|
|
|
@ -8,6 +8,7 @@ import com.android.tools.smali.dexlib2.Opcode
|
||||||
/**
|
/**
|
||||||
* Resolves to the same method as [StoryboardRendererDecoderSpecFingerprint].
|
* Resolves to the same method as [StoryboardRendererDecoderSpecFingerprint].
|
||||||
*/
|
*/
|
||||||
|
@Deprecated("Fingerprint is obsolete and will be deleted soon")
|
||||||
internal object StoryboardRendererDecoderRecommendedLevelFingerprint : MethodFingerprint(
|
internal object StoryboardRendererDecoderRecommendedLevelFingerprint : MethodFingerprint(
|
||||||
returnType = "V",
|
returnType = "V",
|
||||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||||
|
|
|
@ -8,6 +8,7 @@ import com.android.tools.smali.dexlib2.Opcode
|
||||||
/**
|
/**
|
||||||
* Resolves to the same method as [StoryboardRendererDecoderRecommendedLevelFingerprint].
|
* Resolves to the same method as [StoryboardRendererDecoderRecommendedLevelFingerprint].
|
||||||
*/
|
*/
|
||||||
|
@Deprecated("Fingerprint is obsolete and will be deleted soon")
|
||||||
internal object StoryboardRendererDecoderSpecFingerprint : MethodFingerprint(
|
internal object StoryboardRendererDecoderSpecFingerprint : MethodFingerprint(
|
||||||
returnType = "V",
|
returnType = "V",
|
||||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||||
|
|
|
@ -4,6 +4,7 @@ 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
|
||||||
|
|
||||||
|
@Deprecated("Fingerprint is obsolete and will be deleted soon")
|
||||||
internal object StoryboardRendererSpecFingerprint : MethodFingerprint(
|
internal object StoryboardRendererSpecFingerprint : MethodFingerprint(
|
||||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
|
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
|
||||||
returnType = "L",
|
returnType = "L",
|
||||||
|
|
|
@ -8,6 +8,7 @@ import com.android.tools.smali.dexlib2.Opcode
|
||||||
/**
|
/**
|
||||||
* Resolves using the class found in [StoryboardThumbnailParentFingerprint].
|
* Resolves using the class found in [StoryboardThumbnailParentFingerprint].
|
||||||
*/
|
*/
|
||||||
|
@Deprecated("Fingerprint is obsolete and will be deleted soon")
|
||||||
internal object StoryboardThumbnailFingerprint : MethodFingerprint(
|
internal object StoryboardThumbnailFingerprint : MethodFingerprint(
|
||||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||||
returnType = "Z",
|
returnType = "Z",
|
||||||
|
|
|
@ -1092,10 +1092,9 @@
|
||||||
<string name="revanced_spoof_client_summary_on">Client is spoofed</string>
|
<string name="revanced_spoof_client_summary_on">Client is spoofed</string>
|
||||||
<string name="revanced_spoof_client_summary_off">Client is not spoofed\n\nVideo playback may not work</string>
|
<string name="revanced_spoof_client_summary_off">Client is not spoofed\n\nVideo playback may not work</string>
|
||||||
<string name="revanced_spoof_client_user_dialog_message">Turning off this setting may cause video playback issues.</string>
|
<string name="revanced_spoof_client_user_dialog_message">Turning off this setting may cause video playback issues.</string>
|
||||||
<string name="revanced_spoof_client_use_testsuite_title">Spoof client to Android Testsuite</string>
|
<string name="revanced_spoof_client_use_ios_title">Spoof client to iOS</string>
|
||||||
<string name="revanced_spoof_client_use_testsuite_summary">Spoof the client to Android Testsuite</string>
|
<string name="revanced_spoof_client_use_ios_summary_on">Client is currently spoofed to iOS\n\nSide effects include:\n• No HDR video\n• Speed menu is missing\n• Watch history may not work\n• Live streams cannot play as audio only\n• Live streams not available on older devices</string>
|
||||||
<string name="revanced_spoof_client_use_testsuite_summary_on">Client is spoofed to an Android Testsuite client (iOS client is used for live streams)\n\nSide effects include, but are not limited to:\n• Speed flyout menu is missing\n• Captions are missing\n• Player swipe gestures may not work\n• Low quality Shorts seekbar thumbnails\n• Watch history may not work</string>
|
<string name="revanced_spoof_client_use_ios_summary_off">Client is currently spoofed to Android VR\n\nSide effects include:\n• No HDR video\n• Player swipe gestures do not work\n• Kids videos do not playback\n• Paused videos can randomly resume</string>
|
||||||
<string name="revanced_spoof_client_use_testsuite_summary_off">Client is spoofed to an iOS client\n\nSide effects include:\n• No HDR video\n• Speed flyout menu is missing</string>
|
|
||||||
<string name="revanced_spoof_client_storyboard_timeout">Spoof client thumbnails not available (API timed out)</string>
|
<string name="revanced_spoof_client_storyboard_timeout">Spoof client thumbnails not available (API timed out)</string>
|
||||||
<string name="revanced_spoof_client_storyboard_io_exception">Spoof client thumbnails temporarily not available: %s</string>
|
<string name="revanced_spoof_client_storyboard_io_exception">Spoof client thumbnails temporarily not available: %s</string>
|
||||||
</patch>
|
</patch>
|
||||||
|
|
Loading…
Reference in a new issue