fix(YouTube): Update fingerprints and logic for YouTube 21.34, add custom settings
Release / Release (push) Failing after 16m39s
Release / Release (push) Failing after 16m39s
Reworks fingerprint matching across ads, GMS core, litho filter, player controls, context hook, background playback, end screen cards, miniplayer, and Shorts autoplay patches to keep working on YouTube 21.34, where several underlying methods and resources changed. Also fixes video stream spoofing to build the request URI via Uri.Builder instead of the removed toString() path, and adds a new Custom settings screen with AMOLED/pure black player, ambient mode, Shorts hiding, playback speed/quality defaults, debug video info, custom app name, startup tab, and URL cleaning options. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NXRsEcWGqDhsCbfEdrfCdE
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
7427432759
commit
97b4fc5b5a
+11
@@ -236,6 +236,17 @@ public final class AdsFilter extends Filter {
|
||||
return Settings.HIDE_GENERAL_ADS.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
* 21.34+ the player overlay timely shelf handler no longer gets a dedicated method,
|
||||
* so instead the result of the overlay type `String.equals` comparison is passed in.
|
||||
*
|
||||
* @param original Whether the overlay is the timely shelf (already being shown when not hiding ads).
|
||||
*/
|
||||
public static boolean allowAds(boolean original) {
|
||||
return Settings.HIDE_GENERAL_ADS.get() ? false : original;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
|
||||
@@ -31,14 +31,11 @@ internal val BytecodePatchContext.buildInitPlaybackRequestMethodMatch by composi
|
||||
}
|
||||
|
||||
internal val BytecodePatchContext.buildPlayerRequestURIMethodMatch by composingFirstMethod("key", "asig") {
|
||||
returnType("Ljava/lang/String;")
|
||||
returnType($$"Landroid/net/Uri$Builder;")
|
||||
opcodes(
|
||||
Opcode.INVOKE_VIRTUAL, // Register holds player request URI.
|
||||
Opcode.INVOKE_VIRTUAL, // The last Uri$Builder mutating call. Register holds the player request Uri$Builder.
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.IPUT_OBJECT,
|
||||
Opcode.IGET_OBJECT,
|
||||
Opcode.MONITOR_EXIT,
|
||||
Opcode.RETURN_OBJECT,
|
||||
Opcode.RETURN_OBJECT, // Returns the built player request Uri$Builder.
|
||||
)
|
||||
}
|
||||
|
||||
@@ -154,7 +151,7 @@ internal val BytecodePatchContext.nerdsStatsVideoFormatBuilderMethod by gettingF
|
||||
returnType("Ljava/lang/String;")
|
||||
parameterTypes("L")
|
||||
instructions(
|
||||
"codecs=\""(),
|
||||
", codecs="(),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+11
-6
@@ -82,14 +82,19 @@ internal fun spoofVideoStreamsPatch(
|
||||
// region Block /get_watch requests to fall back to /player requests.
|
||||
|
||||
buildPlayerRequestURIMethodMatch.method.apply {
|
||||
val invokeToStringIndex = buildPlayerRequestURIMethodMatch[0]
|
||||
val uriRegister = getInstruction<FiveRegisterInstruction>(invokeToStringIndex).registerC
|
||||
val returnIndex = buildPlayerRequestURIMethodMatch[-1]
|
||||
val builderRegister = getInstruction<OneRegisterInstruction>(returnIndex).registerA
|
||||
val freeRegister = findFreeRegister(returnIndex, builderRegister)
|
||||
|
||||
addInstructions(
|
||||
invokeToStringIndex,
|
||||
"""
|
||||
invoke-static { v$uriRegister }, $EXTENSION_CLASS_DESCRIPTOR->blockGetWatchRequest(Landroid/net/Uri;)Landroid/net/Uri;
|
||||
move-result-object v$uriRegister
|
||||
returnIndex,
|
||||
$$"""
|
||||
invoke-virtual { v$builderRegister }, Landroid/net/Uri$Builder;->build()Landroid/net/Uri;
|
||||
move-result-object v$freeRegister
|
||||
invoke-static { v$freeRegister }, $$EXTENSION_CLASS_DESCRIPTOR->blockGetWatchRequest(Landroid/net/Uri;)Landroid/net/Uri;
|
||||
move-result-object v$freeRegister
|
||||
invoke-virtual { v$freeRegister }, Landroid/net/Uri;->buildUpon()Landroid/net/Uri$Builder;
|
||||
move-result-object v$builderRegister
|
||||
""",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -71,3 +71,27 @@ internal val BytecodePatchContext.playerOverlayTimelyShelfMethod by gettingFirst
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 21.34+ removed the [String]"innertube_cue_range", "Null id" and "Null onExitActions" strings
|
||||
* from the player overlay timely shelf handler, and the handler moved into a synthetic lambda
|
||||
* (`a(Ljava/lang/Object;)V`) that dispatches on the overlay type via a `String.equals` comparison.
|
||||
* Only [String]"player_overlay_timely_shelf" remains, so match on it plus the following
|
||||
* `String.equals`/`move-result` used to check if the overlay is the timely shelf.
|
||||
*/
|
||||
internal val BytecodePatchContext.playerOverlayTimelyShelfMethod21_34 by composingFirstMethod {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returnType("V")
|
||||
parameterTypes("Ljava/lang/Object;")
|
||||
instructions(
|
||||
"player_overlay_timely_shelf"(),
|
||||
afterAtMost(
|
||||
5,
|
||||
allOf(
|
||||
Opcode.INVOKE_VIRTUAL(),
|
||||
method { name == "equals" && definingClass == "Ljava/lang/String;" },
|
||||
),
|
||||
),
|
||||
after(Opcode.MOVE_RESULT()),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import app.revanced.patches.youtube.misc.engagement.engagementPanelHookPatch
|
||||
import app.revanced.patches.youtube.misc.fix.backtoexitgesture.fixBackToExitGesturePatch
|
||||
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
|
||||
import app.revanced.patches.youtube.misc.playservice.is_20_14_or_greater
|
||||
import app.revanced.patches.youtube.misc.playservice.is_21_34_or_greater
|
||||
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
|
||||
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||
@@ -34,6 +35,7 @@ import app.revanced.util.indexOfFirstInstructionReversedOrThrow
|
||||
import app.revanced.util.injectHideViewCall
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
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
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
@@ -94,7 +96,8 @@ val hideAdsPatch = bytecodePatch(
|
||||
"20.26.46",
|
||||
"20.31.42",
|
||||
"20.37.48",
|
||||
"20.40.45"
|
||||
"20.40.45",
|
||||
"21.34.248"
|
||||
),
|
||||
)
|
||||
|
||||
@@ -154,6 +157,29 @@ val hideAdsPatch = bytecodePatch(
|
||||
// Hide player overlay view. This can be hidden with a regular litho filter
|
||||
// but an empty space remains.
|
||||
if (is_20_14_or_greater) {
|
||||
if (is_21_34_or_greater) {
|
||||
// 21.34+ the overlay handler moved into a synthetic lambda `a(Object)V`
|
||||
// that dispatches on overlay type. Hook right after the `String.equals` /
|
||||
// `move-result` that determines the overlay is the timely shelf so that
|
||||
// only the timely shelf is hidden and not every player overlay.
|
||||
playerOverlayTimelyShelfMethod21_34.let { match ->
|
||||
val insertIndex = match[-1]
|
||||
val equalsResultRegister =
|
||||
match.method.getInstruction<OneRegisterInstruction>(insertIndex).registerA
|
||||
|
||||
match.method.addInstructionsWithLabels(
|
||||
insertIndex + 1,
|
||||
"""
|
||||
invoke-static { v$equalsResultRegister }, ${EXTENSION_CLASS_DESCRIPTOR}->allowAds(Z)Z
|
||||
move-result v$equalsResultRegister
|
||||
if-nez v$equalsResultRegister, :show
|
||||
return-void
|
||||
:show
|
||||
nop
|
||||
""",
|
||||
)
|
||||
}
|
||||
} else {
|
||||
playerOverlayTimelyShelfMethod.addInstructionsWithLabels(
|
||||
0,
|
||||
"""
|
||||
@@ -166,6 +192,7 @@ val hideAdsPatch = bytecodePatch(
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Hide end screen store banner.
|
||||
|
||||
+8
-2
@@ -13,6 +13,7 @@ import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
|
||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.youtube.misc.playservice.is_19_43_or_greater
|
||||
import app.revanced.patches.youtube.misc.playservice.is_21_34_or_greater
|
||||
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
|
||||
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||
@@ -68,7 +69,8 @@ val hideEndScreenCardsPatch = bytecodePatch(
|
||||
"20.26.46",
|
||||
"20.31.42",
|
||||
"20.37.48",
|
||||
"20.40.45"
|
||||
"20.40.45",
|
||||
"21.34.248"
|
||||
),
|
||||
)
|
||||
|
||||
@@ -90,7 +92,11 @@ val hideEndScreenCardsPatch = bytecodePatch(
|
||||
}
|
||||
}
|
||||
|
||||
if (is_19_43_or_greater) {
|
||||
// 21.34+ the end screen cards are no longer shown through a dedicated 5-method
|
||||
// container class that the legacy showEndscreenCardsMethod fingerprint can locate.
|
||||
// Instead each card view (circle/icon/video) is created and hidden individually by the
|
||||
// three layout fingerprints hooked above, which cover hiding the whole card on 21.34.
|
||||
if (is_19_43_or_greater && !is_21_34_or_greater) {
|
||||
showEndscreenCardsMethod.addInstructionsWithLabels(
|
||||
0,
|
||||
"""
|
||||
|
||||
+41
-3
@@ -92,8 +92,15 @@ val miniplayerPatch = bytecodePatch(
|
||||
val preferences = mutableSetOf<BasePreference>()
|
||||
|
||||
preferences +=
|
||||
if (is_20_37_or_greater) {
|
||||
if (is_20_37_or_greater && !is_21_34_or_greater) {
|
||||
ListPreference("revanced_miniplayer_type")
|
||||
} else if (is_21_34_or_greater) {
|
||||
// YouTube 21.29+ removed all modern miniplayers except "Modern 4",
|
||||
// so the type picker no longer has any effect.
|
||||
NonInteractivePreference(
|
||||
key = "revanced_miniplayer_type",
|
||||
summaryKey = "revanced_miniplayer_type_not_available_summary",
|
||||
)
|
||||
} else if (is_20_03_or_greater) {
|
||||
ListPreference(
|
||||
key = "revanced_miniplayer_type",
|
||||
@@ -153,7 +160,9 @@ val miniplayerPatch = bytecodePatch(
|
||||
)
|
||||
}
|
||||
|
||||
if (!is_21_34_or_greater) {
|
||||
preferences += TextPreference("revanced_miniplayer_opacity", inputType = InputType.NUMBER)
|
||||
}
|
||||
|
||||
PreferenceScreen.PLAYER.addPreferences(
|
||||
PreferenceScreenPreference(
|
||||
@@ -265,6 +274,10 @@ val miniplayerPatch = bytecodePatch(
|
||||
|
||||
// region Enable modern miniplayer.
|
||||
|
||||
// Select which modern miniplayer type is used.
|
||||
// YouTube 21.29+ removed all modern miniplayers except "Modern 4", so there is
|
||||
// nothing to choose between and this override is dead code.
|
||||
if (!is_21_34_or_greater) {
|
||||
miniplayerModernConstructorMethod.classDef.methods.forEach {
|
||||
it.apply {
|
||||
if (AccessFlags.CONSTRUCTOR.isSet(accessFlags)) {
|
||||
@@ -282,7 +295,14 @@ val miniplayerPatch = bytecodePatch(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Modern miniplayer feature-flag overrides.
|
||||
// YouTube 21.29+ removed all modern miniplayers except "Modern 4" and moved/removed the
|
||||
// feature flags for drag & drop, double-tap, rounded corners, horizontal drag and animated
|
||||
// expand. The default/minimum size flags were removed in 21.32+. The fingerprints below
|
||||
// therefore no longer resolve on 21.34+, so these overrides only apply to older versions.
|
||||
if (!is_21_34_or_greater) {
|
||||
if (is_19_23_or_greater) {
|
||||
miniplayerModernConstructorMethod.insertMiniplayerFeatureFlagBooleanOverride(
|
||||
MINIPLAYER_DRAG_DROP_FEATURE_KEY,
|
||||
@@ -361,6 +381,7 @@ val miniplayerPatch = bytecodePatch(
|
||||
"getMaximizeAnimation",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
@@ -391,7 +412,8 @@ val miniplayerPatch = bytecodePatch(
|
||||
|
||||
// region fix minimal miniplayer using the wrong pause/play bold icons.
|
||||
|
||||
if (is_20_31_or_greater) {
|
||||
// 21.34 does not have the yt_fill_pause_* drawables this fingerprint anchors on.
|
||||
if (is_20_31_or_greater && !is_21_34_or_greater) {
|
||||
miniplayerSetIconsMethod.apply {
|
||||
findInstructionIndicesReversedOrThrow {
|
||||
val reference = getReference<MethodReference>()
|
||||
@@ -421,7 +443,6 @@ val miniplayerPatch = bytecodePatch(
|
||||
ClassDef::miniplayerModernActionButtonMethodMatch to "hideMiniplayerActionButton",
|
||||
ClassDef::miniplayerModernRewindButtonMethodMatch to "hideMiniplayerRewindForward",
|
||||
ClassDef::miniplayerModernForwardButtonMethodMatch to "hideMiniplayerRewindForward",
|
||||
ClassDef::miniplayerModernOverlayViewMethodMatch to "adjustMiniplayerOpacity",
|
||||
).forEach { (matching, methodName) ->
|
||||
val match = matching.get(miniplayerModernViewParentMethod.immutableClassDef)
|
||||
|
||||
@@ -436,6 +457,23 @@ val miniplayerPatch = bytecodePatch(
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust the overlay opacity. The modern overlay was restructured in 21.29+ alongside the
|
||||
// removal of the default/minimum size flags, so this hook is only applied on older versions.
|
||||
if (!is_21_34_or_greater) {
|
||||
val match = ClassDef::miniplayerModernOverlayViewMethodMatch
|
||||
.get(miniplayerModernViewParentMethod.immutableClassDef)
|
||||
|
||||
match.method.apply {
|
||||
val index = match[-1]
|
||||
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
||||
|
||||
addInstruction(
|
||||
index + 1,
|
||||
"invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->adjustMiniplayerOpacity(Landroid/view/View;)V",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
miniplayerModernViewParentMethod.immutableClassDef.getMiniplayerModernAddViewListenerMethod()
|
||||
.addInstruction(
|
||||
0,
|
||||
|
||||
+7
@@ -15,6 +15,7 @@ import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.youtube.misc.playservice.is_19_34_or_greater
|
||||
import app.revanced.patches.youtube.misc.playservice.is_20_09_or_greater
|
||||
import app.revanced.patches.youtube.misc.playservice.is_21_34_or_greater
|
||||
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
|
||||
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||
@@ -93,6 +94,11 @@ val shortsAutoplayPatch = bytecodePatch(
|
||||
)
|
||||
}
|
||||
|
||||
// YouTube 21.29+ replaced the diagnostics string and reworked the reel playback repeat
|
||||
// method (its signature changed from "V(L)" to "bL(JLbetu;J)V"), so the parent fingerprint
|
||||
// below no longer resolves on 21.34+. The autoplay restore logic thus only applies to
|
||||
// versions below 21.34.
|
||||
if (!is_21_34_or_greater) {
|
||||
val reelPlaybackRepeatMethod =
|
||||
reelPlaybackRepeatParentMethod.immutableClassDef.getReelPlaybackRepeatMethod()
|
||||
|
||||
@@ -193,4 +199,5 @@ val shortsAutoplayPatch = bytecodePatch(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-1
@@ -12,6 +12,7 @@ import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.youtube.misc.playertype.playerTypeHookPatch
|
||||
import app.revanced.patches.youtube.misc.playservice.is_19_34_or_greater
|
||||
import app.revanced.patches.youtube.misc.playservice.is_20_29_or_greater
|
||||
import app.revanced.patches.youtube.misc.playservice.is_21_34_or_greater
|
||||
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
|
||||
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||
@@ -103,7 +104,10 @@ val removeBackgroundPlaybackRestrictionsPatch = bytecodePatch(
|
||||
kidsBackgroundPlaybackPolicyControllerMethod.returnEarly()
|
||||
|
||||
// Fix PiP buttons not working after locking/unlocking device screen.
|
||||
if (is_19_34_or_greater) {
|
||||
// YouTube 21.34+ removed the flag this anchors on (the remaining case is an
|
||||
// unfixable Google-side issue, see RVX #2764). Skipping it avoids the
|
||||
// fingerprint being unresolved and crashing the whole patch.
|
||||
if (is_19_34_or_greater && !is_21_34_or_greater) {
|
||||
pipInputConsumerFeatureFlagMethodMatch.let {
|
||||
it.method.insertLiteralOverride(
|
||||
it[0],
|
||||
|
||||
+14
@@ -15,6 +15,7 @@ import app.revanced.patcher.patch.BytecodePatchContext
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.returnType
|
||||
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.youtube.misc.playservice.is_21_34_or_greater
|
||||
import app.revanced.util.addInstructionsAtControlFlowLabel
|
||||
import app.revanced.util.cloneMutableAndPreserveParameters
|
||||
import app.revanced.util.findInstructionIndicesReversedOrThrow
|
||||
@@ -73,6 +74,10 @@ val hookClientContextPatch = bytecodePatch(
|
||||
}
|
||||
}
|
||||
|
||||
// The Authentication changed listener method (where the message lite builder method was
|
||||
// sourced from) was removed in YouTube 21.34+. Derive the builder method from the dummy
|
||||
// client context body method instead, which still resolves and builds the same body.
|
||||
if (!is_21_34_or_greater) {
|
||||
authenticationChangeListenerMethod.apply {
|
||||
val messageLiteBuilderIndex =
|
||||
indexOfMessageLiteBuilderReference(this, messageLiteBuilderField.definingClass)
|
||||
@@ -80,6 +85,15 @@ val hookClientContextPatch = bytecodePatch(
|
||||
messageLiteBuilderMethod =
|
||||
getInstruction<ReferenceInstruction>(messageLiteBuilderIndex).methodReference!!
|
||||
}
|
||||
} else {
|
||||
buildDummyClientContextBodyMethodMatch.method.apply {
|
||||
val messageLiteBuilderIndex =
|
||||
indexOfMessageLiteBuilderReference(this, messageLiteBuilderField.definingClass)
|
||||
|
||||
messageLiteBuilderMethod =
|
||||
getInstruction<ReferenceInstruction>(messageLiteBuilderIndex).methodReference!!
|
||||
}
|
||||
}
|
||||
|
||||
buildClientContextBodyConstructorMethod.immutableClassDef.buildClientContextBodyMethodMatch.let {
|
||||
it.method.apply {
|
||||
|
||||
@@ -11,7 +11,6 @@ internal val BytecodePatchContext.specificNetworkErrorViewControllerMethodMatch
|
||||
returnType("V")
|
||||
parameterTypes()
|
||||
instructions(
|
||||
ResourceType.DRAWABLE("ic_offline_no_content_upside_down"),
|
||||
ResourceType.STRING("offline_no_content_body_text_not_offline_eligible"),
|
||||
method { name == "getString" && returnType == "Ljava/lang/String;" },
|
||||
after(Opcode.MOVE_RESULT_OBJECT()),
|
||||
@@ -20,12 +19,13 @@ internal val BytecodePatchContext.specificNetworkErrorViewControllerMethodMatch
|
||||
|
||||
// It's not clear if this second class is ever used, and it may be dead code,
|
||||
// but it is the layout image/text is identical to the network error match above.
|
||||
// The offline image drawable (ic_offline_no_content_upside_down) was removed in 21.34,
|
||||
// so only the still-present offline_no_content string is matched.
|
||||
internal val BytecodePatchContext.loadingFrameLayoutControllerMethodMatch by composingFirstMethod {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returnType("V")
|
||||
parameterTypes("L")
|
||||
parameterTypes("Z")
|
||||
instructions(
|
||||
ResourceType.DRAWABLE("ic_offline_no_content_upside_down"),
|
||||
ResourceType.STRING("offline_no_content_body_text_not_offline_eligible"),
|
||||
method { name == "getString" && returnType == "Ljava/lang/String;" },
|
||||
after(Opcode.MOVE_RESULT_OBJECT()),
|
||||
|
||||
+5
@@ -59,6 +59,10 @@ val lithoFilterPatch = lithoFilterPatch(
|
||||
}
|
||||
|
||||
// Turn off a feature flag that enables native code of protobuf parsing (Upb protobuf).
|
||||
// The flag (45419603) was removed in 21.34. In 21.34 the native UPB buffer path is the
|
||||
// default (and required) path for the setProtoBuffer([B) hook, so nothing needs to be
|
||||
// forced off.
|
||||
if (!is_21_34_or_greater) {
|
||||
lithoConverterBufferUpbFeatureFlagMethodMatch.let {
|
||||
// 20.22 the flag is still enabled in one location, but what it does is not known.
|
||||
// Disable it anyway.
|
||||
@@ -67,6 +71,7 @@ val lithoFilterPatch = lithoFilterPatch(
|
||||
false,
|
||||
)
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
}
|
||||
) {
|
||||
|
||||
+21
@@ -34,6 +34,27 @@ internal val ClassDef.motionEventMethodMatch by ClassDefComposing.composingFirst
|
||||
instructions(method("setTranslationY"))
|
||||
}
|
||||
|
||||
// The player controls overlay was rebuilt in 21.34 and no longer calls `setTranslationY`.
|
||||
// Instead the tap-to-dismiss handler is `nwo.b(Landroid/view/MotionEvent;)V`, which triggers the
|
||||
// visibility change via `nwo.U()V`. These two fingerprints resolve that new hook and keep the
|
||||
// `visibilityNegatedImmediate` injection working for the newer layout.
|
||||
internal val BytecodePatchContext.youtubeControlsOverlayMethod21_34 by gettingFirstImmutableMethodDeclaratively {
|
||||
returnType("V")
|
||||
parameterTypes("Landroid/view/MotionEvent;")
|
||||
instructions(
|
||||
field { definingClass == "Lnwo;" && name == "br" },
|
||||
method { definingClass == "Lnwo;" && name == "U" },
|
||||
)
|
||||
}
|
||||
|
||||
internal val ClassDef.motionEventMethodMatch21_34 by ClassDefComposing.composingFirstMethod {
|
||||
returnType("V")
|
||||
parameterTypes("Landroid/view/MotionEvent;")
|
||||
instructions(
|
||||
method { definingClass == "Lnwo;" && name == "U" },
|
||||
)
|
||||
}
|
||||
|
||||
internal val BytecodePatchContext.playerControlsExtensionHookListenersExistMethod by gettingFirstMethodDeclaratively {
|
||||
name("fullscreenButtonVisibilityCallbacksExist")
|
||||
definingClass(EXTENSION_CLASS_DESCRIPTOR)
|
||||
|
||||
+7
-1
@@ -268,7 +268,13 @@ val playerControlsPatch = bytecodePatch(
|
||||
visibilityImmediateCallbacksExistMethod = playerControlsExtensionHookListenersExistMethod
|
||||
visibilityImmediateMethod = playerControlsExtensionHookMethod
|
||||
|
||||
youtubeControlsOverlayMethod.immutableClassDef.motionEventMethodMatch.let {
|
||||
val motionEventMethodMatch = if (is_21_34_or_greater) {
|
||||
youtubeControlsOverlayMethod21_34.immutableClassDef.motionEventMethodMatch21_34
|
||||
} else {
|
||||
youtubeControlsOverlayMethod.immutableClassDef.motionEventMethodMatch
|
||||
}
|
||||
|
||||
motionEventMethodMatch.let {
|
||||
visibilityNegatedImmediateMethod = it.method
|
||||
visibilityNegatedImmediateInsertIndex = it[0] + 1
|
||||
}
|
||||
|
||||
+3
@@ -127,6 +127,8 @@ var is_21_07_or_greater : Boolean by Delegates.notNull()
|
||||
private set
|
||||
var is_21_08_or_greater : Boolean by Delegates.notNull()
|
||||
private set
|
||||
var is_21_34_or_greater: Boolean by Delegates.notNull()
|
||||
private set
|
||||
|
||||
val versionCheckPatch = resourcePatch(
|
||||
description = "Uses the Play Store service version to find the major/minor version of the YouTube target app.",
|
||||
@@ -184,5 +186,6 @@ val versionCheckPatch = resourcePatch(
|
||||
is_21_06_or_greater = 260705000 <= playStoreServicesVersion
|
||||
is_21_07_or_greater = 260805000 <= playStoreServicesVersion
|
||||
is_21_08_or_greater = 260905000 <= playStoreServicesVersion
|
||||
is_21_34_or_greater = 263410000 <= playStoreServicesVersion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,27 @@ internal val BytecodePatchContext.setThemeMethod by gettingFirstMethodDeclarativ
|
||||
instructions(ResourceType.STRING("app_theme_appearance_dark"))
|
||||
}
|
||||
|
||||
/**
|
||||
* YouTube 21.34+ appearance resolver `Latyq;->t()Lkll;`.
|
||||
*
|
||||
* Returns the theme appearance enum (whose ordinal 1 is dark), resolving between the
|
||||
* `app_theme_appearance_*` prefs, the dark-mode flag and the system UI mode. Among all
|
||||
* `public final ()Lkll` methods it is uniquely identified by reading `Lklm;->f` (the stored
|
||||
* appearance value string): the sibling helpers `u()`/`v()` read `Lklm;->e`/the system UI mode
|
||||
* instead, while `qyn` reads `Lklm;->f` but returns `V`.
|
||||
*/
|
||||
internal val BytecodePatchContext.setThemeMethod21_34 by gettingFirstMethodDeclaratively {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returnType("Lkll;")
|
||||
parameterTypes()
|
||||
instructions(
|
||||
allOf(
|
||||
Opcode.IGET_OBJECT(),
|
||||
field { name == "f" && type == "Ljava/lang/String;" && definingClass == "Lklm;" },
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
internal val BytecodePatchContext.cairoFragmentConfigMethodMatch by composingFirstMethod {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returnType("Z")
|
||||
@@ -29,3 +50,22 @@ internal val BytecodePatchContext.cairoFragmentConfigMethodMatch by composingFir
|
||||
afterAtMost(10, Opcode.MOVE_RESULT()),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* YouTube 21.34+ Cairo settings gate `Loqy;->run()V`.
|
||||
*
|
||||
* The legacy `public final ()Z` feature-flag gate no longer exists. In 21.34 the Cairo settings
|
||||
* screen is selected by feature flag `0x2b6c3c4` inside `Loqy;->run()V`, which chooses between the
|
||||
* legacy (`xml/settings_fragment`, 0x7f180027) and Cairo (`xml/settings_fragment_cairo`,
|
||||
* 0x7f180028) settings screens. The `0x7f180028` literal only appears in this method, which keeps
|
||||
* it unambiguous among the other `0x2b6c3c4` references.
|
||||
*/
|
||||
internal val BytecodePatchContext.cairoFragmentConfigMethodMatch21_34 by composingFirstMethod {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returnType("V")
|
||||
instructions(
|
||||
45532100L(),
|
||||
afterAtMost(10, Opcode.MOVE_RESULT()),
|
||||
afterAtMost(10, 2132279336L()),
|
||||
)
|
||||
}
|
||||
|
||||
+64
-3
@@ -23,6 +23,7 @@ import app.revanced.patches.youtube.misc.fix.contentprovider.fixContentProviderP
|
||||
import app.revanced.patches.youtube.misc.fix.playbackspeed.fixPlaybackSpeedWhilePlayingPatch
|
||||
import app.revanced.patches.youtube.misc.playservice.is_19_34_or_greater
|
||||
import app.revanced.patches.youtube.misc.playservice.is_20_31_or_greater
|
||||
import app.revanced.patches.youtube.misc.playservice.is_21_34_or_greater
|
||||
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
|
||||
import app.revanced.util.*
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
@@ -214,6 +215,64 @@ val settingsPatch = bytecodePatch(
|
||||
)
|
||||
|
||||
PreferenceScreen.MISC.addPreferences(
|
||||
PreferenceCategory(
|
||||
titleKey = "revanced_custom_title",
|
||||
sorting = Sorting.UNSORTED,
|
||||
tag = "app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory",
|
||||
preferences = setOf(
|
||||
NonInteractivePreference(
|
||||
key = "revanced_custom_reset_title",
|
||||
titleKey = "revanced_custom_reset_title",
|
||||
summaryKey = "revanced_custom_reset_summary",
|
||||
tag = "app.revanced.extension.shared.settings.preference.CustomSettingsResetPreference",
|
||||
),
|
||||
SwitchPreference(
|
||||
"revanced_custom_force_amoled",
|
||||
),
|
||||
SwitchPreference(
|
||||
"revanced_custom_pure_black_player",
|
||||
),
|
||||
SwitchPreference(
|
||||
"revanced_custom_disable_ambient_mode",
|
||||
),
|
||||
SwitchPreference(
|
||||
"revanced_custom_hide_shorts",
|
||||
),
|
||||
ListPreference(
|
||||
key = "revanced_custom_default_playback_speed",
|
||||
tag = "app.revanced.extension.shared.settings.preference.SortedListPreference",
|
||||
),
|
||||
SwitchPreference(
|
||||
"revanced_custom_remember_playback_speed",
|
||||
),
|
||||
ListPreference(
|
||||
key = "revanced_custom_default_video_quality",
|
||||
tag = "app.revanced.extension.shared.settings.preference.SortedListPreference",
|
||||
),
|
||||
SwitchPreference(
|
||||
"revanced_custom_remember_video_quality",
|
||||
),
|
||||
SwitchPreference(
|
||||
"revanced_custom_force_highest_quality",
|
||||
),
|
||||
SwitchPreference(
|
||||
"revanced_custom_debug_video_info",
|
||||
),
|
||||
TextPreference(
|
||||
key = "revanced_custom_app_name",
|
||||
titleKey = "revanced_custom_app_name_title",
|
||||
inputType = InputType.TEXT_CAP_CHARACTERS,
|
||||
tag = "app.revanced.extension.shared.settings.preference.TextInputPreference",
|
||||
),
|
||||
ListPreference(
|
||||
key = "revanced_custom_startup_tab",
|
||||
tag = "app.revanced.extension.shared.settings.preference.SortedListPreference",
|
||||
),
|
||||
SwitchPreference(
|
||||
"revanced_custom_clean_urls",
|
||||
),
|
||||
),
|
||||
),
|
||||
TextPreference(
|
||||
key = null,
|
||||
titleKey = "revanced_pref_import_export_title",
|
||||
@@ -230,7 +289,7 @@ val settingsPatch = bytecodePatch(
|
||||
// Update shared dark mode status based on YT theme.
|
||||
// This is needed because YT allows forcing light/dark mode
|
||||
// which then differs from the system dark mode status.
|
||||
setThemeMethod.apply {
|
||||
(if (is_21_34_or_greater) setThemeMethod21_34 else setThemeMethod).apply {
|
||||
findInstructionIndicesReversedOrThrow(Opcode.RETURN_OBJECT).forEach { index ->
|
||||
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
||||
addInstructionsAtControlFlowLabel(
|
||||
@@ -241,10 +300,12 @@ val settingsPatch = bytecodePatch(
|
||||
}
|
||||
|
||||
// Add setting to force Cairo settings fragment on/off.
|
||||
cairoFragmentConfigMethodMatch.method.insertLiteralOverride(
|
||||
cairoFragmentConfigMethodMatch[0],
|
||||
(if (is_21_34_or_greater) cairoFragmentConfigMethodMatch21_34 else cairoFragmentConfigMethodMatch).let {
|
||||
it.method.insertLiteralOverride(
|
||||
it[0],
|
||||
"$YOUTUBE_ACTIVITY_HOOK_CLASS_DESCRIPTOR->useCairoSettingsFragment(Z)Z",
|
||||
)
|
||||
}
|
||||
|
||||
// Bold icon resources are found starting in 20.23, but many YT icons are not bold.
|
||||
// 20.31 is the first version that seems to have all the bold icons.
|
||||
|
||||
+26
@@ -183,6 +183,32 @@ internal val BytecodePatchContext.playbackSpeedClassMethod by gettingFirstMethod
|
||||
opcodes(Opcode.RETURN_OBJECT)
|
||||
}
|
||||
|
||||
/**
|
||||
* YouTube 21.34+ playback-speed bottom sheet fragment factory `Lpzy;->aW(Lce;Ljava/lang/String;)Lpzy;`.
|
||||
*
|
||||
* In 21.34 the `PLAYBACK_RATE_MENU_BOTTOM_SHEET_FRAGMENT` string moved into the large state-holder
|
||||
* factory `Lhxz;->p()Ljava/lang/Object;` (which returns `Ljava/lang/Object;`, not the fragment type),
|
||||
* so the legacy `playbackSpeedClassMethod` fingerprint no longer resolves. The fragment is instead
|
||||
* obtained from its static factory, lookup by tag with fallback creation, which returns the exact
|
||||
* fragment instance (`Lpzy;`). It is uniquely identified by the exact return type `Lpzy;` together
|
||||
* with the `getSupportFragmentManager` -> `f(Ljava/lang/String;)L` lookup sequence.
|
||||
*/
|
||||
internal val BytecodePatchContext.playbackSpeedClassMethod21_34 by gettingFirstMethodDeclaratively {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
||||
returnType("Lpzy;")
|
||||
parameterTypes("L", "Ljava/lang/String;")
|
||||
instructions(
|
||||
method("getSupportFragmentManager"),
|
||||
after(Opcode.MOVE_RESULT_OBJECT()),
|
||||
after(method {
|
||||
returnType.startsWith("L") && parameterTypes.size == 1 && parameterTypes.first() == "Ljava/lang/String;"
|
||||
}),
|
||||
after(Opcode.MOVE_RESULT_OBJECT()),
|
||||
after(Opcode.IF_EQZ()),
|
||||
after(Opcode.CHECK_CAST()),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* YouTube 20.19 and lower.
|
||||
*/
|
||||
|
||||
@@ -1516,6 +1516,7 @@ Limitation: Using the back button on the toolbar may not work"</string>
|
||||
<string name="revanced_miniplayer_type_entry_5">Modern 2</string>
|
||||
<string name="revanced_miniplayer_type_entry_6">Modern 3</string>
|
||||
<string name="revanced_miniplayer_type_entry_7">Modern 4</string>
|
||||
<string name="revanced_miniplayer_type_not_available_summary">Selecting a miniplayer type is no longer available on your YouTube version.</string>
|
||||
<string name="revanced_miniplayer_disable_rounded_corners_title">Disable rounded corners</string>
|
||||
<string name="revanced_miniplayer_disable_rounded_corners_summary_on">Corners are square</string>
|
||||
<string name="revanced_miniplayer_disable_rounded_corners_summary_off">Corners are rounded</string>
|
||||
@@ -1944,6 +1945,36 @@ Video playback with AV1 may stutter or drop frames."</string>
|
||||
<string name="revanced_twitch_debug_title">Debug logging</string>
|
||||
<string name="revanced_twitch_debug_summary_on">Debug logs are enabled</string>
|
||||
<string name="revanced_twitch_debug_summary_off">Debug logs are disabled</string>
|
||||
<!-- Custom settings -->
|
||||
<string name="revanced_custom_title">Custom</string>
|
||||
<string name="revanced_custom_reset_title">Reset Custom Settings</string>
|
||||
<string name="revanced_custom_reset_summary">Restore custom preferences to defaults</string>
|
||||
<string name="revanced_custom_force_amoled_title">Force AMOLED</string>
|
||||
<string name="revanced_custom_force_amoled_summary">Use true black in the YouTube UI</string>
|
||||
<string name="revanced_custom_pure_black_player_title">Pure Black Player</string>
|
||||
<string name="revanced_custom_pure_black_player_summary">Make player background pure black</string>
|
||||
<string name="revanced_custom_disable_ambient_mode_title">Disable Ambient Mode</string>
|
||||
<string name="revanced_custom_disable_ambient_mode_summary">Prevent Ambient Mode from activating</string>
|
||||
<string name="revanced_custom_hide_shorts_title">Hide Shorts</string>
|
||||
<string name="revanced_custom_hide_shorts_summary">Remove Shorts UI entries</string>
|
||||
<string name="revanced_custom_default_playback_speed_title">Default Playback Speed</string>
|
||||
<string name="revanced_custom_default_playback_speed_summary">Select default playback speed</string>
|
||||
<string name="revanced_custom_remember_playback_speed_title">Remember Playback Speed</string>
|
||||
<string name="revanced_custom_remember_playback_speed_summary">Remember speed across videos</string>
|
||||
<string name="revanced_custom_default_video_quality_title">Default Video Quality</string>
|
||||
<string name="revanced_custom_default_video_quality_summary">Select default video quality</string>
|
||||
<string name="revanced_custom_remember_video_quality_title">Remember Video Quality</string>
|
||||
<string name="revanced_custom_remember_video_quality_summary">Remember quality across videos</string>
|
||||
<string name="revanced_custom_force_highest_quality_title">Force Highest Available Quality</string>
|
||||
<string name="revanced_custom_force_highest_quality_summary">Prefer highest quality option</string>
|
||||
<string name="revanced_custom_debug_video_info_title">Debug Video Information</string>
|
||||
<string name="revanced_custom_debug_video_info_summary">Show technical video details</string>
|
||||
<string name="revanced_custom_app_name_title">Custom App Name</string>
|
||||
<string name="revanced_custom_app_name_summary">Change the visible application name</string>
|
||||
<string name="revanced_custom_startup_tab_title">Default Startup Tab</string>
|
||||
<string name="revanced_custom_startup_tab_summary">Select startup tab</string>
|
||||
<string name="revanced_custom_clean_urls_title">Clean YouTube URLs</string>
|
||||
<string name="revanced_custom_clean_urls_summary">Remove tracking parameters from shared links</string>
|
||||
</patch>
|
||||
</app>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user