fix(YouTube Music - Spoof streaming data): Sometimes the app crashes when connected to Wi-Fi

This commit is contained in:
inotia00
2025-01-04 19:09:10 +09:00
parent 697a519365
commit 6a9ad25d30
8 changed files with 196 additions and 94 deletions

View File

@ -6,11 +6,6 @@ public class PatchStatus {
return false; return false;
} }
public static boolean SpoofStreamingData() {
// Replace this with true If the Spoof streaming data patch succeeds
return false;
}
public static boolean SpoofStreamingDataMusic() { public static boolean SpoofStreamingDataMusic() {
// Replace this with true If the Spoof streaming data patch succeeds in YouTube Music // Replace this with true If the Spoof streaming data patch succeeds in YouTube Music
return false; return false;

View File

@ -1,6 +1,6 @@
package app.revanced.extension.shared.patches.spoof; 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.net.Uri;
import android.text.TextUtils; import android.text.TextUtils;
@ -21,7 +21,9 @@ import app.revanced.extension.shared.utils.Utils;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class SpoofStreamingDataPatch { 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 = private static final String PO_TOKEN =
BaseSettings.SPOOF_STREAMING_DATA_PO_TOKEN.get(); BaseSettings.SPOOF_STREAMING_DATA_PO_TOKEN.get();
private static final String VISITOR_DATA = private static final String VISITOR_DATA =
@ -59,16 +61,19 @@ public class SpoofStreamingDataPatch {
*/ */
public static Uri blockGetWatchRequest(Uri playerRequestUri) { public static Uri blockGetWatchRequest(Uri playerRequestUri) {
if (SPOOF_STREAMING_DATA) { if (SPOOF_STREAMING_DATA) {
try { // An exception may be thrown when the /get_watch request is blocked when connected to Wi-Fi in YouTube Music.
String path = playerRequestUri.getPath(); if (SPOOF_STREAMING_DATA_YOUTUBE || Utils.getNetworkType() == Utils.NetworkType.MOBILE) {
try {
String path = playerRequestUri.getPath();
if (path != null && path.contains("get_watch")) { if (path != null && path.contains("get_watch")) {
Logger.printDebug(() -> "Blocking 'get_watch' by returning unreachable uri"); 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; return false;
} }
private static volatile String auth = "";
private static volatile Map<String, String> 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.
* <p>
* 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.
* <p>
* @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<String, String> 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. * Injection point.
*/ */
public static void fetchStreams(String url, Map<String, String> requestHeaders) { public static void fetchStreams(String url, Map<String, String> requestHeaders) {
setRequestHeaders(requestHeaders);
if (SPOOF_STREAMING_DATA) { if (SPOOF_STREAMING_DATA) {
try { try {
Uri uri = Uri.parse(url); Uri uri = Uri.parse(url);
@ -146,7 +213,7 @@ public class SpoofStreamingDataPatch {
StreamingDataRequest.fetchRequest(id, requestHeaders, VISITOR_DATA, PO_TOKEN, droidGuardPoToken); StreamingDataRequest.fetchRequest(id, requestHeaders, VISITOR_DATA, PO_TOKEN, droidGuardPoToken);
} catch (Exception ex) { } 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)}. * Called after {@link #getStreamingData(String)}.
*/ */
public static void setApproxDurationMs(String videoId, long approxDurationMs) { 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); approxDurationMsMap.put(videoId, approxDurationMs);
Logger.printDebug(() -> "New approxDurationMs loaded, video id: " + videoId + ", video length: " + approxDurationMs); Logger.printDebug(() -> "New approxDurationMs loaded, video id: " + videoId + ", video length: " + approxDurationMs);
} }
@ -218,7 +285,7 @@ public class SpoofStreamingDataPatch {
* Called after {@link #getStreamingData(String)}. * Called after {@link #getStreamingData(String)}.
*/ */
public static long getApproxDurationMs(String videoId) { 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); final Long approxDurationMs = approxDurationMsMap.get(videoId);
if (approxDurationMs != null) { if (approxDurationMs != null) {
Logger.printDebug(() -> "Replacing video length: " + approxDurationMs + " for videoId: " + videoId); Logger.printDebug(() -> "Replacing video length: " + approxDurationMs + " for videoId: " + videoId);

View File

@ -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.dismiss.dismissQueueHookPatch
import app.revanced.patches.music.utils.extension.Constants.MISC_PATH 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.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.CategoryType
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.addSwitchPreference import app.revanced.patches.music.utils.settings.addSwitchPreference
import app.revanced.patches.music.utils.settings.settingsPatch import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.patches.music.video.information.videoIdHook import app.revanced.patches.music.video.information.videoIdHook
import app.revanced.patches.music.video.information.videoInformationPatch 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 import app.revanced.util.fingerprint.methodOrThrow
private const val EXTENSION_CLASS_DESCRIPTOR = private const val EXTENSION_CLASS_DESCRIPTOR =
@ -32,23 +32,14 @@ val albumMusicVideoPatch = bytecodePatch(
settingsPatch, settingsPatch,
dismissQueueHookPatch, dismissQueueHookPatch,
videoInformationPatch, videoInformationPatch,
versionCheckPatch, playerResponseMethodHookPatch,
) )
execute { execute {
// region hook player response // region hook player response
val fingerprint = if (is_7_03_or_greater) { hookPlayerResponse("$EXTENSION_CLASS_DESCRIPTOR->newPlayerResponse(Ljava/lang/String;Ljava/lang/String;I)V")
playerParameterBuilderFingerprint
} else {
playerParameterBuilderLegacyFingerprint
}
fingerprint.methodOrThrow().addInstruction(
0,
"invoke-static {p1, p4, p5}, $EXTENSION_CLASS_DESCRIPTOR->newPlayerResponse(Ljava/lang/String;Ljava/lang/String;I)V"
)
// endregion // endregion

View File

@ -3,62 +3,7 @@ package app.revanced.patches.music.misc.album
import app.revanced.util.fingerprint.legacyFingerprint import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.or import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags 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( internal val snackBarParentFingerprint = legacyFingerprint(
name = "snackBarParentFingerprint", name = "snackBarParentFingerprint",

View File

@ -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.addPreferenceWithIntent
import app.revanced.patches.music.utils.settings.addSwitchPreference import app.revanced.patches.music.utils.settings.addSwitchPreference
import app.revanced.patches.music.utils.settings.settingsPatch 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.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.streamingdata.baseSpoofStreamingDataPatch
import app.revanced.patches.shared.spoof.useragent.baseSpoofUserAgentPatch import app.revanced.patches.shared.spoof.useragent.baseSpoofUserAgentPatch
import app.revanced.util.findMethodOrThrow import app.revanced.util.findMethodOrThrow
const val EXTENSION_CLASS_DESCRIPTOR =
"$SPOOF_PATH/SpoofStreamingDataPatch;"
@Suppress("unused") @Suppress("unused")
val spoofStreamingDataPatch = baseSpoofStreamingDataPatch( val spoofStreamingDataPatch = baseSpoofStreamingDataPatch(
{ {
@ -22,6 +28,7 @@ val spoofStreamingDataPatch = baseSpoofStreamingDataPatch(
dependsOn( dependsOn(
baseSpoofUserAgentPatch(YOUTUBE_MUSIC_PACKAGE_NAME), baseSpoofUserAgentPatch(YOUTUBE_MUSIC_PACKAGE_NAME),
settingsPatch, settingsPatch,
playerResponseMethodHookPatch,
) )
}, },
{ {
@ -32,6 +39,11 @@ val spoofStreamingDataPatch = baseSpoofStreamingDataPatch(
"const/4 v0, 0x1" "const/4 v0, 0x1"
) )
hookPlayerResponse(
"$EXTENSION_CLASS_DESCRIPTOR->fetchStreams(Ljava/lang/String;)V",
true
)
addSwitchPreference( addSwitchPreference(
CategoryType.MISC, CategoryType.MISC,
"revanced_spoof_streaming_data", "revanced_spoof_streaming_data",

View File

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

View File

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

View File

@ -6,13 +6,11 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWith
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.instructions import app.revanced.patcher.extensions.InstructionExtensions.instructions
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction 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.BytecodePatchBuilder
import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable 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.extension.Constants.SPOOF_PATH
import app.revanced.patches.shared.formatStreamModelConstructorFingerprint import app.revanced.patches.shared.formatStreamModelConstructorFingerprint
import app.revanced.util.findInstructionIndicesReversedOrThrow import app.revanced.util.findInstructionIndicesReversedOrThrow
@ -381,13 +379,6 @@ fun baseSpoofStreamingDataPatch(
// endregion // endregion
findMethodOrThrow("$PATCHES_PATH/PatchStatus;") {
name == "SpoofStreamingData"
}.replaceInstruction(
0,
"const/4 v0, 0x1"
)
executeBlock() executeBlock()
} }