fix(YouTube - Change form factor): Explore button sometimes shows in Automotive layout

Co-authored-by: inotia00 <108592928+inotia00@users.noreply.github.com>
This commit is contained in:
oSumAtrIX 2026-03-19 20:09:30 +01:00
parent 9fa641ed81
commit 733f3bb2cd
No known key found for this signature in database
GPG key ID: A9B3094ACDB604B4
12 changed files with 222 additions and 115 deletions

View file

@ -2,13 +2,10 @@ package app.revanced.extension.youtube.patches;
import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton; import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton;
import android.view.View;
import androidx.annotation.Nullable; 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.settings.Settings;
import app.revanced.extension.youtube.shared.NavigationBar; import app.revanced.extension.youtube.shared.NavigationBar;
import app.revanced.extension.youtube.shared.PlayerType; import app.revanced.extension.youtube.shared.PlayerType;
@ -20,7 +17,7 @@ public class ChangeFormFactorPatch {
/** /**
* Unmodified, and same as un-patched. * Unmodified, and same as un-patched.
*/ */
DEFAULT(null), DEFAULT(null, false),
/** /**
* <pre> * <pre>
* Some changes include: * Some changes include:
@ -28,60 +25,56 @@ public class ChangeFormFactorPatch {
* - watch history is missing. * - watch history is missing.
* - feed thumbnails fade in. * - feed thumbnails fade in.
*/ */
UNKNOWN(0), UNKNOWN(0, true),
SMALL(1), SMALL(1, false),
LARGE(2), LARGE(2, false),
/** /**
* Cars with 'Google built-in'. * Cars with 'Google built-in'.
* Layout seems identical to {@link #UNKNOWN} * Layout seems identical to {@link #UNKNOWN}
* even when using an Android Automotive device. * even when using an Android Automotive device.
*/ */
AUTOMOTIVE(3), AUTOMOTIVE(3, true),
WEARABLE(4); WEARABLE(4, true);
@Nullable @Nullable
final Integer formFactorType; final Integer formFactorType;
final boolean isBroken;
FormFactor(@Nullable Integer formFactorType) { FormFactor(@Nullable Integer formFactorType, boolean isBroken) {
this.formFactorType = formFactorType; this.formFactorType = formFactorType;
this.isBroken = isBroken;
} }
} }
private static final FormFactor FORM_FACTOR = Settings.CHANGE_FORM_FACTOR.get();
@Nullable @Nullable
private static final Integer FORM_FACTOR_TYPE = Settings.CHANGE_FORM_FACTOR.get().formFactorType; private static final Integer FORM_FACTOR_TYPE = FORM_FACTOR.formFactorType;
private static final boolean USING_AUTOMOTIVE_TYPE = Objects.requireNonNull( private static final boolean IS_BROKEN_FORM_FACTOR = FORM_FACTOR.isBroken;
FormFactor.AUTOMOTIVE.formFactorType).equals(FORM_FACTOR_TYPE);
/** /**
* Injection point. * Injection point.
* <p>
* Called before {@link #replaceBrokenFormFactor(int)}.
* Called from all endpoints.
*/ */
public static int getFormFactor(int original) { public static int getUniversalFormFactor(int original) {
if (FORM_FACTOR_TYPE == null) return original; if (FORM_FACTOR_TYPE == null) {
return original;
if (USING_AUTOMOTIVE_TYPE) { }
// Do not change if the player is opening or is opened, if (IS_BROKEN_FORM_FACTOR
// otherwise the video description cannot be opened. && !PlayerType.getCurrent().isMaximizedOrFullscreen()
PlayerType current = PlayerType.getCurrent(); && !NavigationBar.isSearchBarActive()) {
if (current.isMaximizedOrFullscreen() || current == PlayerType.WATCH_WHILE_SLIDING_MINIMIZED_MAXIMIZED) { // Automotive type shows error 400 when opening a channel page and using some explore tab.
Logger.printDebug(() -> "Using original form factor for player"); // This is a bug in unpatched YouTube that occurs on actual Android Automotive devices.
return original; // Work around the issue by using the tablet form factor if not in search and the
} // navigation back button is present.
if (NavigationBar.isBackButtonVisible()
if (!NavigationBar.isSearchBarActive()) { // Do not change library tab otherwise watch history is hidden.
// Automotive type shows error 400 when opening a channel page and using some explore tab. // Do this check last since the current navigation button is required.
// This is a bug in unpatched YouTube that occurs on actual Android Automotive devices. || NavigationButton.getSelectedNavigationButton() == NavigationButton.LIBRARY) {
// Work around the issue by using the original form factor if not in search and the // The form factor most similar to AUTOMOTIVE is LARGE, so it is replaced with LARGE.
// navigation back button is present. return Optional.ofNullable(FormFactor.LARGE.formFactorType).orElse(original);
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;
}
} }
} }
@ -90,16 +83,31 @@ public class ChangeFormFactorPatch {
/** /**
* Injection point. * Injection point.
* <p>
* Called after {@link #getUniversalFormFactor(int)}.
* Called from the '/get_watch', '/guide', '/next' and '/reel' endpoints.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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) { public static int replaceBrokenFormFactor(int original) {
// On first startup of the app the navigation buttons are fetched and updated. if (FORM_FACTOR_TYPE == null) {
// If the user immediately opens the 'You' or opens a video, then the call to return original;
// update the navigation buttons will use the non-automotive form factor }
// and the explore tab is missing. if (IS_BROKEN_FORM_FACTOR) {
// Fixing this is not so simple because of the concurrent calls for the player and You tab. // The form factor most similar to AUTOMOTIVE is LARGE, so it is replaced with LARGE.
// For now, always hide the explore tab. return Optional.ofNullable(FormFactor.LARGE.formFactorType).orElse(original);
if (USING_AUTOMOTIVE_TYPE && button == NavigationButton.EXPLORE) { } else {
tabView.setVisibility(View.GONE); return original;
} }
} }
} }

View file

@ -4,23 +4,22 @@ import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class VideoAdsPatch { public class VideoAdsPatch {
private static final boolean HIDE_VIDEO_ADS = Settings.HIDE_VIDEO_ADS.get();
private static final boolean SHOW_VIDEO_ADS = !Settings.HIDE_VIDEO_ADS.get();
/** /**
* Injection point. * Injection point.
*/ */
public static boolean shouldShowAds() { public static boolean hideVideoAds() {
return SHOW_VIDEO_ADS; return HIDE_VIDEO_ADS;
} }
/** /**
* Injection point. * Injection point.
*/ */
public static String hideShortsAds(String osName) { public static String hideVideoAds(String osName) {
return SHOW_VIDEO_ADS return HIDE_VIDEO_ADS
? osName ? "Android Automotive"
: "Android Automotive"; : osName;
} }
} }

View file

@ -528,7 +528,7 @@ public final class KeywordContentFilter extends Filter {
} }
return switch (selectedNavButton) { return switch (selectedNavButton) {
case HOME, EXPLORE -> hideHome; case HOME -> hideHome;
case SUBSCRIPTIONS -> hideSubscriptions; case SUBSCRIPTIONS -> hideSubscriptions;
// User is in the Library or notifications. // User is in the Library or notifications.
default -> false; default -> false;

View file

@ -535,7 +535,7 @@ public final class ShortsFilter extends Filter {
} }
return switch (selectedNavButton) { return switch (selectedNavButton) {
case HOME, EXPLORE -> hideHome; case HOME -> hideHome;
case SUBSCRIPTIONS -> hideSubscriptions; case SUBSCRIPTIONS -> hideSubscriptions;
case LIBRARY -> hideHistory; case LIBRARY -> hideHistory;
default -> false; default -> false;

View file

@ -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. * 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"), CREATE("CREATION_TAB_LARGE", "CREATION_TAB_LARGE_CAIRO"),
/**
* Only shown to automotive layout.
*/
EXPLORE("TAB_EXPLORE"),
SUBSCRIPTIONS("PIVOT_SUBSCRIPTIONS", "TAB_SUBSCRIPTIONS_CAIRO"), SUBSCRIPTIONS("PIVOT_SUBSCRIPTIONS", "TAB_SUBSCRIPTIONS_CAIRO"),
/** /**
* Notifications tab. Only present when * Notifications tab. Only present when

View file

@ -215,6 +215,7 @@ val hideAdsPatch = bytecodePatch(
setOf( setOf(
Endpoint.BROWSE, Endpoint.BROWSE,
Endpoint.SEARCH, Endpoint.SEARCH,
Endpoint.NEXT,
).forEach { endpoint -> ).forEach { endpoint ->
addOSNameHook( addOSNameHook(
endpoint, endpoint,

View file

@ -1,9 +1,18 @@
package app.revanced.patches.youtube.ad.video package app.revanced.patches.youtube.ad.video
import app.revanced.patcher.gettingFirstMethodDeclaratively import app.revanced.patcher.gettingFirstMethodDeclaratively
import app.revanced.patcher.parameterTypes
import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.returnType
internal val BytecodePatchContext.loadVideoAdsMethod by gettingFirstMethodDeclaratively( internal val BytecodePatchContext.loadVideoAdsMethod by gettingFirstMethodDeclaratively(
"TriggerBundle doesn't have the required metadata specified by the trigger ", "TriggerBundle doesn't have the required metadata specified by the trigger ",
"Ping migration no associated ping bindings for activated 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")
}

View file

@ -1,16 +1,20 @@
package app.revanced.patches.youtube.ad.video package app.revanced.patches.youtube.ad.video
import app.revanced.patcher.extensions.ExternalLabel
import app.revanced.patcher.extensions.addInstructionsWithLabels import app.revanced.patcher.extensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference 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.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch import app.revanced.patches.youtube.misc.settings.settingsPatch
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/VideoAdsPatch;"
@Suppress("ObjectPropertyName") @Suppress("ObjectPropertyName")
val videoAdsPatch = bytecodePatch( val videoAdsPatch = bytecodePatch(
name = "Video ads", name = "Video ads",
@ -20,6 +24,7 @@ val videoAdsPatch = bytecodePatch(
sharedExtensionPatch, sharedExtensionPatch,
settingsPatch, settingsPatch,
addResourcesPatch, addResourcesPatch,
hookClientContextPatch
) )
compatibleWith( compatibleWith(
@ -41,15 +46,33 @@ val videoAdsPatch = bytecodePatch(
SwitchPreference("revanced_hide_video_ads"), SwitchPreference("revanced_hide_video_ads"),
) )
loadVideoAdsMethod.addInstructionsWithLabels( setOf(
0, loadVideoAdsMethod,
""" playerBytesAdLayoutMethod,
invoke-static { }, Lapp/revanced/extension/youtube/patches/VideoAdsPatch;->shouldShowAds()Z ).forEach { method ->
move-result v0 method.addInstructionsWithLabels(
if-nez v0, :show_video_ads 0,
return-void """
""", invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->hideVideoAds()Z
ExternalLabel("show_video_ads", loadVideoAdsMethod.getInstruction(0)), 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;",
)
}
} }
} }

View file

@ -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.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.ListPreference 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.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.navigation.hookNavigationButtonCreated import app.revanced.patches.youtube.misc.navigation.hookNavigationButtonCreated
import app.revanced.patches.youtube.misc.navigation.navigationBarHookPatch import app.revanced.patches.youtube.misc.navigation.navigationBarHookPatch
@ -27,6 +30,7 @@ val changeFormFactorPatch = bytecodePatch(
sharedExtensionPatch, sharedExtensionPatch,
settingsPatch, settingsPatch,
addResourcesPatch, addResourcesPatch,
hookClientContextPatch,
navigationBarHookPatch navigationBarHookPatch
) )
@ -49,8 +53,6 @@ val changeFormFactorPatch = bytecodePatch(
ListPreference("revanced_change_form_factor"), ListPreference("revanced_change_form_factor"),
) )
hookNavigationButtonCreated(EXTENSION_CLASS_DESCRIPTOR)
val formFactorEnumConstructorClass = formFactorEnumConstructorMethod.definingClass val formFactorEnumConstructorClass = formFactorEnumConstructorMethod.definingClass
val createPlayerRequestBodyWithModelMatch = firstMethodComposite { val createPlayerRequestBodyWithModelMatch = firstMethodComposite {
@ -71,11 +73,23 @@ val changeFormFactorPatch = bytecodePatch(
addInstructions( addInstructions(
index + 1, 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 move-result v$register
""", """,
) )
} }
} }
setOf(
Endpoint.GET_WATCH,
Endpoint.NEXT,
Endpoint.GUIDE,
Endpoint.REEL,
).forEach { endpoint ->
addClientFormFactorHook(
endpoint,
"$EXTENSION_CLASS_DESCRIPTOR->replaceBrokenFormFactor(I)I",
)
}
} }
} }

View file

@ -1,15 +1,21 @@
package app.revanced.patches.youtube.misc.contexthook 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.com.android.tools.smali.dexlib2.mutable.MutableMethod.Companion.toMutable
import app.revanced.patcher.accessFlags import app.revanced.patcher.accessFlags
import app.revanced.patcher.allOf
import app.revanced.patcher.classDef import app.revanced.patcher.classDef
import app.revanced.patcher.composingFirstMethod
import app.revanced.patcher.extensions.addInstructionsWithLabels import app.revanced.patcher.extensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.fieldReference import app.revanced.patcher.extensions.fieldReference
import app.revanced.patcher.extensions.getInstruction import app.revanced.patcher.extensions.getInstruction
import app.revanced.patcher.extensions.methodReference 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.firstMethodDeclaratively
import app.revanced.patcher.immutableClassDef 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.parameterTypes
import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.patch.bytecodePatch 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.FieldReference
import com.android.tools.smali.dexlib2.iface.reference.MethodReference import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod 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 clientInfoField: FieldReference
private lateinit var clientVersionField: FieldReference private lateinit var clientVersionField: FieldReference
private lateinit var messageLiteBuilderField: FieldReference private lateinit var messageLiteBuilderField: FieldReference
@ -41,9 +48,15 @@ enum class Endpoint(
BROWSE( BROWSE(
BytecodePatchContext::browseEndpointParentMethod::get BytecodePatchContext::browseEndpointParentMethod::get
), ),
GET_WATCH(
BytecodePatchContext::getWatchEndpointConstructorPrimaryMethod::get,
BytecodePatchContext::getWatchEndpointConstructorSecondaryMethod::get,
),
GUIDE( GUIDE(
BytecodePatchContext::guideEndpointConstructorMethod::get BytecodePatchContext::guideEndpointConstructorMethod::get
), ),
NEXT(BytecodePatchContext::nextEndpointParentMethod::get),
PLAYER(BytecodePatchContext::playerEndpointParentMethod::get),
REEL( REEL(
BytecodePatchContext::reelCreateItemsEndpointConstructorMethod::get, BytecodePatchContext::reelCreateItemsEndpointConstructorMethod::get,
BytecodePatchContext::reelItemWatchEndpointConstructorMethod::get, BytecodePatchContext::reelItemWatchEndpointConstructorMethod::get,
@ -90,12 +103,8 @@ val hookClientContextPatch = bytecodePatch(
} }
} }
browseEndpointParentMethod.immutableClassDef.browseEndpointConstructorMethodMatch.let { clientFormFactorField = getSetClientFormFactorMethodMatch().let {
it.method.apply { it.method.getInstruction<ReferenceInstruction>(it[0]).fieldReference!!
val browseIdIndex = it[-1]
browseIdField =
getInstruction<ReferenceInstruction>(browseIdIndex).fieldReference!!
}
} }
} }
@ -140,7 +149,7 @@ val hookClientContextPatch = bytecodePatch(
} }
) )
it.findInstructionIndicesReversedOrThrow(Opcode.RETURN_VOID).forEach { index -> it.findInstructionIndicesReversedOrThrow(Opcode.RETURN_VOID).forEach { index ->
it.addInstructionsAtControlFlowLabel( it.addInstructionsAtControlFlowLabel(
index, index,
"invoke-direct/range { p0 .. p0 }, ${it.definingClass}->$helperMethodName()V" "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) { fun addClientVersionHook(endPoint: Endpoint, descriptor: String) {
endPoint.instructions += if (endPoint == Endpoint.BROWSE) """ endPoint.instructions += """
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 """
iget-object v2, v1, $clientVersionField iget-object v2, v1, $clientVersionField
invoke-static { v2 }, $descriptor invoke-static { v2 }, $descriptor
move-result-object v2 move-result-object v2
iput-object v2, v1, $clientVersionField iput-object v2, v1, $clientVersionField
""" """
} }
fun addOSNameHook(endPoint: Endpoint, descriptor: String) { fun addOSNameHook(endPoint: Endpoint, descriptor: String) {
@ -173,5 +185,5 @@ fun addOSNameHook(endPoint: Endpoint, descriptor: String) {
invoke-static { v2 }, $descriptor invoke-static { v2 }, $descriptor
move-result-object v2 move-result-object v2
iput-object v2, v1, $osNameField iput-object v2, v1, $osNameField
""" """
} }

View file

@ -1,6 +1,7 @@
package app.revanced.patches.youtube.misc.contexthook package app.revanced.patches.youtube.misc.contexthook
import app.revanced.patcher.ClassDefComposing import app.revanced.patcher.ClassDefComposing
import app.revanced.patcher.CompositeMatch
import app.revanced.patcher.accessFlags import app.revanced.patcher.accessFlags
import app.revanced.patcher.after import app.revanced.patcher.after
import app.revanced.patcher.afterAtMost import app.revanced.patcher.afterAtMost
@ -9,8 +10,11 @@ import app.revanced.patcher.composingFirstMethod
import app.revanced.patcher.custom import app.revanced.patcher.custom
import app.revanced.patcher.extensions.methodReference import app.revanced.patcher.extensions.methodReference
import app.revanced.patcher.field import app.revanced.patcher.field
import app.revanced.patcher.firstImmutableMethodDeclaratively
import app.revanced.patcher.firstMethodComposite
import app.revanced.patcher.gettingFirstImmutableMethodDeclaratively import app.revanced.patcher.gettingFirstImmutableMethodDeclaratively
import app.revanced.patcher.gettingFirstMethodDeclaratively import app.revanced.patcher.gettingFirstMethodDeclaratively
import app.revanced.patcher.immutableClassDef
import app.revanced.patcher.instructions import app.revanced.patcher.instructions
import app.revanced.patcher.invoke import app.revanced.patcher.invoke
import app.revanced.patcher.method 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.Opcode
import com.android.tools.smali.dexlib2.iface.ClassDef import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.iface.Method import com.android.tools.smali.dexlib2.iface.Method
import com.google.common.io.ByteArrayDataOutput
internal const val CLIENT_INFO_CLASS_DESCRIPTOR = internal const val CLIENT_INFO_CLASS_DESCRIPTOR =
$$"Lcom/google/protos/youtube/api/innertube/InnertubeContext$ClientInfo;" $$"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 internal fun BytecodePatchContext.getSetClientFormFactorMethodMatch(): CompositeMatch {
true 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( return firstMethodComposite {
""(), accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
after( returnType("V")
allOf( parameterTypes("L")
Opcode.IPUT_OBJECT(), instructions(
field { definingClass == methodDefiningClass && type == "Ljava/lang/String;" } allOf(Opcode.IGET(), field { type == "I" && definingClass == CLIENT_INFO_CLASS_DESCRIPTOR }),
) method { this == clientFormFactorEnumConstructorMethod }
), )
) }
} }
internal val BytecodePatchContext.browseEndpointParentMethod by gettingFirstImmutableMethodDeclaratively( internal val BytecodePatchContext.browseEndpointParentMethod by gettingFirstImmutableMethodDeclaratively(
@ -113,6 +124,22 @@ internal val BytecodePatchContext.browseEndpointParentMethod by gettingFirstImmu
returnType("Ljava/lang/String;") 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( internal val BytecodePatchContext.guideEndpointConstructorMethod by gettingFirstImmutableMethodDeclaratively(
"guide" "guide"
) { ) {
@ -120,6 +147,18 @@ internal val BytecodePatchContext.guideEndpointConstructorMethod by gettingFirst
returnType("V") 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( internal val BytecodePatchContext.reelCreateItemsEndpointConstructorMethod by gettingFirstImmutableMethodDeclaratively(
"reel/create_reel_items" "reel/create_reel_items"
) { ) {

View file

@ -1428,10 +1428,16 @@ Ready to submit?"</string>
Tablet layout Tablet layout
• Community posts are hidden • 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 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 • Shorts open in the regular player
• Feed is organized by topics and channels"</string> Video action bar is not collapsed"</string>
</patch> </patch>
<patch id="layout.spoofappversion.spoofAppVersionPatch"> <patch id="layout.spoofappversion.spoofAppVersionPatch">
<string name="revanced_spoof_app_version_title">Spoof app version</string> <string name="revanced_spoof_app_version_title">Spoof app version</string>