= arrayOf(
- ANDROID_VR,
- ANDROID_MUSIC,
- )
}
}
}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/WebClient.kt b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/YouTubeWebClient.kt
similarity index 97%
rename from extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/WebClient.kt
rename to extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/YouTubeWebClient.kt
index f39a56c60..331d1d9a4 100644
--- a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/WebClient.kt
+++ b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/YouTubeWebClient.kt
@@ -4,7 +4,7 @@ package app.revanced.extension.shared.patches.client
* Used to fetch video information.
*/
@Suppress("unused")
-object WebClient {
+object YouTubeWebClient {
/**
* This user agent does not require a PoToken in [ClientType.MWEB]
* https://github.com/yt-dlp/yt-dlp/blob/0b6b7742c2e7f2a1fcb0b54ef3dd484bab404b3f/yt_dlp/extractor/youtube.py#L259
diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/BlockRequestPatch.java b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/BlockRequestPatch.java
new file mode 100644
index 000000000..c0728522d
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/BlockRequestPatch.java
@@ -0,0 +1,110 @@
+package app.revanced.extension.shared.patches.spoof;
+
+import android.net.Uri;
+
+import app.revanced.extension.shared.patches.PatchStatus;
+import app.revanced.extension.shared.settings.BaseSettings;
+import app.revanced.extension.shared.utils.Logger;
+import app.revanced.extension.shared.utils.PackageUtils;
+
+@SuppressWarnings("unused")
+public class BlockRequestPatch {
+ /**
+ * Used in YouTube.
+ */
+ public static final boolean SPOOF_STREAMING_DATA =
+ BaseSettings.SPOOF_STREAMING_DATA.get() && PatchStatus.SpoofStreamingData();
+
+ /**
+ * Used in YouTube Music.
+ */
+ public static final boolean SPOOF_CLIENT =
+ BaseSettings.SPOOF_CLIENT.get() && PatchStatus.SpoofClient();
+
+ /**
+ * In order to load the action bar normally,
+ * Some versions must block the initplayback request.
+ */
+ private static final boolean IS_7_17_OR_GREATER =
+ PackageUtils.getAppVersionName().compareTo("7.17.00") >= 0;
+
+ private static final boolean IS_YOUTUBE_BUTTON =
+ BaseSettings.SPOOF_CLIENT_TYPE.get().isYouTubeButton();
+
+ private static final boolean BLOCK_REQUEST;
+
+ static {
+ if (SPOOF_STREAMING_DATA) {
+ BLOCK_REQUEST = true;
+ } else {
+ if (!SPOOF_CLIENT) {
+ BLOCK_REQUEST = false;
+ } else {
+ if (!IS_7_17_OR_GREATER && !IS_YOUTUBE_BUTTON) {
+ // If the current version is lower than 7.16 and the client action button type is not YouTubeButton,
+ // the initplayback request must be blocked.
+ BLOCK_REQUEST = true;
+ } else {
+ // If the current version is higher than 7.17,
+ // the initplayback request must always be blocked.
+ BLOCK_REQUEST = IS_7_17_OR_GREATER;
+ }
+ }
+ }
+ }
+
+ /**
+ * 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/music/patches/misc/SpoofClientPatch.java b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/SpoofClientPatch.java
similarity index 53%
rename from extensions/shared/src/main/java/app/revanced/extension/music/patches/misc/SpoofClientPatch.java
rename to extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/SpoofClientPatch.java
index e3e651a36..675068ffd 100644
--- a/extensions/shared/src/main/java/app/revanced/extension/music/patches/misc/SpoofClientPatch.java
+++ b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/SpoofClientPatch.java
@@ -1,66 +1,98 @@
-package app.revanced.extension.music.patches.misc;
+package app.revanced.extension.shared.patches.spoof;
-import app.revanced.extension.music.patches.misc.client.AppClient.ClientType;
+import app.revanced.extension.shared.patches.client.MusicAppClient.ClientType;
import app.revanced.extension.music.settings.Settings;
@SuppressWarnings("unused")
-public class SpoofClientPatch {
+public class SpoofClientPatch extends BlockRequestPatch {
private static final ClientType CLIENT_TYPE = Settings.SPOOF_CLIENT_TYPE.get();
- public static final boolean SPOOF_CLIENT = Settings.SPOOF_CLIENT.get();
/**
* Injection point.
*/
- public static int getClientTypeId(int originalClientTypeId) {
+ public static int getClientId(int original) {
if (SPOOF_CLIENT) {
return CLIENT_TYPE.id;
}
- return originalClientTypeId;
+ return original;
}
/**
* Injection point.
*/
- public static String getClientVersion(String originalClientVersion) {
+ public static String getClientVersion(String original) {
if (SPOOF_CLIENT) {
return CLIENT_TYPE.clientVersion;
}
- return originalClientVersion;
+ return original;
}
/**
* Injection point.
*/
- public static String getClientModel(String originalClientModel) {
+ public static String getDeviceBrand(String original) {
+ if (SPOOF_CLIENT) {
+ return CLIENT_TYPE.deviceBrand;
+ }
+
+ return original;
+ }
+
+ /**
+ * Injection point.
+ */
+ public static String getDeviceMake(String original) {
+ if (SPOOF_CLIENT) {
+ return CLIENT_TYPE.deviceMake;
+ }
+
+ return original;
+ }
+
+ /**
+ * Injection point.
+ */
+ public static String getDeviceModel(String original) {
if (SPOOF_CLIENT) {
return CLIENT_TYPE.deviceModel;
}
- return originalClientModel;
+ return original;
}
/**
* Injection point.
*/
- public static String getOsVersion(String originalOsVersion) {
+ public static String getOsName(String original) {
+ if (SPOOF_CLIENT) {
+ return CLIENT_TYPE.osName;
+ }
+
+ return original;
+ }
+
+ /**
+ * Injection point.
+ */
+ public static String getOsVersion(String original) {
if (SPOOF_CLIENT) {
return CLIENT_TYPE.osVersion;
}
- return originalOsVersion;
+ return original;
}
/**
* Injection point.
*/
- public static String getUserAgent(String originalUserAgent) {
+ public static String getUserAgent(String original) {
if (SPOOF_CLIENT) {
return CLIENT_TYPE.userAgent;
}
- return originalUserAgent;
+ return original;
}
/**
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 124790b88..d6db51b90 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,11 +1,8 @@
package app.revanced.extension.shared.patches.spoof;
-import static app.revanced.extension.shared.patches.PatchStatus.SpoofStreamingDataMusic;
-
import android.net.Uri;
import android.text.TextUtils;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.nio.ByteBuffer;
@@ -13,7 +10,7 @@ import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
-import app.revanced.extension.shared.patches.client.AppClient.ClientType;
+import app.revanced.extension.shared.patches.client.YouTubeAppClient.ClientType;
import app.revanced.extension.shared.patches.spoof.requests.StreamingDataRequest;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.settings.Setting;
@@ -21,10 +18,7 @@ import app.revanced.extension.shared.utils.Logger;
import app.revanced.extension.shared.utils.Utils;
@SuppressWarnings("unused")
-public class SpoofStreamingDataPatch {
- private static final boolean SPOOF_STREAMING_DATA = BaseSettings.SPOOF_STREAMING_DATA.get();
- private static final boolean SPOOF_STREAMING_DATA_YOUTUBE = SPOOF_STREAMING_DATA && !SpoofStreamingDataMusic();
- private static final boolean SPOOF_STREAMING_DATA_MUSIC = SPOOF_STREAMING_DATA && SpoofStreamingDataMusic();
+public class SpoofStreamingDataPatch extends BlockRequestPatch {
private static final String PO_TOKEN =
BaseSettings.SPOOF_STREAMING_DATA_PO_TOKEN.get();
private static final String VISITOR_DATA =
@@ -50,58 +44,6 @@ public class SpoofStreamingDataPatch {
}
});
- /**
- * 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) {
- // An exception may be thrown when the /get_watch request is blocked when connected to Wi-Fi in YouTube Music.
- if (SPOOF_STREAMING_DATA_YOUTUBE || Utils.getNetworkType() == Utils.NetworkType.MOBILE) {
- 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.
*/
@@ -120,73 +62,20 @@ public class SpoofStreamingDataPatch {
return false;
}
- private static volatile String auth = "";
- private static volatile Map requestHeader;
-
- private static final String AUTHORIZATION_HEADER = "Authorization";
-
- private static final String[] REQUEST_HEADER_KEYS = {
- AUTHORIZATION_HEADER,
- "X-GOOG-API-FORMAT-VERSION",
- "X-Goog-Visitor-Id"
+ /**
+ * Parameters causing playback issues.
+ */
+ private static final String[] PATH_NO_VIDEO_ID = {
+ "ad_break", // This request fetches a list of times when ads can be displayed.
+ "get_drm_license", // Waiting for a paid video to start.
+ "heartbeat", // This request determines whether to pause playback when the user is AFK.
+ "refresh", // Waiting for a livestream to start.
};
- /**
- * If the /get_watch request is not blocked,
- * fetchRequest will not be invoked at the point where the video starts.
- *
- * An additional method is used to invoke fetchRequest in YouTube Music:
- * 1. Save the requestHeader in a field.
- * 2. Invoke fetchRequest with the videoId used in PlaybackStartDescriptor.
- *
- *
- * @param requestHeaders Save the request Headers used for login to a field.
- * Only used in YouTube Music where login is required.
- */
- private static void setRequestHeaders(Map requestHeaders) {
- if (SPOOF_STREAMING_DATA_MUSIC) {
- try {
- // Save requestHeaders whenever an account is switched.
- String authorization = requestHeaders.get(AUTHORIZATION_HEADER);
- if (authorization == null || auth.equals(authorization)) {
- return;
- }
- for (String key : REQUEST_HEADER_KEYS) {
- if (requestHeaders.get(key) == null) {
- return;
- }
- }
- auth = authorization;
- requestHeader = requestHeaders;
- } catch (Exception ex) {
- Logger.printException(() -> "setRequestHeaders failure", ex);
- }
- }
- }
-
- /**
- * Injection point.
- */
- public static void fetchStreams(@NonNull String videoId) {
- if (SPOOF_STREAMING_DATA_MUSIC) {
- try {
- if (requestHeader != null) {
- StreamingDataRequest.fetchRequest(videoId, requestHeader, VISITOR_DATA, PO_TOKEN);
- } else {
- Logger.printDebug(() -> "Ignoring request with no header.");
- }
- } catch (Exception ex) {
- Logger.printException(() -> "fetchStreams failure", ex);
- }
- }
- }
-
/**
* Injection point.
*/
public static void fetchStreams(String url, Map requestHeaders) {
- setRequestHeaders(requestHeaders);
-
if (SPOOF_STREAMING_DATA) {
try {
Uri uri = Uri.parse(url);
@@ -195,11 +84,7 @@ public class SpoofStreamingDataPatch {
return;
}
- // 'get_drm_license' has no video id and appears to happen when waiting for a paid video to start.
- // 'heartbeat' has no video id and appears to be only after playback has started.
- // 'refresh' has no video id and appears to happen when waiting for a livestream to start.
- // 'ad_break' has no video id.
- if (path.contains("get_drm_license") || path.contains("heartbeat") || path.contains("refresh") || path.contains("ad_break")) {
+ if (Utils.containsAny(path, PATH_NO_VIDEO_ID)) {
Logger.printDebug(() -> "Ignoring path: " + path);
return;
}
@@ -262,7 +147,7 @@ public class SpoofStreamingDataPatch {
* Called after {@link #getStreamingData(String)}.
*/
public static void setApproxDurationMs(String videoId, long approxDurationMs) {
- if (SPOOF_STREAMING_DATA_YOUTUBE && approxDurationMs != Long.MAX_VALUE) {
+ if (SPOOF_STREAMING_DATA && approxDurationMs != Long.MAX_VALUE) {
approxDurationMsMap.put(videoId, approxDurationMs);
Logger.printDebug(() -> "New approxDurationMs loaded, video id: " + videoId + ", video length: " + approxDurationMs);
}
@@ -284,7 +169,7 @@ public class SpoofStreamingDataPatch {
* Called after {@link #getStreamingData(String)}.
*/
public static long getApproxDurationMs(String videoId) {
- if (SPOOF_STREAMING_DATA_YOUTUBE && 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.kt b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/requests/PlayerRoutes.kt
index 5bd228d84..158354778 100644
--- a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/requests/PlayerRoutes.kt
+++ b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/requests/PlayerRoutes.kt
@@ -1,7 +1,7 @@
package app.revanced.extension.shared.patches.spoof.requests
-import app.revanced.extension.shared.patches.client.AppClient
-import app.revanced.extension.shared.patches.client.WebClient
+import app.revanced.extension.shared.patches.client.YouTubeAppClient
+import app.revanced.extension.shared.patches.client.YouTubeWebClient
import app.revanced.extension.shared.requests.Requester
import app.revanced.extension.shared.requests.Route
import app.revanced.extension.shared.requests.Route.CompiledRoute
@@ -69,7 +69,7 @@ object PlayerRoutes {
@JvmStatic
fun createApplicationRequestBody(
- clientType: AppClient.ClientType,
+ clientType: YouTubeAppClient.ClientType,
videoId: String,
playlistId: String? = null,
botGuardPoToken: String = "",
@@ -130,7 +130,7 @@ object PlayerRoutes {
@JvmStatic
fun createWebInnertubeBody(
- clientType: WebClient.ClientType,
+ clientType: YouTubeWebClient.ClientType,
videoId: String
): ByteArray {
val innerTubeBody = JSONObject()
@@ -161,7 +161,7 @@ object PlayerRoutes {
@JvmStatic
fun getPlayerResponseConnectionFromRoute(
route: CompiledRoute,
- clientType: AppClient.ClientType
+ clientType: YouTubeAppClient.ClientType
): HttpURLConnection {
return getPlayerResponseConnectionFromRoute(
route,
@@ -174,7 +174,7 @@ object PlayerRoutes {
@JvmStatic
fun getPlayerResponseConnectionFromRoute(
route: CompiledRoute,
- clientType: WebClient.ClientType
+ clientType: YouTubeWebClient.ClientType
): HttpURLConnection {
return getPlayerResponseConnectionFromRoute(
route,
diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/requests/StreamingDataRequest.kt b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/requests/StreamingDataRequest.kt
index bd82b3fee..88fc71da2 100644
--- a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/requests/StreamingDataRequest.kt
+++ b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/requests/StreamingDataRequest.kt
@@ -1,8 +1,7 @@
package app.revanced.extension.shared.patches.spoof.requests
import androidx.annotation.GuardedBy
-import app.revanced.extension.shared.patches.client.AppClient
-import app.revanced.extension.shared.patches.client.AppClient.availableClientTypes
+import app.revanced.extension.shared.patches.client.YouTubeAppClient
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes.GET_STREAMING_DATA
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes.createApplicationRequestBody
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes.getPlayerResponseConnectionFromRoute
@@ -93,16 +92,16 @@ class StreamingDataRequest private constructor(
"X-GOOG-API-FORMAT-VERSION",
VISITOR_ID_HEADER
)
- private val SPOOF_STREAMING_DATA_TYPE: AppClient.ClientType =
+ private val SPOOF_STREAMING_DATA_TYPE: YouTubeAppClient.ClientType =
BaseSettings.SPOOF_STREAMING_DATA_TYPE.get()
- private val CLIENT_ORDER_TO_USE: Array =
- availableClientTypes(SPOOF_STREAMING_DATA_TYPE)
+ private val CLIENT_ORDER_TO_USE: Array =
+ YouTubeAppClient.availableClientTypes(SPOOF_STREAMING_DATA_TYPE)
private val DEFAULT_CLIENT_IS_ANDROID_VR_NO_AUTH: Boolean =
- SPOOF_STREAMING_DATA_TYPE == AppClient.ClientType.ANDROID_VR_NO_AUTH
+ SPOOF_STREAMING_DATA_TYPE == YouTubeAppClient.ClientType.ANDROID_VR_NO_AUTH
- private var lastSpoofedClientType: AppClient.ClientType? = null
+ private var lastSpoofedClientType: YouTubeAppClient.ClientType? = null
/**
@@ -163,7 +162,7 @@ class StreamingDataRequest private constructor(
}
private fun send(
- clientType: AppClient.ClientType,
+ clientType: YouTubeAppClient.ClientType,
videoId: String,
playerHeaders: Map,
visitorId: String,
@@ -279,7 +278,7 @@ class StreamingDataRequest private constructor(
} else {
BufferedInputStream(connection.inputStream).use { inputStream ->
ByteArrayOutputStream().use { stream ->
- val buffer = ByteArray(4096)
+ val buffer = ByteArray(2048)
var bytesRead: Int
while ((inputStream.read(buffer)
.also { bytesRead = it }) >= 0
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 63c3c5b05..a8c3e5fbd 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
@@ -5,7 +5,8 @@ import static java.lang.Boolean.TRUE;
import static app.revanced.extension.shared.patches.PatchStatus.HideFullscreenAdsDefaultBoolean;
import app.revanced.extension.shared.patches.ReturnYouTubeUsernamePatch.DisplayFormat;
-import app.revanced.extension.shared.patches.client.AppClient.ClientType;
+import app.revanced.extension.shared.patches.client.MusicAppClient;
+import app.revanced.extension.shared.patches.client.YouTubeAppClient;
import app.revanced.extension.shared.patches.spoof.SpoofStreamingDataPatch.AudioStreamLanguageOverrideAvailability;
/**
@@ -25,6 +26,28 @@ public class BaseSettings {
public static final EnumSetting REVANCED_LANGUAGE = new EnumSetting<>("revanced_language", AppLanguage.DEFAULT, true);
+ /**
+ * These settings are used by YouTube Music.
+ * Some patches are in a shared path, so they are declared here.
+ */
+ public static final BooleanSetting SPOOF_CLIENT = new BooleanSetting("revanced_spoof_client", TRUE, true);
+ public static final EnumSetting SPOOF_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_client_type", MusicAppClient.ClientType.IOS_MUSIC_6_21, true);
+
+ /**
+ * These settings are used by YouTube.
+ * Some patches are in a shared path, so they are declared here.
+ */
+ 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_LANGUAGE = new EnumSetting<>("revanced_spoof_streaming_data_language", AppLanguage.DEFAULT, new AudioStreamLanguageOverrideAvailability());
+ public static final BooleanSetting SPOOF_STREAMING_DATA_IOS_FORCE_AVC = new BooleanSetting("revanced_spoof_streaming_data_ios_force_avc", FALSE, true,
+ "revanced_spoof_streaming_data_ios_force_avc_user_dialog_message");
+ public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_streaming_data_stats_for_nerds", TRUE);
+ // Client type must be last spoof setting due to cyclic references.
+ public static final EnumSetting SPOOF_STREAMING_DATA_TYPE = new EnumSetting<>("revanced_spoof_streaming_data_type", YouTubeAppClient.ClientType.ANDROID_VR, true);
+
+ public static final StringSetting SPOOF_STREAMING_DATA_PO_TOKEN = new StringSetting("revanced_spoof_streaming_data_po_token", "", true);
+ public static final StringSetting SPOOF_STREAMING_DATA_VISITOR_DATA = new StringSetting("revanced_spoof_streaming_data_visitor_data", "", true);
+
/**
* These settings are used by YouTube and YouTube Music.
*/
@@ -39,17 +62,6 @@ public class BaseSettings {
public static final EnumSetting RETURN_YOUTUBE_USERNAME_DISPLAY_FORMAT = new EnumSetting<>("revanced_return_youtube_username_display_format", DisplayFormat.USERNAME_ONLY, true);
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_LANGUAGE = new EnumSetting<>("revanced_spoof_streaming_data_language", AppLanguage.DEFAULT, new AudioStreamLanguageOverrideAvailability());
- public static final BooleanSetting SPOOF_STREAMING_DATA_IOS_FORCE_AVC = new BooleanSetting("revanced_spoof_streaming_data_ios_force_avc", FALSE, true,
- "revanced_spoof_streaming_data_ios_force_avc_user_dialog_message");
- public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_streaming_data_stats_for_nerds", TRUE);
- // Client type must be last spoof setting due to cyclic references.
- public static final EnumSetting SPOOF_STREAMING_DATA_TYPE = new EnumSetting<>("revanced_spoof_streaming_data_type", ClientType.ANDROID_VR, true);
-
- public static final StringSetting SPOOF_STREAMING_DATA_PO_TOKEN = new StringSetting("revanced_spoof_streaming_data_po_token", "", true);
- public static final StringSetting SPOOF_STREAMING_DATA_VISITOR_DATA = new StringSetting("revanced_spoof_streaming_data_visitor_data", "", true);
-
/**
* @noinspection DeprecatedIsStillUsed
*/
diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/requests/VideoDetailsRequest.kt b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/requests/VideoDetailsRequest.kt
index e7eddf002..1fc0f0476 100644
--- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/requests/VideoDetailsRequest.kt
+++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/requests/VideoDetailsRequest.kt
@@ -2,7 +2,7 @@ package app.revanced.extension.youtube.patches.general.requests
import android.annotation.SuppressLint
import androidx.annotation.GuardedBy
-import app.revanced.extension.shared.patches.client.WebClient
+import app.revanced.extension.shared.patches.client.YouTubeWebClient
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes
import app.revanced.extension.shared.requests.Requester
import app.revanced.extension.shared.utils.Logger
@@ -81,7 +81,7 @@ class VideoDetailsRequest private constructor(
private fun sendRequest(videoId: String): JSONObject? {
val startTime = System.currentTimeMillis()
- val clientType = WebClient.ClientType.MWEB
+ val clientType = YouTubeWebClient.ClientType.MWEB
val clientTypeName = clientType.name
Logger.printDebug { "Fetching video details request for: $videoId, using client: $clientTypeName" }
diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/requests/MusicRequest.kt b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/requests/MusicRequest.kt
index 5ceb2615f..d48ca8b51 100644
--- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/requests/MusicRequest.kt
+++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/requests/MusicRequest.kt
@@ -2,8 +2,8 @@ package app.revanced.extension.youtube.patches.video.requests
import android.annotation.SuppressLint
import androidx.annotation.GuardedBy
-import app.revanced.extension.shared.patches.client.AppClient
-import app.revanced.extension.shared.patches.client.WebClient
+import app.revanced.extension.shared.patches.client.YouTubeAppClient
+import app.revanced.extension.shared.patches.client.YouTubeWebClient
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes
import app.revanced.extension.shared.requests.Requester
import app.revanced.extension.shared.utils.Logger
@@ -119,7 +119,7 @@ class MusicRequest private constructor(
Objects.requireNonNull(videoId)
val startTime = System.currentTimeMillis()
- val clientType = AppClient.ClientType.ANDROID_VR
+ val clientType = YouTubeAppClient.ClientType.ANDROID_VR
val clientTypeName = clientType.name
Logger.printDebug { "Fetching playlist request for: $videoId, using client: $clientTypeName" }
@@ -163,7 +163,7 @@ class MusicRequest private constructor(
Objects.requireNonNull(videoId)
val startTime = System.currentTimeMillis()
- val clientType = WebClient.ClientType.MWEB
+ val clientType = YouTubeWebClient.ClientType.MWEB
val clientTypeName = clientType.name
Logger.printDebug { "Fetching microformat request for: $videoId, using client: $clientTypeName" }
diff --git a/patches/src/main/kotlin/app/revanced/patches/music/utils/fix/client/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/music/utils/fix/client/Fingerprints.kt
index fcbbdf9ac..2e75b52cd 100644
--- a/patches/src/main/kotlin/app/revanced/patches/music/utils/fix/client/Fingerprints.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/music/utils/fix/client/Fingerprints.kt
@@ -2,13 +2,9 @@ package app.revanced.patches.music.utils.fix.client
import app.revanced.patches.shared.spoof.useragent.baseSpoofUserAgentPatch
import app.revanced.util.fingerprint.legacyFingerprint
-import app.revanced.util.getReference
-import app.revanced.util.indexOfFirstInstruction
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
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 createPlayerRequestBodyFingerprint = legacyFingerprint(
name = "createPlayerRequestBodyFingerprint",
@@ -22,26 +18,6 @@ internal val createPlayerRequestBodyFingerprint = legacyFingerprint(
strings = listOf("ms"),
)
-internal val createPlayerRequestBodyWithVersionReleaseFingerprint = legacyFingerprint(
- name = "createPlayerRequestBodyWithVersionReleaseFingerprint",
- returnType = "V",
- accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
- parameters = listOf("L"),
- strings = listOf("Google Inc."),
- customFingerprint = { method, _ ->
- indexOfBuildInstruction(method) >= 0
- },
-)
-
-fun indexOfBuildInstruction(method: Method) =
- method.indexOfFirstInstruction {
- val reference = getReference()
- opcode == Opcode.INVOKE_VIRTUAL &&
- reference?.name == "build" &&
- reference.parameterTypes.isEmpty() &&
- reference.returnType.startsWith("L")
- }
-
internal val setPlayerRequestClientTypeFingerprint = legacyFingerprint(
name = "setPlayerRequestClientTypeFingerprint",
opcodes = listOf(
diff --git a/patches/src/main/kotlin/app/revanced/patches/music/utils/fix/client/SpoofClientPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/utils/fix/client/SpoofClientPatch.kt
index af3dc5463..f689c5dc5 100644
--- a/patches/src/main/kotlin/app/revanced/patches/music/utils/fix/client/SpoofClientPatch.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/music/utils/fix/client/SpoofClientPatch.kt
@@ -3,24 +3,28 @@ package app.revanced.patches.music.utils.fix.client
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
-import app.revanced.patcher.extensions.InstructionExtensions.instructions
+import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.bytecodePatch
+import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
-import app.revanced.patches.music.utils.compatibility.Constants
-import app.revanced.patches.music.utils.extension.Constants.MISC_PATH
+import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.patch.PatchList.SPOOF_CLIENT
import app.revanced.patches.music.utils.playbackSpeedBottomSheetFingerprint
-import app.revanced.patches.music.utils.playservice.is_7_25_or_greater
-import app.revanced.patches.music.utils.playservice.versionCheckPatch
import app.revanced.patches.music.utils.settings.CategoryType
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.spoof.blockrequest.blockRequestPatch
import app.revanced.patches.shared.createPlayerRequestBodyWithModelFingerprint
+import app.revanced.patches.shared.extension.Constants.PATCHES_PATH
+import app.revanced.patches.shared.extension.Constants.SPOOF_PATH
+import app.revanced.patches.shared.indexOfBrandInstruction
+import app.revanced.patches.shared.indexOfManufacturerInstruction
import app.revanced.patches.shared.indexOfModelInstruction
-import app.revanced.util.Utils.printWarn
+import app.revanced.patches.shared.indexOfReleaseInstruction
+import app.revanced.util.findMethodOrThrow
import app.revanced.util.fingerprint.matchOrThrow
import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.fingerprint.mutableClassOrThrow
@@ -32,109 +36,118 @@ import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
+import com.android.tools.smali.dexlib2.iface.instruction.Instruction
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.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
+import com.android.tools.smali.dexlib2.iface.reference.Reference
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
private const val EXTENSION_CLASS_DESCRIPTOR =
- "$MISC_PATH/SpoofClientPatch;"
+ "$SPOOF_PATH/SpoofClientPatch;"
private const val CLIENT_INFO_CLASS_DESCRIPTOR =
"Lcom/google/protos/youtube/api/innertube/InnertubeContext\$ClientInfo;"
@Suppress("unused")
val spoofClientPatch = bytecodePatch(
SPOOF_CLIENT.title,
- SPOOF_CLIENT.summary,
- false,
+ SPOOF_CLIENT.summary
) {
- compatibleWith(
- Constants.YOUTUBE_MUSIC_PACKAGE_NAME(
- "6.20.51",
- "6.29.59",
- "6.42.55",
- "6.51.53",
- "7.16.53",
- ),
- )
+ compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(
settingsPatch,
- versionCheckPatch,
+ blockRequestPatch,
)
execute {
- if (is_7_25_or_greater) {
- printWarn("\"${SPOOF_CLIENT.title}\" is not supported in this version. Use YouTube Music 7.24.51 or earlier.")
- return@execute
- }
+ lateinit var clientInfoReference: Reference
+ lateinit var clientIdReference: Reference
+ lateinit var clientVersionReference: Reference
+ lateinit var deviceBrandReference: Reference
+ lateinit var deviceMakeReference: Reference
+ lateinit var deviceModelReference: Reference
+ lateinit var osNameReference: Reference
+ lateinit var osVersionReference: Reference
+
+ fun MutableMethod.getFieldReference(index: Int) =
+ getInstruction(index).reference
// region Get field references to be used below.
- val (clientInfoField, clientInfoClientTypeField, clientInfoClientVersionField) =
- setPlayerRequestClientTypeFingerprint.matchOrThrow().let { result ->
- with(result.method) {
- // Field in the player request object that holds the client info object.
- val clientInfoField = instructions.find { instruction ->
- // requestMessage.clientInfo = clientInfoBuilder.build();
- instruction.opcode == Opcode.IPUT_OBJECT &&
- instruction.getReference()?.type == CLIENT_INFO_CLASS_DESCRIPTOR
- }?.getReference()
- ?: throw PatchException("Could not find clientInfoField")
-
- // Client info object's client type field.
- val clientInfoClientTypeField =
- getInstruction(result.patternMatch!!.endIndex)
- .getReference()
- ?: throw PatchException("Could not find clientInfoClientTypeField")
-
- val clientInfoVersionIndex = result.stringMatches!!.first().index
- val clientInfoVersionRegister =
- getInstruction(clientInfoVersionIndex).registerA
- val clientInfoClientVersionFieldIndex =
- indexOfFirstInstructionOrThrow(clientInfoVersionIndex) {
- opcode == Opcode.IPUT_OBJECT &&
- (this as TwoRegisterInstruction).registerA == clientInfoVersionRegister
- }
-
- // Client info object's client version field.
- val clientInfoClientVersionField =
- getInstruction(clientInfoClientVersionFieldIndex)
- .getReference()
- ?: throw PatchException("Could not find clientInfoClientVersionField")
-
- Triple(clientInfoField, clientInfoClientTypeField, clientInfoClientVersionField)
- }
- }
-
- val clientInfoClientModelField =
- with(createPlayerRequestBodyWithModelFingerprint.methodOrThrow()) {
- // The next IPUT_OBJECT instruction after getting the client model is setting the client model field.
- val clientInfoClientModelIndex =
- indexOfFirstInstructionOrThrow(indexOfModelInstruction(this)) {
- val reference = getReference()
- opcode == Opcode.IPUT_OBJECT &&
- reference?.definingClass == CLIENT_INFO_CLASS_DESCRIPTOR &&
- reference.type == "Ljava/lang/String;"
- }
- getInstruction(clientInfoClientModelIndex).reference
- }
-
- val clientInfoOsVersionField =
- with(createPlayerRequestBodyWithVersionReleaseFingerprint.methodOrThrow()) {
- val buildIndex = indexOfBuildInstruction(this)
- val clientInfoOsVersionIndex = indexOfFirstInstructionOrThrow(buildIndex - 5) {
- val reference = getReference()
+ setPlayerRequestClientTypeFingerprint.matchOrThrow().let {
+ it.method.apply {
+ val clientInfoIndex = indexOfFirstInstructionOrThrow {
opcode == Opcode.IPUT_OBJECT &&
- reference?.definingClass == CLIENT_INFO_CLASS_DESCRIPTOR &&
- reference.type == "Ljava/lang/String;"
+ getReference()?.type == CLIENT_INFO_CLASS_DESCRIPTOR
}
- getInstruction(clientInfoOsVersionIndex).reference
+ val clientIdIndex = it.patternMatch!!.endIndex
+ val dummyClientVersionIndex = it.stringMatches!!.first().index
+ val dummyClientVersionRegister =
+ getInstruction(dummyClientVersionIndex).registerA
+ val clientVersionIndex =
+ indexOfFirstInstructionOrThrow(dummyClientVersionIndex) {
+ opcode == Opcode.IPUT_OBJECT &&
+ (this as TwoRegisterInstruction).registerA == dummyClientVersionRegister
+ }
+
+ clientInfoReference =
+ getFieldReference(clientInfoIndex)
+ clientIdReference =
+ getFieldReference(clientIdIndex)
+ clientVersionReference =
+ getFieldReference(clientVersionIndex)
}
+ }
+
+ fun MutableMethod.getClientInfoIndex(
+ startIndex: Int,
+ reversed: Boolean = false
+ ): Int {
+ val filter: Instruction.() -> Boolean = {
+ val reference = getReference()
+ opcode == Opcode.IPUT_OBJECT &&
+ reference?.definingClass == CLIENT_INFO_CLASS_DESCRIPTOR &&
+ reference.type == "Ljava/lang/String;"
+ }
+ return if (reversed) {
+ indexOfFirstInstructionReversedOrThrow(startIndex, filter)
+ } else {
+ indexOfFirstInstructionOrThrow(startIndex, filter)
+ }
+ }
+
+ createPlayerRequestBodyWithModelFingerprint.methodOrThrow().apply {
+ val buildManufacturerIndex =
+ indexOfManufacturerInstruction(this)
+ val deviceBrandIndex =
+ getClientInfoIndex(indexOfBrandInstruction(this))
+ val deviceMakeIndex =
+ getClientInfoIndex(buildManufacturerIndex)
+ val deviceModelIndex =
+ getClientInfoIndex(indexOfModelInstruction(this))
+ val chipSetIndex =
+ getClientInfoIndex(buildManufacturerIndex, true)
+ val osNameIndex =
+ getClientInfoIndex(chipSetIndex - 1, true)
+ val osVersionIndex =
+ getClientInfoIndex(indexOfReleaseInstruction(this))
+
+ deviceBrandReference =
+ getFieldReference(deviceBrandIndex)
+ deviceMakeReference =
+ getFieldReference(deviceMakeIndex)
+ deviceModelReference =
+ getFieldReference(deviceModelIndex)
+ osNameReference =
+ getFieldReference(osNameIndex)
+ osVersionReference =
+ getFieldReference(osVersionIndex)
+ }
// endregion
@@ -181,31 +194,49 @@ val spoofClientPatch = bytecodePatch(
move-result v0
if-eqz v0, :disabled
- iget-object v0, p0, $clientInfoField
+ iget-object v0, p0, $clientInfoReference
- # Set client type to the spoofed value.
- iget v1, v0, $clientInfoClientTypeField
- invoke-static { v1 }, $EXTENSION_CLASS_DESCRIPTOR->getClientTypeId(I)I
+ # Set client id.
+ iget v1, v0, $clientIdReference
+ invoke-static { v1 }, $EXTENSION_CLASS_DESCRIPTOR->getClientId(I)I
move-result v1
- iput v1, v0, $clientInfoClientTypeField
-
- # Set client model to the spoofed value.
- iget-object v1, v0, $clientInfoClientModelField
- invoke-static { v1 }, $EXTENSION_CLASS_DESCRIPTOR->getClientModel(Ljava/lang/String;)Ljava/lang/String;
- move-result-object v1
- iput-object v1, v0, $clientInfoClientModelField
+ iput v1, v0, $clientIdReference
- # Set client version to the spoofed value.
- iget-object v1, v0, $clientInfoClientVersionField
+ # Set client version.
+ iget-object v1, v0, $clientVersionReference
invoke-static { v1 }, $EXTENSION_CLASS_DESCRIPTOR->getClientVersion(Ljava/lang/String;)Ljava/lang/String;
move-result-object v1
- iput-object v1, v0, $clientInfoClientVersionField
+ iput-object v1, v0, $clientVersionReference
- # Set client os version to the spoofed value.
- iget-object v1, v0, $clientInfoOsVersionField
+ # Set device brand.
+ iget-object v1, v0, $deviceBrandReference
+ invoke-static { v1 }, $EXTENSION_CLASS_DESCRIPTOR->getDeviceBrand(Ljava/lang/String;)Ljava/lang/String;
+ move-result-object v1
+ iput-object v1, v0, $deviceBrandReference
+
+ # Set device make.
+ iget-object v1, v0, $deviceMakeReference
+ invoke-static { v1 }, $EXTENSION_CLASS_DESCRIPTOR->getDeviceMake(Ljava/lang/String;)Ljava/lang/String;
+ move-result-object v1
+ iput-object v1, v0, $deviceMakeReference
+
+ # Set device model.
+ iget-object v1, v0, $deviceModelReference
+ invoke-static { v1 }, $EXTENSION_CLASS_DESCRIPTOR->getDeviceModel(Ljava/lang/String;)Ljava/lang/String;
+ move-result-object v1
+ iput-object v1, v0, $deviceModelReference
+
+ # Set os name.
+ iget-object v1, v0, $osNameReference
+ invoke-static { v1 }, $EXTENSION_CLASS_DESCRIPTOR->getOsName(Ljava/lang/String;)Ljava/lang/String;
+ move-result-object v1
+ iput-object v1, v0, $osNameReference
+
+ # Set os version.
+ iget-object v1, v0, $osVersionReference
invoke-static { v1 }, $EXTENSION_CLASS_DESCRIPTOR->getOsVersion(Ljava/lang/String;)Ljava/lang/String;
move-result-object v1
- iput-object v1, v0, $clientInfoOsVersionField
+ iput-object v1, v0, $osVersionReference
:disabled
return-void
@@ -273,10 +304,17 @@ val spoofClientPatch = bytecodePatch(
// endregion
+ findMethodOrThrow("$PATCHES_PATH/PatchStatus;") {
+ name == "SpoofClient"
+ }.replaceInstruction(
+ 0,
+ "const/4 v0, 0x1"
+ )
+
addSwitchPreference(
CategoryType.MISC,
"revanced_spoof_client",
- "false"
+ "true"
)
addPreferenceWithIntent(
CategoryType.MISC,
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
deleted file mode 100644
index a30c435c2..000000000
--- a/patches/src/main/kotlin/app/revanced/patches/music/utils/fix/streamingdata/SpoofStreamingDataPatch.kt
+++ /dev/null
@@ -1,67 +0,0 @@
-package app.revanced.patches.music.utils.fix.streamingdata
-
-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
-import app.revanced.patches.music.utils.settings.CategoryType
-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.music.video.playerresponse.hookPlayerResponse
-import app.revanced.patches.music.video.playerresponse.playerResponseMethodHookPatch
-import app.revanced.patches.shared.extension.Constants.PATCHES_PATH
-import app.revanced.patches.shared.extension.Constants.SPOOF_PATH
-import app.revanced.patches.shared.spoof.streamingdata.baseSpoofStreamingDataPatch
-import app.revanced.patches.shared.spoof.useragent.baseSpoofUserAgentPatch
-import app.revanced.util.findMethodOrThrow
-
-const val EXTENSION_CLASS_DESCRIPTOR =
- "$SPOOF_PATH/SpoofStreamingDataPatch;"
-
-@Suppress("unused")
-val spoofStreamingDataPatch = baseSpoofStreamingDataPatch(
- {
- compatibleWith(COMPATIBLE_PACKAGE)
-
- dependsOn(
- baseSpoofUserAgentPatch(YOUTUBE_MUSIC_PACKAGE_NAME),
- settingsPatch,
- playerResponseMethodHookPatch,
- )
- },
- {
- findMethodOrThrow("$PATCHES_PATH/PatchStatus;") {
- name == "SpoofStreamingDataMusic"
- }.replaceInstruction(
- 0,
- "const/4 v0, 0x1"
- )
-
- hookPlayerResponse(
- "$EXTENSION_CLASS_DESCRIPTOR->fetchStreams(Ljava/lang/String;)V",
- true
- )
-
- addSwitchPreference(
- CategoryType.MISC,
- "revanced_spoof_streaming_data",
- "true"
- )
- addPreferenceWithIntent(
- CategoryType.MISC,
- "revanced_spoof_streaming_data_type",
- "revanced_spoof_streaming_data"
- )
- addSwitchPreference(
- CategoryType.MISC,
- "revanced_spoof_streaming_data_stats_for_nerds",
- "true",
- "revanced_spoof_streaming_data"
- )
-
- updatePatchStatus(SPOOF_STREAMING_DATA)
-
- }
-)
diff --git a/patches/src/main/kotlin/app/revanced/patches/music/utils/patch/PatchList.kt b/patches/src/main/kotlin/app/revanced/patches/music/utils/patch/PatchList.kt
index bb89a82b8..b4c7ed91c 100644
--- a/patches/src/main/kotlin/app/revanced/patches/music/utils/patch/PatchList.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/music/utils/patch/PatchList.kt
@@ -153,10 +153,6 @@ internal enum class PatchList(
"Spoof client",
"Adds options to spoof the client to allow playback."
),
- SPOOF_STREAMING_DATA(
- "Spoof streaming data",
- "Adds options to spoof the streaming data to allow playback."
- ),
TRANSLATIONS_FOR_YOUTUBE_MUSIC(
"Translations for YouTube Music",
"Add translations or remove string resources."
diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/shared/Fingerprints.kt
index 699e4fb0e..1bd5d1bab 100644
--- a/patches/src/main/kotlin/app/revanced/patches/shared/Fingerprints.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/shared/Fingerprints.kt
@@ -18,11 +18,19 @@ internal val createPlayerRequestBodyWithModelFingerprint = legacyFingerprint(
parameters = emptyList(),
opcodes = listOf(Opcode.OR_INT_LIT16),
customFingerprint = { method, _ ->
- indexOfModelInstruction(method) >= 0 &&
+ indexOfBrandInstruction(method) >= 0 &&
+ indexOfManufacturerInstruction(method) >= 0 &&
+ indexOfModelInstruction(method) >= 0 &&
indexOfReleaseInstruction(method) >= 0
}
)
+fun indexOfBrandInstruction(method: Method) =
+ method.indexOfFieldReference("Landroid/os/Build;->BRAND:Ljava/lang/String;")
+
+fun indexOfManufacturerInstruction(method: Method) =
+ method.indexOfFieldReference("Landroid/os/Build;->MANUFACTURER:Ljava/lang/String;")
+
fun indexOfModelInstruction(method: Method) =
method.indexOfFieldReference("Landroid/os/Build;->MODEL:Ljava/lang/String;")
diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/spoof/blockrequest/BlockRequestPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/spoof/blockrequest/BlockRequestPatch.kt
new file mode 100644
index 000000000..1063044ad
--- /dev/null
+++ b/patches/src/main/kotlin/app/revanced/patches/shared/spoof/blockrequest/BlockRequestPatch.kt
@@ -0,0 +1,57 @@
+package app.revanced.patches.shared.spoof.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.SPOOF_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 =
+ "$SPOOF_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/spoof/blockrequest/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/shared/spoof/blockrequest/Fingerprints.kt
new file mode 100644
index 000000000..13797f4de
--- /dev/null
+++ b/patches/src/main/kotlin/app/revanced/patches/shared/spoof/blockrequest/Fingerprints.kt
@@ -0,0 +1,40 @@
+package app.revanced.patches.shared.spoof.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
deleted file mode 100644
index 451a17c12..000000000
--- a/patches/src/main/kotlin/app/revanced/patches/shared/spoof/streamingdata/BaseSpoofStreamingDataPatch.kt
+++ /dev/null
@@ -1,370 +0,0 @@
-package app.revanced.patches.shared.spoof.streamingdata
-
-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.instructions
-import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
-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.extension.Constants.SPOOF_PATH
-import app.revanced.patches.shared.formatStreamModelConstructorFingerprint
-import app.revanced.util.findInstructionIndicesReversedOrThrow
-import app.revanced.util.fingerprint.definingClassOrThrow
-import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall
-import app.revanced.util.fingerprint.matchOrThrow
-import app.revanced.util.fingerprint.methodOrThrow
-import app.revanced.util.getReference
-import app.revanced.util.indexOfFirstInstructionOrThrow
-import com.android.tools.smali.dexlib2.AccessFlags
-import com.android.tools.smali.dexlib2.Opcode
-import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
-import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
-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.instruction.TwoRegisterInstruction
-import com.android.tools.smali.dexlib2.iface.reference.FieldReference
-import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
-import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
-
-const val EXTENSION_CLASS_DESCRIPTOR =
- "$SPOOF_PATH/SpoofStreamingDataPatch;"
-
-fun baseSpoofStreamingDataPatch(
- block: BytecodePatchBuilder.() -> Unit = {},
- executeBlock: BytecodePatchContext.() -> Unit = {},
-) = bytecodePatch(
- name = "Spoof streaming data",
- description = "Adds options to spoof the streaming data to allow playback."
-) {
- 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 {
- val newRequestBuilderIndex = indexOfNewUrlRequestBuilderInstruction(this)
- val urlRegister =
- getInstruction(newRequestBuilderIndex).registerD
-
- val entrySetIndex = indexOfEntrySetInstruction(this)
- val mapRegister = if (entrySetIndex < 0)
- urlRegister + 1
- else
- getInstruction(entrySetIndex).registerC
-
- var smaliInstructions =
- "invoke-static { v$urlRegister, v$mapRegister }, " +
- "$EXTENSION_CLASS_DESCRIPTOR->" +
- "fetchStreams(Ljava/lang/String;Ljava/util/Map;)V"
-
- if (entrySetIndex < 0) smaliInstructions = """
- move-object/from16 v$mapRegister, p1
-
- """ + smaliInstructions
-
- // Copy request headers for streaming data fetch.
- addInstructions(newRequestBuilderIndex + 2, smaliInstructions)
- }
-
- // endregion
-
- // region Replace the streaming data.
-
- val approxDurationMsReference = formatStreamModelConstructorFingerprint.matchOrThrow().let {
- with(it.method) {
- getInstruction(it.patternMatch!!.startIndex).reference
- }
- }
-
- val streamingDataFormatsReference = with(
- videoStreamingDataConstructorFingerprint.methodOrThrow(
- videoStreamingDataToStringFingerprint
- )
- ) {
- val getFormatsFieldIndex = indexOfGetFormatsFieldInstruction(this)
- val longMaxValueIndex = indexOfLongMaxValueInstruction(this, getFormatsFieldIndex)
- val longMaxValueRegister =
- getInstruction(longMaxValueIndex).registerA
- val videoIdIndex =
- indexOfFirstInstructionOrThrow(longMaxValueIndex) {
- val reference = getReference()
- opcode == Opcode.IGET_OBJECT &&
- reference?.type == "Ljava/lang/String;" &&
- reference.definingClass == definingClass
- }
-
- val definingClassRegister =
- getInstruction(videoIdIndex).registerB
- val videoIdReference =
- getInstruction(videoIdIndex).reference
-
- addInstructions(
- longMaxValueIndex + 1, """
- # Get video id.
- iget-object v$longMaxValueRegister, v$definingClassRegister, $videoIdReference
-
- # Override approxDurationMs.
- invoke-static { v$longMaxValueRegister }, $EXTENSION_CLASS_DESCRIPTOR->getApproxDurationMs(Ljava/lang/String;)J
- move-result-wide v$longMaxValueRegister
- """
- )
- removeInstruction(longMaxValueIndex)
-
- getInstruction(getFormatsFieldIndex).reference
- }
-
- createStreamingDataFingerprint.matchOrThrow(createStreamingDataParentFingerprint)
- .let { result ->
- result.method.apply {
- val setStreamDataMethodName = "patch_setStreamingData"
- val calcApproxDurationMsMethodName = "patch_calcApproxDurationMs"
- val resultClassDef = result.classDef
- val resultMethodType = resultClassDef.type
- val setStreamingDataIndex = result.patternMatch!!.startIndex
- val setStreamingDataField =
- getInstruction(setStreamingDataIndex).getReference()
- .toString()
-
- val playerProtoClass =
- getInstruction(setStreamingDataIndex + 1).getReference()!!.definingClass
- val protobufClass =
- protobufClassParseByteBufferFingerprint.definingClassOrThrow()
-
- val getStreamingDataField = instructions.find { instruction ->
- instruction.opcode == Opcode.IGET_OBJECT &&
- instruction.getReference()?.definingClass == playerProtoClass
- }?.getReference()
- ?: throw PatchException("Could not find getStreamingDataField")
-
- val videoDetailsIndex = result.patternMatch!!.endIndex
- val videoDetailsRegister =
- getInstruction(videoDetailsIndex).registerA
- val videoDetailsClass =
- getInstruction(videoDetailsIndex).getReference()!!.type
-
- addInstruction(
- videoDetailsIndex + 1,
- "invoke-direct { p0, v$videoDetailsRegister }, " +
- "$resultMethodType->$setStreamDataMethodName($videoDetailsClass)V",
- )
-
- result.classDef.methods.add(
- ImmutableMethod(
- resultMethodType,
- setStreamDataMethodName,
- listOf(
- ImmutableMethodParameter(
- videoDetailsClass,
- annotations,
- "videoDetails"
- )
- ),
- "V",
- AccessFlags.PRIVATE.value or AccessFlags.FINAL.value,
- annotations,
- null,
- MutableMethodImplementation(9),
- ).toMutable().apply {
- addInstructionsWithLabels(
- 0,
- """
- invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->isSpoofingEnabled()Z
- move-result v0
- if-eqz v0, :disabled
-
- # Get video id.
- iget-object v2, p1, $videoDetailsClass->c:Ljava/lang/String;
- if-eqz v2, :disabled
-
- # Get streaming data.
- invoke-static { v2 }, $EXTENSION_CLASS_DESCRIPTOR->getStreamingData(Ljava/lang/String;)Ljava/nio/ByteBuffer;
- move-result-object v3
-
- if-eqz v3, :disabled
-
- # Parse streaming data.
- sget-object v4, $playerProtoClass->a:$playerProtoClass
- invoke-static { v4, v3 }, $protobufClass->parseFrom(${protobufClass}Ljava/nio/ByteBuffer;)$protobufClass
- move-result-object v5
- check-cast v5, $playerProtoClass
-
- iget-object v6, v5, $getStreamingDataField
- if-eqz v6, :disabled
-
- # Caculate approxDurationMs.
- invoke-direct { p0, v2 }, $resultMethodType->$calcApproxDurationMsMethodName(Ljava/lang/String;)V
-
- # Set spoofed streaming data.
- iput-object v6, p0, $setStreamingDataField
-
- :disabled
- return-void
- """,
- )
- },
- )
-
- resultClassDef.methods.add(
- ImmutableMethod(
- resultMethodType,
- calcApproxDurationMsMethodName,
- listOf(
- ImmutableMethodParameter(
- "Ljava/lang/String;",
- annotations,
- "videoId"
- )
- ),
- "V",
- AccessFlags.PRIVATE.value or AccessFlags.FINAL.value,
- annotations,
- null,
- MutableMethodImplementation(12),
- ).toMutable().apply {
- addInstructionsWithLabels(
- 0,
- """
- # Get video format list.
- iget-object v0, p0, $setStreamingDataField
- iget-object v0, v0, $streamingDataFormatsReference
- invoke-interface {v0}, Ljava/util/List;->iterator()Ljava/util/Iterator;
- move-result-object v0
-
- # Initialize approxDurationMs field.
- const-wide v1, 0x7fffffffffffffffL
-
- :loop
- # Loop over all video formats to get the approxDurationMs
- invoke-interface {v0}, Ljava/util/Iterator;->hasNext()Z
- move-result v3
- const-wide/16 v4, 0x0
-
- if-eqz v3, :exit
- invoke-interface {v0}, Ljava/util/Iterator;->next()Ljava/lang/Object;
- move-result-object v3
- check-cast v3, ${(approxDurationMsReference as FieldReference).definingClass}
-
- # Get approxDurationMs from format
- iget-wide v6, v3, $approxDurationMsReference
-
- # Compare with zero to make sure approxDurationMs is not negative
- cmp-long v8, v6, v4
- if-lez v8, :loop
-
- # Only use the min value of approxDurationMs
- invoke-static {v1, v2, v6, v7}, Ljava/lang/Math;->min(JJ)J
- move-result-wide v1
- goto :loop
-
- :exit
- # Save approxDurationMs to integrations
- invoke-static { p1, v1, v2 }, $EXTENSION_CLASS_DESCRIPTOR->setApproxDurationMs(Ljava/lang/String;J)V
-
- return-void
- """,
- )
- },
- )
- }
- }
-
- // endregion
-
- // region Remove /videoplayback request body to fix playback.
- // This is needed when using iOS client as streaming data source.
-
- buildMediaDataSourceFingerprint.methodOrThrow().apply {
- val targetIndex = instructions.lastIndex
-
- addInstructions(
- targetIndex,
- """
- # Field a: Stream uri.
- # Field c: Http method.
- # Field d: Post data.
- move-object/from16 v0, p0
- iget-object v1, v0, $definingClass->a:Landroid/net/Uri;
- iget v2, v0, $definingClass->c:I
- iget-object v3, v0, $definingClass->d:[B
- invoke-static { v1, v2, v3 }, $EXTENSION_CLASS_DESCRIPTOR->removeVideoPlaybackPostBody(Landroid/net/Uri;I[B)[B
- move-result-object v1
- iput-object v1, v0, $definingClass->d:[B
- """,
- )
- }
-
- // endregion
-
- // region Append spoof info.
-
- nerdsStatsVideoFormatBuilderFingerprint.methodOrThrow().apply {
- findInstructionIndicesReversedOrThrow(Opcode.RETURN_OBJECT).forEach { index ->
- val register = getInstruction(index).registerA
-
- addInstructions(
- index, """
- invoke-static {v$register}, $EXTENSION_CLASS_DESCRIPTOR->appendSpoofedClient(Ljava/lang/String;)Ljava/lang/String;
- move-result-object v$register
- """
- )
- }
- }
-
- // endregion
-
- // region Fix iOS livestream current time.
-
- hlsCurrentTimeFingerprint.injectLiteralInstructionBooleanCall(
- HLS_CURRENT_TIME_FEATURE_FLAG,
- "$EXTENSION_CLASS_DESCRIPTOR->fixHLSCurrentTime(Z)Z"
- )
-
- // endregion
-
- executeBlock()
-
- }
-}
diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/spoof/streamingdata/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/streamingdata/Fingerprints.kt
similarity index 86%
rename from patches/src/main/kotlin/app/revanced/patches/shared/spoof/streamingdata/Fingerprints.kt
rename to patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/streamingdata/Fingerprints.kt
index 79e334099..d7bf166aa 100644
--- a/patches/src/main/kotlin/app/revanced/patches/shared/spoof/streamingdata/Fingerprints.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/streamingdata/Fingerprints.kt
@@ -1,4 +1,4 @@
-package app.revanced.patches.shared.spoof.streamingdata
+package app.revanced.patches.youtube.utils.fix.streamingdata
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.getReference
@@ -15,37 +15,6 @@ 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..fbb9151f0 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,29 +1,355 @@
package app.revanced.patches.youtube.utils.fix.streamingdata
-import app.revanced.patches.shared.spoof.streamingdata.baseSpoofStreamingDataPatch
+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.instructions
+import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
+import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
+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.extension.Constants.PATCHES_PATH
+import app.revanced.patches.shared.extension.Constants.SPOOF_PATH
+import app.revanced.patches.shared.formatStreamModelConstructorFingerprint
+import app.revanced.patches.shared.spoof.blockrequest.blockRequestPatch
import app.revanced.patches.shared.spoof.useragent.baseSpoofUserAgentPatch
import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.youtube.utils.compatibility.Constants.YOUTUBE_PACKAGE_NAME
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.findInstructionIndicesReversedOrThrow
+import app.revanced.util.findMethodOrThrow
+import app.revanced.util.fingerprint.definingClassOrThrow
+import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall
+import app.revanced.util.fingerprint.matchOrThrow
+import app.revanced.util.fingerprint.methodOrThrow
+import app.revanced.util.getReference
+import app.revanced.util.indexOfFirstInstructionOrThrow
+import com.android.tools.smali.dexlib2.AccessFlags
+import com.android.tools.smali.dexlib2.Opcode
+import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
+import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
+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.instruction.TwoRegisterInstruction
+import com.android.tools.smali.dexlib2.iface.reference.FieldReference
+import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
+import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
-val spoofStreamingDataPatch = baseSpoofStreamingDataPatch(
- {
- compatibleWith(COMPATIBLE_PACKAGE)
+const val EXTENSION_CLASS_DESCRIPTOR =
+ "$SPOOF_PATH/SpoofStreamingDataPatch;"
- dependsOn(
- baseSpoofUserAgentPatch(YOUTUBE_PACKAGE_NAME),
- settingsPatch
+val spoofStreamingDataPatch = bytecodePatch(
+ SPOOF_STREAMING_DATA.title,
+ SPOOF_STREAMING_DATA.summary
+) {
+ compatibleWith(COMPATIBLE_PACKAGE)
+
+ dependsOn(
+ settingsPatch,
+ baseSpoofUserAgentPatch(YOUTUBE_PACKAGE_NAME),
+ blockRequestPatch,
+ )
+
+ execute {
+
+ // region Get replacement streams at player requests.
+
+ buildRequestFingerprint.methodOrThrow().apply {
+ val newRequestBuilderIndex = indexOfNewUrlRequestBuilderInstruction(this)
+ val urlRegister =
+ getInstruction(newRequestBuilderIndex).registerD
+
+ val entrySetIndex = indexOfEntrySetInstruction(this)
+ val mapRegister = if (entrySetIndex < 0)
+ urlRegister + 1
+ else
+ getInstruction(entrySetIndex).registerC
+
+ var smaliInstructions =
+ "invoke-static { v$urlRegister, v$mapRegister }, " +
+ "$EXTENSION_CLASS_DESCRIPTOR->" +
+ "fetchStreams(Ljava/lang/String;Ljava/util/Map;)V"
+
+ if (entrySetIndex < 0) smaliInstructions = """
+ move-object/from16 v$mapRegister, p1
+
+ """ + smaliInstructions
+
+ // Copy request headers for streaming data fetch.
+ addInstructions(newRequestBuilderIndex + 2, smaliInstructions)
+ }
+
+ // endregion
+
+ // region Replace the streaming data.
+
+ val approxDurationMsReference = formatStreamModelConstructorFingerprint.matchOrThrow().let {
+ with(it.method) {
+ getInstruction(it.patternMatch!!.startIndex).reference
+ }
+ }
+
+ val streamingDataFormatsReference = with(
+ videoStreamingDataConstructorFingerprint.methodOrThrow(
+ videoStreamingDataToStringFingerprint
+ )
+ ) {
+ val getFormatsFieldIndex = indexOfGetFormatsFieldInstruction(this)
+ val longMaxValueIndex = indexOfLongMaxValueInstruction(this, getFormatsFieldIndex)
+ val longMaxValueRegister =
+ getInstruction(longMaxValueIndex).registerA
+ val videoIdIndex =
+ indexOfFirstInstructionOrThrow(longMaxValueIndex) {
+ val reference = getReference()
+ opcode == Opcode.IGET_OBJECT &&
+ reference?.type == "Ljava/lang/String;" &&
+ reference.definingClass == definingClass
+ }
+
+ val definingClassRegister =
+ getInstruction(videoIdIndex).registerB
+ val videoIdReference =
+ getInstruction(videoIdIndex).reference
+
+ addInstructions(
+ longMaxValueIndex + 1, """
+ # Get video id.
+ iget-object v$longMaxValueRegister, v$definingClassRegister, $videoIdReference
+
+ # Override approxDurationMs.
+ invoke-static { v$longMaxValueRegister }, $EXTENSION_CLASS_DESCRIPTOR->getApproxDurationMs(Ljava/lang/String;)J
+ move-result-wide v$longMaxValueRegister
+ """
+ )
+ removeInstruction(longMaxValueIndex)
+
+ getInstruction(getFormatsFieldIndex).reference
+ }
+
+ createStreamingDataFingerprint.matchOrThrow(createStreamingDataParentFingerprint)
+ .let { result ->
+ result.method.apply {
+ val setStreamDataMethodName = "patch_setStreamingData"
+ val calcApproxDurationMsMethodName = "patch_calcApproxDurationMs"
+ val resultClassDef = result.classDef
+ val resultMethodType = resultClassDef.type
+ val setStreamingDataIndex = result.patternMatch!!.startIndex
+ val setStreamingDataField =
+ getInstruction(setStreamingDataIndex).getReference()
+ .toString()
+
+ val playerProtoClass =
+ getInstruction(setStreamingDataIndex + 1).getReference()!!.definingClass
+ val protobufClass =
+ protobufClassParseByteBufferFingerprint.definingClassOrThrow()
+
+ val getStreamingDataField = instructions.find { instruction ->
+ instruction.opcode == Opcode.IGET_OBJECT &&
+ instruction.getReference()?.definingClass == playerProtoClass
+ }?.getReference()
+ ?: throw PatchException("Could not find getStreamingDataField")
+
+ val videoDetailsIndex = result.patternMatch!!.endIndex
+ val videoDetailsRegister =
+ getInstruction(videoDetailsIndex).registerA
+ val videoDetailsClass =
+ getInstruction(videoDetailsIndex).getReference()!!.type
+
+ addInstruction(
+ videoDetailsIndex + 1,
+ "invoke-direct { p0, v$videoDetailsRegister }, " +
+ "$resultMethodType->$setStreamDataMethodName($videoDetailsClass)V",
+ )
+
+ result.classDef.methods.add(
+ ImmutableMethod(
+ resultMethodType,
+ setStreamDataMethodName,
+ listOf(
+ ImmutableMethodParameter(
+ videoDetailsClass,
+ annotations,
+ "videoDetails"
+ )
+ ),
+ "V",
+ AccessFlags.PRIVATE.value or AccessFlags.FINAL.value,
+ annotations,
+ null,
+ MutableMethodImplementation(9),
+ ).toMutable().apply {
+ addInstructionsWithLabels(
+ 0,
+ """
+ invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->isSpoofingEnabled()Z
+ move-result v0
+ if-eqz v0, :disabled
+
+ # Get video id.
+ iget-object v2, p1, $videoDetailsClass->c:Ljava/lang/String;
+ if-eqz v2, :disabled
+
+ # Get streaming data.
+ invoke-static { v2 }, $EXTENSION_CLASS_DESCRIPTOR->getStreamingData(Ljava/lang/String;)Ljava/nio/ByteBuffer;
+ move-result-object v3
+
+ if-eqz v3, :disabled
+
+ # Parse streaming data.
+ sget-object v4, $playerProtoClass->a:$playerProtoClass
+ invoke-static { v4, v3 }, $protobufClass->parseFrom(${protobufClass}Ljava/nio/ByteBuffer;)$protobufClass
+ move-result-object v5
+ check-cast v5, $playerProtoClass
+
+ iget-object v6, v5, $getStreamingDataField
+ if-eqz v6, :disabled
+
+ # Caculate approxDurationMs.
+ invoke-direct { p0, v2 }, $resultMethodType->$calcApproxDurationMsMethodName(Ljava/lang/String;)V
+
+ # Set spoofed streaming data.
+ iput-object v6, p0, $setStreamingDataField
+
+ :disabled
+ return-void
+ """,
+ )
+ },
+ )
+
+ resultClassDef.methods.add(
+ ImmutableMethod(
+ resultMethodType,
+ calcApproxDurationMsMethodName,
+ listOf(
+ ImmutableMethodParameter(
+ "Ljava/lang/String;",
+ annotations,
+ "videoId"
+ )
+ ),
+ "V",
+ AccessFlags.PRIVATE.value or AccessFlags.FINAL.value,
+ annotations,
+ null,
+ MutableMethodImplementation(12),
+ ).toMutable().apply {
+ addInstructionsWithLabels(
+ 0,
+ """
+ # Get video format list.
+ iget-object v0, p0, $setStreamingDataField
+ iget-object v0, v0, $streamingDataFormatsReference
+ invoke-interface {v0}, Ljava/util/List;->iterator()Ljava/util/Iterator;
+ move-result-object v0
+
+ # Initialize approxDurationMs field.
+ const-wide v1, 0x7fffffffffffffffL
+
+ :loop
+ # Loop over all video formats to get the approxDurationMs
+ invoke-interface {v0}, Ljava/util/Iterator;->hasNext()Z
+ move-result v3
+ const-wide/16 v4, 0x0
+
+ if-eqz v3, :exit
+ invoke-interface {v0}, Ljava/util/Iterator;->next()Ljava/lang/Object;
+ move-result-object v3
+ check-cast v3, ${(approxDurationMsReference as FieldReference).definingClass}
+
+ # Get approxDurationMs from format
+ iget-wide v6, v3, $approxDurationMsReference
+
+ # Compare with zero to make sure approxDurationMs is not negative
+ cmp-long v8, v6, v4
+ if-lez v8, :loop
+
+ # Only use the min value of approxDurationMs
+ invoke-static {v1, v2, v6, v7}, Ljava/lang/Math;->min(JJ)J
+ move-result-wide v1
+ goto :loop
+
+ :exit
+ # Save approxDurationMs to integrations
+ invoke-static { p1, v1, v2 }, $EXTENSION_CLASS_DESCRIPTOR->setApproxDurationMs(Ljava/lang/String;J)V
+
+ return-void
+ """,
+ )
+ },
+ )
+ }
+ }
+
+ // endregion
+
+ // region Remove /videoplayback request body to fix playback.
+ // This is needed when using iOS client as streaming data source.
+
+ buildMediaDataSourceFingerprint.methodOrThrow().apply {
+ val targetIndex = instructions.lastIndex
+
+ addInstructions(
+ targetIndex,
+ """
+ # Field a: Stream uri.
+ # Field c: Http method.
+ # Field d: Post data.
+ move-object/from16 v0, p0
+ iget-object v1, v0, $definingClass->a:Landroid/net/Uri;
+ iget v2, v0, $definingClass->c:I
+ iget-object v3, v0, $definingClass->d:[B
+ invoke-static { v1, v2, v3 }, $EXTENSION_CLASS_DESCRIPTOR->removeVideoPlaybackPostBody(Landroid/net/Uri;I[B)[B
+ move-result-object v1
+ iput-object v1, v0, $definingClass->d:[B
+ """,
+ )
+ }
+
+ // endregion
+
+ // region Append spoof info.
+
+ nerdsStatsVideoFormatBuilderFingerprint.methodOrThrow().apply {
+ findInstructionIndicesReversedOrThrow(Opcode.RETURN_OBJECT).forEach { index ->
+ val register = getInstruction(index).registerA
+
+ addInstructions(
+ index, """
+ invoke-static {v$register}, $EXTENSION_CLASS_DESCRIPTOR->appendSpoofedClient(Ljava/lang/String;)Ljava/lang/String;
+ move-result-object v$register
+ """
+ )
+ }
+ }
+
+ // endregion
+
+ // region Fix iOS livestream current time.
+
+ hlsCurrentTimeFingerprint.injectLiteralInstructionBooleanCall(
+ HLS_CURRENT_TIME_FEATURE_FLAG,
+ "$EXTENSION_CLASS_DESCRIPTOR->fixHLSCurrentTime(Z)Z"
)
- },
- {
+
+ // endregion
+
+ findMethodOrThrow("$PATCHES_PATH/PatchStatus;") {
+ name == "SpoofStreamingData"
+ }.replaceInstruction(
+ 0,
+ "const/4 v0, 0x1"
+ )
+
addPreference(
arrayOf(
"SETTINGS: SPOOF_STREAMING_DATA"
),
SPOOF_STREAMING_DATA
)
-
}
-)
+}
diff --git a/patches/src/main/resources/music/settings/host/values/arrays.xml b/patches/src/main/resources/music/settings/host/values/arrays.xml
index 4d7f253d5..68fa7ba45 100644
--- a/patches/src/main/resources/music/settings/host/values/arrays.xml
+++ b/patches/src/main/resources/music/settings/host/values/arrays.xml
@@ -76,14 +76,16 @@
- 6.42.55
- - @string/revanced_spoof_client_type_entry_ios_music_6_21
- - @string/revanced_spoof_client_type_entry_android_music_5_29
- @string/revanced_spoof_client_type_entry_android_music_4_27
+ - @string/revanced_spoof_client_type_entry_android_music_5_29
+ - @string/revanced_spoof_client_type_entry_ios_music_6_21
+ - @string/revanced_spoof_client_type_entry_ios_music_7_04
- - IOS_MUSIC_6_21
- - ANDROID_MUSIC_5_29
- ANDROID_MUSIC_4_27
+ - ANDROID_MUSIC_5_29
+ - IOS_MUSIC_6_21
+ - IOS_MUSIC_7_04
- @string/revanced_spoof_streaming_data_type_entry_android_vr
diff --git a/patches/src/main/resources/music/settings/host/values/strings.xml b/patches/src/main/resources/music/settings/host/values/strings.xml
index 2c400f9e3..fbc3a29db 100644
--- a/patches/src/main/resources/music/settings/host/values/strings.xml
+++ b/patches/src/main/resources/music/settings/host/values/strings.xml
@@ -479,17 +479,7 @@ Info:
Android Music 4.27.53
Android Music 5.29.53
iOS Music 6.21
-
- Spoof streaming data
- "Spoof the streaming data to prevent playback issues.
-
-• When used with 'Spoof client', playback issues may occur."
- Default client
- Defines a default client that fetches streaming data.
- Show in Stats for nerds
- Shows the client used to fetch streaming data in Stats for nerds.
- Android VR
- Android Music
+ iOS Music 7.04
Open default app settings
To open YouTube Music links in RVX Music, enable Open supported links and enable all the Supported web addresses.