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:
parent
9fa641ed81
commit
733f3bb2cd
12 changed files with 222 additions and 115 deletions
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;",
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue