From 89d038fdb859641cd146e713ceddf70c9f3b63f3 Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Sun, 22 Dec 2024 16:33:29 +0900 Subject: [PATCH] fix(YouTube - Spoof streaming data): Change the default client to `Android VR`, add `iOS TV` to the selectable default clients, and add the `Use Android clients only` setting https://github.com/inotia00/ReVanced_Extended/issues/2593 --- .../shared/patches/BlockRequestPatch.java | 80 --------- .../extension/shared/patches/PatchStatus.java | 11 +- .../shared/patches/client/AppClient.java | 162 ++++++++++++++---- .../spoof/SpoofStreamingDataPatch.java | 63 ++++++- .../patches/spoof/requests/PlayerRoutes.java | 4 + .../spoof/requests/StreamingDataRequest.java | 20 ++- .../shared/settings/BaseSettings.java | 6 +- ...eamingDataDefaultClientListPreference.java | 87 ++++++++++ ...oofStreamingDataSideEffectsPreference.java | 11 +- .../streamingdata/SpoofStreamingDataPatch.kt | 20 --- .../shared/blockrequest/BlockRequestPatch.kt | 57 ------ .../shared/blockrequest/Fingerprints.kt | 40 ----- .../BaseSpoofStreamingDataPatch.kt | 45 ++++- .../spoof/streamingdata/Fingerprints.kt | 31 ++++ .../streamingdata/SpoofStreamingDataPatch.kt | 10 ++ .../youtube/settings/host/values/arrays.xml | 16 +- .../youtube/settings/host/values/strings.xml | 14 +- .../youtube/settings/xml/revanced_prefs.xml | 3 +- 18 files changed, 416 insertions(+), 264 deletions(-) delete mode 100644 extensions/shared/src/main/java/app/revanced/extension/shared/patches/BlockRequestPatch.java create mode 100644 extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/SpoofStreamingDataDefaultClientListPreference.java delete mode 100644 patches/src/main/kotlin/app/revanced/patches/shared/blockrequest/BlockRequestPatch.kt delete mode 100644 patches/src/main/kotlin/app/revanced/patches/shared/blockrequest/Fingerprints.kt diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/BlockRequestPatch.java b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/BlockRequestPatch.java deleted file mode 100644 index d4c1c458e..000000000 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/BlockRequestPatch.java +++ /dev/null @@ -1,80 +0,0 @@ -package app.revanced.extension.shared.patches; - -import static app.revanced.extension.shared.patches.PatchStatus.SpoofClient; -import static app.revanced.extension.shared.patches.PatchStatus.SpoofStreamingData; - -import android.net.Uri; - -import app.revanced.extension.shared.settings.BaseSettings; -import app.revanced.extension.shared.utils.Logger; - -@SuppressWarnings("unused") -public class BlockRequestPatch { - /** - * Used in YouTube and YouTube Music. - */ - public static final boolean SPOOF_STREAMING_DATA = BaseSettings.SPOOF_STREAMING_DATA.get(); - - /** - * Used in YouTube Music. - * Disabled by default. - */ - public static final boolean SPOOF_CLIENT = BaseSettings.SPOOF_CLIENT.get(); - - private static final boolean BLOCK_REQUEST = (SpoofStreamingData() && SPOOF_STREAMING_DATA) || (SpoofClient() && SPOOF_CLIENT); - - /** - * Any unreachable ip address. Used to intentionally fail requests. - */ - private static final String UNREACHABLE_HOST_URI_STRING = "https://127.0.0.0"; - private static final Uri UNREACHABLE_HOST_URI = Uri.parse(UNREACHABLE_HOST_URI_STRING); - - /** - * Injection point. - * Blocks /get_watch requests by returning an unreachable URI. - * - * @param playerRequestUri The URI of the player request. - * @return An unreachable URI if the request is a /get_watch request, otherwise the original URI. - */ - public static Uri blockGetWatchRequest(Uri playerRequestUri) { - if (BLOCK_REQUEST) { - try { - String path = playerRequestUri.getPath(); - - if (path != null && path.contains("get_watch")) { - Logger.printDebug(() -> "Blocking 'get_watch' by returning unreachable uri"); - - return UNREACHABLE_HOST_URI; - } - } catch (Exception ex) { - Logger.printException(() -> "blockGetWatchRequest failure", ex); - } - } - - return playerRequestUri; - } - - /** - * Injection point. - *

- * Blocks /initplayback requests. - */ - public static String blockInitPlaybackRequest(String originalUrlString) { - if (BLOCK_REQUEST) { - try { - var originalUri = Uri.parse(originalUrlString); - String path = originalUri.getPath(); - - if (path != null && path.contains("initplayback")) { - Logger.printDebug(() -> "Blocking 'initplayback' by clearing query"); - - return originalUri.buildUpon().clearQuery().build().toString(); - } - } catch (Exception ex) { - Logger.printException(() -> "blockInitPlaybackRequest failure", ex); - } - } - - return originalUrlString; - } -} diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/PatchStatus.java b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/PatchStatus.java index 75d62dc85..f5bcef97f 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/PatchStatus.java +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/PatchStatus.java @@ -1,19 +1,18 @@ package app.revanced.extension.shared.patches; -import app.revanced.extension.shared.patches.client.AppClient.ClientType; - @SuppressWarnings("unused") public class PatchStatus { public static boolean HideFullscreenAdsDefaultBoolean() { return false; } - public static ClientType SpoofStreamingDataDefaultClient() { - return ClientType.IOS; - } - public static boolean SpoofStreamingData() { // Replace this with true If the Spoof streaming data patch succeeds return false; } + + public static boolean SpoofStreamingDataAndroidOnlyDefaultBoolean() { + // Replace this with true If the Spoof streaming data patch succeeds in YouTube + return false; + } } diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/AppClient.java b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/AppClient.java index 28cc29f83..67eee0854 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/AppClient.java +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/AppClient.java @@ -6,8 +6,15 @@ import android.os.Build; import androidx.annotation.Nullable; +import app.revanced.extension.shared.settings.BaseSettings; + public class AppClient { // IOS + /** + * Video not playable: Paid / Movie / Private / Age-restricted + * Note: Audio track available + */ + private static final String PACKAGE_NAME_IOS = "com.google.ios.youtube"; /** * The hardcoded client version of the iOS app used for InnerTube requests with this client. * @@ -29,15 +36,35 @@ public class AppClient { private static final String DEVICE_MODEL_IOS = "iPhone16,2"; private static final String OS_VERSION_IOS = "17.7.2.21H221"; private static final String USER_AGENT_VERSION_IOS = "17_7_2"; - private static final String USER_AGENT_IOS = "com.google.ios.youtube/" + - CLIENT_VERSION_IOS + - "(" + - DEVICE_MODEL_IOS + - "; U; CPU iOS " + - USER_AGENT_VERSION_IOS + - " like Mac OS X)"; + private static final String USER_AGENT_IOS = + iOSUserAgent(PACKAGE_NAME_IOS, CLIENT_VERSION_IOS); + + + // IOS UNPLUGGED + /** + * Video not playable: Paid / Movie + * Note: Audio track available + */ + private static final String PACKAGE_NAME_IOS_UNPLUGGED = "com.google.ios.youtubeunplugged"; + /** + * The hardcoded client version of the iOS app used for InnerTube requests with this client. + * + *

+ * It can be extracted by getting the latest release version of the app on + * the App + * Store page of the YouTube TV app, in the {@code What’s New} section. + *

+ */ + private static final String CLIENT_VERSION_IOS_UNPLUGGED = "8.33"; + private static final String USER_AGENT_IOS_UNPLUGGED = + iOSUserAgent(PACKAGE_NAME_IOS_UNPLUGGED, CLIENT_VERSION_IOS_UNPLUGGED); + // IOS MUSIC + /** + * Video not playable: All videos that can't be played on YouTube Music + */ + private static final String PACKAGE_NAME_IOS_MUSIC = "com.google.ios.youtubemusic"; /** * The hardcoded client version of the iOS app used for InnerTube requests with this client. * @@ -47,16 +74,21 @@ public class AppClient { * Store page of the YouTube Music app, in the {@code What’s New} section. *

*/ - private static final String CLIENT_VERSION_IOS_MUSIC = "7.31.2"; - private static final String USER_AGENT_IOS_MUSIC = "com.google.ios.youtubemusic/" + - CLIENT_VERSION_IOS_MUSIC + - "(" + - DEVICE_MODEL_IOS + - "; U; CPU iOS " + - USER_AGENT_VERSION_IOS + - " like Mac OS X)"; + private static final String CLIENT_VERSION_IOS_MUSIC = "7.04"; + private static final String USER_AGENT_IOS_MUSIC = + iOSUserAgent(PACKAGE_NAME_IOS_MUSIC, CLIENT_VERSION_IOS_MUSIC); + // ANDROID VR + /** + * Video not playable: Kids + * Note: Audio track is not available + *

+ * Package name for YouTube VR (Google DayDream): com.google.android.apps.youtube.vr (Deprecated) + * Package name for YouTube VR (Meta Quests): com.google.android.apps.youtube.vr.oculus + * Package name for YouTube VR (ByteDance Pico 4): com.google.android.apps.youtube.vr.pico + */ + private static final String PACKAGE_NAME_ANDROID_VR = "com.google.android.apps.youtube.vr.oculus"; /** * The hardcoded client version of the Android VR app used for InnerTube requests with this client. * @@ -66,7 +98,7 @@ public class AppClient { * Store page of the YouTube app, in the {@code Additional details} section. *

*/ - private static final String CLIENT_VERSION_ANDROID_VR = "1.61.47"; + private static final String CLIENT_VERSION_ANDROID_VR = "1.61.48"; /** * The device machine id for the Meta Quest 3, used to get opus codec with the Android VR client. * @@ -82,19 +114,17 @@ public class AppClient { * but for some reason the build.props for the {@code Quest 3} state that the SDK version is 32. */ private static final String ANDROID_SDK_VERSION_ANDROID_VR = "32"; - /** - * Package name for YouTube VR (Google DayDream): com.google.android.apps.youtube.vr (Deprecated) - * Package name for YouTube VR (Meta Quests): com.google.android.apps.youtube.vr.oculus - * Package name for YouTube VR (ByteDance Pico 4): com.google.android.apps.youtube.vr.pico - */ - private static final String USER_AGENT_ANDROID_VR = "com.google.android.apps.youtube.vr.oculus/" + - CLIENT_VERSION_ANDROID_VR + - " (Linux; U; Android " + - OS_VERSION_ANDROID_VR + - "; GB) gzip"; + private static final String USER_AGENT_ANDROID_VR = + androidUserAgent(PACKAGE_NAME_ANDROID_VR, CLIENT_VERSION_ANDROID_VR, OS_VERSION_ANDROID_VR); + // ANDROID UNPLUGGED - private static final String CLIENT_VERSION_ANDROID_UNPLUGGED = "8.49.0"; + /** + * Video not playable: Playlists / Music + * Note: Audio track is not available + */ + private static final String PACKAGE_NAME_ANDROID_UNPLUGGED = "com.google.android.apps.youtube.unplugged"; + private static final String CLIENT_VERSION_ANDROID_UNPLUGGED = "8.16.0"; /** * The device machine id for the Chromecast with Google TV 4K. * @@ -106,15 +136,47 @@ public class AppClient { private static final String DEVICE_MODEL_ANDROID_UNPLUGGED = "Google TV Streamer"; private static final String OS_VERSION_ANDROID_UNPLUGGED = "14"; private static final String ANDROID_SDK_VERSION_ANDROID_UNPLUGGED = "34"; - private static final String USER_AGENT_ANDROID_UNPLUGGED = "com.google.android.apps.youtube.unplugged/" + - CLIENT_VERSION_ANDROID_UNPLUGGED + - " (Linux; U; Android " + - OS_VERSION_ANDROID_UNPLUGGED + - "; GB) gzip"; + private static final String USER_AGENT_ANDROID_UNPLUGGED = + androidUserAgent(PACKAGE_NAME_ANDROID_UNPLUGGED, CLIENT_VERSION_ANDROID_UNPLUGGED, OS_VERSION_ANDROID_UNPLUGGED); + + + // ANDROID CREATOR + /** + * Video not playable: Livestream + * Note: Audio track is not available + */ + private static final String PACKAGE_NAME_ANDROID_CREATOR = "com.google.android.apps.youtube.creator"; + private static final String CLIENT_VERSION_ANDROID_CREATOR = "24.14.101"; + private static final String DEVICE_MODEL_ANDROID_CREATOR = Build.MODEL; + private static final String OS_VERSION_ANDROID_CREATOR = Build.VERSION.RELEASE; + private static final String ANDROID_SDK_VERSION_ANDROID_CREATOR = String.valueOf(Build.VERSION.SDK_INT); + private static final String USER_AGENT_ANDROID_CREATOR = + androidUserAgent(PACKAGE_NAME_ANDROID_CREATOR, CLIENT_VERSION_ANDROID_CREATOR, OS_VERSION_ANDROID_CREATOR); + private AppClient() { } + private static String androidUserAgent(String packageName, String clientVersion, String osVersion) { + return packageName + + "/" + + clientVersion + + " (Linux; U; Android " + + osVersion + + "; GB) gzip"; + } + + private static String iOSUserAgent(String packageName, String clientVersion) { + return packageName + + "/" + + clientVersion + + "(" + + DEVICE_MODEL_IOS + + "; U; CPU iOS " + + USER_AGENT_VERSION_IOS + + " like Mac OS X)"; + } + public enum ClientType { IOS(5, DEVICE_MODEL_IOS, @@ -140,6 +202,22 @@ public class AppClient { CLIENT_VERSION_ANDROID_UNPLUGGED, true ), + ANDROID_CREATOR(14, + DEVICE_MODEL_ANDROID_CREATOR, + OS_VERSION_ANDROID_CREATOR, + USER_AGENT_ANDROID_CREATOR, + ANDROID_SDK_VERSION_ANDROID_CREATOR, + CLIENT_VERSION_ANDROID_CREATOR, + true + ), + IOS_UNPLUGGED(33, + DEVICE_MODEL_IOS, + OS_VERSION_IOS, + USER_AGENT_IOS_UNPLUGGED, + null, + CLIENT_VERSION_IOS_UNPLUGGED, + true + ), IOS_MUSIC( 26, DEVICE_MODEL_IOS, @@ -208,8 +286,28 @@ public class AppClient { this.canLogin = canLogin; } + private static final ClientType[] CLIENT_ORDER_TO_USE_ANDROID = { + ANDROID_VR, + ANDROID_UNPLUGGED, + ANDROID_CREATOR, + }; + + private static final ClientType[] CLIENT_ORDER_TO_USE_DEFAULT = { + IOS, + ANDROID_VR, + ANDROID_UNPLUGGED, + IOS_UNPLUGGED, + IOS_MUSIC, + }; + public final String getFriendlyName() { return getString("revanced_spoof_streaming_data_type_entry_" + name().toLowerCase()); } } + + public static ClientType[] getAvailableClientTypes() { + return BaseSettings.SPOOF_STREAMING_DATA_ANDROID_ONLY.get() + ? ClientType.CLIENT_ORDER_TO_USE_ANDROID + : ClientType.CLIENT_ORDER_TO_USE_DEFAULT; + } } diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/SpoofStreamingDataPatch.java b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/SpoofStreamingDataPatch.java index 181d0607a..f1ca98e32 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/SpoofStreamingDataPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/SpoofStreamingDataPatch.java @@ -1,5 +1,7 @@ package app.revanced.extension.shared.patches.spoof; +import static app.revanced.extension.shared.patches.PatchStatus.SpoofStreamingData; + import android.net.Uri; import android.text.TextUtils; @@ -10,14 +12,20 @@ import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; -import app.revanced.extension.shared.patches.BlockRequestPatch; import app.revanced.extension.shared.patches.spoof.requests.StreamingDataRequest; import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.utils.Logger; import app.revanced.extension.shared.utils.Utils; @SuppressWarnings("unused") -public class SpoofStreamingDataPatch extends BlockRequestPatch { +public class SpoofStreamingDataPatch { + public static final boolean SPOOF_STREAMING_DATA = SpoofStreamingData() && BaseSettings.SPOOF_STREAMING_DATA.get(); + + /** + * Any unreachable ip address. Used to intentionally fail requests. + */ + private static final String UNREACHABLE_HOST_URI_STRING = "https://127.0.0.0"; + private static final Uri UNREACHABLE_HOST_URI = Uri.parse(UNREACHABLE_HOST_URI_STRING); /** * Key: video id @@ -33,6 +41,55 @@ public class SpoofStreamingDataPatch extends BlockRequestPatch { } }); + /** + * Injection point. + * Blocks /get_watch requests by returning an unreachable URI. + * + * @param playerRequestUri The URI of the player request. + * @return An unreachable URI if the request is a /get_watch request, otherwise the original URI. + */ + public static Uri blockGetWatchRequest(Uri playerRequestUri) { + if (SPOOF_STREAMING_DATA) { + try { + String path = playerRequestUri.getPath(); + + if (path != null && path.contains("get_watch")) { + Logger.printDebug(() -> "Blocking 'get_watch' by returning unreachable uri"); + + return UNREACHABLE_HOST_URI; + } + } catch (Exception ex) { + Logger.printException(() -> "blockGetWatchRequest failure", ex); + } + } + + return playerRequestUri; + } + + /** + * Injection point. + *

+ * Blocks /initplayback requests. + */ + public static String blockInitPlaybackRequest(String originalUrlString) { + if (SPOOF_STREAMING_DATA) { + try { + var originalUri = Uri.parse(originalUrlString); + String path = originalUri.getPath(); + + if (path != null && path.contains("initplayback")) { + Logger.printDebug(() -> "Blocking 'initplayback' by clearing query"); + + return originalUri.buildUpon().clearQuery().build().toString(); + } + } catch (Exception ex) { + Logger.printException(() -> "blockInitPlaybackRequest failure", ex); + } + } + + return originalUrlString; + } + /** * Injection point. */ @@ -146,7 +203,7 @@ public class SpoofStreamingDataPatch extends BlockRequestPatch { * Called after {@link #getStreamingData(String)}. */ public static long getApproxDurationMs(String videoId) { - if (videoId != null) { + if (SPOOF_STREAMING_DATA && videoId != null) { final Long approxDurationMs = approxDurationMsMap.get(videoId); if (approxDurationMs != null) { Logger.printDebug(() -> "Replacing video length: " + approxDurationMs + " for videoId: " + videoId); diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/requests/PlayerRoutes.java b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/requests/PlayerRoutes.java index f42e5443c..4eb16d20c 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/requests/PlayerRoutes.java +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/requests/PlayerRoutes.java @@ -52,6 +52,10 @@ public final class PlayerRoutes { client.put("osVersion", clientType.osVersion); if (clientType.androidSdkVersion != null) { client.put("androidSdkVersion", clientType.androidSdkVersion); + client.put("osName", "Android"); + } else { + client.put("deviceMake", "Apple"); + client.put("osName", "iOS"); } client.put("hl", LOCALE_LANGUAGE); diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/requests/StreamingDataRequest.java b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/requests/StreamingDataRequest.java index 5637f392b..a76c8a2df 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/requests/StreamingDataRequest.java +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/requests/StreamingDataRequest.java @@ -1,5 +1,6 @@ package app.revanced.extension.shared.patches.spoof.requests; +import static app.revanced.extension.shared.patches.client.AppClient.getAvailableClientTypes; import static app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes.GET_STREAMING_DATA; import androidx.annotation.NonNull; @@ -13,6 +14,7 @@ import java.net.HttpURLConnection; import java.net.SocketTimeoutException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; @@ -80,16 +82,20 @@ public class StreamingDataRequest { } static { - ClientType[] allClientTypes = ClientType.values(); + ClientType[] allClientTypes = getAvailableClientTypes(); ClientType preferredClient = BaseSettings.SPOOF_STREAMING_DATA_TYPE.get(); - CLIENT_ORDER_TO_USE = new ClientType[allClientTypes.length]; - CLIENT_ORDER_TO_USE[0] = preferredClient; + if (Arrays.stream(allClientTypes).noneMatch(preferredClient::equals)) { + CLIENT_ORDER_TO_USE = allClientTypes; + } else { + CLIENT_ORDER_TO_USE = new ClientType[allClientTypes.length]; + CLIENT_ORDER_TO_USE[0] = preferredClient; - int i = 1; - for (ClientType c : allClientTypes) { - if (c != preferredClient) { - CLIENT_ORDER_TO_USE[i++] = c; + int i = 1; + for (ClientType c : allClientTypes) { + if (c != preferredClient) { + CLIENT_ORDER_TO_USE[i++] = c; + } } } } diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/settings/BaseSettings.java b/extensions/shared/src/main/java/app/revanced/extension/shared/settings/BaseSettings.java index a9acc8be6..973550208 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/settings/BaseSettings.java +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/settings/BaseSettings.java @@ -3,7 +3,7 @@ package app.revanced.extension.shared.settings; import static java.lang.Boolean.FALSE; import static java.lang.Boolean.TRUE; import static app.revanced.extension.shared.patches.PatchStatus.HideFullscreenAdsDefaultBoolean; -import static app.revanced.extension.shared.patches.PatchStatus.SpoofStreamingDataDefaultClient; +import static app.revanced.extension.shared.patches.PatchStatus.SpoofStreamingDataAndroidOnlyDefaultBoolean; import app.revanced.extension.shared.patches.ReturnYouTubeUsernamePatch.DisplayFormat; import app.revanced.extension.shared.patches.client.AppClient.ClientType; @@ -37,8 +37,8 @@ public class BaseSettings { public static final StringSetting RETURN_YOUTUBE_USERNAME_YOUTUBE_DATA_API_V3_DEVELOPER_KEY = new StringSetting("revanced_return_youtube_username_youtube_data_api_v3_developer_key", "", true, false); public static final BooleanSetting SPOOF_STREAMING_DATA = new BooleanSetting("revanced_spoof_streaming_data", TRUE, true, "revanced_spoof_streaming_data_user_dialog_message"); - public static final EnumSetting SPOOF_STREAMING_DATA_TYPE = new EnumSetting<>("revanced_spoof_streaming_data_type", SpoofStreamingDataDefaultClient(), true); - public static final BooleanSetting SPOOF_STREAMING_DATA_SYNC_VIDEO_LENGTH = new BooleanSetting("revanced_spoof_streaming_data_sync_video_length", TRUE, true); + public static final EnumSetting SPOOF_STREAMING_DATA_TYPE = new EnumSetting<>("revanced_spoof_streaming_data_type", ClientType.ANDROID_VR, true); + public static final BooleanSetting SPOOF_STREAMING_DATA_ANDROID_ONLY = new BooleanSetting("revanced_spoof_streaming_data_android_only", SpoofStreamingDataAndroidOnlyDefaultBoolean(), true, "revanced_spoof_streaming_data_android_only_user_dialog_message"); public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_streaming_data_stats_for_nerds", TRUE); /** diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/SpoofStreamingDataDefaultClientListPreference.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/SpoofStreamingDataDefaultClientListPreference.java new file mode 100644 index 000000000..b3fabe111 --- /dev/null +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/SpoofStreamingDataDefaultClientListPreference.java @@ -0,0 +1,87 @@ +package app.revanced.extension.youtube.settings.preference; + +import static app.revanced.extension.shared.utils.ResourceUtils.getStringArray; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.ListPreference; +import android.preference.PreferenceManager; +import android.util.AttributeSet; + +import app.revanced.extension.shared.patches.client.AppClient.ClientType; +import app.revanced.extension.shared.settings.EnumSetting; +import app.revanced.extension.shared.settings.Setting; +import app.revanced.extension.shared.utils.Utils; +import app.revanced.extension.youtube.settings.Settings; + +@SuppressWarnings({"unused", "deprecation"}) +public class SpoofStreamingDataDefaultClientListPreference extends ListPreference { + + private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> { + // Because this listener may run before the ReVanced settings fragment updates Settings, + // this could show the prior config and not the current. + // + // Push this call to the end of the main run queue, + // so all other listeners are done and Settings is up to date. + Utils.runOnMainThread(this::updateUI); + }; + + public SpoofStreamingDataDefaultClientListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public SpoofStreamingDataDefaultClientListPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public SpoofStreamingDataDefaultClientListPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public SpoofStreamingDataDefaultClientListPreference(Context context) { + super(context); + } + + private void addChangeListener() { + Setting.preferences.preferences.registerOnSharedPreferenceChangeListener(listener); + } + + private void removeChangeListener() { + Setting.preferences.preferences.unregisterOnSharedPreferenceChangeListener(listener); + } + + @Override + protected void onAttachedToHierarchy(PreferenceManager preferenceManager) { + super.onAttachedToHierarchy(preferenceManager); + updateUI(); + addChangeListener(); + } + + @Override + protected void onPrepareForRemoval() { + super.onPrepareForRemoval(); + removeChangeListener(); + } + + private void updateUI() { + final boolean spoofStreamingDataAndroidOnly = Settings.SPOOF_STREAMING_DATA_ANDROID_ONLY.get(); + final String entryKey = spoofStreamingDataAndroidOnly + ? "revanced_spoof_streaming_data_type_android_entries" + : "revanced_spoof_streaming_data_type_android_ios_entries"; + final String entryValueKey = spoofStreamingDataAndroidOnly + ? "revanced_spoof_streaming_data_type_android_entry_values" + : "revanced_spoof_streaming_data_type_android_ios_entry_values"; + final String[] mEntries = getStringArray(entryKey); + final String[] mEntryValues = getStringArray(entryValueKey); + setEntries(mEntries); + setEntryValues(mEntryValues); + + final EnumSetting clientType = Settings.SPOOF_STREAMING_DATA_TYPE; + final boolean isAndroid = clientType.get().name().startsWith("ANDROID"); + if (spoofStreamingDataAndroidOnly && !isAndroid) { + clientType.resetToDefault(); + } + + setEnabled(Settings.SPOOF_STREAMING_DATA.get()); + } +} diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/SpoofStreamingDataSideEffectsPreference.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/SpoofStreamingDataSideEffectsPreference.java index 92eec6bcf..f8e062270 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/SpoofStreamingDataSideEffectsPreference.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/SpoofStreamingDataSideEffectsPreference.java @@ -8,7 +8,6 @@ import android.preference.Preference; import android.preference.PreferenceManager; import android.util.AttributeSet; -import app.revanced.extension.shared.patches.client.AppClient.ClientType; import app.revanced.extension.shared.settings.Setting; import app.revanced.extension.shared.utils.Utils; import app.revanced.extension.youtube.settings.Settings; @@ -63,8 +62,14 @@ public class SpoofStreamingDataSideEffectsPreference extends Preference { } private void updateUI() { - final ClientType clientType = Settings.SPOOF_STREAMING_DATA_TYPE.get(); - final String summaryTextKey = "revanced_spoof_streaming_data_side_effects_" + clientType.name().toLowerCase(); + final String clientName = Settings.SPOOF_STREAMING_DATA_TYPE.get().name().toLowerCase(); + String summaryTextKey = "revanced_spoof_streaming_data_side_effects_"; + + if (Settings.SPOOF_STREAMING_DATA_ANDROID_ONLY.get()) { + summaryTextKey += "android"; + } else { + summaryTextKey += clientName; + } setSummary(str(summaryTextKey)); setEnabled(Settings.SPOOF_STREAMING_DATA.get()); diff --git a/patches/src/main/kotlin/app/revanced/patches/music/utils/fix/streamingdata/SpoofStreamingDataPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/utils/fix/streamingdata/SpoofStreamingDataPatch.kt index 3b8ae37b3..a37eeeeb8 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/utils/fix/streamingdata/SpoofStreamingDataPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/utils/fix/streamingdata/SpoofStreamingDataPatch.kt @@ -1,7 +1,5 @@ package app.revanced.patches.music.utils.fix.streamingdata -import app.revanced.patcher.extensions.InstructionExtensions.getInstruction -import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE import app.revanced.patches.music.utils.compatibility.Constants.YOUTUBE_MUSIC_PACKAGE_NAME import app.revanced.patches.music.utils.patch.PatchList.SPOOF_STREAMING_DATA @@ -10,15 +8,8 @@ import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus import app.revanced.patches.music.utils.settings.addPreferenceWithIntent import app.revanced.patches.music.utils.settings.addSwitchPreference import app.revanced.patches.music.utils.settings.settingsPatch -import app.revanced.patches.shared.extension.Constants.PATCHES_PATH import app.revanced.patches.shared.spoof.streamingdata.baseSpoofStreamingDataPatch import app.revanced.patches.shared.spoof.useragent.baseSpoofUserAgentPatch -import app.revanced.util.findMethodOrThrow -import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction -import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction -import com.android.tools.smali.dexlib2.iface.reference.FieldReference - -private const val DEFAULT_CLIENT_TYPE = "ANDROID_VR" @Suppress("unused") val spoofStreamingDataPatch = baseSpoofStreamingDataPatch( @@ -31,17 +22,6 @@ val spoofStreamingDataPatch = baseSpoofStreamingDataPatch( ) }, { - findMethodOrThrow("$PATCHES_PATH/PatchStatus;") { - name == "SpoofStreamingDataDefaultClient" - }.apply { - val register = getInstruction(0).registerA - val type = (getInstruction(0).reference as FieldReference).type - replaceInstruction( - 0, - "sget-object v$register, $type->$DEFAULT_CLIENT_TYPE:$type" - ) - } - addSwitchPreference( CategoryType.MISC, "revanced_spoof_streaming_data", diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/blockrequest/BlockRequestPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/blockrequest/BlockRequestPatch.kt deleted file mode 100644 index ae3d9a72c..000000000 --- a/patches/src/main/kotlin/app/revanced/patches/shared/blockrequest/BlockRequestPatch.kt +++ /dev/null @@ -1,57 +0,0 @@ -package app.revanced.patches.shared.blockrequest - -import app.revanced.patcher.extensions.InstructionExtensions.addInstructions -import app.revanced.patcher.extensions.InstructionExtensions.getInstruction -import app.revanced.patcher.patch.bytecodePatch -import app.revanced.patches.shared.extension.Constants.PATCHES_PATH -import app.revanced.util.fingerprint.matchOrThrow -import app.revanced.util.fingerprint.methodOrThrow -import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction -import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction - -const val EXTENSION_CLASS_DESCRIPTOR = - "$PATCHES_PATH/BlockRequestPatch;" - -val blockRequestPatch = bytecodePatch( - description = "blockRequestPatch" -) { - execute { - // region Block /initplayback requests to fall back to /get_watch requests. - - buildInitPlaybackRequestFingerprint.matchOrThrow().let { - it.method.apply { - val moveUriStringIndex = it.patternMatch!!.startIndex - val targetRegister = - getInstruction(moveUriStringIndex).registerA - - addInstructions( - moveUriStringIndex + 1, - """ - invoke-static { v$targetRegister }, $EXTENSION_CLASS_DESCRIPTOR->blockInitPlaybackRequest(Ljava/lang/String;)Ljava/lang/String; - move-result-object v$targetRegister - """, - ) - } - } - - // endregion - - // region Block /get_watch requests to fall back to /player requests. - - buildPlayerRequestURIFingerprint.methodOrThrow().apply { - val invokeToStringIndex = indexOfToStringInstruction(this) - val uriRegister = - getInstruction(invokeToStringIndex).registerC - - addInstructions( - invokeToStringIndex, - """ - invoke-static { v$uriRegister }, $EXTENSION_CLASS_DESCRIPTOR->blockGetWatchRequest(Landroid/net/Uri;)Landroid/net/Uri; - move-result-object v$uriRegister - """, - ) - } - - // endregion - } -} \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/blockrequest/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/shared/blockrequest/Fingerprints.kt deleted file mode 100644 index 30ed66ea0..000000000 --- a/patches/src/main/kotlin/app/revanced/patches/shared/blockrequest/Fingerprints.kt +++ /dev/null @@ -1,40 +0,0 @@ -package app.revanced.patches.shared.blockrequest - -import app.revanced.util.fingerprint.legacyFingerprint -import app.revanced.util.getReference -import app.revanced.util.indexOfFirstInstruction -import com.android.tools.smali.dexlib2.Opcode -import com.android.tools.smali.dexlib2.iface.Method -import com.android.tools.smali.dexlib2.iface.reference.MethodReference - -internal val buildInitPlaybackRequestFingerprint = legacyFingerprint( - name = "buildInitPlaybackRequestFingerprint", - returnType = "Lorg/chromium/net/UrlRequest\$Builder;", - opcodes = listOf( - Opcode.MOVE_RESULT_OBJECT, - Opcode.IGET_OBJECT, // Moves the request URI string to a register to build the request with. - ), - strings = listOf( - "Content-Type", - "Range", - ), -) - -internal val buildPlayerRequestURIFingerprint = legacyFingerprint( - name = "buildPlayerRequestURIFingerprint", - returnType = "Ljava/lang/String;", - strings = listOf( - "key", - "asig", - ), - customFingerprint = { method, _ -> - indexOfToStringInstruction(method) >= 0 - }, -) - -internal fun indexOfToStringInstruction(method: Method) = - method.indexOfFirstInstruction { - opcode == Opcode.INVOKE_VIRTUAL && - getReference().toString() == "Landroid/net/Uri;->toString()Ljava/lang/String;" - } - diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/spoof/streamingdata/BaseSpoofStreamingDataPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/spoof/streamingdata/BaseSpoofStreamingDataPatch.kt index 86723635b..0ddda9abf 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/spoof/streamingdata/BaseSpoofStreamingDataPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/spoof/streamingdata/BaseSpoofStreamingDataPatch.kt @@ -4,15 +4,14 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels import app.revanced.patcher.extensions.InstructionExtensions.getInstruction -import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction import app.revanced.patcher.extensions.InstructionExtensions.instructions +import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patcher.patch.BytecodePatchBuilder import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable -import app.revanced.patches.shared.blockrequest.blockRequestPatch import app.revanced.patches.shared.extension.Constants.PATCHES_PATH import app.revanced.patches.shared.extension.Constants.SPOOF_PATH import app.revanced.patches.shared.formatStreamModelConstructorFingerprint @@ -45,11 +44,47 @@ fun baseSpoofStreamingDataPatch( name = "Spoof streaming data", description = "Adds options to spoof the streaming data to allow playback." ) { - dependsOn(blockRequestPatch) - block() execute { + // region Block /initplayback requests to fall back to /get_watch requests. + + buildInitPlaybackRequestFingerprint.matchOrThrow().let { + it.method.apply { + val moveUriStringIndex = it.patternMatch!!.startIndex + val targetRegister = + getInstruction(moveUriStringIndex).registerA + + addInstructions( + moveUriStringIndex + 1, + """ + invoke-static { v$targetRegister }, $EXTENSION_CLASS_DESCRIPTOR->blockInitPlaybackRequest(Ljava/lang/String;)Ljava/lang/String; + move-result-object v$targetRegister + """, + ) + } + } + + // endregion + + // region Block /get_watch requests to fall back to /player requests. + + buildPlayerRequestURIFingerprint.methodOrThrow().apply { + val invokeToStringIndex = indexOfToStringInstruction(this) + val uriRegister = + getInstruction(invokeToStringIndex).registerC + + addInstructions( + invokeToStringIndex, + """ + invoke-static { v$uriRegister }, $EXTENSION_CLASS_DESCRIPTOR->blockGetWatchRequest(Landroid/net/Uri;)Landroid/net/Uri; + move-result-object v$uriRegister + """, + ) + } + + // endregion + // region Get replacement streams at player requests. buildRequestFingerprint.methodOrThrow().apply { @@ -154,7 +189,7 @@ fun baseSpoofStreamingDataPatch( "$resultMethodType->$setStreamDataMethodName($videoDetailsClass)V", ) - resultClassDef.methods.add( + result.classDef.methods.add( ImmutableMethod( resultMethodType, setStreamDataMethodName, diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/spoof/streamingdata/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/shared/spoof/streamingdata/Fingerprints.kt index 7a735fcb4..36ae9360b 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/spoof/streamingdata/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/spoof/streamingdata/Fingerprints.kt @@ -15,6 +15,37 @@ import com.android.tools.smali.dexlib2.iface.reference.MethodReference const val STREAMING_DATA_INTERFACE = "Lcom/google/protos/youtube/api/innertube/StreamingDataOuterClass${'$'}StreamingData;" +internal val buildInitPlaybackRequestFingerprint = legacyFingerprint( + name = "buildInitPlaybackRequestFingerprint", + returnType = "Lorg/chromium/net/UrlRequest\$Builder;", + opcodes = listOf( + Opcode.MOVE_RESULT_OBJECT, + Opcode.IGET_OBJECT, // Moves the request URI string to a register to build the request with. + ), + strings = listOf( + "Content-Type", + "Range", + ), +) + +internal val buildPlayerRequestURIFingerprint = legacyFingerprint( + name = "buildPlayerRequestURIFingerprint", + returnType = "Ljava/lang/String;", + strings = listOf( + "key", + "asig", + ), + customFingerprint = { method, _ -> + indexOfToStringInstruction(method) >= 0 + }, +) + +internal fun indexOfToStringInstruction(method: Method) = + method.indexOfFirstInstruction { + opcode == Opcode.INVOKE_VIRTUAL && + getReference().toString() == "Landroid/net/Uri;->toString()Ljava/lang/String;" + } + internal val buildMediaDataSourceFingerprint = legacyFingerprint( name = "buildMediaDataSourceFingerprint", accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/streamingdata/SpoofStreamingDataPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/streamingdata/SpoofStreamingDataPatch.kt index a2ba66890..c82bbb612 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/streamingdata/SpoofStreamingDataPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/streamingdata/SpoofStreamingDataPatch.kt @@ -1,5 +1,7 @@ package app.revanced.patches.youtube.utils.fix.streamingdata +import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction +import app.revanced.patches.shared.extension.Constants.PATCHES_PATH import app.revanced.patches.shared.spoof.streamingdata.baseSpoofStreamingDataPatch import app.revanced.patches.shared.spoof.useragent.baseSpoofUserAgentPatch import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE @@ -7,6 +9,7 @@ import app.revanced.patches.youtube.utils.compatibility.Constants.YOUTUBE_PACKAG import app.revanced.patches.youtube.utils.patch.PatchList.SPOOF_STREAMING_DATA import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference import app.revanced.patches.youtube.utils.settings.settingsPatch +import app.revanced.util.findMethodOrThrow val spoofStreamingDataPatch = baseSpoofStreamingDataPatch( { @@ -18,6 +21,13 @@ val spoofStreamingDataPatch = baseSpoofStreamingDataPatch( ) }, { + findMethodOrThrow("$PATCHES_PATH/PatchStatus;") { + name == "SpoofStreamingDataAndroidOnlyDefaultBoolean" + }.replaceInstruction( + 0, + "const/4 v0, 0x1" + ) + addPreference( arrayOf( "SETTINGS: SPOOF_STREAMING_DATA" diff --git a/patches/src/main/resources/youtube/settings/host/values/arrays.xml b/patches/src/main/resources/youtube/settings/host/values/arrays.xml index d5f4ce10f..8538bf654 100644 --- a/patches/src/main/resources/youtube/settings/host/values/arrays.xml +++ b/patches/src/main/resources/youtube/settings/host/values/arrays.xml @@ -235,13 +235,23 @@ HEART_TINT HIDDEN - - @string/revanced_spoof_streaming_data_type_entry_ios + @string/revanced_spoof_streaming_data_type_entry_android_unplugged @string/revanced_spoof_streaming_data_type_entry_android_vr - + + ANDROID_UNPLUGGED + ANDROID_VR + + + @string/revanced_spoof_streaming_data_type_entry_ios + @string/revanced_spoof_streaming_data_type_entry_ios_unplugged + @string/revanced_spoof_streaming_data_type_entry_android_unplugged + @string/revanced_spoof_streaming_data_type_entry_android_vr + + IOS + IOS_UNPLUGGED ANDROID_UNPLUGGED ANDROID_VR diff --git a/patches/src/main/resources/youtube/settings/host/values/strings.xml b/patches/src/main/resources/youtube/settings/host/values/strings.xml index fa7c5f4a5..57025c2a6 100644 --- a/patches/src/main/resources/youtube/settings/host/values/strings.xml +++ b/patches/src/main/resources/youtube/settings/host/values/strings.xml @@ -1914,14 +1914,20 @@ Tap the continue button and allow optimization changes." Default client iOS iOS Music + iOS TV + Android Creator Android TV Android VR Spoofing side effects • Not yet found. - "• Audio track menu is missing. -• Stable volume is not available." - "• Audio track menu is missing. -• Stable volume is not available." + • Movies or paid videos may not play. + "• Audio track menu is missing. +• Stable volume is not available. +• Disable forced auto audio tracks is not available." + Use Android clients only + Android clients are used to fetch streaming data. + Android and iOS clients are used to fetch streaming data. + Turning off this setting may cause video playback issues. Show in Stats for nerds Client used to fetch streaming data is shown in Stats for nerds. Client used to fetch streaming data is hidden in Stats for nerds. diff --git a/patches/src/main/resources/youtube/settings/xml/revanced_prefs.xml b/patches/src/main/resources/youtube/settings/xml/revanced_prefs.xml index bda85ece1..dbe7b8c0e 100644 --- a/patches/src/main/resources/youtube/settings/xml/revanced_prefs.xml +++ b/patches/src/main/resources/youtube/settings/xml/revanced_prefs.xml @@ -791,8 +791,9 @@