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