mirror of
https://github.com/inotia00/revanced-patches.git
synced 2025-06-12 13:17:46 +02:00
feat(YouTube): remove Spoof client
patch
This commit is contained in:
@ -276,7 +276,6 @@ object VisualPreferencesIconsPatch : BaseResourcePatch(
|
||||
"revanced_preference_screen_seekbar",
|
||||
"revanced_preference_screen_settings_menu",
|
||||
"revanced_preference_screen_shorts_player",
|
||||
"revanced_preference_screen_spoof_client",
|
||||
"revanced_preference_screen_toolbar",
|
||||
"revanced_preference_screen_video_description",
|
||||
"revanced_preference_screen_video_filter",
|
||||
|
@ -1,496 +0,0 @@
|
||||
package app.revanced.patches.youtube.utils.fix.client
|
||||
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.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.PatchException
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||
import app.revanced.patcher.util.smali.ExternalLabel
|
||||
import app.revanced.patches.shared.fingerprints.CreatePlayerRequestBodyWithModelFingerprint
|
||||
import app.revanced.patches.shared.fingerprints.CreatePlayerRequestBodyWithModelFingerprint.indexOfModelInstruction
|
||||
import app.revanced.patches.youtube.misc.backgroundplayback.BackgroundPlaybackPatch
|
||||
import app.revanced.patches.youtube.utils.compatibility.Constants
|
||||
import app.revanced.patches.youtube.utils.fingerprints.PlaybackRateBottomSheetBuilderFingerprint
|
||||
import app.revanced.patches.youtube.utils.fix.client.fingerprints.BuildInitPlaybackRequestFingerprint
|
||||
import app.revanced.patches.youtube.utils.fix.client.fingerprints.BuildPlaybackStatsRequestURIFingerprint
|
||||
import app.revanced.patches.youtube.utils.fix.client.fingerprints.BuildPlayerRequestURIFingerprint
|
||||
import app.revanced.patches.youtube.utils.fix.client.fingerprints.CreatePlaybackSpeedMenuItemFingerprint
|
||||
import app.revanced.patches.youtube.utils.fix.client.fingerprints.CreatePlayerRequestBodyFingerprint
|
||||
import app.revanced.patches.youtube.utils.fix.client.fingerprints.CreatePlayerRequestBodyWithVersionReleaseFingerprint
|
||||
import app.revanced.patches.youtube.utils.fix.client.fingerprints.CreatePlayerRequestBodyWithVersionReleaseFingerprint.indexOfBuildInstruction
|
||||
import app.revanced.patches.youtube.utils.fix.client.fingerprints.NerdsStatsVideoFormatBuilderFingerprint
|
||||
import app.revanced.patches.youtube.utils.fix.client.fingerprints.OrganicPlaybackContextModelFingerprint
|
||||
import app.revanced.patches.youtube.utils.fix.client.fingerprints.PlayerGestureConfigSyntheticFingerprint
|
||||
import app.revanced.patches.youtube.utils.fix.client.fingerprints.PlayerResponseModelBackgroundAudioPlaybackFingerprint
|
||||
import app.revanced.patches.youtube.utils.fix.client.fingerprints.SetPlayerRequestClientTypeFingerprint
|
||||
import app.revanced.patches.youtube.utils.fix.client.fingerprints.UserAgentHeaderBuilderFingerprint
|
||||
import app.revanced.patches.youtube.utils.integrations.Constants.MISC_PATH
|
||||
import app.revanced.patches.youtube.utils.integrations.Constants.PATCH_STATUS_CLASS_DESCRIPTOR
|
||||
import app.revanced.patches.youtube.utils.playertype.PlayerTypeHookPatch
|
||||
import app.revanced.patches.youtube.utils.settings.SettingsPatch
|
||||
import app.revanced.patches.youtube.utils.storyboard.StoryboardHookPatch
|
||||
import app.revanced.patches.youtube.utils.trackingurlhook.TrackingUrlHookPatch
|
||||
import app.revanced.patches.youtube.video.information.VideoInformationPatch
|
||||
import app.revanced.patches.youtube.video.playerresponse.PlayerResponseMethodHookPatch
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.getStringInstructionIndex
|
||||
import app.revanced.util.getWalkerMethod
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import app.revanced.util.patch.BaseBytecodePatch
|
||||
import app.revanced.util.resultOrThrow
|
||||
import app.revanced.util.updatePatchStatus
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
|
||||
|
||||
object SpoofClientPatch : BaseBytecodePatch(
|
||||
name = "Spoof client",
|
||||
description = "Adds options to spoof the client to allow video playback.",
|
||||
dependencies = setOf(
|
||||
// Required since iOS livestream fix partially enables background playback.
|
||||
BackgroundPlaybackPatch::class,
|
||||
PlayerTypeHookPatch::class,
|
||||
|
||||
TrackingUrlHookPatch::class,
|
||||
PlayerResponseMethodHookPatch::class,
|
||||
SettingsPatch::class,
|
||||
VideoInformationPatch::class,
|
||||
SpoofUserAgentPatch::class,
|
||||
StoryboardHookPatch::class,
|
||||
),
|
||||
compatiblePackages = Constants.COMPATIBLE_PACKAGE,
|
||||
fingerprints = setOf(
|
||||
// Client type spoof.
|
||||
BuildInitPlaybackRequestFingerprint,
|
||||
BuildPlayerRequestURIFingerprint,
|
||||
SetPlayerRequestClientTypeFingerprint,
|
||||
CreatePlayerRequestBodyFingerprint,
|
||||
CreatePlayerRequestBodyWithModelFingerprint,
|
||||
CreatePlayerRequestBodyWithVersionReleaseFingerprint,
|
||||
UserAgentHeaderBuilderFingerprint,
|
||||
|
||||
// Player gesture config.
|
||||
PlayerGestureConfigSyntheticFingerprint,
|
||||
|
||||
// Player speed menu item.
|
||||
CreatePlaybackSpeedMenuItemFingerprint,
|
||||
PlaybackRateBottomSheetBuilderFingerprint,
|
||||
|
||||
// Livestream audio only background playback.
|
||||
PlayerResponseModelBackgroundAudioPlaybackFingerprint,
|
||||
|
||||
// Watch history.
|
||||
BuildPlaybackStatsRequestURIFingerprint,
|
||||
OrganicPlaybackContextModelFingerprint,
|
||||
|
||||
// Nerds stats video format.
|
||||
NerdsStatsVideoFormatBuilderFingerprint,
|
||||
)
|
||||
) {
|
||||
private const val INTEGRATIONS_CLASS_DESCRIPTOR =
|
||||
"$MISC_PATH/SpoofClientPatch;"
|
||||
private const val CLIENT_INFO_CLASS_DESCRIPTOR =
|
||||
"Lcom/google/protos/youtube/api/innertube/InnertubeContext\$ClientInfo;"
|
||||
|
||||
override fun execute(context: BytecodeContext) {
|
||||
|
||||
var settingArray = arrayOf(
|
||||
"SETTINGS: SPOOF_CLIENT"
|
||||
)
|
||||
|
||||
// region Block /initplayback requests to fall back to /get_watch requests.
|
||||
|
||||
BuildInitPlaybackRequestFingerprint.resultOrThrow().let {
|
||||
it.mutableMethod.apply {
|
||||
val moveUriStringIndex = it.scanResult.patternScanResult!!.startIndex
|
||||
val targetRegister =
|
||||
getInstruction<OneRegisterInstruction>(moveUriStringIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
moveUriStringIndex + 1,
|
||||
"""
|
||||
invoke-static { v$targetRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->blockInitPlaybackRequest(Ljava/lang/String;)Ljava/lang/String;
|
||||
move-result-object v$targetRegister
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Block /get_watch requests to fall back to /player requests.
|
||||
|
||||
BuildPlayerRequestURIFingerprint.resultOrThrow().let {
|
||||
it.mutableMethod.apply {
|
||||
val invokeToStringIndex = it.scanResult.patternScanResult!!.startIndex
|
||||
val uriRegister =
|
||||
getInstruction<FiveRegisterInstruction>(invokeToStringIndex).registerC
|
||||
|
||||
addInstructions(
|
||||
invokeToStringIndex,
|
||||
"""
|
||||
invoke-static { v$uriRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->blockGetWatchRequest(Landroid/net/Uri;)Landroid/net/Uri;
|
||||
move-result-object v$uriRegister
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Get field references to be used below.
|
||||
|
||||
val (clientInfoField, clientInfoClientTypeField, clientInfoClientVersionField) =
|
||||
SetPlayerRequestClientTypeFingerprint.resultOrThrow().let { result ->
|
||||
with(result.mutableMethod) {
|
||||
// Field in the player request object that holds the client info object.
|
||||
val clientInfoField = getInstructions().find { instruction ->
|
||||
// requestMessage.clientInfo = clientInfoBuilder.build();
|
||||
instruction.opcode == Opcode.IPUT_OBJECT &&
|
||||
instruction.getReference<FieldReference>()?.type == CLIENT_INFO_CLASS_DESCRIPTOR
|
||||
}?.getReference<FieldReference>()
|
||||
?: throw PatchException("Could not find clientInfoField")
|
||||
|
||||
// Client info object's client type field.
|
||||
val clientInfoClientTypeField =
|
||||
getInstruction(result.scanResult.patternScanResult!!.endIndex)
|
||||
.getReference<FieldReference>()
|
||||
?: throw PatchException("Could not find clientInfoClientTypeField")
|
||||
|
||||
val clientInfoVersionIndex = getStringInstructionIndex("10.29")
|
||||
val clientInfoVersionRegister =
|
||||
getInstruction<OneRegisterInstruction>(clientInfoVersionIndex).registerA
|
||||
val clientInfoClientVersionFieldIndex = implementation!!.instructions.let {
|
||||
clientInfoVersionIndex + it.subList(clientInfoVersionIndex, it.size - 1)
|
||||
.indexOfFirst { instruction ->
|
||||
instruction.opcode == Opcode.IPUT_OBJECT
|
||||
&& (instruction as TwoRegisterInstruction).registerA == clientInfoVersionRegister
|
||||
}
|
||||
}
|
||||
|
||||
// Client info object's client version field.
|
||||
val clientInfoClientVersionField =
|
||||
getInstruction(clientInfoClientVersionFieldIndex)
|
||||
.getReference<FieldReference>()
|
||||
?: throw PatchException("Could not find clientInfoClientVersionField")
|
||||
|
||||
Triple(clientInfoField, clientInfoClientTypeField, clientInfoClientVersionField)
|
||||
}
|
||||
}
|
||||
|
||||
val clientInfoClientModelField =
|
||||
CreatePlayerRequestBodyWithModelFingerprint.resultOrThrow().mutableMethod.let {
|
||||
val instructions = it.getInstructions()
|
||||
val getClientModelIndex = indexOfModelInstruction(it)
|
||||
|
||||
// The next IPUT_OBJECT instruction after getting the client model is setting the client model field.
|
||||
instructions.subList(
|
||||
getClientModelIndex,
|
||||
instructions.size,
|
||||
).find { instruction ->
|
||||
val reference = instruction.getReference<FieldReference>()
|
||||
instruction.opcode == Opcode.IPUT_OBJECT
|
||||
&& reference?.definingClass == CLIENT_INFO_CLASS_DESCRIPTOR
|
||||
&& reference.type == "Ljava/lang/String;"
|
||||
}?.getReference<FieldReference>()
|
||||
?: throw PatchException("Could not find clientInfoClientModelField")
|
||||
}
|
||||
|
||||
val clientInfoOsVersionField =
|
||||
CreatePlayerRequestBodyWithVersionReleaseFingerprint.resultOrThrow().mutableMethod.let {
|
||||
val buildIndex = indexOfBuildInstruction(it)
|
||||
val instructions = it.getInstructions()
|
||||
|
||||
instructions.subList(
|
||||
buildIndex - 5,
|
||||
buildIndex,
|
||||
).find { instruction ->
|
||||
val reference = instruction.getReference<FieldReference>()
|
||||
instruction.opcode == Opcode.IPUT_OBJECT
|
||||
&& reference?.definingClass == CLIENT_INFO_CLASS_DESCRIPTOR
|
||||
&& reference.type == "Ljava/lang/String;"
|
||||
}?.getReference<FieldReference>()
|
||||
?: throw PatchException("Could not find clientInfoOsVersionField")
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Spoof client type for /player requests.
|
||||
|
||||
CreatePlayerRequestBodyFingerprint.resultOrThrow().let {
|
||||
it.mutableMethod.apply {
|
||||
val setClientInfoMethodName = "setClientInfo"
|
||||
val checkCastIndex = it.scanResult.patternScanResult!!.startIndex
|
||||
|
||||
val checkCastInstruction = getInstruction<OneRegisterInstruction>(checkCastIndex)
|
||||
val requestMessageInstanceRegister = checkCastInstruction.registerA
|
||||
val clientInfoContainerClassName =
|
||||
checkCastInstruction.getReference<TypeReference>()!!.type
|
||||
|
||||
addInstruction(
|
||||
checkCastIndex + 1,
|
||||
"invoke-static { v$requestMessageInstanceRegister }," +
|
||||
" $definingClass->$setClientInfoMethodName($clientInfoContainerClassName)V",
|
||||
)
|
||||
|
||||
// Change client info to use the spoofed values.
|
||||
// Do this in a helper method, to remove the need of picking out multiple free registers from the hooked code.
|
||||
it.mutableClass.methods.add(
|
||||
ImmutableMethod(
|
||||
definingClass,
|
||||
setClientInfoMethodName,
|
||||
listOf(
|
||||
ImmutableMethodParameter(
|
||||
clientInfoContainerClassName,
|
||||
annotations,
|
||||
"clientInfoContainer"
|
||||
)
|
||||
),
|
||||
"V",
|
||||
AccessFlags.PRIVATE or AccessFlags.STATIC,
|
||||
annotations,
|
||||
null,
|
||||
MutableMethodImplementation(3),
|
||||
).toMutable().apply {
|
||||
addInstructions(
|
||||
"""
|
||||
invoke-static { }, $INTEGRATIONS_CLASS_DESCRIPTOR->isClientSpoofingEnabled()Z
|
||||
move-result v0
|
||||
if-eqz v0, :disabled
|
||||
|
||||
iget-object v0, p0, $clientInfoField
|
||||
|
||||
# Set client type to the spoofed value.
|
||||
iget v1, v0, $clientInfoClientTypeField
|
||||
invoke-static { v1 }, $INTEGRATIONS_CLASS_DESCRIPTOR->getClientTypeId(I)I
|
||||
move-result v1
|
||||
iput v1, v0, $clientInfoClientTypeField
|
||||
|
||||
# Set client model to the spoofed value.
|
||||
iget-object v1, v0, $clientInfoClientModelField
|
||||
invoke-static { v1 }, $INTEGRATIONS_CLASS_DESCRIPTOR->getClientModel(Ljava/lang/String;)Ljava/lang/String;
|
||||
move-result-object v1
|
||||
iput-object v1, v0, $clientInfoClientModelField
|
||||
|
||||
# Set client version to the spoofed value.
|
||||
iget-object v1, v0, $clientInfoClientVersionField
|
||||
invoke-static { v1 }, $INTEGRATIONS_CLASS_DESCRIPTOR->getClientVersion(Ljava/lang/String;)Ljava/lang/String;
|
||||
move-result-object v1
|
||||
iput-object v1, v0, $clientInfoClientVersionField
|
||||
|
||||
# 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 Spoof user-agent
|
||||
|
||||
UserAgentHeaderBuilderFingerprint.resultOrThrow().mutableMethod.apply {
|
||||
val insertIndex = implementation!!.instructions.lastIndex
|
||||
val insertRegister = getInstruction<OneRegisterInstruction>(insertIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
insertIndex, """
|
||||
invoke-static { v$insertRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->getUserAgent(Ljava/lang/String;)Ljava/lang/String;
|
||||
move-result-object v$insertRegister
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region check whether video is Shorts or Clips.
|
||||
|
||||
PlayerResponseMethodHookPatch += PlayerResponseMethodHookPatch.Hook.PlayerParameter(
|
||||
"$INTEGRATIONS_CLASS_DESCRIPTOR->setPlayerResponseVideoId(" +
|
||||
"Ljava/lang/String;Ljava/lang/String;Z)Ljava/lang/String;",
|
||||
)
|
||||
|
||||
// endregion
|
||||
|
||||
// region fix player gesture.
|
||||
|
||||
PlayerGestureConfigSyntheticFingerprint.resultOrThrow().let {
|
||||
val endIndex = it.scanResult.patternScanResult!!.endIndex
|
||||
val downAndOutLandscapeAllowedIndex = endIndex - 3
|
||||
val downAndOutPortraitAllowedIndex = endIndex - 9
|
||||
|
||||
arrayOf(
|
||||
downAndOutLandscapeAllowedIndex,
|
||||
downAndOutPortraitAllowedIndex,
|
||||
).forEach { index ->
|
||||
val gestureAllowedMethod = it.getWalkerMethod(context, index)
|
||||
|
||||
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 playback speed menu item.
|
||||
|
||||
// fix for iOS, Android Testsuite
|
||||
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
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// fix for Android TV
|
||||
PlaybackRateBottomSheetBuilderFingerprint.resultOrThrow().let {
|
||||
it.mutableMethod.apply {
|
||||
val targetIndex = it.scanResult.patternScanResult!!.endIndex
|
||||
val targetRegister = getInstruction<OneRegisterInstruction>(targetIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
targetIndex,
|
||||
"""
|
||||
invoke-static { v$targetRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->forceCreatePlaybackSpeedMenuReversed(Z)Z
|
||||
move-result v$targetRegister
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region fix background playback in live stream, if spoofing to iOS
|
||||
// This force enables audio background playback.
|
||||
PlayerResponseModelBackgroundAudioPlaybackFingerprint.resultOrThrow().mutableMethod.apply {
|
||||
addInstructionsWithLabels(
|
||||
0, """
|
||||
invoke-static { }, $INTEGRATIONS_CLASS_DESCRIPTOR->overrideBackgroundAudioPlayback()Z
|
||||
move-result v0
|
||||
if-eqz v0, :disabled
|
||||
return v0
|
||||
""", ExternalLabel("disabled", getInstruction(0))
|
||||
)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region watch history if spoofing to iOS
|
||||
|
||||
if (!SettingsPatch.upward1839) {
|
||||
BuildPlaybackStatsRequestURIFingerprint.resultOrThrow().let {
|
||||
val walkerMethod =
|
||||
it.getWalkerMethod(context, it.scanResult.patternScanResult!!.startIndex)
|
||||
|
||||
walkerMethod.addInstructions(
|
||||
0, """
|
||||
invoke-static {p0}, $INTEGRATIONS_CLASS_DESCRIPTOR->overrideTrackingUrl(Landroid/net/Uri;)Landroid/net/Uri;
|
||||
move-result-object p0
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
OrganicPlaybackContextModelFingerprint.resultOrThrow().let {
|
||||
it.mutableMethod.apply {
|
||||
val insertIndex = it.scanResult.patternScanResult!!.endIndex
|
||||
val insertRegister =
|
||||
getInstruction<TwoRegisterInstruction>(insertIndex).registerA
|
||||
|
||||
addInstruction(
|
||||
insertIndex,
|
||||
"invoke-static { v$insertRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->setCpn(Ljava/lang/String;)V"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
TrackingUrlHookPatch.hookTrackingUrl("$INTEGRATIONS_CLASS_DESCRIPTOR->setTrackingUriParameter(Landroid/net/Uri;)V")
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region append spoof info.
|
||||
|
||||
NerdsStatsVideoFormatBuilderFingerprint.resultOrThrow().mutableMethod.apply {
|
||||
for (index in implementation!!.instructions.size - 1 downTo 0) {
|
||||
val instruction = getInstruction(index)
|
||||
if (instruction.opcode != Opcode.RETURN_OBJECT)
|
||||
continue
|
||||
|
||||
val register = (instruction as OneRegisterInstruction).registerA
|
||||
|
||||
addInstructions(
|
||||
index, """
|
||||
invoke-static {v$register}, $INTEGRATIONS_CLASS_DESCRIPTOR->appendSpoofedClient(Ljava/lang/String;)Ljava/lang/String;
|
||||
move-result-object v$register
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region hook storyboard.
|
||||
|
||||
StoryboardHookPatch.hook(INTEGRATIONS_CLASS_DESCRIPTOR)
|
||||
|
||||
// endregion
|
||||
|
||||
if (!SettingsPatch.upward1839) {
|
||||
settingArray += "SETTINGS: IOS_CLIENT_SIDE_EFFECT_1838"
|
||||
} else {
|
||||
settingArray += "SETTINGS: IOS_CLIENT_SIDE_EFFECT_1839"
|
||||
|
||||
context.updatePatchStatus(PATCH_STATUS_CLASS_DESCRIPTOR, "SpoofClient")
|
||||
}
|
||||
|
||||
/**
|
||||
* Add settings
|
||||
*/
|
||||
SettingsPatch.addPreference(settingArray)
|
||||
|
||||
SettingsPatch.updatePatchStatus(this)
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package app.revanced.patches.youtube.utils.fix.client
|
||||
|
||||
import app.revanced.patches.shared.spoofuseragent.BaseSpoofUserAgentPatch
|
||||
|
||||
object SpoofUserAgentPatch : BaseSpoofUserAgentPatch("com.google.android.youtube")
|
@ -1,16 +0,0 @@
|
||||
package app.revanced.patches.youtube.utils.fix.client.fingerprints
|
||||
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal object BuildInitPlaybackRequestFingerprint : MethodFingerprint(
|
||||
returnType = "Lorg/chromium/net/UrlRequest\$Builder;",
|
||||
opcodes = listOf(
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.IGET_OBJECT, // Moves the request URI string to a register to build the request with.
|
||||
),
|
||||
strings = listOf(
|
||||
"Content-Type",
|
||||
"Range",
|
||||
),
|
||||
)
|
@ -1,17 +0,0 @@
|
||||
package app.revanced.patches.youtube.utils.fix.client.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 BuildPlaybackStatsRequestURIFingerprint : MethodFingerprint(
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
|
||||
returnType = "L",
|
||||
opcodes = listOf(
|
||||
Opcode.INVOKE_STATIC,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.CONST_STRING,
|
||||
),
|
||||
strings = listOf("event", "streamingstats"),
|
||||
)
|
@ -1,20 +0,0 @@
|
||||
package app.revanced.patches.youtube.utils.fix.client.fingerprints
|
||||
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal object BuildPlayerRequestURIFingerprint : MethodFingerprint(
|
||||
returnType = "Ljava/lang/String;",
|
||||
opcodes = listOf(
|
||||
Opcode.INVOKE_VIRTUAL, // Register holds player request URI.
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.IPUT_OBJECT,
|
||||
Opcode.IGET_OBJECT,
|
||||
Opcode.MONITOR_EXIT,
|
||||
Opcode.RETURN_OBJECT,
|
||||
),
|
||||
strings = listOf(
|
||||
"key",
|
||||
"asig",
|
||||
),
|
||||
)
|
@ -1,34 +0,0 @@
|
||||
package app.revanced.patches.youtube.utils.fix.client.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.utils.fix.client.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,30 +0,0 @@
|
||||
package app.revanced.patches.youtube.utils.fix.client.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import app.revanced.patches.youtube.utils.fix.client.fingerprints.CreatePlayerRequestBodyWithVersionReleaseFingerprint.indexOfBuildInstruction
|
||||
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 CreatePlayerRequestBodyWithVersionReleaseFingerprint : MethodFingerprint(
|
||||
returnType = "V",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
parameters = listOf("L"),
|
||||
strings = listOf("Google Inc."),
|
||||
customFingerprint = { methodDef, _ ->
|
||||
indexOfBuildInstruction(methodDef) >= 0
|
||||
},
|
||||
) {
|
||||
fun indexOfBuildInstruction(methodDef: Method) =
|
||||
methodDef.indexOfFirstInstruction {
|
||||
val reference = getReference<MethodReference>()
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
reference?.name == "build" &&
|
||||
reference.parameterTypes.isEmpty() &&
|
||||
reference.returnType.startsWith("L")
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package app.revanced.patches.youtube.utils.fix.client.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
internal object NerdsStatsVideoFormatBuilderFingerprint : MethodFingerprint(
|
||||
returnType = "Ljava/lang/String;",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
|
||||
parameters = listOf("Lcom/google/android/libraries/youtube/innertube/model/media/FormatStreamModel;"),
|
||||
strings = listOf("codecs=\""),
|
||||
)
|
@ -1,24 +0,0 @@
|
||||
package app.revanced.patches.youtube.utils.fix.client.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 OrganicPlaybackContextModelFingerprint : MethodFingerprint(
|
||||
returnType = "V",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
|
||||
parameters = listOf(
|
||||
"Ljava/lang/String;", // cpn
|
||||
"Z",
|
||||
"Z",
|
||||
"Z",
|
||||
"Z"
|
||||
),
|
||||
opcodes = listOf(
|
||||
Opcode.INVOKE_DIRECT,
|
||||
Opcode.IF_EQZ,
|
||||
Opcode.IPUT_OBJECT,
|
||||
),
|
||||
strings = listOf("Null contentCpn")
|
||||
)
|
@ -1,55 +0,0 @@
|
||||
package app.revanced.patches.youtube.utils.fix.client.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import app.revanced.patcher.fingerprint.annotation.FuzzyPatternScanMethod
|
||||
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
|
||||
|
||||
/**
|
||||
* [FuzzyPatternScanMethod] was added to maintain compatibility for YouTube v18.29.38 to v18.32.39.
|
||||
* TODO: Remove this annotation if support for YouTube v18.29.38 to v18.32.39 is dropped.
|
||||
*/
|
||||
@FuzzyPatternScanMethod(5)
|
||||
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,43 +0,0 @@
|
||||
package app.revanced.patches.youtube.utils.fix.client.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
|
||||
|
||||
/**
|
||||
* On iOS clients, this method always returns false in live streams.
|
||||
*
|
||||
* This fingerprint seems to break easily because there are many [Opcode] patterns, but it is not.
|
||||
* This fingerprint has been tested on all versions from YouTube 17.34.36 to YouTube 19.29.42.
|
||||
*/
|
||||
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.IF_EQZ,
|
||||
Opcode.INVOKE_INTERFACE,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.INVOKE_STATIC,
|
||||
Opcode.MOVE_RESULT,
|
||||
Opcode.IF_EQZ,
|
||||
Opcode.INVOKE_INTERFACE,
|
||||
Opcode.MOVE_RESULT,
|
||||
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,12 +0,0 @@
|
||||
package app.revanced.patches.youtube.utils.fix.client.fingerprints
|
||||
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal object SetPlayerRequestClientTypeFingerprint : MethodFingerprint(
|
||||
strings = listOf("10.29"),
|
||||
opcodes = listOf(
|
||||
Opcode.IGET,
|
||||
Opcode.IPUT, // Sets ClientInfo.clientId.
|
||||
),
|
||||
)
|
@ -1,19 +0,0 @@
|
||||
package app.revanced.patches.youtube.utils.fix.client.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
|
||||
|
||||
/**
|
||||
* This is the fingerprint used in the 'client-spoof' patch around 2022.
|
||||
* (Integrated into 'UserAgentClientSpoofPatch' now.)
|
||||
*
|
||||
* This method is modified by 'UserAgentClientSpoofPatch', so the fingerprint does not check the [Opcode].
|
||||
*/
|
||||
internal object UserAgentHeaderBuilderFingerprint : MethodFingerprint(
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
|
||||
returnType = "Ljava/lang/String;",
|
||||
parameters = listOf("Landroid/content/Context;", "Ljava/lang/String;", "Ljava/lang/String;"),
|
||||
strings = listOf("(Linux; U; Android "),
|
||||
)
|
@ -1,53 +0,0 @@
|
||||
package app.revanced.patches.youtube.utils.fix.parameter
|
||||
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
|
||||
import app.revanced.patches.youtube.utils.integrations.Constants.MISC_PATH
|
||||
import app.revanced.patches.youtube.utils.playertype.PlayerTypeHookPatch
|
||||
import app.revanced.patches.youtube.utils.settings.SettingsPatch
|
||||
import app.revanced.patches.youtube.utils.storyboard.StoryboardHookPatch
|
||||
import app.revanced.patches.youtube.video.information.VideoInformationPatch
|
||||
import app.revanced.patches.youtube.video.playerresponse.PlayerResponseMethodHookPatch
|
||||
import app.revanced.util.patch.BaseBytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
@Deprecated("This patch will be removed in the future.")
|
||||
object SpoofPlayerParameterPatch : BaseBytecodePatch(
|
||||
// name = "Spoof player parameters",
|
||||
description = "Adds options to spoof player parameters to prevent playback issues.",
|
||||
dependencies = setOf(
|
||||
PlayerTypeHookPatch::class,
|
||||
PlayerResponseMethodHookPatch::class,
|
||||
SettingsPatch::class,
|
||||
VideoInformationPatch::class,
|
||||
StoryboardHookPatch::class,
|
||||
),
|
||||
compatiblePackages = COMPATIBLE_PACKAGE,
|
||||
use = false
|
||||
) {
|
||||
private const val INTEGRATIONS_CLASS_DESCRIPTOR =
|
||||
"$MISC_PATH/SpoofPlayerParameterPatch;"
|
||||
|
||||
override fun execute(context: BytecodeContext) {
|
||||
|
||||
// Hook the player parameters.
|
||||
PlayerResponseMethodHookPatch += PlayerResponseMethodHookPatch.Hook.PlayerParameter(
|
||||
"$INTEGRATIONS_CLASS_DESCRIPTOR->spoofParameter(Ljava/lang/String;Ljava/lang/String;Z)Ljava/lang/String;"
|
||||
)
|
||||
|
||||
// Hook storyboard.
|
||||
StoryboardHookPatch.hook(INTEGRATIONS_CLASS_DESCRIPTOR)
|
||||
|
||||
/**
|
||||
* Add settings
|
||||
*/
|
||||
SettingsPatch.addPreference(
|
||||
arrayOf(
|
||||
"PREFERENCE_CATEGORY: MISC_EXPERIMENTAL_FLAGS",
|
||||
"SETTINGS: SPOOF_PLAYER_PARAMETER"
|
||||
)
|
||||
)
|
||||
|
||||
SettingsPatch.updatePatchStatus(this)
|
||||
}
|
||||
}
|
@ -3,8 +3,6 @@ package app.revanced.patches.youtube.utils.gms
|
||||
import app.revanced.patches.shared.gms.BaseGmsCoreSupportPatch
|
||||
import app.revanced.patches.shared.gms.BaseGmsCoreSupportResourcePatch.Companion.ORIGINAL_PACKAGE_NAME_YOUTUBE
|
||||
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
|
||||
import app.revanced.patches.youtube.utils.fix.client.SpoofClientPatch
|
||||
import app.revanced.patches.youtube.utils.fix.client.SpoofUserAgentPatch
|
||||
import app.revanced.patches.youtube.utils.integrations.IntegrationsPatch
|
||||
import app.revanced.patches.youtube.utils.mainactivity.fingerprints.MainActivityFingerprint
|
||||
import app.revanced.patches.youtube.utils.settings.SettingsPatch
|
||||
@ -15,8 +13,6 @@ object GmsCoreSupportPatch : BaseGmsCoreSupportPatch(
|
||||
mainActivityOnCreateFingerprint = MainActivityFingerprint,
|
||||
integrationsPatchDependency = IntegrationsPatch::class,
|
||||
dependencies = setOf(
|
||||
SpoofClientPatch::class,
|
||||
SpoofUserAgentPatch::class,
|
||||
SettingsPatch::class
|
||||
),
|
||||
gmsCoreSupportResourcePatch = GmsCoreSupportResourcePatch,
|
||||
|
@ -1,154 +0,0 @@
|
||||
package app.revanced.patches.youtube.utils.storyboard
|
||||
|
||||
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.removeInstruction
|
||||
import app.revanced.patcher.patch.BytecodePatch
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patcher.util.smali.ExternalLabel
|
||||
import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch
|
||||
import app.revanced.patches.youtube.utils.storyboard.fingerprints.PlayerResponseModelGeneralStoryboardRendererFingerprint
|
||||
import app.revanced.patches.youtube.utils.storyboard.fingerprints.PlayerResponseModelLiveStreamStoryboardRendererFingerprint
|
||||
import app.revanced.patches.youtube.utils.storyboard.fingerprints.PlayerResponseModelStoryboardRecommendedLevelFingerprint
|
||||
import app.revanced.patches.youtube.utils.storyboard.fingerprints.StoryboardRendererDecoderRecommendedLevelFingerprint
|
||||
import app.revanced.patches.youtube.utils.storyboard.fingerprints.StoryboardRendererDecoderSpecFingerprint
|
||||
import app.revanced.patches.youtube.utils.storyboard.fingerprints.StoryboardRendererSpecFingerprint
|
||||
import app.revanced.patches.youtube.utils.storyboard.fingerprints.StoryboardThumbnailFingerprint
|
||||
import app.revanced.patches.youtube.utils.storyboard.fingerprints.StoryboardThumbnailParentFingerprint
|
||||
import app.revanced.util.resultOrThrow
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
@Patch(
|
||||
description = "Force inject the Storyboard by fetching YouTube API.",
|
||||
dependencies = [SharedResourceIdPatch::class],
|
||||
)
|
||||
object StoryboardHookPatch : BytecodePatch(
|
||||
setOf(
|
||||
PlayerResponseModelGeneralStoryboardRendererFingerprint,
|
||||
PlayerResponseModelLiveStreamStoryboardRendererFingerprint,
|
||||
PlayerResponseModelStoryboardRecommendedLevelFingerprint,
|
||||
StoryboardRendererDecoderRecommendedLevelFingerprint,
|
||||
StoryboardRendererDecoderSpecFingerprint,
|
||||
StoryboardRendererSpecFingerprint,
|
||||
StoryboardThumbnailParentFingerprint,
|
||||
)
|
||||
) {
|
||||
private lateinit var context: BytecodeContext
|
||||
|
||||
override fun execute(context: BytecodeContext) {
|
||||
this.context = context
|
||||
}
|
||||
|
||||
internal fun hook(classDescriptor: 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.
|
||||
StoryboardThumbnailFingerprint.resolve(
|
||||
context,
|
||||
StoryboardThumbnailParentFingerprint.resultOrThrow().classDef
|
||||
)
|
||||
StoryboardThumbnailFingerprint.resultOrThrow().let {
|
||||
it.mutableMethod.apply {
|
||||
val targetIndex = it.scanResult.patternScanResult!!.endIndex
|
||||
val targetRegister =
|
||||
getInstruction<OneRegisterInstruction>(targetIndex).registerA
|
||||
|
||||
// Since this is end of the method must replace one line then add the rest.
|
||||
addInstructions(
|
||||
targetIndex + 1,
|
||||
"""
|
||||
invoke-static {}, $classDescriptor->getSeekbarThumbnailOverrideValue()Z
|
||||
move-result v$targetRegister
|
||||
return v$targetRegister
|
||||
"""
|
||||
)
|
||||
removeInstruction(targetIndex)
|
||||
}
|
||||
}
|
||||
|
||||
// Hook storyboard renderer url.
|
||||
arrayOf(
|
||||
PlayerResponseModelGeneralStoryboardRendererFingerprint,
|
||||
PlayerResponseModelLiveStreamStoryboardRendererFingerprint
|
||||
).forEach { fingerprint ->
|
||||
fingerprint.resultOrThrow().let {
|
||||
it.mutableMethod.apply {
|
||||
val getStoryboardIndex = it.scanResult.patternScanResult!!.endIndex
|
||||
val getStoryboardRegister =
|
||||
getInstruction<OneRegisterInstruction>(getStoryboardIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
getStoryboardIndex,
|
||||
"""
|
||||
invoke-static { v$getStoryboardRegister }, $classDescriptor->getStoryboardRendererSpec(Ljava/lang/String;)Ljava/lang/String;
|
||||
move-result-object v$getStoryboardRegister
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hook recommended seekbar thumbnails quality level.
|
||||
StoryboardRendererDecoderRecommendedLevelFingerprint.resultOrThrow().let {
|
||||
it.mutableMethod.apply {
|
||||
val moveOriginalRecommendedValueIndex = it.scanResult.patternScanResult!!.endIndex
|
||||
val originalValueRegister =
|
||||
getInstruction<OneRegisterInstruction>(moveOriginalRecommendedValueIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
moveOriginalRecommendedValueIndex + 1, """
|
||||
invoke-static { v$originalValueRegister }, $classDescriptor->getStoryboardRecommendedLevel(I)I
|
||||
move-result v$originalValueRegister
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Hook the recommended precise seeking thumbnails quality level.
|
||||
PlayerResponseModelStoryboardRecommendedLevelFingerprint.resultOrThrow().let {
|
||||
it.mutableMethod.apply {
|
||||
val moveOriginalRecommendedValueIndex = it.scanResult.patternScanResult!!.endIndex
|
||||
val originalValueRegister =
|
||||
getInstruction<OneRegisterInstruction>(moveOriginalRecommendedValueIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
moveOriginalRecommendedValueIndex, """
|
||||
invoke-static { v$originalValueRegister }, $classDescriptor->getStoryboardRecommendedLevel(I)I
|
||||
move-result v$originalValueRegister
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
StoryboardRendererSpecFingerprint.resultOrThrow().let {
|
||||
it.mutableMethod.apply {
|
||||
val storyBoardUrlParams = 0
|
||||
|
||||
addInstructionsWithLabels(
|
||||
0, """
|
||||
if-nez p$storyBoardUrlParams, :ignore
|
||||
invoke-static { p$storyBoardUrlParams }, $classDescriptor->getStoryboardRendererSpec(Ljava/lang/String;)Ljava/lang/String;
|
||||
move-result-object p$storyBoardUrlParams
|
||||
""", ExternalLabel("ignore", getInstruction(0))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Hook the seekbar thumbnail decoder and use a NULL spec for live streams.
|
||||
StoryboardRendererDecoderSpecFingerprint.resultOrThrow().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 }, $classDescriptor->getStoryboardDecoderRendererSpec(Ljava/lang/String;)Ljava/lang/String;
|
||||
move-result-object v$storyboardUrlRegister
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
package app.revanced.patches.youtube.utils.storyboard.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import app.revanced.util.containsWideLiteralInstructionIndex
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal object PlayerResponseModelGeneralStoryboardRendererFingerprint : 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.containsWideLiteralInstructionIndex(55735497)
|
||||
}
|
||||
)
|
@ -1,24 +0,0 @@
|
||||
package app.revanced.patches.youtube.utils.storyboard.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import app.revanced.util.containsWideLiteralInstructionIndex
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal object PlayerResponseModelLiveStreamStoryboardRendererFingerprint : 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.containsWideLiteralInstructionIndex(70276274)
|
||||
}
|
||||
)
|
@ -1,24 +0,0 @@
|
||||
package app.revanced.patches.youtube.utils.storyboard.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import app.revanced.util.containsWideLiteralInstructionIndex
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal object PlayerResponseModelStoryboardRecommendedLevelFingerprint : 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.containsWideLiteralInstructionIndex(55735497)
|
||||
}
|
||||
)
|
@ -1,23 +0,0 @@
|
||||
package app.revanced.patches.youtube.utils.storyboard.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].
|
||||
*/
|
||||
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,23 +0,0 @@
|
||||
package app.revanced.patches.youtube.utils.storyboard.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].
|
||||
*/
|
||||
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,12 +0,0 @@
|
||||
package app.revanced.patches.youtube.utils.storyboard.fingerprints
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
internal object StoryboardRendererSpecFingerprint : MethodFingerprint(
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
|
||||
returnType = "L",
|
||||
parameters = listOf("Ljava/lang/String;", "J"),
|
||||
strings = listOf("\\|"),
|
||||
)
|
@ -1,23 +0,0 @@
|
||||
package app.revanced.patches.youtube.utils.storyboard.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].
|
||||
*/
|
||||
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,17 +0,0 @@
|
||||
package app.revanced.patches.youtube.utils.storyboard.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`)
|
||||
*/
|
||||
internal object StoryboardThumbnailParentFingerprint : MethodFingerprint(
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
returnType = "Landroid/graphics/Bitmap;",
|
||||
strings = listOf("Storyboard regionDecoder.decodeRegion exception - "),
|
||||
)
|
Reference in New Issue
Block a user