fix(YouTube - Spoof app version): Remove broken spoof targets that YouTube no longer supports (#4610)

This commit is contained in:
LisoUseInAIKyrios 2025-03-19 18:08:51 +01:00 committed by GitHub
parent 2090f7ec8f
commit 883fbe7123
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 43 additions and 298 deletions

View File

@ -21,7 +21,6 @@ import java.util.Objects;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.patches.components.ReturnYouTubeDislikeFilterPatch;
import app.revanced.extension.youtube.patches.spoof.SpoofAppVersionPatch;
import app.revanced.extension.youtube.returnyoutubedislike.ReturnYouTubeDislike;
import app.revanced.extension.youtube.returnyoutubedislike.requests.ReturnYouTubeDislikeApi;
import app.revanced.extension.youtube.settings.Settings;
@ -47,9 +46,6 @@ import app.revanced.extension.youtube.shared.PlayerType;
@SuppressWarnings("unused")
public class ReturnYouTubeDislikePatch {
public static final boolean IS_SPOOFING_TO_NON_LITHO_SHORTS_PLAYER =
SpoofAppVersionPatch.isSpoofingToLessThan("18.34.00");
/**
* RYD data for the current video on screen.
*/
@ -347,137 +343,6 @@ public class ReturnYouTubeDislikePatch {
}
}
//
// Non litho Shorts player.
//
/**
* Replacement text to use for "Dislikes" while RYD is fetching.
*/
private static final Spannable SHORTS_LOADING_SPAN = new SpannableString("-");
/**
* Dislikes TextViews used by Shorts.
*
* Multiple TextViews are loaded at once (for the prior and next videos to swipe to).
* Keep track of all of them, and later pick out the correct one based on their on screen position.
*/
private static final List<WeakReference<TextView>> shortsTextViewRefs = new ArrayList<>();
private static void clearRemovedShortsTextViews() {
shortsTextViewRefs.removeIf(ref -> ref.get() == null);
}
/**
* Injection point. Called when a Shorts dislike is updated. Always on main thread.
* Handles update asynchronously, otherwise Shorts video will be frozen while the UI thread is blocked.
*
* @return if RYD is enabled and the TextView was updated.
*/
public static boolean setShortsDislikes(@NonNull View likeDislikeView) {
try {
if (!Settings.RYD_ENABLED.get()) {
return false;
}
if (!Settings.RYD_SHORTS.get() || Settings.HIDE_SHORTS_DISLIKE_BUTTON.get()) {
// Must clear the data here, in case a new video was loaded while PlayerType
// suggested the video was not a short (can happen when spoofing to an old app version).
clearData();
return false;
}
Logger.printDebug(() -> "setShortsDislikes");
TextView textView = (TextView) likeDislikeView;
textView.setText(SHORTS_LOADING_SPAN); // Change 'Dislike' text to the loading text.
shortsTextViewRefs.add(new WeakReference<>(textView));
if (likeDislikeView.isSelected() && isShortTextViewOnScreen(textView)) {
Logger.printDebug(() -> "Shorts dislike is already selected");
ReturnYouTubeDislike videoData = currentVideoData;
if (videoData != null) videoData.setUserVote(Vote.DISLIKE);
}
// For the first short played, the Shorts dislike hook is called after the video id hook.
// But for most other times this hook is called before the video id (which is not ideal).
// Must update the TextViews here, and also after the videoId changes.
updateOnScreenShortsTextViews(false);
return true;
} catch (Exception ex) {
Logger.printException(() -> "setShortsDislikes failure", ex);
return false;
}
}
/**
* @param forceUpdate if false, then only update the 'loading text views.
* If true, update all on screen text views.
*/
private static void updateOnScreenShortsTextViews(boolean forceUpdate) {
try {
clearRemovedShortsTextViews();
if (shortsTextViewRefs.isEmpty()) {
return;
}
ReturnYouTubeDislike videoData = currentVideoData;
if (videoData == null) {
return;
}
Logger.printDebug(() -> "updateShortsTextViews");
Runnable update = () -> {
Spanned shortsDislikesSpan = videoData.getDislikeSpanForShort(SHORTS_LOADING_SPAN);
Utils.runOnMainThreadNowOrLater(() -> {
String videoId = videoData.getVideoId();
if (!videoId.equals(VideoInformation.getVideoId())) {
// User swiped to new video before fetch completed
Logger.printDebug(() -> "Ignoring stale dislikes data for short: " + videoId);
return;
}
// Update text views that appear to be visible on screen.
// Only 1 will be the actual textview for the current Short,
// but discarded and not yet garbage collected views can remain.
// So must set the dislike span on all views that match.
for (WeakReference<TextView> textViewRef : shortsTextViewRefs) {
TextView textView = textViewRef.get();
if (textView == null) {
continue;
}
if (isShortTextViewOnScreen(textView)
&& (forceUpdate || textView.getText().toString().equals(SHORTS_LOADING_SPAN.toString()))) {
Logger.printDebug(() -> "Setting Shorts TextView to: " + shortsDislikesSpan);
textView.setText(shortsDislikesSpan);
}
}
});
};
if (videoData.fetchCompleted()) {
update.run(); // Network call is completed, no need to wait on background thread.
} else {
Utils.runOnBackgroundThread(update);
}
} catch (Exception ex) {
Logger.printException(() -> "updateOnScreenShortsTextViews failure", ex);
}
}
/**
* Check if a view is within the screen bounds.
*/
private static boolean isShortTextViewOnScreen(@NonNull View view) {
final int[] location = new int[2];
view.getLocationInWindow(location);
if (location[0] <= 0 && location[1] <= 0) { // Lower bound
return false;
}
Rect windowRect = new Rect();
view.getWindowVisibleDisplayFrame(windowRect); // Upper bound
return location[0] < windowRect.width() && location[1] < windowRect.height();
}
//
// Video Id and voting hooks (all players).
//
@ -503,8 +368,7 @@ public class ReturnYouTubeDislikePatch {
if (videoIdIsShort && (!isShortAndOpeningOrPlaying || !Settings.RYD_SHORTS.get())) {
return;
}
final boolean waitForFetchToComplete = !IS_SPOOFING_TO_NON_LITHO_SHORTS_PLAYER
&& videoIdIsShort && !lastPlayerResponseWasShort;
final boolean waitForFetchToComplete = videoIdIsShort && !lastPlayerResponseWasShort;
Logger.printDebug(() -> "Prefetching RYD for video: " + videoId);
ReturnYouTubeDislike fetch = ReturnYouTubeDislike.getFetchForVideoId(videoId);
@ -557,12 +421,6 @@ public class ReturnYouTubeDislikePatch {
data.setVideoIdIsShort(true);
}
currentVideoData = data;
// Current video id hook can be called out of order with the non litho Shorts text view hook.
// Must manually update again here.
if (isNoneHiddenOrSlidingMinimized) {
updateOnScreenShortsTextViews(true);
}
} catch (Exception ex) {
Logger.printException(() -> "newVideoLoaded failure", ex);
}

View File

@ -37,7 +37,6 @@ import java.util.concurrent.*;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.ThemeHelper;
import app.revanced.extension.youtube.patches.spoof.SpoofAppVersionPatch;
import app.revanced.extension.youtube.returnyoutubedislike.requests.RYDVoteData;
import app.revanced.extension.youtube.returnyoutubedislike.requests.ReturnYouTubeDislikeApi;
import app.revanced.extension.youtube.settings.Settings;
@ -87,9 +86,6 @@ public class ReturnYouTubeDislike {
*/
private static final char MIDDLE_SEPARATOR_CHARACTER = '◎'; // 'bullseye'
private static final boolean IS_SPOOFING_TO_OLD_SEPARATOR_COLOR
= SpoofAppVersionPatch.isSpoofingToLessThan("18.10.00");
/**
* Cached lookup of all video ids.
*/
@ -184,17 +180,8 @@ public class ReturnYouTubeDislike {
* Color of the left and middle separator, based on the color of the right separator.
* It's unknown where YT gets the color from, and the values here are approximated by hand.
* Ideally, this would be the actual color YT uses at runtime.
*
* Older versions before the 'Me' library tab use a slightly different color.
* If spoofing was previously used and is now turned off,
* or an old version was recently upgraded then the old colors are sometimes still used.
*/
private static int getSeparatorColor() {
if (IS_SPOOFING_TO_OLD_SEPARATOR_COLOR) {
return ThemeHelper.isDarkTheme()
? 0x29AAAAAA // transparent dark gray
: 0xFFD9D9D9; // light gray
}
return ThemeHelper.isDarkTheme()
? 0x33FFFFFF
: 0xFFD9D9D9;

View File

@ -3,7 +3,6 @@ package app.revanced.extension.youtube.settings;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static app.revanced.extension.shared.settings.Setting.Availability;
import static app.revanced.extension.shared.settings.Setting.migrateFromOldPreferences;
import static app.revanced.extension.shared.settings.Setting.migrateOldSettingToNew;
import static app.revanced.extension.shared.settings.Setting.parent;
import static app.revanced.extension.shared.settings.Setting.parentsAny;
@ -21,7 +20,6 @@ import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerT
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType.MODERN_4;
import static app.revanced.extension.youtube.patches.OpenShortsInRegularPlayerPatch.ShortsPlayerType;
import static app.revanced.extension.youtube.patches.SeekbarThumbnailsPatch.SeekbarThumbnailsHighQualityAvailability;
import static app.revanced.extension.youtube.patches.VersionCheckPatch.IS_19_17_OR_GREATER;
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.IGNORE;
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.MANUAL_SKIP;
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY;
@ -38,7 +36,6 @@ import app.revanced.extension.shared.settings.IntegerSetting;
import app.revanced.extension.shared.settings.LongSetting;
import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.shared.settings.StringSetting;
import app.revanced.extension.shared.settings.preference.SharedPrefCategory;
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.DeArrowAvailability;
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.StillImagesAvailability;
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.ThumbnailOption;
@ -221,7 +218,7 @@ public class Settings extends BaseSettings {
public static final BooleanSetting SPOOF_APP_VERSION = new BooleanSetting("revanced_spoof_app_version", FALSE, true, "revanced_spoof_app_version_user_dialog_message");
public static final BooleanSetting WIDE_SEARCHBAR = new BooleanSetting("revanced_wide_searchbar", FALSE, true);
public static final EnumSetting<StartPage> CHANGE_START_PAGE = new EnumSetting<>("revanced_change_start_page", StartPage.DEFAULT, true);
public static final StringSetting SPOOF_APP_VERSION_TARGET = new StringSetting("revanced_spoof_app_version_target", IS_19_17_OR_GREATER ? "19.26.42" : "17.33.42", true, parent(SPOOF_APP_VERSION));
public static final StringSetting SPOOF_APP_VERSION_TARGET = new StringSetting("revanced_spoof_app_version_target", "19.26.42", true, parent(SPOOF_APP_VERSION));
// Custom filter
public static final BooleanSetting CUSTOM_FILTER = new BooleanSetting("revanced_custom_filter", FALSE);
public static final StringSetting CUSTOM_FILTER_STRINGS = new StringSetting("revanced_custom_filter_strings", "", true, parent(CUSTOM_FILTER));
@ -395,7 +392,6 @@ public class Settings extends BaseSettings {
public static final FloatSetting SB_CATEGORY_UNSUBMITTED_OPACITY = new FloatSetting("sb_unsubmitted_opacity", 1.0f);
// Deprecated migrations
private static final StringSetting DEPRECATED_SB_UUID_OLD_MIGRATION_SETTING = new StringSetting("uuid", ""); // Delete sometime in 2024
private static final BooleanSetting DEPRECATED_HIDE_PLAYER_BUTTONS = new BooleanSetting("revanced_hide_player_buttons", FALSE, true);
private static final BooleanSetting DEPRECATED_HIDE_PLAYER_FLYOUT_VIDEO_QUALITY_FOOTER = new BooleanSetting("revanced_hide_video_quality_menu_footer", FALSE);
private static final IntegerSetting DEPRECATED_SWIPE_OVERLAY_BACKGROUND_ALPHA = new IntegerSetting("revanced_swipe_overlay_background_alpha", 127);
@ -406,16 +402,6 @@ public class Settings extends BaseSettings {
static {
// region Migration
// Do _not_ delete this SB private user id migration property until sometime in early 2025.
// This is the only setting that cannot be reconfigured if lost,
// and more time should be given for users who rarely upgrade.
SharedPrefCategory sbPrefs = new SharedPrefCategory("sponsor-block");
// Remove the "sb_" prefix, as old settings are saved without it.
String key = DEPRECATED_SB_UUID_OLD_MIGRATION_SETTING.key.substring(3);
migrateFromOldPreferences(sbPrefs, DEPRECATED_SB_UUID_OLD_MIGRATION_SETTING, key);
migrateOldSettingToNew(DEPRECATED_SB_UUID_OLD_MIGRATION_SETTING, SB_PRIVATE_USER_ID);
migrateOldSettingToNew(DEPRECATED_HIDE_PLAYER_BUTTONS, HIDE_PLAYER_PREVIOUS_NEXT_BUTTONS);
migrateOldSettingToNew(DEPRECATED_HIDE_PLAYER_FLYOUT_VIDEO_QUALITY_FOOTER, HIDE_PLAYER_FLYOUT_VIDEO_QUALITY_FOOTER);
@ -459,6 +445,12 @@ public class Settings extends BaseSettings {
DEPRECATED_SWIPE_OVERLAY_BACKGROUND_ALPHA.resetToDefault();
}
// Old spoof versions that no longer work.
if (SPOOF_APP_VERSION_TARGET.get().compareTo(SPOOF_APP_VERSION_TARGET.defaultValue) < 0) {
Logger.printInfo(() -> "Resetting spoof app version target");
SPOOF_APP_VERSION_TARGET.resetToDefault();
}
// endregion
// region SB import/export callbacks

View File

@ -86,9 +86,7 @@ public class ReturnYouTubeDislikePreferenceFragment extends PreferenceFragment {
shortsPreference = new SwitchPreference(context);
shortsPreference.setChecked(Settings.RYD_SHORTS.get());
shortsPreference.setTitle(str("revanced_ryd_shorts_title"));
String shortsSummary = ReturnYouTubeDislikePatch.IS_SPOOFING_TO_NON_LITHO_SHORTS_PLAYER
? str("revanced_ryd_shorts_summary_on")
: str("revanced_ryd_shorts_summary_on_disclaimer");
String shortsSummary = str("revanced_ryd_shorts_summary_on_disclaimer");
shortsPreference.setSummaryOn(shortsSummary);
shortsPreference.setSummaryOff(str("revanced_ryd_shorts_summary_off"));
shortsPreference.setOnPreferenceChangeListener((pref, newValue) -> {

View File

@ -98,20 +98,6 @@ internal val rollingNumberTextViewFingerprint = fingerprint {
}
}
internal val shortsTextViewFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("V")
parameters("L", "L")
opcodes(
Opcode.INVOKE_SUPER, // first instruction of method
Opcode.IF_NEZ,
null,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.CHECK_CAST,
)
}
internal val textComponentConstructorFingerprint = fingerprint {
accessFlags(AccessFlags.CONSTRUCTOR, AccessFlags.PRIVATE)
strings("TextComponent")

View File

@ -1,9 +1,7 @@
package app.revanced.patches.youtube.layout.returnyoutubedislike
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.patch.PatchException
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.all.misc.resources.addResources
@ -169,51 +167,7 @@ val returnYouTubeDislikePatch = bytecodePatch(
// endregion
// region Hook for non-litho Short videos.
shortsTextViewFingerprint.method.apply {
val insertIndex = shortsTextViewFingerprint.patternMatch!!.endIndex + 1
// If the field is true, the TextView is for a dislike button.
val isDisLikesBooleanInstruction = instructions.first { instruction ->
instruction.opcode == Opcode.IGET_BOOLEAN
} as ReferenceInstruction
val isDisLikesBooleanReference = isDisLikesBooleanInstruction.reference
// Like/Dislike button TextView field.
val textViewFieldInstruction = instructions.first { instruction ->
instruction.opcode == Opcode.IGET_OBJECT
} as ReferenceInstruction
val textViewFieldReference = textViewFieldInstruction.reference
// Check if the hooked TextView object is that of the dislike button.
// If RYD is disabled, or the TextView object is not that of the dislike button, the execution flow is not interrupted.
// Otherwise, the TextView object is modified, and the execution flow is interrupted to prevent it from being changed afterward.
addInstructionsWithLabels(
insertIndex,
"""
# Check, if the TextView is for a dislike button
iget-boolean v0, p0, $isDisLikesBooleanReference
if-eqz v0, :is_like
# Hook the TextView, if it is for the dislike button
iget-object v0, p0, $textViewFieldReference
invoke-static {v0}, $EXTENSION_CLASS_DESCRIPTOR->setShortsDislikes(Landroid/view/View;)Z
move-result v0
if-eqz v0, :ryd_disabled
return-void
:is_like
:ryd_disabled
nop
""",
)
}
// endregion
// region Hook for litho Shorts
// region Hook Shorts
// Filter that parses the video id from the UI
addLithoFilter(FILTER_CLASS_DESCRIPTOR)
@ -255,22 +209,25 @@ val returnYouTubeDislikePatch = bytecodePatch(
)
}
// Rolling Number text views use the measured width of the raw string for layout.
// Modify the measure text calculation to include the left drawable separator if needed.
val patternMatch = rollingNumberMeasureAnimatedTextFingerprint.patternMatch!!
// Additional check to verify the opcodes are at the start of the method
if (patternMatch.startIndex != 0) throw PatchException("Unexpected opcode location")
val endIndex = patternMatch.endIndex
rollingNumberMeasureAnimatedTextFingerprint.method.apply {
val measuredTextWidthRegister = getInstruction<OneRegisterInstruction>(endIndex).registerA
rollingNumberMeasureAnimatedTextFingerprint.let {
// Rolling Number text views use the measured width of the raw string for layout.
// Modify the measure text calculation to include the left drawable separator if needed.
val patternMatch = it.patternMatch!!
// Verify the opcodes are at the start of the method.
if (patternMatch.startIndex != 0) throw PatchException("Unexpected opcode location")
val endIndex = patternMatch.endIndex
addInstructions(
endIndex + 1,
"""
invoke-static {p1, v$measuredTextWidthRegister}, $EXTENSION_CLASS_DESCRIPTOR->onRollingNumberMeasured(Ljava/lang/String;F)F
move-result v$measuredTextWidthRegister
""",
)
it.method.apply {
val measuredTextWidthRegister = getInstruction<OneRegisterInstruction>(endIndex).registerA
addInstructions(
endIndex + 1,
"""
invoke-static {p1, v$measuredTextWidthRegister}, $EXTENSION_CLASS_DESCRIPTOR->onRollingNumberMeasured(Ljava/lang/String;F)F
move-result v$measuredTextWidthRegister
"""
)
}
}
// Additional text measurement method. Used if YouTube decides not to animate the likes count
@ -291,15 +248,14 @@ val returnYouTubeDislikePatch = bytecodePatch(
)
}
}
// The rolling number Span is missing styling since it's initially set as a String.
// Modify the UI text view and use the styled like/dislike Span.
// Initial TextView is set in this method.
val initiallyCreatedTextViewMethod = rollingNumberTextViewFingerprint.method
// Videos less than 24 hours after uploaded, like counts will be updated in real time.
// Whenever like counts are updated, TextView is set in this method.
arrayOf(
initiallyCreatedTextViewMethod,
// The rolling number Span is missing styling since it's initially set as a String.
// Modify the UI text view and use the styled like/dislike Span.
// Initial TextView is set in this method.
rollingNumberTextViewFingerprint.method,
// Videos less than 24 hours after uploaded, like counts will be updated in real time.
// Whenever like counts are updated, TextView is set in this method.
rollingNumberTextViewAnimationUpdateFingerprint.method,
).forEach { insertMethod ->
insertMethod.apply {
@ -315,9 +271,9 @@ val returnYouTubeDislikePatch = bytecodePatch(
addInstructions(
setTextIndex,
"""
invoke-static {v$textViewRegister, v$textSpanRegister}, $EXTENSION_CLASS_DESCRIPTOR->updateRollingNumber(Landroid/widget/TextView;Ljava/lang/CharSequence;)Ljava/lang/CharSequence;
move-result-object v$textSpanRegister
""",
invoke-static {v$textViewRegister, v$textSpanRegister}, $EXTENSION_CLASS_DESCRIPTOR->updateRollingNumber(Landroid/widget/TextView;Ljava/lang/CharSequence;)Ljava/lang/CharSequence;
move-result-object v$textSpanRegister
"""
)
}
}

View File

@ -16,7 +16,6 @@ import app.revanced.patches.shared.misc.settings.preference.PreferenceCategory
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.playservice.is_19_17_or_greater
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
@ -46,8 +45,7 @@ private const val EXTENSION_CLASS_DESCRIPTOR =
val spoofAppVersionPatch = bytecodePatch(
name = "Spoof app version",
description = "Adds an option to trick YouTube into thinking you are running an older version of the app. " +
"This can be used to restore old UI elements and features. " +
"Patching 19.16.39 includes additional older spoofing targets.",
"This can be used to restore old UI elements and features."
) {
dependsOn(
spoofAppVersionResourcePatch,
@ -59,7 +57,7 @@ val spoofAppVersionPatch = bytecodePatch(
compatibleWith(
"com.google.android.youtube"(
"19.16.39",
// "19.16.39", // Cannot be supported because the lowest spoof target is higher.
// "19.25.37", // Cannot be supported because the lowest spoof target is higher.
// "19.34.42", // Cannot be supported because the lowest spoof target is higher.
"19.43.41",
@ -81,19 +79,10 @@ val spoofAppVersionPatch = bytecodePatch(
tag = "app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory",
preferences = setOf(
SwitchPreference("revanced_spoof_app_version"),
if (is_19_17_or_greater) {
ListPreference(
key = "revanced_spoof_app_version_target",
summaryKey = null,
)
} else {
ListPreference(
key = "revanced_spoof_app_version_target",
summaryKey = null,
entriesKey = "revanced_spoof_app_version_target_legacy_entries",
entryValuesKey = "revanced_spoof_app_version_target_legacy_entry_values"
)
}
ListPreference(
key = "revanced_spoof_app_version_target",
summaryKey = null,
)
)
)
)

View File

@ -143,24 +143,9 @@
<item>@string/revanced_spoof_app_version_target_entry_2</item>
</string-array>
<string-array name="revanced_spoof_app_version_target_entry_values">
<!-- Patching 19.23+ or later cannot spoof to 19.20 or lower
due to a missing library tab image resource. That can be fixed
by patching the code that loads the images and ignoring unknown images. -->
<item>19.35.36</item>
<item>19.26.42</item>
</string-array>
<string-array name="revanced_spoof_app_version_target_legacy_entries">
<item>@string/revanced_spoof_app_version_target_legacy_entry_1</item>
<item>@string/revanced_spoof_app_version_target_legacy_entry_2</item>
<item>@string/revanced_spoof_app_version_target_legacy_entry_3</item>
<item>@string/revanced_spoof_app_version_target_legacy_entry_4</item>
</string-array>
<string-array name="revanced_spoof_app_version_target_legacy_entry_values">
<item>18.33.40</item>
<item>18.20.39</item>
<item>18.09.39</item>
<item>17.33.42</item>
</string-array>
</patch>
<patch id="layout.formfactor.changeFormFactorPatch">
<string-array name="revanced_change_form_factor_entries">

View File

@ -868,7 +868,6 @@ Settings → Playback → Autoplay next video"</string>
<string name="revanced_ryd_enable_summary_on">Dislikes are shown</string>
<string name="revanced_ryd_enable_summary_off">Dislikes are not shown</string>
<string name="revanced_ryd_shorts_title">Show dislikes on Shorts</string>
<string name="revanced_ryd_shorts_summary_on">Dislikes on Shorts are shown</string>
<string name="revanced_ryd_shorts_summary_on_disclaimer">"Dislikes on Shorts are shown
Limitation: Dislikes may not appear in incognito mode"</string>
@ -1157,11 +1156,6 @@ If later turned off, it is recommended to clear the app data to prevent UI bugs.
<string name="revanced_spoof_app_version_target_title">Spoof app version target</string>
<string name="revanced_spoof_app_version_target_entry_1">19.35.36 - Restore old Shorts player icons</string>
<string name="revanced_spoof_app_version_target_entry_2">19.26.42 - Restore old navigation icons</string>
<!-- 'RYD' is 'Return YouTube Dislike' -->
<string name="revanced_spoof_app_version_target_legacy_entry_1">18.33.40 - Restore RYD on Shorts incognito mode</string>
<string name="revanced_spoof_app_version_target_legacy_entry_2">18.20.39 - Restore wide video speed &amp; quality menu</string>
<string name="revanced_spoof_app_version_target_legacy_entry_3">18.09.39 - Restore library tab</string>
<string name="revanced_spoof_app_version_target_legacy_entry_4">17.33.42 - Restore old playlist shelf</string>
</patch>
<patch id="layout.startpage.changeStartPagePatch">
<string name="revanced_change_start_page_title">Set start page</string>