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 468528831..20bf9d5e0 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 @@ -6,11 +6,6 @@ public class PatchStatus { return false; } - public static boolean SpoofStreamingData() { - // Replace this with true If the Spoof streaming data patch succeeds - return false; - } - public static boolean SpoofStreamingDataMusic() { // Replace this with true If the Spoof streaming data patch succeeds in YouTube Music return false; 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 860728123..fc888b116 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,6 +1,6 @@ package app.revanced.extension.shared.patches.spoof; -import static app.revanced.extension.shared.patches.PatchStatus.SpoofStreamingData; +import static app.revanced.extension.shared.patches.PatchStatus.SpoofStreamingDataMusic; import android.net.Uri; import android.text.TextUtils; @@ -21,7 +21,9 @@ import app.revanced.extension.shared.utils.Utils; @SuppressWarnings("unused") public class SpoofStreamingDataPatch { - private static final boolean SPOOF_STREAMING_DATA = SpoofStreamingData() && BaseSettings.SPOOF_STREAMING_DATA.get(); + 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(); private static final String PO_TOKEN = BaseSettings.SPOOF_STREAMING_DATA_PO_TOKEN.get(); private static final String VISITOR_DATA = @@ -59,16 +61,19 @@ public class SpoofStreamingDataPatch { */ public static Uri blockGetWatchRequest(Uri playerRequestUri) { if (SPOOF_STREAMING_DATA) { - try { - String path = playerRequestUri.getPath(); + // 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"); + if (path != null && path.contains("get_watch")) { + Logger.printDebug(() -> "Blocking 'get_watch' by returning unreachable uri"); - return UNREACHABLE_HOST_URI; + return UNREACHABLE_HOST_URI; + } + } catch (Exception ex) { + Logger.printException(() -> "blockGetWatchRequest failure", ex); } - } catch (Exception ex) { - Logger.printException(() -> "blockGetWatchRequest failure", ex); } } @@ -117,10 +122,72 @@ 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" + }; + + /** + * 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, droidGuardPoToken); + } 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); @@ -146,7 +213,7 @@ public class SpoofStreamingDataPatch { StreamingDataRequest.fetchRequest(id, requestHeaders, VISITOR_DATA, PO_TOKEN, droidGuardPoToken); } catch (Exception ex) { - Logger.printException(() -> "buildRequest failure", ex); + Logger.printException(() -> "fetchStreams failure", ex); } } } @@ -196,7 +263,7 @@ public class SpoofStreamingDataPatch { * Called after {@link #getStreamingData(String)}. */ public static void setApproxDurationMs(String videoId, long approxDurationMs) { - if (approxDurationMs != Long.MAX_VALUE) { + if (SPOOF_STREAMING_DATA_YOUTUBE && approxDurationMs != Long.MAX_VALUE) { approxDurationMsMap.put(videoId, approxDurationMs); Logger.printDebug(() -> "New approxDurationMs loaded, video id: " + videoId + ", video length: " + approxDurationMs); } @@ -218,7 +285,7 @@ public class SpoofStreamingDataPatch { * Called after {@link #getStreamingData(String)}. */ public static long getApproxDurationMs(String videoId) { - if (SPOOF_STREAMING_DATA && videoId != null) { + if (SPOOF_STREAMING_DATA_YOUTUBE && videoId != null) { final Long approxDurationMs = approxDurationMsMap.get(videoId); if (approxDurationMs != null) { Logger.printDebug(() -> "Replacing video length: " + approxDurationMs + " for videoId: " + videoId); diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/album/AlbumMusicVideoPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/misc/album/AlbumMusicVideoPatch.kt index 900ec0f75..51bf2d4c1 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/misc/album/AlbumMusicVideoPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/misc/album/AlbumMusicVideoPatch.kt @@ -7,14 +7,14 @@ import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKA import app.revanced.patches.music.utils.dismiss.dismissQueueHookPatch import app.revanced.patches.music.utils.extension.Constants.MISC_PATH import app.revanced.patches.music.utils.patch.PatchList.DISABLE_MUSIC_VIDEO_IN_ALBUM -import app.revanced.patches.music.utils.playservice.is_7_03_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.addSwitchPreference import app.revanced.patches.music.utils.settings.settingsPatch import app.revanced.patches.music.video.information.videoIdHook import app.revanced.patches.music.video.information.videoInformationPatch +import app.revanced.patches.music.video.playerresponse.hookPlayerResponse +import app.revanced.patches.music.video.playerresponse.playerResponseMethodHookPatch import app.revanced.util.fingerprint.methodOrThrow private const val EXTENSION_CLASS_DESCRIPTOR = @@ -32,23 +32,14 @@ val albumMusicVideoPatch = bytecodePatch( settingsPatch, dismissQueueHookPatch, videoInformationPatch, - versionCheckPatch, + playerResponseMethodHookPatch, ) execute { // region hook player response - val fingerprint = if (is_7_03_or_greater) { - playerParameterBuilderFingerprint - } else { - playerParameterBuilderLegacyFingerprint - } - - fingerprint.methodOrThrow().addInstruction( - 0, - "invoke-static {p1, p4, p5}, $EXTENSION_CLASS_DESCRIPTOR->newPlayerResponse(Ljava/lang/String;Ljava/lang/String;I)V" - ) + hookPlayerResponse("$EXTENSION_CLASS_DESCRIPTOR->newPlayerResponse(Ljava/lang/String;Ljava/lang/String;I)V") // endregion diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/album/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/music/misc/album/Fingerprints.kt index e77dc2012..e91201b8c 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/misc/album/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/misc/album/Fingerprints.kt @@ -3,62 +3,7 @@ package app.revanced.patches.music.misc.album import app.revanced.util.fingerprint.legacyFingerprint import app.revanced.util.or import com.android.tools.smali.dexlib2.AccessFlags -import com.android.tools.smali.dexlib2.Opcode -/** - * For targets 7.03 and later. - */ -internal val playerParameterBuilderFingerprint = legacyFingerprint( - name = "playerParameterBuilderFingerprint", - accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, - returnType = "L", - parameters = listOf( - "Ljava/lang/String;", // VideoId. - "[B", - "Ljava/lang/String;", // Player parameters proto buffer. - "Ljava/lang/String;", // PlaylistId. - "I", // PlaylistIndex. - "I", - "L", - "Ljava/util/Set;", - "Ljava/lang/String;", - "Ljava/lang/String;", - "L", - "Z", - "Z", - "Z", // Appears to indicate if the video id is being opened or is currently playing. - ), - strings = listOf("psps") -) - -/** - * For targets 7.02 and earlier. - */ -internal val playerParameterBuilderLegacyFingerprint = legacyFingerprint( - name = "playerParameterBuilderLegacyFingerprint", - accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, - returnType = "L", - parameters = listOf( - "Ljava/lang/String;", // VideoId. - "[B", - "Ljava/lang/String;", // Player parameters proto buffer. - "Ljava/lang/String;", // PlaylistId. - "I", // PlaylistIndex. - "I", - "Ljava/util/Set;", - "Ljava/lang/String;", - "Ljava/lang/String;", - "L", - "Z", - "Z", // Appears to indicate if the video id is being opened or is currently playing. - ), - opcodes = listOf( - Opcode.INVOKE_INTERFACE, - Opcode.MOVE_RESULT_OBJECT, - Opcode.CHECK_CAST, - Opcode.INVOKE_INTERFACE - ) -) internal val snackBarParentFingerprint = legacyFingerprint( name = "snackBarParentFingerprint", 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 7bab56e59..a30c435c2 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 @@ -9,11 +9,17 @@ 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( { @@ -22,6 +28,7 @@ val spoofStreamingDataPatch = baseSpoofStreamingDataPatch( dependsOn( baseSpoofUserAgentPatch(YOUTUBE_MUSIC_PACKAGE_NAME), settingsPatch, + playerResponseMethodHookPatch, ) }, { @@ -32,6 +39,11 @@ val spoofStreamingDataPatch = baseSpoofStreamingDataPatch( "const/4 v0, 0x1" ) + hookPlayerResponse( + "$EXTENSION_CLASS_DESCRIPTOR->fetchStreams(Ljava/lang/String;)V", + true + ) + addSwitchPreference( CategoryType.MISC, "revanced_spoof_streaming_data", diff --git a/patches/src/main/kotlin/app/revanced/patches/music/video/playerresponse/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/music/video/playerresponse/Fingerprints.kt new file mode 100644 index 000000000..0564715e1 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/music/video/playerresponse/Fingerprints.kt @@ -0,0 +1,61 @@ +package app.revanced.patches.music.video.playerresponse + +import app.revanced.util.fingerprint.legacyFingerprint +import app.revanced.util.or +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode + +/** + * For targets 7.03 and later. + */ +internal val playerParameterBuilderFingerprint = legacyFingerprint( + name = "playerParameterBuilderFingerprint", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + returnType = "L", + parameters = listOf( + "Ljava/lang/String;", // VideoId. + "[B", + "Ljava/lang/String;", // Player parameters proto buffer. + "Ljava/lang/String;", // PlaylistId. + "I", // PlaylistIndex. + "I", + "L", + "Ljava/util/Set;", + "Ljava/lang/String;", + "Ljava/lang/String;", + "L", + "Z", + "Z", + "Z", // Appears to indicate if the video id is being opened or is currently playing. + ), + strings = listOf("psps") +) + +/** + * For targets 7.02 and earlier. + */ +internal val playerParameterBuilderLegacyFingerprint = legacyFingerprint( + name = "playerParameterBuilderLegacyFingerprint", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + returnType = "L", + parameters = listOf( + "Ljava/lang/String;", // VideoId. + "[B", + "Ljava/lang/String;", // Player parameters proto buffer. + "Ljava/lang/String;", // PlaylistId. + "I", // PlaylistIndex. + "I", + "Ljava/util/Set;", + "Ljava/lang/String;", + "Ljava/lang/String;", + "L", + "Z", + "Z", // Appears to indicate if the video id is being opened or is currently playing. + ), + opcodes = listOf( + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT_OBJECT, + Opcode.CHECK_CAST, + Opcode.INVOKE_INTERFACE + ) +) \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/music/video/playerresponse/PlayerResponseMethodHookPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/video/playerresponse/PlayerResponseMethodHookPatch.kt new file mode 100644 index 000000000..8570097d0 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/music/video/playerresponse/PlayerResponseMethodHookPatch.kt @@ -0,0 +1,40 @@ +package app.revanced.patches.music.video.playerresponse + +import app.revanced.patcher.extensions.InstructionExtensions.addInstruction +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.patches.music.utils.playservice.is_7_03_or_greater +import app.revanced.patches.music.utils.playservice.versionCheckPatch +import app.revanced.util.fingerprint.methodOrThrow + +private const val REGISTER_VIDEO_ID = "p1" +private const val REGISTER_PLAYLIST_ID = "p4" +private const val REGISTER_PLAYLIST_INDEX = "p5" + +private lateinit var playerResponseMethod: MutableMethod + +val playerResponseMethodHookPatch = bytecodePatch( + description = "playerResponseMethodHookPatch" +) { + dependsOn(versionCheckPatch) + + execute { + playerResponseMethod = if (is_7_03_or_greater) { + playerParameterBuilderFingerprint + } else { + playerParameterBuilderLegacyFingerprint + }.methodOrThrow() + } +} + +fun hookPlayerResponse( + descriptor: String, + onlyVideoId: Boolean = false +) { + val smaliInstruction = if (onlyVideoId) + "invoke-static {$REGISTER_VIDEO_ID}, $descriptor" + else + "invoke-static {$REGISTER_VIDEO_ID, $REGISTER_PLAYLIST_ID, $REGISTER_PLAYLIST_INDEX}, $descriptor" + + playerResponseMethod.addInstruction(0, smaliInstruction) +} 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 8c023ac2c..2f34434c4 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 @@ -6,13 +6,11 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWith 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.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.PATCHES_PATH import app.revanced.patches.shared.extension.Constants.SPOOF_PATH import app.revanced.patches.shared.formatStreamModelConstructorFingerprint import app.revanced.util.findInstructionIndicesReversedOrThrow @@ -381,13 +379,6 @@ fun baseSpoofStreamingDataPatch( // endregion - findMethodOrThrow("$PATCHES_PATH/PatchStatus;") { - name == "SpoofStreamingData" - }.replaceInstruction( - 0, - "const/4 v0, 0x1" - ) - executeBlock() }