mirror of
https://github.com/inotia00/revanced-patches.git
synced 2025-05-28 12:50:19 +02:00
feat(YouTube Music): Add Spoof player parameter
patch https://github.com/inotia00/ReVanced_Extended/issues/2832
This commit is contained in:
parent
806976b6d8
commit
d4af0f1ee0
@ -0,0 +1,174 @@
|
||||
package app.revanced.extension.music.patches.misc;
|
||||
|
||||
import static app.revanced.extension.music.shared.VideoInformation.parameterIsAgeRestricted;
|
||||
import static app.revanced.extension.music.shared.VideoInformation.parameterIsSample;
|
||||
|
||||
import androidx.annotation.GuardedBy;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import app.revanced.extension.music.settings.Settings;
|
||||
import app.revanced.extension.music.shared.VideoInformation;
|
||||
import app.revanced.extension.shared.utils.Logger;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class SpoofPlayerParameterPatch {
|
||||
/**
|
||||
* Used in YouTube Music.
|
||||
*/
|
||||
private static final boolean SPOOF_PLAYER_PARAMETER = Settings.SPOOF_PLAYER_PARAMETER.get();
|
||||
|
||||
/**
|
||||
* Parameter to fix playback issues.
|
||||
* Used in YouTube Music Samples.
|
||||
*/
|
||||
private static final String PLAYER_PARAMETER_SAMPLES =
|
||||
"8AEB2AUBogYVAUY4C8W9wrM-FdhjSW4MnCgH44uhkAcI";
|
||||
|
||||
/**
|
||||
* Parameter to fix playback issues.
|
||||
* Used in YouTube Shorts.
|
||||
*/
|
||||
private static final String PLAYER_PARAMETER_SHORTS =
|
||||
"8AEByAMkuAQ0ogYVAePzwRN3uesV1sPI2x4-GkDYlvqUkAcC";
|
||||
|
||||
/**
|
||||
* On app first start, the first video played usually contains a single non-default window setting value
|
||||
* and all other subtitle settings for the video are (incorrect) default Samples window settings.
|
||||
* For this situation, the Samples settings must be replaced.
|
||||
* <p>
|
||||
* But some videos use multiple text positions on screen (such as youtu.be/3hW1rMNC89o),
|
||||
* and by chance many of the subtitles uses window positions that match a default Samples position.
|
||||
* To handle these videos, selectively allowing the Samples specific window settings to 'pass thru' unchanged,
|
||||
* but only if the video contains multiple non-default subtitle window positions.
|
||||
* <p>
|
||||
* Do not enable 'pass thru mode' until this many non default subtitle settings are observed for a single video.
|
||||
*/
|
||||
private static final int NUMBER_OF_NON_DEFAULT_SUBTITLES_BEFORE_ENABLING_PASSTHRU = 2;
|
||||
|
||||
/**
|
||||
* The number of non default subtitle settings encountered for the current video.
|
||||
*/
|
||||
private static int numberOfNonDefaultSettingsObserved;
|
||||
|
||||
@GuardedBy("itself")
|
||||
private static final Map<String, Boolean> lastVideoIds = new LinkedHashMap<>() {
|
||||
private static final int NUMBER_OF_LAST_VIDEO_IDS_TO_TRACK = 5;
|
||||
|
||||
@Override
|
||||
protected boolean removeEldestEntry(Entry eldest) {
|
||||
return size() > NUMBER_OF_LAST_VIDEO_IDS_TO_TRACK;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static String spoofParameter(@NonNull String videoId, @Nullable String parameter) {
|
||||
if (SPOOF_PLAYER_PARAMETER) {
|
||||
synchronized (lastVideoIds) {
|
||||
Boolean isSamples = parameterIsSample(parameter);
|
||||
if (lastVideoIds.put(videoId, isSamples) == null) {
|
||||
Logger.printDebug(() -> "New video loaded (videoId: " + videoId + ", isSamples: " + isSamples + ")");
|
||||
}
|
||||
}
|
||||
return parameterIsAgeRestricted(parameter)
|
||||
? PLAYER_PARAMETER_SHORTS
|
||||
: PLAYER_PARAMETER_SAMPLES;
|
||||
}
|
||||
return parameter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point. Overrides values passed into SubtitleWindowSettings constructor.
|
||||
*
|
||||
* @param ap anchor position. A bitmask with 6 bit fields, that appears to indicate the layout position on screen
|
||||
* @param ah anchor horizontal. A percentage [0, 100], that appears to be a horizontal text anchor point
|
||||
* @param av anchor vertical. A percentage [0, 100], that appears to be a vertical text anchor point
|
||||
* @param vs appears to indicate if subtitles exist, and the value is always true.
|
||||
* @param sd function is not entirely clear
|
||||
*/
|
||||
public static int[] fixSubtitleWindowPosition(int ap, int ah, int av, boolean vs, boolean sd) {
|
||||
// Videos with custom captions that specify screen positions appear to always have correct screen positions (even with spoofing).
|
||||
// But for auto generated and most other captions, the spoof incorrectly gives various default Samples caption settings.
|
||||
// Check for these known default Samples captions parameters, and replace with the known correct values.
|
||||
//
|
||||
// If a regular video uses a custom subtitle setting that match a default Samples setting,
|
||||
// then this will incorrectly replace the setting.
|
||||
// But, if the video uses multiple subtitles in different screen locations, then detect the non-default values
|
||||
// and do not replace any window settings for the video (regardless if they match a Samples default).
|
||||
if (SPOOF_PLAYER_PARAMETER &&
|
||||
numberOfNonDefaultSettingsObserved < NUMBER_OF_NON_DEFAULT_SUBTITLES_BEFORE_ENABLING_PASSTHRU) {
|
||||
synchronized (lastVideoIds) {
|
||||
String videoId = VideoInformation.getVideoId();
|
||||
Boolean isSample = lastVideoIds.get(videoId);
|
||||
if (BooleanUtils.isFalse(isSample)) {
|
||||
for (SubtitleWindowReplacementSettings setting : SubtitleWindowReplacementSettings.values()) {
|
||||
if (setting.match(ap, ah, av, vs, sd)) {
|
||||
return setting.replacementSetting();
|
||||
}
|
||||
}
|
||||
|
||||
numberOfNonDefaultSettingsObserved++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new int[]{ap, ah, av};
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
* <p>
|
||||
* Return false to force disable age restricted playback feature flag.
|
||||
*/
|
||||
public static boolean forceDisableAgeRestrictedPlaybackFeatureFlag(boolean original) {
|
||||
if (SPOOF_PLAYER_PARAMETER) {
|
||||
return false;
|
||||
}
|
||||
return original;
|
||||
}
|
||||
|
||||
/**
|
||||
* Known incorrect default Samples subtitle parameters, and the corresponding correct (non-Samples) values.
|
||||
*/
|
||||
private enum SubtitleWindowReplacementSettings {
|
||||
DEFAULT_SAMPLES_PARAMETERS_1(10, 50, 0, true, false,
|
||||
34, 50, 95),
|
||||
DEFAULT_SAMPLES_PARAMETERS_2(9, 20, 0, true, false,
|
||||
34, 50, 90),
|
||||
DEFAULT_SAMPLES_PARAMETERS_3(9, 20, 0, true, true,
|
||||
33, 20, 100);
|
||||
|
||||
// original values
|
||||
final int ap, ah, av;
|
||||
final boolean vs, sd;
|
||||
|
||||
// replacement int values
|
||||
final int[] replacement;
|
||||
|
||||
SubtitleWindowReplacementSettings(int ap, int ah, int av, boolean vs, boolean sd,
|
||||
int replacementAp, int replacementAh, int replacementAv) {
|
||||
this.ap = ap;
|
||||
this.ah = ah;
|
||||
this.av = av;
|
||||
this.vs = vs;
|
||||
this.sd = sd;
|
||||
this.replacement = new int[]{replacementAp, replacementAh, replacementAv};
|
||||
}
|
||||
|
||||
boolean match(int ap, int ah, int av, boolean vs, boolean sd) {
|
||||
return this.ap == ap && this.ah == ah && this.av == av && this.vs == vs && this.sd == sd;
|
||||
}
|
||||
|
||||
int[] replacementSetting() {
|
||||
return replacement;
|
||||
}
|
||||
}
|
||||
}
|
@ -190,6 +190,7 @@ public class Settings extends BaseSettings {
|
||||
public static final BooleanSetting DISABLE_DRC_AUDIO = new BooleanSetting("revanced_disable_drc_audio", FALSE, true);
|
||||
public static final BooleanSetting DISABLE_MUSIC_VIDEO_IN_ALBUM = new BooleanSetting("revanced_disable_music_video_in_album", FALSE, true);
|
||||
public static final EnumSetting<RedirectType> DISABLE_MUSIC_VIDEO_IN_ALBUM_REDIRECT_TYPE = new EnumSetting<>("revanced_disable_music_video_in_album_redirect_type", RedirectType.REDIRECT, true);
|
||||
public static final BooleanSetting SPOOF_PLAYER_PARAMETER = new BooleanSetting("revanced_spoof_player_parameter", TRUE, true);
|
||||
public static final BooleanSetting SETTINGS_IMPORT_EXPORT = new BooleanSetting("revanced_extended_settings_import_export", FALSE, false);
|
||||
|
||||
// PreferenceScreen: Return YouTube Dislike
|
||||
|
@ -22,12 +22,25 @@ public final class VideoInformation {
|
||||
private static final float DEFAULT_YOUTUBE_MUSIC_PLAYBACK_SPEED = 1.0f;
|
||||
private static final int DEFAULT_YOUTUBE_MUSIC_VIDEO_QUALITY = -2;
|
||||
private static final String DEFAULT_YOUTUBE_MUSIC_VIDEO_QUALITY_STRING = getString("quality_auto");
|
||||
/**
|
||||
* Prefix present in all Age-restricted music player parameters signature.
|
||||
*/
|
||||
private static final String AGE_RESTRICTED_PLAYER_PARAMETER = "ygYQ";
|
||||
/**
|
||||
* Prefix present in all Sample player parameters signature.
|
||||
*/
|
||||
private static final String SAMPLES_PLAYER_PARAMETERS = "8AEB";
|
||||
|
||||
@NonNull
|
||||
private static String videoId = "";
|
||||
|
||||
private static long videoLength = 0;
|
||||
private static long videoTime = -1;
|
||||
|
||||
@NonNull
|
||||
private static volatile String playerResponseVideoId = "";
|
||||
private static volatile boolean playerResponseVideoIdIsSample;
|
||||
|
||||
/**
|
||||
* The current playback speed
|
||||
*/
|
||||
@ -85,6 +98,65 @@ public final class VideoInformation {
|
||||
videoId = newlyLoadedVideoId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Differs from {@link #videoId} as this is the video id for the
|
||||
* last player response received, which may not be the last video opened.
|
||||
* <p>
|
||||
* If Shorts are loading the background, this commonly will be
|
||||
* different from the Short that is currently on screen.
|
||||
* <p>
|
||||
* For most use cases, you should instead use {@link #getVideoId()}.
|
||||
*
|
||||
* @return The id of the last video loaded, or an empty string if no videos have been loaded yet.
|
||||
*/
|
||||
@NonNull
|
||||
public static String getPlayerResponseVideoId() {
|
||||
return playerResponseVideoId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If the last player response video id was a Sample.
|
||||
*/
|
||||
public static boolean lastPlayerResponseIsSample() {
|
||||
return playerResponseVideoIdIsSample;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point. Called off the main thread.
|
||||
*
|
||||
* @param videoId The id of the last video loaded.
|
||||
*/
|
||||
public static void setPlayerResponseVideoId(@NonNull String videoId) {
|
||||
if (!playerResponseVideoId.equals(videoId)) {
|
||||
playerResponseVideoId = videoId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If the player parameter is for a Age-restricted video.
|
||||
*/
|
||||
public static boolean parameterIsAgeRestricted(@Nullable String parameter) {
|
||||
return parameter != null && parameter.startsWith(AGE_RESTRICTED_PLAYER_PARAMETER);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If the player parameter is for a Sample.
|
||||
*/
|
||||
public static boolean parameterIsSample(@Nullable String parameter) {
|
||||
return parameter != null && parameter.startsWith(SAMPLES_PLAYER_PARAMETERS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
@Nullable
|
||||
public static String newPlayerResponseParameter(@NonNull String videoId, @Nullable String playerParameter) {
|
||||
playerResponseVideoIdIsSample = parameterIsSample(playerParameter);
|
||||
Logger.printDebug(() -> "videoId: " + videoId + ", playerParameter: " + playerParameter);
|
||||
|
||||
return playerParameter; // Return the original value since we are observing and not modifying.
|
||||
}
|
||||
|
||||
/**
|
||||
* Seek on the current video.
|
||||
* Does not function for playback of Shorts.
|
||||
|
@ -14,7 +14,8 @@ 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.Hook
|
||||
import app.revanced.patches.music.video.playerresponse.addPlayerResponseMethodHook
|
||||
import app.revanced.patches.music.video.playerresponse.playerResponseMethodHookPatch
|
||||
import app.revanced.util.findMethodOrThrow
|
||||
import app.revanced.util.fingerprint.methodOrThrow
|
||||
@ -46,7 +47,11 @@ val albumMusicVideoPatch = bytecodePatch(
|
||||
|
||||
// region hook player response
|
||||
|
||||
hookPlayerResponse("$EXTENSION_CLASS_DESCRIPTOR->newPlayerResponse(Ljava/lang/String;Ljava/lang/String;I)V")
|
||||
addPlayerResponseMethodHook(
|
||||
Hook.VideoIdAndPlaylistId(
|
||||
"$EXTENSION_CLASS_DESCRIPTOR->newPlayerResponse(Ljava/lang/String;Ljava/lang/String;I)V"
|
||||
),
|
||||
)
|
||||
|
||||
// endregion
|
||||
|
||||
|
@ -0,0 +1,26 @@
|
||||
package app.revanced.patches.music.utils.fix.parameter
|
||||
|
||||
import app.revanced.util.fingerprint.legacyFingerprint
|
||||
import app.revanced.util.or
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
internal val subtitleWindowFingerprint = legacyFingerprint(
|
||||
name = "subtitleWindowFingerprint",
|
||||
returnType = "V",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
|
||||
parameters = listOf("I", "I", "I", "Z", "Z"),
|
||||
strings = listOf("invalid anchorHorizontalPos: %s"),
|
||||
)
|
||||
|
||||
/**
|
||||
* If this flag is activated, a playback issue occurs in age-restricted videos.
|
||||
*/
|
||||
internal const val AGE_RESTRICTED_PLAYBACK_FEATURE_FLAG = 45651506L
|
||||
|
||||
internal val ageRestrictedPlaybackFeatureFlagFingerprint = legacyFingerprint(
|
||||
name = "ageRestrictedPlaybackFeatureFlagFingerprint",
|
||||
returnType = "Z",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
parameters = emptyList(),
|
||||
literals = listOf(AGE_RESTRICTED_PLAYBACK_FEATURE_FLAG),
|
||||
)
|
@ -0,0 +1,82 @@
|
||||
package app.revanced.patches.music.utils.fix.parameter
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
|
||||
import app.revanced.patches.music.utils.extension.Constants.MISC_PATH
|
||||
import app.revanced.patches.music.utils.patch.PatchList.SPOOF_PLAYER_PARAMETER
|
||||
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.videoInformationPatch
|
||||
import app.revanced.patches.music.video.playerresponse.Hook
|
||||
import app.revanced.patches.music.video.playerresponse.addPlayerResponseMethodHook
|
||||
import app.revanced.patches.music.video.playerresponse.playerResponseMethodHookPatch
|
||||
import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall
|
||||
import app.revanced.util.fingerprint.methodOrThrow
|
||||
import app.revanced.util.fingerprint.resolvable
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"$MISC_PATH/SpoofPlayerParameterPatch;"
|
||||
|
||||
@Suppress("unused")
|
||||
val spoofPlayerParameterPatch = bytecodePatch(
|
||||
SPOOF_PLAYER_PARAMETER.title,
|
||||
SPOOF_PLAYER_PARAMETER.summary
|
||||
) {
|
||||
compatibleWith(COMPATIBLE_PACKAGE)
|
||||
|
||||
dependsOn(
|
||||
settingsPatch,
|
||||
videoInformationPatch,
|
||||
playerResponseMethodHookPatch,
|
||||
)
|
||||
|
||||
execute {
|
||||
|
||||
addPlayerResponseMethodHook(
|
||||
Hook.PlayerParameter(
|
||||
"$EXTENSION_CLASS_DESCRIPTOR->spoofParameter(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"
|
||||
),
|
||||
)
|
||||
|
||||
// region fix for subtitles position
|
||||
|
||||
subtitleWindowFingerprint.methodOrThrow().addInstructions(
|
||||
0,
|
||||
"""
|
||||
invoke-static {p1, p2, p3, p4, p5}, $EXTENSION_CLASS_DESCRIPTOR->fixSubtitleWindowPosition(IIIZZ)[I
|
||||
move-result-object v0
|
||||
const/4 v1, 0x0
|
||||
aget p1, v0, v1 # ap, anchor position
|
||||
const/4 v1, 0x1
|
||||
aget p2, v0, v1 # ah, horizontal anchor
|
||||
const/4 v1, 0x2
|
||||
aget p3, v0, v1 # av, vertical anchor
|
||||
"""
|
||||
)
|
||||
|
||||
// endregion
|
||||
|
||||
// region fix for feature flags
|
||||
|
||||
if (ageRestrictedPlaybackFeatureFlagFingerprint.resolvable()) {
|
||||
ageRestrictedPlaybackFeatureFlagFingerprint.injectLiteralInstructionBooleanCall(
|
||||
AGE_RESTRICTED_PLAYBACK_FEATURE_FLAG,
|
||||
"$EXTENSION_CLASS_DESCRIPTOR->forceDisableAgeRestrictedPlaybackFeatureFlag(Z)Z"
|
||||
)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
addSwitchPreference(
|
||||
CategoryType.MISC,
|
||||
"revanced_spoof_player_parameter",
|
||||
"true"
|
||||
)
|
||||
|
||||
updatePatchStatus(SPOOF_PLAYER_PARAMETER)
|
||||
|
||||
}
|
||||
}
|
@ -157,6 +157,10 @@ internal enum class PatchList(
|
||||
"Spoof client",
|
||||
"Adds options to spoof the client to allow playback."
|
||||
),
|
||||
SPOOF_PLAYER_PARAMETER(
|
||||
"Spoof player parameter",
|
||||
"Adds options to spoof player parameter to allow playback."
|
||||
),
|
||||
TRANSLATIONS_FOR_YOUTUBE_MUSIC(
|
||||
"Translations for YouTube Music",
|
||||
"Add translations or remove string resources."
|
||||
|
@ -13,6 +13,9 @@ import app.revanced.patches.music.utils.extension.Constants.SHARED_PATH
|
||||
import app.revanced.patches.music.utils.playbackSpeedFingerprint
|
||||
import app.revanced.patches.music.utils.playbackSpeedParentFingerprint
|
||||
import app.revanced.patches.music.utils.resourceid.sharedResourceIdPatch
|
||||
import app.revanced.patches.music.video.playerresponse.Hook
|
||||
import app.revanced.patches.music.video.playerresponse.addPlayerResponseMethodHook
|
||||
import app.revanced.patches.music.video.playerresponse.playerResponseMethodHookPatch
|
||||
import app.revanced.patches.shared.mdxPlayerDirectorSetVideoStageFingerprint
|
||||
import app.revanced.patches.shared.videoLengthFingerprint
|
||||
import app.revanced.util.addStaticFieldToExtension
|
||||
@ -71,7 +74,10 @@ private var videoTimeConstructorInsertIndex = 2
|
||||
val videoInformationPatch = bytecodePatch(
|
||||
description = "videoInformationPatch",
|
||||
) {
|
||||
dependsOn(sharedResourceIdPatch)
|
||||
dependsOn(
|
||||
playerResponseMethodHookPatch,
|
||||
sharedResourceIdPatch
|
||||
)
|
||||
|
||||
execute {
|
||||
fun addSeekInterfaceMethods(
|
||||
@ -241,7 +247,18 @@ val videoInformationPatch = bytecodePatch(
|
||||
* Set current video id
|
||||
*/
|
||||
videoIdHook("$EXTENSION_CLASS_DESCRIPTOR->setVideoId(Ljava/lang/String;)V")
|
||||
|
||||
addPlayerResponseMethodHook(
|
||||
Hook.VideoId(
|
||||
"$EXTENSION_CLASS_DESCRIPTOR->setPlayerResponseVideoId(Ljava/lang/String;)V"
|
||||
),
|
||||
)
|
||||
// Call before any other video id hooks,
|
||||
// so they can use VideoInformation and check if the video id is for a Short.
|
||||
addPlayerResponseMethodHook(
|
||||
Hook.PlayerParameterBeforeVideoId(
|
||||
"$EXTENSION_CLASS_DESCRIPTOR->newPlayerResponseParameter(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"
|
||||
)
|
||||
)
|
||||
/**
|
||||
* Hook current playback speed
|
||||
*/
|
||||
|
@ -11,8 +11,7 @@ private val PLAYER_PARAMETER_STARTS_WITH_PARAMETER_LIST = listOf(
|
||||
"[B",
|
||||
"Ljava/lang/String;", // Player parameters proto buffer.
|
||||
"Ljava/lang/String;", // PlaylistId.
|
||||
"I", // PlaylistIndex.
|
||||
"I"
|
||||
"I" // PlaylistIndex.
|
||||
)
|
||||
|
||||
/**
|
||||
@ -30,7 +29,7 @@ internal val playerParameterBuilderFingerprint = legacyFingerprint(
|
||||
return@custom false
|
||||
}
|
||||
|
||||
val startsWithMethodParameterList = parameterTypes.slice(0..5)
|
||||
val startsWithMethodParameterList = parameterTypes.slice(0..4)
|
||||
|
||||
parametersEqual(
|
||||
PLAYER_PARAMETER_STARTS_WITH_PARAMETER_LIST,
|
||||
|
@ -1,22 +1,35 @@
|
||||
package app.revanced.patches.music.video.playerresponse
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patches.music.utils.extension.sharedExtensionPatch
|
||||
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 val hooks = mutableSetOf<Hook>()
|
||||
|
||||
fun addPlayerResponseMethodHook(hook: Hook) {
|
||||
hooks += hook
|
||||
}
|
||||
|
||||
private const val REGISTER_VIDEO_ID = "p1"
|
||||
private const val REGISTER_PLAYER_PARAMETER = "p3"
|
||||
private const val REGISTER_PLAYLIST_ID = "p4"
|
||||
private const val REGISTER_PLAYLIST_INDEX = "p5"
|
||||
|
||||
private lateinit var playerResponseMethod: MutableMethod
|
||||
private var numberOfInstructionsAdded = 0
|
||||
|
||||
val playerResponseMethodHookPatch = bytecodePatch(
|
||||
description = "playerResponseMethodHookPatch"
|
||||
) {
|
||||
dependsOn(versionCheckPatch)
|
||||
dependsOn(
|
||||
sharedExtensionPatch,
|
||||
versionCheckPatch,
|
||||
)
|
||||
|
||||
execute {
|
||||
playerResponseMethod = if (is_7_03_or_greater) {
|
||||
@ -25,16 +38,70 @@ val playerResponseMethodHookPatch = bytecodePatch(
|
||||
playerParameterBuilderLegacyFingerprint
|
||||
}.methodOrThrow()
|
||||
}
|
||||
|
||||
finalize {
|
||||
fun hookVideoId(hook: Hook) {
|
||||
playerResponseMethod.addInstruction(
|
||||
0,
|
||||
"invoke-static {$REGISTER_VIDEO_ID}, $hook",
|
||||
)
|
||||
numberOfInstructionsAdded++
|
||||
}
|
||||
|
||||
fun hookVideoIdAndPlaylistId(hook: Hook) {
|
||||
playerResponseMethod.addInstruction(
|
||||
0,
|
||||
"invoke-static {$REGISTER_VIDEO_ID, $REGISTER_PLAYLIST_ID, $REGISTER_PLAYLIST_INDEX}, $hook",
|
||||
)
|
||||
numberOfInstructionsAdded++
|
||||
}
|
||||
|
||||
fun hookPlayerParameter(hook: Hook) {
|
||||
playerResponseMethod.addInstructions(
|
||||
0,
|
||||
"""
|
||||
invoke-static {$REGISTER_VIDEO_ID, v0}, $hook
|
||||
move-result-object v0
|
||||
""",
|
||||
)
|
||||
numberOfInstructionsAdded += 2
|
||||
}
|
||||
|
||||
// Reverse the order in order to preserve insertion order of the hooks.
|
||||
val beforeVideoIdHooks =
|
||||
hooks.filterIsInstance<Hook.PlayerParameterBeforeVideoId>().asReversed()
|
||||
val videoIdHooks = hooks.filterIsInstance<Hook.VideoId>().asReversed()
|
||||
val videoIdAndPlaylistIdHooks = hooks.filterIsInstance<Hook.VideoIdAndPlaylistId>().asReversed()
|
||||
val afterVideoIdHooks = hooks.filterIsInstance<Hook.PlayerParameter>().asReversed()
|
||||
|
||||
// Add the hooks in this specific order as they insert instructions at the beginning of the method.
|
||||
afterVideoIdHooks.forEach(::hookPlayerParameter)
|
||||
videoIdAndPlaylistIdHooks.forEach(::hookVideoIdAndPlaylistId)
|
||||
videoIdHooks.forEach(::hookVideoId)
|
||||
beforeVideoIdHooks.forEach(::hookPlayerParameter)
|
||||
|
||||
playerResponseMethod.apply {
|
||||
addInstruction(
|
||||
0,
|
||||
"move-object/from16 v0, $REGISTER_PLAYER_PARAMETER"
|
||||
)
|
||||
numberOfInstructionsAdded++
|
||||
|
||||
// Move the modified register back.
|
||||
addInstruction(
|
||||
numberOfInstructionsAdded,
|
||||
"move-object/from16 $REGISTER_PLAYER_PARAMETER, v0"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
sealed class Hook(private val methodDescriptor: String) {
|
||||
class VideoId(methodDescriptor: String) : Hook(methodDescriptor)
|
||||
class VideoIdAndPlaylistId(methodDescriptor: String) : Hook(methodDescriptor)
|
||||
|
||||
playerResponseMethod.addInstruction(0, smaliInstruction)
|
||||
class PlayerParameter(methodDescriptor: String) : Hook(methodDescriptor)
|
||||
class PlayerParameterBeforeVideoId(methodDescriptor: String) : Hook(methodDescriptor)
|
||||
|
||||
override fun toString() = methodDescriptor
|
||||
}
|
||||
|
@ -501,6 +501,11 @@ Info:
|
||||
<string name="revanced_spoof_client_type_entry_android_music_5_29">Android Music 5.29.53</string>
|
||||
<string name="revanced_spoof_client_type_entry_ios_music_6_21">iOS Music 6.21</string>
|
||||
<string name="revanced_spoof_client_type_entry_ios_music_7_04">iOS Music 7.04</string>
|
||||
<string name="revanced_spoof_player_parameter_title">Spoof player parameter</string>
|
||||
<string name="revanced_spoof_player_parameter_summary">"Spoof the player parameter to prevent playback issues.
|
||||
|
||||
Side effect:
|
||||
• Sometimes the subtitles are located at the top of the player instead of the bottom."</string>
|
||||
<string name="revanced_watch_history_type_title">Watch history type</string>
|
||||
<string name="revanced_watch_history_type_summary">"• Original: Follows the watch history settings of Google account, but watch history may not work due to DNS or VPN.
|
||||
• Replace domain: Follows the watch history settings of Google account.
|
||||
|
Loading…
x
Reference in New Issue
Block a user