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