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 @@