diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ChangeFormFactorPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ChangeFormFactorPatch.java index d7cf0faba1..240e9b9b08 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ChangeFormFactorPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ChangeFormFactorPatch.java @@ -2,13 +2,10 @@ package app.revanced.extension.youtube.patches; import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton; -import android.view.View; - import androidx.annotation.Nullable; -import java.util.Objects; +import java.util.Optional; -import app.revanced.extension.shared.Logger; import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.shared.NavigationBar; import app.revanced.extension.youtube.shared.PlayerType; @@ -20,7 +17,7 @@ public class ChangeFormFactorPatch { /** * Unmodified, and same as un-patched. */ - DEFAULT(null), + DEFAULT(null, false), /** *
* Some changes include:
@@ -28,60 +25,56 @@ public class ChangeFormFactorPatch {
* - watch history is missing.
* - feed thumbnails fade in.
*/
- UNKNOWN(0),
- SMALL(1),
- LARGE(2),
+ UNKNOWN(0, true),
+ SMALL(1, false),
+ LARGE(2, false),
/**
* Cars with 'Google built-in'.
* Layout seems identical to {@link #UNKNOWN}
* even when using an Android Automotive device.
*/
- AUTOMOTIVE(3),
- WEARABLE(4);
+ AUTOMOTIVE(3, true),
+ WEARABLE(4, true);
@Nullable
final Integer formFactorType;
+ final boolean isBroken;
- FormFactor(@Nullable Integer formFactorType) {
+ FormFactor(@Nullable Integer formFactorType, boolean isBroken) {
this.formFactorType = formFactorType;
+ this.isBroken = isBroken;
}
}
+ private static final FormFactor FORM_FACTOR = Settings.CHANGE_FORM_FACTOR.get();
+
@Nullable
- private static final Integer FORM_FACTOR_TYPE = Settings.CHANGE_FORM_FACTOR.get().formFactorType;
- private static final boolean USING_AUTOMOTIVE_TYPE = Objects.requireNonNull(
- FormFactor.AUTOMOTIVE.formFactorType).equals(FORM_FACTOR_TYPE);
+ private static final Integer FORM_FACTOR_TYPE = FORM_FACTOR.formFactorType;
+ private static final boolean IS_BROKEN_FORM_FACTOR = FORM_FACTOR.isBroken;
/**
* Injection point.
+ *
+ * Called before {@link #replaceBrokenFormFactor(int)}.
+ * Called from all endpoints.
*/
- public static int getFormFactor(int original) {
- if (FORM_FACTOR_TYPE == null) return original;
-
- if (USING_AUTOMOTIVE_TYPE) {
- // Do not change if the player is opening or is opened,
- // otherwise the video description cannot be opened.
- PlayerType current = PlayerType.getCurrent();
- if (current.isMaximizedOrFullscreen() || current == PlayerType.WATCH_WHILE_SLIDING_MINIMIZED_MAXIMIZED) {
- Logger.printDebug(() -> "Using original form factor for player");
- return original;
- }
-
- if (!NavigationBar.isSearchBarActive()) {
- // Automotive type shows error 400 when opening a channel page and using some explore tab.
- // This is a bug in unpatched YouTube that occurs on actual Android Automotive devices.
- // Work around the issue by using the original form factor if not in search and the
- // navigation back button is present.
- if (NavigationBar.isBackButtonVisible()) {
- Logger.printDebug(() -> "Using original form factor, as back button is visible without search present");
- return original;
- }
-
- // Do not change library tab otherwise watch history is hidden.
- // Do this check last since the current navigation button is required.
- if (NavigationButton.getSelectedNavigationButton() == NavigationButton.LIBRARY) {
- return original;
- }
+ public static int getUniversalFormFactor(int original) {
+ if (FORM_FACTOR_TYPE == null) {
+ return original;
+ }
+ if (IS_BROKEN_FORM_FACTOR
+ && !PlayerType.getCurrent().isMaximizedOrFullscreen()
+ && !NavigationBar.isSearchBarActive()) {
+ // Automotive type shows error 400 when opening a channel page and using some explore tab.
+ // This is a bug in unpatched YouTube that occurs on actual Android Automotive devices.
+ // Work around the issue by using the tablet form factor if not in search and the
+ // navigation back button is present.
+ if (NavigationBar.isBackButtonVisible()
+ // Do not change library tab otherwise watch history is hidden.
+ // Do this check last since the current navigation button is required.
+ || NavigationButton.getSelectedNavigationButton() == NavigationButton.LIBRARY) {
+ // The form factor most similar to AUTOMOTIVE is LARGE, so it is replaced with LARGE.
+ return Optional.ofNullable(FormFactor.LARGE.formFactorType).orElse(original);
}
}
@@ -90,16 +83,31 @@ public class ChangeFormFactorPatch {
/**
* Injection point.
+ *
+ * Called after {@link #getUniversalFormFactor(int)}.
+ * Called from the '/get_watch', '/guide', '/next' and '/reel' endpoints.
+ *
+ * The '/guide' endpoint relates to navigation buttons.
+ * If {@link #IS_BROKEN_FORM_FACTOR} is true in this endpoint,
+ * the explore button (which is deprecated) will be shown.
+ *
+ * The '/get_watch' and '/next' endpoints relate to elements below the player (channel bar, comments, related videos).
+ * If {@link #IS_BROKEN_FORM_FACTOR} is true in this endpoint,
+ * the video description panel will not open.
+ *
+ * The '/reel' endpoint relates to Shorts player.
+ * If {@link #IS_BROKEN_FORM_FACTOR} is true in this endpoint,
+ * the Shorts comment panel will not open.
*/
- public static void navigationTabCreated(NavigationButton button, View tabView) {
- // On first startup of the app the navigation buttons are fetched and updated.
- // If the user immediately opens the 'You' or opens a video, then the call to
- // update the navigation buttons will use the non-automotive form factor
- // and the explore tab is missing.
- // Fixing this is not so simple because of the concurrent calls for the player and You tab.
- // For now, always hide the explore tab.
- if (USING_AUTOMOTIVE_TYPE && button == NavigationButton.EXPLORE) {
- tabView.setVisibility(View.GONE);
+ public static int replaceBrokenFormFactor(int original) {
+ if (FORM_FACTOR_TYPE == null) {
+ return original;
+ }
+ if (IS_BROKEN_FORM_FACTOR) {
+ // The form factor most similar to AUTOMOTIVE is LARGE, so it is replaced with LARGE.
+ return Optional.ofNullable(FormFactor.LARGE.formFactorType).orElse(original);
+ } else {
+ return original;
}
}
}
\ No newline at end of file
diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/VideoAdsPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/VideoAdsPatch.java
index 22447193c9..496ac92fbf 100644
--- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/VideoAdsPatch.java
+++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/VideoAdsPatch.java
@@ -4,23 +4,22 @@ import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
public class VideoAdsPatch {
-
- private static final boolean SHOW_VIDEO_ADS = !Settings.HIDE_VIDEO_ADS.get();
+ private static final boolean HIDE_VIDEO_ADS = Settings.HIDE_VIDEO_ADS.get();
/**
* Injection point.
*/
- public static boolean shouldShowAds() {
- return SHOW_VIDEO_ADS;
+ public static boolean hideVideoAds() {
+ return HIDE_VIDEO_ADS;
}
/**
* Injection point.
*/
- public static String hideShortsAds(String osName) {
- return SHOW_VIDEO_ADS
- ? osName
- : "Android Automotive";
+ public static String hideVideoAds(String osName) {
+ return HIDE_VIDEO_ADS
+ ? "Android Automotive"
+ : osName;
}
}
diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/KeywordContentFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/KeywordContentFilter.java
index 4f18eb412d..75c678c12e 100644
--- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/KeywordContentFilter.java
+++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/KeywordContentFilter.java
@@ -528,7 +528,7 @@ public final class KeywordContentFilter extends Filter {
}
return switch (selectedNavButton) {
- case HOME, EXPLORE -> hideHome;
+ case HOME -> hideHome;
case SUBSCRIPTIONS -> hideSubscriptions;
// User is in the Library or notifications.
default -> false;
diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ShortsFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ShortsFilter.java
index 44507e234f..f8e3f102b5 100644
--- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ShortsFilter.java
+++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/litho/ShortsFilter.java
@@ -535,7 +535,7 @@ public final class ShortsFilter extends Filter {
}
return switch (selectedNavButton) {
- case HOME, EXPLORE -> hideHome;
+ case HOME -> hideHome;
case SUBSCRIPTIONS -> hideSubscriptions;
case LIBRARY -> hideHistory;
default -> false;
diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/NavigationBar.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/NavigationBar.java
index 8f15b79c51..6b06a76de7 100644
--- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/NavigationBar.java
+++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/NavigationBar.java
@@ -310,10 +310,6 @@ public final class NavigationBar {
* This tab will never be in a selected state, even if the Create video UI is on screen.
*/
CREATE("CREATION_TAB_LARGE", "CREATION_TAB_LARGE_CAIRO"),
- /**
- * Only shown to automotive layout.
- */
- EXPLORE("TAB_EXPLORE"),
SUBSCRIPTIONS("PIVOT_SUBSCRIPTIONS", "TAB_SUBSCRIPTIONS_CAIRO"),
/**
* Notifications tab. Only present when
diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/ad/general/HideAdsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/ad/general/HideAdsPatch.kt
index 584c8bfe56..81e546ddfc 100644
--- a/patches/src/main/kotlin/app/revanced/patches/youtube/ad/general/HideAdsPatch.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/youtube/ad/general/HideAdsPatch.kt
@@ -215,6 +215,7 @@ val hideAdsPatch = bytecodePatch(
setOf(
Endpoint.BROWSE,
Endpoint.SEARCH,
+ Endpoint.NEXT,
).forEach { endpoint ->
addOSNameHook(
endpoint,
diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/ad/video/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/ad/video/Fingerprints.kt
index 2565c0e79f..b4f7f04ce8 100644
--- a/patches/src/main/kotlin/app/revanced/patches/youtube/ad/video/Fingerprints.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/youtube/ad/video/Fingerprints.kt
@@ -1,9 +1,18 @@
package app.revanced.patches.youtube.ad.video
import app.revanced.patcher.gettingFirstMethodDeclaratively
+import app.revanced.patcher.parameterTypes
import app.revanced.patcher.patch.BytecodePatchContext
+import app.revanced.patcher.returnType
internal val BytecodePatchContext.loadVideoAdsMethod by gettingFirstMethodDeclaratively(
"TriggerBundle doesn't have the required metadata specified by the trigger ",
"Ping migration no associated ping bindings for activated trigger: ",
)
+
+internal val BytecodePatchContext.playerBytesAdLayoutMethod by gettingFirstMethodDeclaratively(
+ "Bootstrapped layout construction resulted in non PlayerBytesLayout. PlayerAds count: "
+) {
+ returnType("V")
+ parameterTypes("L")
+}
diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/ad/video/VideoAdsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/ad/video/VideoAdsPatch.kt
index 8cf3d57d6e..056faf82e9 100644
--- a/patches/src/main/kotlin/app/revanced/patches/youtube/ad/video/VideoAdsPatch.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/youtube/ad/video/VideoAdsPatch.kt
@@ -1,16 +1,20 @@
package app.revanced.patches.youtube.ad.video
-import app.revanced.patcher.extensions.ExternalLabel
import app.revanced.patcher.extensions.addInstructionsWithLabels
-import app.revanced.patcher.extensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
+import app.revanced.patches.youtube.misc.contexthook.Endpoint
+import app.revanced.patches.youtube.misc.contexthook.addOSNameHook
+import app.revanced.patches.youtube.misc.contexthook.hookClientContextPatch
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
+private const val EXTENSION_CLASS_DESCRIPTOR =
+ "Lapp/revanced/extension/youtube/patches/VideoAdsPatch;"
+
@Suppress("ObjectPropertyName")
val videoAdsPatch = bytecodePatch(
name = "Video ads",
@@ -20,6 +24,7 @@ val videoAdsPatch = bytecodePatch(
sharedExtensionPatch,
settingsPatch,
addResourcesPatch,
+ hookClientContextPatch
)
compatibleWith(
@@ -41,15 +46,33 @@ val videoAdsPatch = bytecodePatch(
SwitchPreference("revanced_hide_video_ads"),
)
- loadVideoAdsMethod.addInstructionsWithLabels(
- 0,
- """
- invoke-static { }, Lapp/revanced/extension/youtube/patches/VideoAdsPatch;->shouldShowAds()Z
- move-result v0
- if-nez v0, :show_video_ads
- return-void
- """,
- ExternalLabel("show_video_ads", loadVideoAdsMethod.getInstruction(0)),
- )
+ setOf(
+ loadVideoAdsMethod,
+ playerBytesAdLayoutMethod,
+ ).forEach { method ->
+ method.addInstructionsWithLabels(
+ 0,
+ """
+ invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->hideVideoAds()Z
+ move-result v0
+ if-eqz v0, :show_video_ads
+ return-void
+ :show_video_ads
+ nop
+ """
+ )
+ }
+
+
+ setOf(
+ Endpoint.GET_WATCH,
+ Endpoint.PLAYER,
+ Endpoint.REEL,
+ ).forEach { endpoint ->
+ addOSNameHook(
+ endpoint,
+ "$EXTENSION_CLASS_DESCRIPTOR->hideVideoAds(Ljava/lang/String;)Ljava/lang/String;",
+ )
+ }
}
}
diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/formfactor/ChangeFormFactorPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/formfactor/ChangeFormFactorPatch.kt
index 83e753720c..042fa97ef9 100644
--- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/formfactor/ChangeFormFactorPatch.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/formfactor/ChangeFormFactorPatch.kt
@@ -7,6 +7,9 @@ import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.ListPreference
+import app.revanced.patches.youtube.misc.contexthook.Endpoint
+import app.revanced.patches.youtube.misc.contexthook.addClientFormFactorHook
+import app.revanced.patches.youtube.misc.contexthook.hookClientContextPatch
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.navigation.hookNavigationButtonCreated
import app.revanced.patches.youtube.misc.navigation.navigationBarHookPatch
@@ -27,6 +30,7 @@ val changeFormFactorPatch = bytecodePatch(
sharedExtensionPatch,
settingsPatch,
addResourcesPatch,
+ hookClientContextPatch,
navigationBarHookPatch
)
@@ -49,8 +53,6 @@ val changeFormFactorPatch = bytecodePatch(
ListPreference("revanced_change_form_factor"),
)
- hookNavigationButtonCreated(EXTENSION_CLASS_DESCRIPTOR)
-
val formFactorEnumConstructorClass = formFactorEnumConstructorMethod.definingClass
val createPlayerRequestBodyWithModelMatch = firstMethodComposite {
@@ -71,11 +73,23 @@ val changeFormFactorPatch = bytecodePatch(
addInstructions(
index + 1,
"""
- invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->getFormFactor(I)I
+ invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->getUniversalFormFactor(I)I
move-result v$register
""",
)
}
}
+
+ setOf(
+ Endpoint.GET_WATCH,
+ Endpoint.NEXT,
+ Endpoint.GUIDE,
+ Endpoint.REEL,
+ ).forEach { endpoint ->
+ addClientFormFactorHook(
+ endpoint,
+ "$EXTENSION_CLASS_DESCRIPTOR->replaceBrokenFormFactor(I)I",
+ )
+ }
}
}
diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/contexthook/ClientContextHookPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/contexthook/ClientContextHookPatch.kt
index ec82e4bdbe..7725d7e6a7 100644
--- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/contexthook/ClientContextHookPatch.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/contexthook/ClientContextHookPatch.kt
@@ -1,15 +1,21 @@
package app.revanced.patches.youtube.misc.contexthook
-import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableMethod
import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableMethod.Companion.toMutable
import app.revanced.patcher.accessFlags
+import app.revanced.patcher.allOf
import app.revanced.patcher.classDef
+import app.revanced.patcher.composingFirstMethod
import app.revanced.patcher.extensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.fieldReference
import app.revanced.patcher.extensions.getInstruction
import app.revanced.patcher.extensions.methodReference
+import app.revanced.patcher.field
+import app.revanced.patcher.firstMethodComposite
import app.revanced.patcher.firstMethodDeclaratively
import app.revanced.patcher.immutableClassDef
+import app.revanced.patcher.instructions
+import app.revanced.patcher.invoke
+import app.revanced.patcher.method
import app.revanced.patcher.parameterTypes
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.patch.bytecodePatch
@@ -26,8 +32,9 @@ import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
+import java.lang.ref.WeakReference
-private lateinit var browseIdField: FieldReference
+private lateinit var clientFormFactorField: FieldReference
private lateinit var clientInfoField: FieldReference
private lateinit var clientVersionField: FieldReference
private lateinit var messageLiteBuilderField: FieldReference
@@ -41,9 +48,15 @@ enum class Endpoint(
BROWSE(
BytecodePatchContext::browseEndpointParentMethod::get
),
+ GET_WATCH(
+ BytecodePatchContext::getWatchEndpointConstructorPrimaryMethod::get,
+ BytecodePatchContext::getWatchEndpointConstructorSecondaryMethod::get,
+ ),
GUIDE(
BytecodePatchContext::guideEndpointConstructorMethod::get
),
+ NEXT(BytecodePatchContext::nextEndpointParentMethod::get),
+ PLAYER(BytecodePatchContext::playerEndpointParentMethod::get),
REEL(
BytecodePatchContext::reelCreateItemsEndpointConstructorMethod::get,
BytecodePatchContext::reelItemWatchEndpointConstructorMethod::get,
@@ -90,12 +103,8 @@ val hookClientContextPatch = bytecodePatch(
}
}
- browseEndpointParentMethod.immutableClassDef.browseEndpointConstructorMethodMatch.let {
- it.method.apply {
- val browseIdIndex = it[-1]
- browseIdField =
- getInstruction(browseIdIndex).fieldReference!!
- }
+ clientFormFactorField = getSetClientFormFactorMethodMatch().let {
+ it.method.getInstruction(it[0]).fieldReference!!
}
}
@@ -140,7 +149,7 @@ val hookClientContextPatch = bytecodePatch(
}
)
- it.findInstructionIndicesReversedOrThrow(Opcode.RETURN_VOID).forEach { index ->
+ it.findInstructionIndicesReversedOrThrow(Opcode.RETURN_VOID).forEach { index ->
it.addInstructionsAtControlFlowLabel(
index,
"invoke-direct/range { p0 .. p0 }, ${it.definingClass}->$helperMethodName()V"
@@ -152,19 +161,22 @@ val hookClientContextPatch = bytecodePatch(
}
}
+fun addClientFormFactorHook(endPoint: Endpoint, descriptor: String) {
+ endPoint.instructions += """
+ iget v2, v1, $clientFormFactorField
+ invoke-static { v2 }, $descriptor
+ move-result v2
+ iput v2, v1, $clientFormFactorField
+ """
+}
+
fun addClientVersionHook(endPoint: Endpoint, descriptor: String) {
- endPoint.instructions += if (endPoint == Endpoint.BROWSE) """
- iget-object v3, p0, $browseIdField
- iget-object v2, v1, $clientVersionField
- invoke-static { v3, v2 }, $descriptor
- move-result-object v2
- iput-object v2, v1, $clientVersionField
- """ else """
+ endPoint.instructions += """
iget-object v2, v1, $clientVersionField
invoke-static { v2 }, $descriptor
move-result-object v2
iput-object v2, v1, $clientVersionField
- """
+ """
}
fun addOSNameHook(endPoint: Endpoint, descriptor: String) {
@@ -173,5 +185,5 @@ fun addOSNameHook(endPoint: Endpoint, descriptor: String) {
invoke-static { v2 }, $descriptor
move-result-object v2
iput-object v2, v1, $osNameField
- """
+ """
}
diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/contexthook/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/contexthook/Fingerprints.kt
index 9c52762689..749b577656 100644
--- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/contexthook/Fingerprints.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/contexthook/Fingerprints.kt
@@ -1,6 +1,7 @@
package app.revanced.patches.youtube.misc.contexthook
import app.revanced.patcher.ClassDefComposing
+import app.revanced.patcher.CompositeMatch
import app.revanced.patcher.accessFlags
import app.revanced.patcher.after
import app.revanced.patcher.afterAtMost
@@ -9,8 +10,11 @@ import app.revanced.patcher.composingFirstMethod
import app.revanced.patcher.custom
import app.revanced.patcher.extensions.methodReference
import app.revanced.patcher.field
+import app.revanced.patcher.firstImmutableMethodDeclaratively
+import app.revanced.patcher.firstMethodComposite
import app.revanced.patcher.gettingFirstImmutableMethodDeclaratively
import app.revanced.patcher.gettingFirstMethodDeclaratively
+import app.revanced.patcher.immutableClassDef
import app.revanced.patcher.instructions
import app.revanced.patcher.invoke
import app.revanced.patcher.method
@@ -22,7 +26,6 @@ import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.iface.Method
-import com.google.common.io.ByteArrayDataOutput
internal const val CLIENT_INFO_CLASS_DESCRIPTOR =
$$"Lcom/google/protos/youtube/api/innertube/InnertubeContext$ClientInfo;"
@@ -86,25 +89,33 @@ internal val BytecodePatchContext.buildDummyClientContextBodyMethodMatch by comp
)
}
-internal val ClassDef.browseEndpointConstructorMethodMatch by ClassDefComposing.composingFirstMethod {
- accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
- returnType("V")
- var methodDefiningClass = ""
- custom {
- methodDefiningClass = this.definingClass
- true
+
+
+internal fun BytecodePatchContext.getSetClientFormFactorMethodMatch(): CompositeMatch {
+ val clientFormFactorEnumConstructorMethod = firstImmutableMethodDeclaratively(
+ "UNKNOWN_FORM_FACTOR",
+ "SMALL_FORM_FACTOR",
+ "LARGE_FORM_FACTOR",
+ "AUTOMOTIVE_FORM_FACTOR",
+ "WEARABLE_FORM_FACTOR",
+ ) {
+ accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR)
+ }.immutableClassDef.firstMethodComposite {
+ accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
+ returnType("L")
+ parameterTypes("I")
}
- instructions(
- ""(),
- after(
- allOf(
- Opcode.IPUT_OBJECT(),
- field { definingClass == methodDefiningClass && type == "Ljava/lang/String;" }
- )
- ),
- )
+ return firstMethodComposite {
+ accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
+ returnType("V")
+ parameterTypes("L")
+ instructions(
+ allOf(Opcode.IGET(), field { type == "I" && definingClass == CLIENT_INFO_CLASS_DESCRIPTOR }),
+ method { this == clientFormFactorEnumConstructorMethod }
+ )
+ }
}
internal val BytecodePatchContext.browseEndpointParentMethod by gettingFirstImmutableMethodDeclaratively(
@@ -113,6 +124,22 @@ internal val BytecodePatchContext.browseEndpointParentMethod by gettingFirstImmu
returnType("Ljava/lang/String;")
}
+internal val BytecodePatchContext.getWatchEndpointConstructorPrimaryMethod by gettingFirstMethodDeclaratively(
+ "get_watch"
+) {
+ accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
+ returnType("V")
+ custom { immutableClassDef.fields.any { it.type == "Ljava/util/function/Consumer;" } }
+}
+
+internal val BytecodePatchContext.getWatchEndpointConstructorSecondaryMethod by gettingFirstMethodDeclaratively(
+ "get_watch"
+) {
+ accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
+ returnType("V")
+ custom { immutableClassDef.fields.none { it.type == "Ljava/util/function/Consumer;" } }
+}
+
internal val BytecodePatchContext.guideEndpointConstructorMethod by gettingFirstImmutableMethodDeclaratively(
"guide"
) {
@@ -120,6 +147,18 @@ internal val BytecodePatchContext.guideEndpointConstructorMethod by gettingFirst
returnType("V")
}
+internal val BytecodePatchContext.nextEndpointParentMethod by gettingFirstImmutableMethodDeclaratively(
+ "watchNextType"
+) {
+ returnType("Ljava/lang/String;")
+}
+
+internal val BytecodePatchContext.playerEndpointParentMethod by gettingFirstImmutableMethodDeclaratively(
+ "dataExpiredForSeconds"
+) {
+ returnType("Ljava/lang/String;")
+}
+
internal val BytecodePatchContext.reelCreateItemsEndpointConstructorMethod by gettingFirstImmutableMethodDeclaratively(
"reel/create_reel_items"
) {
diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml
index 48edc4f46c..47c483e5c5 100644
--- a/patches/src/main/resources/addresources/values/strings.xml
+++ b/patches/src/main/resources/addresources/values/strings.xml
@@ -1428,10 +1428,16 @@ Ready to submit?"
Tablet layout
• Community posts are hidden
+• Playback in feeds setting is not available
+• Remix button and Sound button are not available in Shorts
+• Video action bar is not collapsed
Automotive layout
+• Feed is organized by topics and channels
+• Playback in feeds setting is not available
+• Remix button and Sound button are not available in Shorts
• Shorts open in the regular player
-• Feed is organized by topics and channels"
+• Video action bar is not collapsed"
Spoof app version