mirror of
https://github.com/revanced/revanced-patches.git
synced 2025-12-24 01:44:08 +01:00
Compare commits
7 Commits
v5.4.1-dev
...
v5.5.0-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c44982cde | ||
|
|
a7aab9aeca | ||
|
|
7a8486f562 | ||
|
|
ccb6a7f161 | ||
|
|
c792edfb77 | ||
|
|
339cd6cc70 | ||
|
|
68304fd96a |
21
CHANGELOG.md
21
CHANGELOG.md
@@ -1,3 +1,24 @@
|
|||||||
|
# [5.5.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.5.0-dev.2...v5.5.0-dev.3) (2024-12-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Hide feed components:** Remove obsolete `Hide search result shelf header` option ([#4134](https://github.com/ReVanced/revanced-patches/issues/4134)) ([c71443a](https://github.com/ReVanced/revanced-patches/commit/c71443a08883ab10ef2553213c03b00e7c580a43))
|
||||||
|
|
||||||
|
# [5.5.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.5.0-dev.1...v5.5.0-dev.2) (2024-12-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube Music:** Add `Spoof client patch` to fix playback ([#4132](https://github.com/ReVanced/revanced-patches/issues/4132)) ([b092508](https://github.com/ReVanced/revanced-patches/commit/b0925088e8b41636e285cb234593d545604ce461))
|
||||||
|
|
||||||
|
# [5.5.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.4.1-dev.1...v5.5.0-dev.1) (2024-12-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube:** Add `Force original audio` patch ([#4122](https://github.com/ReVanced/revanced-patches/issues/4122)) ([f4aa440](https://github.com/ReVanced/revanced-patches/commit/f4aa4406080b91f01d623e54b11b99ea849ddcdf))
|
||||||
|
|
||||||
## [5.4.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.4.0...v5.4.1-dev.1) (2024-12-14)
|
## [5.4.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.4.0...v5.4.1-dev.1) (2024-12-14)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
1
extensions/music/build.gradle.kts
Normal file
1
extensions/music/build.gradle.kts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
// Do not remove. Necessary for the extension plugin to be applied to the project.
|
||||||
1
extensions/music/src/main/AndroidManifest.xml
Normal file
1
extensions/music/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<manifest/>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package app.revanced.extension.music.spoof;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @noinspection unused
|
||||||
|
*/
|
||||||
|
public class SpoofClientPatch {
|
||||||
|
private static final int CLIENT_TYPE_ID = 26;
|
||||||
|
private static final String CLIENT_VERSION = "6.21";
|
||||||
|
private static final String DEVICE_MODEL = "iPhone16,2";
|
||||||
|
private static final String OS_VERSION = "17.7.2.21H221";
|
||||||
|
|
||||||
|
public static int getClientId() {
|
||||||
|
return CLIENT_TYPE_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getClientVersion() {
|
||||||
|
return CLIENT_VERSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getClientModel() {
|
||||||
|
return DEVICE_MODEL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getOsVersion() {
|
||||||
|
return OS_VERSION;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ package app.revanced.extension.shared.settings;
|
|||||||
import static java.lang.Boolean.FALSE;
|
import static java.lang.Boolean.FALSE;
|
||||||
import static java.lang.Boolean.TRUE;
|
import static java.lang.Boolean.TRUE;
|
||||||
import static app.revanced.extension.shared.settings.Setting.parent;
|
import static app.revanced.extension.shared.settings.Setting.parent;
|
||||||
import static app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.ForceiOSAVCAvailability;
|
import static app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.SpoofiOSAvailability;
|
||||||
|
|
||||||
import app.revanced.extension.shared.spoof.AudioStreamLanguage;
|
import app.revanced.extension.shared.spoof.AudioStreamLanguage;
|
||||||
import app.revanced.extension.shared.spoof.ClientType;
|
import app.revanced.extension.shared.spoof.ClientType;
|
||||||
@@ -22,9 +22,9 @@ public class BaseSettings {
|
|||||||
public static final IntegerSetting CHECK_ENVIRONMENT_WARNINGS_ISSUED = new IntegerSetting("revanced_check_environment_warnings_issued", 0, true, false);
|
public static final IntegerSetting CHECK_ENVIRONMENT_WARNINGS_ISSUED = new IntegerSetting("revanced_check_environment_warnings_issued", 0, true, false);
|
||||||
|
|
||||||
public static final BooleanSetting SPOOF_VIDEO_STREAMS = new BooleanSetting("revanced_spoof_video_streams", TRUE, true, "revanced_spoof_video_streams_user_dialog_message");
|
public static final BooleanSetting SPOOF_VIDEO_STREAMS = new BooleanSetting("revanced_spoof_video_streams", TRUE, true, "revanced_spoof_video_streams_user_dialog_message");
|
||||||
public static final EnumSetting<AudioStreamLanguage> SPOOF_VIDEO_STREAMS_LANGUAGE = new EnumSetting<>("revanced_spoof_video_streams_language", AudioStreamLanguage.DEFAULT, parent(SPOOF_VIDEO_STREAMS));
|
public static final EnumSetting<AudioStreamLanguage> SPOOF_VIDEO_STREAMS_LANGUAGE = new EnumSetting<>("revanced_spoof_video_streams_language", AudioStreamLanguage.DEFAULT, new SpoofiOSAvailability());
|
||||||
public static final BooleanSetting SPOOF_VIDEO_STREAMS_IOS_FORCE_AVC = new BooleanSetting("revanced_spoof_video_streams_ios_force_avc", FALSE, true,
|
public static final BooleanSetting SPOOF_VIDEO_STREAMS_IOS_FORCE_AVC = new BooleanSetting("revanced_spoof_video_streams_ios_force_avc", FALSE, true,
|
||||||
"revanced_spoof_video_streams_ios_force_avc_user_dialog_message", new ForceiOSAVCAvailability());
|
"revanced_spoof_video_streams_ios_force_avc_user_dialog_message", new SpoofiOSAvailability());
|
||||||
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client", ClientType.ANDROID_VR, true, parent(SPOOF_VIDEO_STREAMS));
|
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client", ClientType.ANDROID_VR, true, parent(SPOOF_VIDEO_STREAMS));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,11 @@ package app.revanced.extension.shared.spoof;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
public enum AudioStreamLanguage {
|
public enum AudioStreamLanguage {
|
||||||
|
/**
|
||||||
|
* YouTube default.
|
||||||
|
* Can be the original language or can be app language,
|
||||||
|
* depending on what YouTube decides to pick as the default.
|
||||||
|
*/
|
||||||
DEFAULT,
|
DEFAULT,
|
||||||
|
|
||||||
// Language codes found in locale_config.xml
|
// Language codes found in locale_config.xml
|
||||||
@@ -86,15 +91,21 @@ public enum AudioStreamLanguage {
|
|||||||
private final String iso639_1;
|
private final String iso639_1;
|
||||||
|
|
||||||
AudioStreamLanguage() {
|
AudioStreamLanguage() {
|
||||||
iso639_1 = name().replace('_', '-');
|
String name = name();
|
||||||
|
final int regionSeparatorIndex = name.indexOf('_');
|
||||||
|
if (regionSeparatorIndex >= 0) {
|
||||||
|
iso639_1 = name.substring(0, regionSeparatorIndex).toLowerCase(Locale.US)
|
||||||
|
+ name.substring(regionSeparatorIndex);
|
||||||
|
} else {
|
||||||
|
iso639_1 = name().toLowerCase(Locale.US);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getIso639_1() {
|
public String getIso639_1() {
|
||||||
// Changing the app language does not force the app to completely restart,
|
// Changing the app language does not force the app to completely restart,
|
||||||
// so the default needs to be the current language and not a static field.
|
// so the default needs to be the current language and not a static field.
|
||||||
if (this == DEFAULT) {
|
if (this == DEFAULT) {
|
||||||
// Android VR requires uppercase language code.
|
return Locale.getDefault().toLanguageTag();
|
||||||
return Locale.getDefault().toLanguageTag().toUpperCase(Locale.US);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return iso639_1;
|
return iso639_1;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ public enum ClientType {
|
|||||||
"32", // Android 12.1
|
"32", // Android 12.1
|
||||||
"1.56.21",
|
"1.56.21",
|
||||||
true,
|
true,
|
||||||
true),
|
false),
|
||||||
// Specific for kids videos.
|
// Specific for kids videos.
|
||||||
IOS(5,
|
IOS(5,
|
||||||
"IOS",
|
"IOS",
|
||||||
@@ -40,21 +40,8 @@ public enum ClientType {
|
|||||||
? "17.40.5"
|
? "17.40.5"
|
||||||
: "19.47.7",
|
: "19.47.7",
|
||||||
false,
|
false,
|
||||||
true),
|
true
|
||||||
/**
|
);
|
||||||
* Android VR with no language code.
|
|
||||||
* Used for age restricted videos and YouTube Music to disable stable volume.
|
|
||||||
*/
|
|
||||||
ANDROID_VR_NO_HL(
|
|
||||||
ANDROID_VR.id,
|
|
||||||
ANDROID_VR.clientName,
|
|
||||||
ANDROID_VR.deviceModel,
|
|
||||||
ANDROID_VR.osVersion,
|
|
||||||
ANDROID_VR.userAgent,
|
|
||||||
ANDROID_VR.androidSdkVersion,
|
|
||||||
ANDROID_VR.clientVersion,
|
|
||||||
ANDROID_VR.canLogin,
|
|
||||||
false);
|
|
||||||
|
|
||||||
private static boolean forceAVC() {
|
private static boolean forceAVC() {
|
||||||
return BaseSettings.SPOOF_VIDEO_STREAMS_IOS_FORCE_AVC.get();
|
return BaseSettings.SPOOF_VIDEO_STREAMS_IOS_FORCE_AVC.get();
|
||||||
|
|||||||
@@ -22,14 +22,6 @@ public class SpoofVideoStreamsPatch {
|
|||||||
private static final String UNREACHABLE_HOST_URI_STRING = "https://127.0.0.0";
|
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);
|
private static final Uri UNREACHABLE_HOST_URI = Uri.parse(UNREACHABLE_HOST_URI_STRING);
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection point. Used by YT Music to disable stable volume.
|
|
||||||
*/
|
|
||||||
public static void setClientTypeToAndroidVrNoHl() {
|
|
||||||
Logger.printDebug(() -> "Setting stream spoofing to: " + ClientType.ANDROID_VR_NO_HL);
|
|
||||||
BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.save(ClientType.ANDROID_VR_NO_HL);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
* Blocks /get_watch requests by returning an unreachable URI.
|
* Blocks /get_watch requests by returning an unreachable URI.
|
||||||
@@ -173,10 +165,11 @@ public class SpoofVideoStreamsPatch {
|
|||||||
return postData;
|
return postData;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class ForceiOSAVCAvailability implements Setting.Availability {
|
public static final class SpoofiOSAvailability implements Setting.Availability {
|
||||||
@Override
|
@Override
|
||||||
public boolean isAvailable() {
|
public boolean isAvailable() {
|
||||||
return BaseSettings.SPOOF_VIDEO_STREAMS.get() && BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.IOS;
|
return BaseSettings.SPOOF_VIDEO_STREAMS.get()
|
||||||
|
&& BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.IOS;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ final class PlayerRoutes {
|
|||||||
"?fields=streamingData" +
|
"?fields=streamingData" +
|
||||||
"&alt=proto"
|
"&alt=proto"
|
||||||
).compile();
|
).compile();
|
||||||
|
|
||||||
private static final String YT_API_URL = "https://youtubei.googleapis.com/youtubei/v1/";
|
private static final String YT_API_URL = "https://youtubei.googleapis.com/youtubei/v1/";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TCP connection and HTTP read timeout
|
* TCP connection and HTTP read timeout
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package app.revanced.extension.youtube.patches;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class ForceOriginalAudioPatch {
|
||||||
|
|
||||||
|
private static final String DEFAULT_AUDIO_TRACKS_IDENTIFIER = "original";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static boolean isDefaultAudioStream(boolean isDefault, String audioTrackId, String audioTrackDisplayName) {
|
||||||
|
try {
|
||||||
|
if (!Settings.FORCE_ORIGINAL_AUDIO.get()) {
|
||||||
|
return isDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audioTrackDisplayName.isEmpty()) {
|
||||||
|
// Older app targets can have empty audio tracks and these might be placeholders.
|
||||||
|
// The real audio tracks are called after these.
|
||||||
|
return isDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.printDebug(() -> "default: " + String.format("%-5s", isDefault) + " id: "
|
||||||
|
+ String.format("%-8s", audioTrackId) + " name:" + audioTrackDisplayName);
|
||||||
|
|
||||||
|
final boolean isOriginal = audioTrackDisplayName.contains(DEFAULT_AUDIO_TRACKS_IDENTIFIER);
|
||||||
|
if (isOriginal) {
|
||||||
|
Logger.printDebug(() -> "Using audio: " + audioTrackId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return isOriginal;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "isDefaultAudioStream failure", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return isDefault;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,7 +36,6 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
);
|
);
|
||||||
|
|
||||||
private final StringTrieSearch exceptions = new StringTrieSearch();
|
private final StringTrieSearch exceptions = new StringTrieSearch();
|
||||||
private final StringFilterGroup searchResultShelfHeader;
|
|
||||||
private final StringFilterGroup inFeedSurvey;
|
private final StringFilterGroup inFeedSurvey;
|
||||||
private final StringFilterGroup notifyMe;
|
private final StringFilterGroup notifyMe;
|
||||||
private final StringFilterGroup expandableMetadata;
|
private final StringFilterGroup expandableMetadata;
|
||||||
@@ -194,11 +193,6 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
"timed_reaction"
|
"timed_reaction"
|
||||||
);
|
);
|
||||||
|
|
||||||
searchResultShelfHeader = new StringFilterGroup(
|
|
||||||
Settings.HIDE_SEARCH_RESULT_SHELF_HEADER,
|
|
||||||
"shelf_header.eml"
|
|
||||||
);
|
|
||||||
|
|
||||||
notifyMe = new StringFilterGroup(
|
notifyMe = new StringFilterGroup(
|
||||||
Settings.HIDE_NOTIFY_ME_BUTTON,
|
Settings.HIDE_NOTIFY_ME_BUTTON,
|
||||||
"set_reminder_button"
|
"set_reminder_button"
|
||||||
@@ -324,9 +318,6 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This also hides the feed Shorts shelf header
|
|
||||||
if (matchedGroup == searchResultShelfHeader && contentIndex != 0) return false;
|
|
||||||
|
|
||||||
if (matchedGroup == horizontalShelves) {
|
if (matchedGroup == horizontalShelves) {
|
||||||
if (contentIndex == 0 && hideShelves()) {
|
if (contentIndex == 0 && hideShelves()) {
|
||||||
return super.isFiltered(path, identifier, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
return super.isFiltered(path, identifier, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ public class Settings extends BaseSettings {
|
|||||||
public static final FloatSetting PLAYBACK_SPEED_DEFAULT = new FloatSetting("revanced_playback_speed_default", -2.0f);
|
public static final FloatSetting PLAYBACK_SPEED_DEFAULT = new FloatSetting("revanced_playback_speed_default", -2.0f);
|
||||||
public static final StringSetting CUSTOM_PLAYBACK_SPEEDS = new StringSetting("revanced_custom_playback_speeds",
|
public static final StringSetting CUSTOM_PLAYBACK_SPEEDS = new StringSetting("revanced_custom_playback_speeds",
|
||||||
"0.25\n0.5\n0.75\n0.9\n0.95\n1.0\n1.05\n1.1\n1.25\n1.5\n1.75\n2.0\n3.0\n4.0\n5.0", true);
|
"0.25\n0.5\n0.75\n0.9\n0.95\n1.0\n1.05\n1.1\n1.25\n1.5\n1.75\n2.0\n3.0\n4.0\n5.0", true);
|
||||||
|
// Audio
|
||||||
|
public static final BooleanSetting FORCE_ORIGINAL_AUDIO = new BooleanSetting("revanced_force_original_audio", FALSE);
|
||||||
|
|
||||||
// Ads
|
// Ads
|
||||||
public static final BooleanSetting HIDE_BUTTONED_ADS = new BooleanSetting("revanced_hide_buttoned_ads", TRUE);
|
public static final BooleanSetting HIDE_BUTTONED_ADS = new BooleanSetting("revanced_hide_buttoned_ads", TRUE);
|
||||||
@@ -91,7 +93,6 @@ public class Settings extends BaseSettings {
|
|||||||
public static final BooleanSetting HIDE_NOTIFY_ME_BUTTON = new BooleanSetting("revanced_hide_notify_me_button", TRUE);
|
public static final BooleanSetting HIDE_NOTIFY_ME_BUTTON = new BooleanSetting("revanced_hide_notify_me_button", TRUE);
|
||||||
public static final BooleanSetting HIDE_PLAYABLES = new BooleanSetting("revanced_hide_playables", TRUE);
|
public static final BooleanSetting HIDE_PLAYABLES = new BooleanSetting("revanced_hide_playables", TRUE);
|
||||||
public static final BooleanSetting HIDE_SEARCH_RESULT_RECOMMENDATIONS = new BooleanSetting("revanced_hide_search_result_recommendations", TRUE);
|
public static final BooleanSetting HIDE_SEARCH_RESULT_RECOMMENDATIONS = new BooleanSetting("revanced_hide_search_result_recommendations", TRUE);
|
||||||
public static final BooleanSetting HIDE_SEARCH_RESULT_SHELF_HEADER = new BooleanSetting("revanced_hide_search_result_shelf_header", FALSE);
|
|
||||||
public static final BooleanSetting HIDE_SHOW_MORE_BUTTON = new BooleanSetting("revanced_hide_show_more_button", TRUE, true);
|
public static final BooleanSetting HIDE_SHOW_MORE_BUTTON = new BooleanSetting("revanced_hide_show_more_button", TRUE, true);
|
||||||
// Alternative thumbnails
|
// Alternative thumbnails
|
||||||
public static final EnumSetting<ThumbnailOption> ALT_THUMBNAIL_HOME = new EnumSetting<>("revanced_alt_thumbnail_home", ThumbnailOption.ORIGINAL);
|
public static final EnumSetting<ThumbnailOption> ALT_THUMBNAIL_HOME = new EnumSetting<>("revanced_alt_thumbnail_home", ThumbnailOption.ORIGINAL);
|
||||||
|
|||||||
@@ -48,35 +48,44 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sorts a preference list by menu entries, but preserves the first value as the first entry.
|
* Sorts a preference list by menu entries, but preserves the first value as the first entry.
|
||||||
|
*
|
||||||
|
* @noinspection SameParameterValue
|
||||||
*/
|
*/
|
||||||
private static void sortListPreferenceByValues(ListPreference listPreference) {
|
private static void sortListPreferenceByValues(ListPreference listPreference, int firstEntriesToPreserve) {
|
||||||
CharSequence[] entries = listPreference.getEntries();
|
CharSequence[] entries = listPreference.getEntries();
|
||||||
CharSequence[] entryValues = listPreference.getEntryValues();
|
CharSequence[] entryValues = listPreference.getEntryValues();
|
||||||
final int entrySize = entries.length;
|
final int entrySize = entries.length;
|
||||||
|
|
||||||
if (entrySize != entryValues.length) {
|
if (entrySize != entryValues.length) {
|
||||||
|
// Xml array declaration has a missing/extra entry.
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the first entry remains the first after sorting.
|
List<Pair<String, String>> firstPairs = new ArrayList<>(firstEntriesToPreserve);
|
||||||
CharSequence firstEntry = entries[0];
|
List<Pair<String, String>> pairsToSort = new ArrayList<>(entrySize);
|
||||||
CharSequence firstEntryValue = entryValues[0];
|
|
||||||
|
|
||||||
List<Pair<String, String>> entryPairs = new ArrayList<>(entrySize);
|
for (int i = 0; i < entrySize; i++) {
|
||||||
for (int i = 1; i < entrySize; i++) {
|
Pair<String, String> pair = new Pair<>(entries[i].toString(), entryValues[i].toString());
|
||||||
entryPairs.add(new Pair<>(entries[i].toString(), entryValues[i].toString()));
|
if (i < firstEntriesToPreserve) {
|
||||||
|
firstPairs.add(pair);
|
||||||
|
} else {
|
||||||
|
pairsToSort.add(pair);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Collections.sort(entryPairs, (pair1, pair2) -> pair1.first.compareToIgnoreCase(pair2.first));
|
Collections.sort(pairsToSort, (pair1, pair2) -> pair1.first.compareToIgnoreCase(pair2.first));
|
||||||
|
|
||||||
CharSequence[] sortedEntries = new CharSequence[entrySize];
|
CharSequence[] sortedEntries = new CharSequence[entrySize];
|
||||||
CharSequence[] sortedEntryValues = new CharSequence[entrySize];
|
CharSequence[] sortedEntryValues = new CharSequence[entrySize];
|
||||||
|
|
||||||
sortedEntries[0] = firstEntry;
|
int i = 0;
|
||||||
sortedEntryValues[0] = firstEntryValue;
|
for (Pair<String, String> pair : firstPairs) {
|
||||||
|
sortedEntries[i] = pair.first;
|
||||||
|
sortedEntryValues[i] = pair.second;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
int i = 1;
|
for (Pair<String, String> pair : pairsToSort) {
|
||||||
for (Pair<String, String> pair : entryPairs) {
|
|
||||||
sortedEntries[i] = pair.first;
|
sortedEntries[i] = pair.first;
|
||||||
sortedEntryValues[i] = pair.second;
|
sortedEntryValues[i] = pair.second;
|
||||||
i++;
|
i++;
|
||||||
@@ -102,7 +111,7 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
|||||||
|
|
||||||
preference = findPreference(Settings.SPOOF_VIDEO_STREAMS_LANGUAGE.key);
|
preference = findPreference(Settings.SPOOF_VIDEO_STREAMS_LANGUAGE.key);
|
||||||
if (preference instanceof ListPreference languagePreference) {
|
if (preference instanceof ListPreference languagePreference) {
|
||||||
sortListPreferenceByValues(languagePreference);
|
sortListPreferenceByValues(languagePreference, 1);
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "initialize failure", ex);
|
Logger.printException(() -> "initialize failure", ex);
|
||||||
|
|||||||
@@ -3,4 +3,4 @@ org.gradle.jvmargs = -Xms512M -Xmx2048M
|
|||||||
org.gradle.parallel = true
|
org.gradle.parallel = true
|
||||||
android.useAndroidX = true
|
android.useAndroidX = true
|
||||||
kotlin.code.style = official
|
kotlin.code.style = official
|
||||||
version = 5.4.1-dev.1
|
version = 5.5.0-dev.3
|
||||||
|
|||||||
@@ -324,8 +324,12 @@ public final class app/revanced/patches/music/misc/gms/GmsCoreSupportPatchKt {
|
|||||||
public static final fun getGmsCoreSupportPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getGmsCoreSupportPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/music/misc/spoof/SpoofVideoStreamsPatchKt {
|
public final class app/revanced/patches/music/misc/spoof/SpoofClientPatchKt {
|
||||||
public static final fun getSpoofVideoStreamsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getSpoofClientPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/music/misc/spoof/UserAgentClientSpoofPatchKt {
|
||||||
|
public static final fun getUserAgentClientSpoofPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/myexpenses/misc/pro/UnlockProPatchKt {
|
public final class app/revanced/patches/myexpenses/misc/pro/UnlockProPatchKt {
|
||||||
@@ -766,6 +770,10 @@ public final class app/revanced/patches/shared/misc/spoof/SpoofVideoStreamsPatch
|
|||||||
public static synthetic fun spoofVideoStreamsPatch$default (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/BytecodePatch;
|
public static synthetic fun spoofVideoStreamsPatch$default (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/shared/misc/spoof/UserAgentClientSpoofPatchKt {
|
||||||
|
public static final fun userAgentClientSpoofPatch (Ljava/lang/String;)Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/solidexplorer2/functionality/filesize/RemoveFileSizeLimitPatchKt {
|
public final class app/revanced/patches/solidexplorer2/functionality/filesize/RemoveFileSizeLimitPatchKt {
|
||||||
public static final fun getRemoveFileSizeLimitPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getRemoveFileSizeLimitPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
@@ -1392,6 +1400,10 @@ public final class app/revanced/patches/youtube/shared/FingerprintsKt {
|
|||||||
public static final fun getRollingNumberTextViewAnimationUpdateFingerprint ()Lapp/revanced/patcher/Fingerprint;
|
public static final fun getRollingNumberTextViewAnimationUpdateFingerprint ()Lapp/revanced/patcher/Fingerprint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/youtube/video/audio/ForceOriginalAudioPatchKt {
|
||||||
|
public static final fun getForceOriginalAudioPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/youtube/video/information/VideoInformationPatchKt {
|
public final class app/revanced/patches/youtube/video/information/VideoInformationPatchKt {
|
||||||
public static final fun getVideoInformationPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getVideoInformationPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
public static final fun userSelectedPlaybackSpeedHook (Ljava/lang/String;Ljava/lang/String;)V
|
public static final fun userSelectedPlaybackSpeedHook (Ljava/lang/String;Ljava/lang/String;)V
|
||||||
|
|||||||
@@ -4,5 +4,6 @@ import app.revanced.patches.music.misc.extension.hooks.applicationInitHook
|
|||||||
import app.revanced.patches.shared.misc.extension.sharedExtensionPatch
|
import app.revanced.patches.shared.misc.extension.sharedExtensionPatch
|
||||||
|
|
||||||
val sharedExtensionPatch = sharedExtensionPatch(
|
val sharedExtensionPatch = sharedExtensionPatch(
|
||||||
|
"music",
|
||||||
applicationInitHook,
|
applicationInitHook,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import app.revanced.patcher.patch.Option
|
|||||||
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
|
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
|
||||||
import app.revanced.patches.music.misc.gms.Constants.MUSIC_PACKAGE_NAME
|
import app.revanced.patches.music.misc.gms.Constants.MUSIC_PACKAGE_NAME
|
||||||
import app.revanced.patches.music.misc.gms.Constants.REVANCED_MUSIC_PACKAGE_NAME
|
import app.revanced.patches.music.misc.gms.Constants.REVANCED_MUSIC_PACKAGE_NAME
|
||||||
import app.revanced.patches.music.misc.spoof.spoofVideoStreamsPatch
|
import app.revanced.patches.music.misc.spoof.spoofClientPatch
|
||||||
import app.revanced.patches.shared.castContextFetchFingerprint
|
import app.revanced.patches.shared.castContextFetchFingerprint
|
||||||
import app.revanced.patches.shared.misc.gms.gmsCoreSupportPatch
|
import app.revanced.patches.shared.misc.gms.gmsCoreSupportPatch
|
||||||
import app.revanced.patches.shared.primeMethodFingerprint
|
import app.revanced.patches.shared.primeMethodFingerprint
|
||||||
@@ -21,7 +21,7 @@ val gmsCoreSupportPatch = gmsCoreSupportPatch(
|
|||||||
extensionPatch = sharedExtensionPatch,
|
extensionPatch = sharedExtensionPatch,
|
||||||
gmsCoreSupportResourcePatchFactory = ::gmsCoreSupportResourcePatch,
|
gmsCoreSupportResourcePatchFactory = ::gmsCoreSupportResourcePatch,
|
||||||
) {
|
) {
|
||||||
dependsOn(spoofVideoStreamsPatch)
|
dependsOn(spoofClientPatch)
|
||||||
|
|
||||||
compatibleWith(MUSIC_PACKAGE_NAME)
|
compatibleWith(MUSIC_PACKAGE_NAME)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package app.revanced.patches.music.misc.spoof
|
||||||
|
|
||||||
|
import app.revanced.patcher.fingerprint
|
||||||
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
|
|
||||||
|
internal val playerRequestConstructorFingerprint = fingerprint {
|
||||||
|
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
|
||||||
|
strings("player")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matches using the class found in [playerRequestConstructorFingerprint].
|
||||||
|
*/
|
||||||
|
internal val createPlayerRequestBodyFingerprint = fingerprint {
|
||||||
|
parameters("L")
|
||||||
|
returns("V")
|
||||||
|
opcodes(
|
||||||
|
Opcode.CHECK_CAST,
|
||||||
|
Opcode.IGET,
|
||||||
|
Opcode.AND_INT_LIT16,
|
||||||
|
)
|
||||||
|
strings("ms")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to get a reference to other clientInfo fields.
|
||||||
|
*/
|
||||||
|
internal val setClientInfoFieldsFingerprint = fingerprint {
|
||||||
|
returns("L")
|
||||||
|
strings("Google Inc.")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to get a reference to the clientInfo and clientInfo.clientVersion field.
|
||||||
|
*/
|
||||||
|
internal val setClientInfoClientVersionFingerprint = fingerprint {
|
||||||
|
strings("10.29")
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package app.revanced.patches.music.misc.spoof
|
||||||
|
|
||||||
|
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.patch.bytecodePatch
|
||||||
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||||
|
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
|
||||||
|
import app.revanced.util.getReference
|
||||||
|
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.OneRegisterInstruction
|
||||||
|
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||||
|
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
|
||||||
|
|
||||||
|
internal const val EXTENSION_CLASS_DESCRIPTOR =
|
||||||
|
"Lapp/revanced/extension/music/spoof/SpoofClientPatch;"
|
||||||
|
|
||||||
|
// TODO: Replace this patch with spoofVideoStreamsPatch once possible.
|
||||||
|
val spoofClientPatch = bytecodePatch(
|
||||||
|
name = "Spoof client",
|
||||||
|
description = "Spoofs the client to fix playback.",
|
||||||
|
) {
|
||||||
|
compatibleWith("com.google.android.apps.youtube.music")
|
||||||
|
|
||||||
|
dependsOn(
|
||||||
|
sharedExtensionPatch,
|
||||||
|
// TODO: Add settingsPatch
|
||||||
|
userAgentClientSpoofPatch,
|
||||||
|
)
|
||||||
|
|
||||||
|
execute {
|
||||||
|
val playerRequestClass = playerRequestConstructorFingerprint.classDef
|
||||||
|
|
||||||
|
val createPlayerRequestBodyMatch = createPlayerRequestBodyFingerprint.match(playerRequestClass)
|
||||||
|
|
||||||
|
val clientInfoContainerClass = createPlayerRequestBodyMatch.method
|
||||||
|
.getInstruction(createPlayerRequestBodyMatch.patternMatch!!.startIndex)
|
||||||
|
.getReference<TypeReference>()!!.type
|
||||||
|
|
||||||
|
val clientInfoField = setClientInfoClientVersionFingerprint.method.instructions.first {
|
||||||
|
it.opcode == Opcode.IPUT_OBJECT && it.getReference<FieldReference>()!!.definingClass == clientInfoContainerClass
|
||||||
|
}.getReference<FieldReference>()!!
|
||||||
|
|
||||||
|
val setClientInfoFieldInstructions = setClientInfoFieldsFingerprint.method.instructions.filter {
|
||||||
|
(it.opcode == Opcode.IPUT_OBJECT || it.opcode == Opcode.IPUT) &&
|
||||||
|
it.getReference<FieldReference>()!!.definingClass == clientInfoField.type
|
||||||
|
}.map { it.getReference<FieldReference>()!! }
|
||||||
|
|
||||||
|
// Offsets are known for the fields in the clientInfo object.
|
||||||
|
val clientIdField = setClientInfoFieldInstructions[0]
|
||||||
|
val clientModelField = setClientInfoFieldInstructions[5]
|
||||||
|
val osVersionField = setClientInfoFieldInstructions[7]
|
||||||
|
val clientVersionField = setClientInfoClientVersionFingerprint.method
|
||||||
|
.getInstruction(setClientInfoClientVersionFingerprint.stringMatches!!.first().index + 1)
|
||||||
|
.getReference<FieldReference>()
|
||||||
|
|
||||||
|
// Helper method to spoof the client info.
|
||||||
|
val spoofClientInfoMethod = ImmutableMethod(
|
||||||
|
playerRequestClass.type,
|
||||||
|
"spoofClientInfo",
|
||||||
|
listOf(ImmutableMethodParameter(clientInfoContainerClass, null, null)),
|
||||||
|
"V",
|
||||||
|
AccessFlags.PRIVATE.value or AccessFlags.STATIC.value,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
MutableMethodImplementation(3),
|
||||||
|
).toMutable().also(playerRequestClass.methods::add).apply {
|
||||||
|
addInstructions(
|
||||||
|
"""
|
||||||
|
iget-object v0, p0, $clientInfoField
|
||||||
|
|
||||||
|
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->getClientId()I
|
||||||
|
move-result v1
|
||||||
|
iput v1, v0, $clientIdField
|
||||||
|
|
||||||
|
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->getClientModel()Ljava/lang/String;
|
||||||
|
move-result-object v1
|
||||||
|
iput-object v1, v0, $clientModelField
|
||||||
|
|
||||||
|
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->getClientVersion()Ljava/lang/String;
|
||||||
|
move-result-object v1
|
||||||
|
iput-object v1, v0, $clientVersionField
|
||||||
|
|
||||||
|
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->getOsVersion()Ljava/lang/String;
|
||||||
|
move-result-object v1
|
||||||
|
iput-object v1, v0, $osVersionField
|
||||||
|
|
||||||
|
return-void
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
createPlayerRequestBodyMatch.method.apply {
|
||||||
|
val checkCastIndex = createPlayerRequestBodyMatch.patternMatch!!.startIndex
|
||||||
|
val clientInfoContainerRegister = getInstruction<OneRegisterInstruction>(checkCastIndex).registerA
|
||||||
|
|
||||||
|
addInstruction(checkCastIndex + 1, "invoke-static {v$clientInfoContainerRegister}, $spoofClientInfoMethod")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package app.revanced.patches.music.misc.spoof
|
|
||||||
|
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
|
||||||
import app.revanced.patches.music.misc.gms.musicActivityOnCreateFingerprint
|
|
||||||
import app.revanced.patches.shared.misc.spoof.EXTENSION_CLASS_DESCRIPTOR
|
|
||||||
import app.revanced.patches.shared.misc.spoof.spoofVideoStreamsPatch
|
|
||||||
|
|
||||||
val spoofVideoStreamsPatch = spoofVideoStreamsPatch({
|
|
||||||
compatibleWith("com.google.android.apps.youtube.music")
|
|
||||||
}, {
|
|
||||||
musicActivityOnCreateFingerprint.method.addInstruction(
|
|
||||||
0,
|
|
||||||
"invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->setClientTypeToAndroidVrNoHl()V"
|
|
||||||
)
|
|
||||||
})
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package app.revanced.patches.music.misc.spoof
|
||||||
|
|
||||||
|
import app.revanced.patches.shared.misc.spoof.userAgentClientSpoofPatch
|
||||||
|
|
||||||
|
val userAgentClientSpoofPatch = userAgentClientSpoofPatch("com.google.android.apps.youtube.music")
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package app.revanced.patches.shared.misc.spoof
|
||||||
|
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||||
|
import app.revanced.patches.all.misc.transformation.IMethodCall
|
||||||
|
import app.revanced.patches.all.misc.transformation.filterMapInstruction35c
|
||||||
|
import app.revanced.patches.all.misc.transformation.transformInstructionsPatch
|
||||||
|
import app.revanced.util.getReference
|
||||||
|
import app.revanced.util.indexOfFirstInstruction
|
||||||
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
|
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||||
|
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||||
|
import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
||||||
|
|
||||||
|
private const val USER_AGENT_STRING_BUILDER_APPEND_METHOD_REFERENCE =
|
||||||
|
"Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;"
|
||||||
|
|
||||||
|
fun userAgentClientSpoofPatch(originalPackageName: String) = transformInstructionsPatch(
|
||||||
|
filterMap = { classDef, _, instruction, instructionIndex ->
|
||||||
|
filterMapInstruction35c<MethodCall>(
|
||||||
|
"Lapp/revanced/extension",
|
||||||
|
classDef,
|
||||||
|
instruction,
|
||||||
|
instructionIndex,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
transform = transform@{ mutableMethod, entry ->
|
||||||
|
val (_, _, instructionIndex) = entry
|
||||||
|
|
||||||
|
// Replace the result of context.getPackageName(), if it is used in a user agent string.
|
||||||
|
mutableMethod.apply {
|
||||||
|
// After context.getPackageName() the result is moved to a register.
|
||||||
|
val targetRegister = (
|
||||||
|
getInstruction(instructionIndex + 1)
|
||||||
|
as? OneRegisterInstruction ?: return@transform
|
||||||
|
).registerA
|
||||||
|
|
||||||
|
// IndexOutOfBoundsException is technically possible here,
|
||||||
|
// but no such occurrences are present in the app.
|
||||||
|
val referee = getInstruction(instructionIndex + 2).getReference<MethodReference>()?.toString()
|
||||||
|
|
||||||
|
// Only replace string builder usage.
|
||||||
|
if (referee != USER_AGENT_STRING_BUILDER_APPEND_METHOD_REFERENCE) {
|
||||||
|
return@transform
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not change the package name in methods that use resources, or for methods that use GmsCore.
|
||||||
|
// Changing these package names will result in playback limitations,
|
||||||
|
// particularly Android VR background audio only playback.
|
||||||
|
val resourceOrGmsStringInstructionIndex = indexOfFirstInstruction {
|
||||||
|
val reference = getReference<StringReference>()
|
||||||
|
opcode == Opcode.CONST_STRING &&
|
||||||
|
(reference?.string == "android.resource://" || reference?.string == "gcore_")
|
||||||
|
}
|
||||||
|
if (resourceOrGmsStringInstructionIndex >= 0) {
|
||||||
|
return@transform
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overwrite the result of context.getPackageName() with the original package name.
|
||||||
|
replaceInstruction(
|
||||||
|
instructionIndex + 1,
|
||||||
|
"const-string v$targetRegister, \"$originalPackageName\"",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
private enum class MethodCall(
|
||||||
|
override val definedClassName: String,
|
||||||
|
override val methodName: String,
|
||||||
|
override val methodParams: Array<String>,
|
||||||
|
override val returnType: String,
|
||||||
|
) : IMethodCall {
|
||||||
|
GetPackageName(
|
||||||
|
"Landroid/content/Context;",
|
||||||
|
"getPackageName",
|
||||||
|
emptyArray(),
|
||||||
|
"Ljava/lang/String;",
|
||||||
|
),
|
||||||
|
}
|
||||||
@@ -222,7 +222,6 @@ val hideLayoutComponentsPatch = bytecodePatch(
|
|||||||
SwitchPreference("revanced_hide_notify_me_button"),
|
SwitchPreference("revanced_hide_notify_me_button"),
|
||||||
SwitchPreference("revanced_hide_playables"),
|
SwitchPreference("revanced_hide_playables"),
|
||||||
SwitchPreference("revanced_hide_search_result_recommendations"),
|
SwitchPreference("revanced_hide_search_result_recommendations"),
|
||||||
SwitchPreference("revanced_hide_search_result_shelf_header"),
|
|
||||||
SwitchPreference("revanced_hide_show_more_button"),
|
SwitchPreference("revanced_hide_show_more_button"),
|
||||||
SwitchPreference("revanced_hide_doodles"),
|
SwitchPreference("revanced_hide_doodles"),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -110,7 +110,6 @@ val enableDebuggingPatch = bytecodePatch(
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
experimentalStringFeatureFlagFingerprint.match(
|
experimentalStringFeatureFlagFingerprint.match(
|
||||||
experimentalFeatureFlagParentFingerprint.originalClassDef
|
experimentalFeatureFlagParentFingerprint.originalClassDef
|
||||||
@@ -127,6 +126,7 @@ val enableDebuggingPatch = bytecodePatch(
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// There exists other experimental accessor methods for byte[]
|
// There exists other experimental accessor methods for byte[]
|
||||||
// and wrappers for obfuscated classes, but currently none of those are hooked.
|
// and wrappers for obfuscated classes, but currently none of those are hooked.
|
||||||
|
|||||||
@@ -1,82 +1,5 @@
|
|||||||
package app.revanced.patches.youtube.misc.spoof
|
package app.revanced.patches.youtube.misc.spoof
|
||||||
|
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
import app.revanced.patches.shared.misc.spoof.userAgentClientSpoofPatch
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
|
||||||
import app.revanced.patches.all.misc.transformation.IMethodCall
|
|
||||||
import app.revanced.patches.all.misc.transformation.filterMapInstruction35c
|
|
||||||
import app.revanced.patches.all.misc.transformation.transformInstructionsPatch
|
|
||||||
import app.revanced.util.getReference
|
|
||||||
import app.revanced.util.indexOfFirstInstruction
|
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
|
||||||
|
|
||||||
private const val ORIGINAL_PACKAGE_NAME = "com.google.android.youtube"
|
val userAgentClientSpoofPatch = userAgentClientSpoofPatch("com.google.android.youtube")
|
||||||
private const val USER_AGENT_STRING_BUILDER_APPEND_METHOD_REFERENCE =
|
|
||||||
"Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;"
|
|
||||||
|
|
||||||
val userAgentClientSpoofPatch = transformInstructionsPatch(
|
|
||||||
filterMap = { classDef, _, instruction, instructionIndex ->
|
|
||||||
filterMapInstruction35c<MethodCall>(
|
|
||||||
"Lapp/revanced/extension",
|
|
||||||
classDef,
|
|
||||||
instruction,
|
|
||||||
instructionIndex,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
transform = transform@{ mutableMethod, entry ->
|
|
||||||
val (_, _, instructionIndex) = entry
|
|
||||||
|
|
||||||
// Replace the result of context.getPackageName(), if it is used in a user agent string.
|
|
||||||
mutableMethod.apply {
|
|
||||||
// After context.getPackageName() the result is moved to a register.
|
|
||||||
val targetRegister = (
|
|
||||||
getInstruction(instructionIndex + 1)
|
|
||||||
as? OneRegisterInstruction ?: return@transform
|
|
||||||
).registerA
|
|
||||||
|
|
||||||
// IndexOutOfBoundsException is technically possible here,
|
|
||||||
// but no such occurrences are present in the app.
|
|
||||||
val referee = getInstruction(instructionIndex + 2).getReference<MethodReference>()?.toString()
|
|
||||||
|
|
||||||
// Only replace string builder usage.
|
|
||||||
if (referee != USER_AGENT_STRING_BUILDER_APPEND_METHOD_REFERENCE) {
|
|
||||||
return@transform
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do not change the package name in methods that use resources, or for methods that use GmsCore.
|
|
||||||
// Changing these package names will result in playback limitations,
|
|
||||||
// particularly Android VR background audio only playback.
|
|
||||||
val resourceOrGmsStringInstructionIndex = indexOfFirstInstruction {
|
|
||||||
val reference = getReference<StringReference>()
|
|
||||||
opcode == Opcode.CONST_STRING &&
|
|
||||||
(reference?.string == "android.resource://" || reference?.string == "gcore_")
|
|
||||||
}
|
|
||||||
if (resourceOrGmsStringInstructionIndex >= 0) {
|
|
||||||
return@transform
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overwrite the result of context.getPackageName() with the original package name.
|
|
||||||
replaceInstruction(
|
|
||||||
instructionIndex + 1,
|
|
||||||
"const-string v$targetRegister, \"$ORIGINAL_PACKAGE_NAME\"",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
@Suppress("unused")
|
|
||||||
private enum class MethodCall(
|
|
||||||
override val definedClassName: String,
|
|
||||||
override val methodName: String,
|
|
||||||
override val methodParams: Array<String>,
|
|
||||||
override val returnType: String,
|
|
||||||
) : IMethodCall {
|
|
||||||
GetPackageName(
|
|
||||||
"Landroid/content/Context;",
|
|
||||||
"getPackageName",
|
|
||||||
emptyArray(),
|
|
||||||
"Ljava/lang/String;",
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package app.revanced.patches.youtube.video.audio
|
||||||
|
|
||||||
|
import app.revanced.patcher.fingerprint
|
||||||
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
|
|
||||||
|
internal val streamingModelBuilderFingerprint = fingerprint {
|
||||||
|
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||||
|
returns("L")
|
||||||
|
strings("vprng")
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val menuItemAudioTrackFingerprint = fingerprint {
|
||||||
|
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||||
|
parameters("L")
|
||||||
|
returns("V")
|
||||||
|
strings("menu_item_audio_track")
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val audioStreamingTypeSelector = fingerprint {
|
||||||
|
accessFlags(AccessFlags.PRIVATE, AccessFlags.FINAL)
|
||||||
|
returns("L")
|
||||||
|
strings("raw") // String is not unique
|
||||||
|
}
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
package app.revanced.patches.youtube.video.audio
|
||||||
|
|
||||||
|
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.patch.bytecodePatch
|
||||||
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
|
||||||
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||||
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||||
|
import app.revanced.patches.all.misc.resources.addResources
|
||||||
|
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||||
|
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||||
|
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
||||||
|
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||||
|
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||||
|
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.Method
|
||||||
|
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||||
|
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||||
|
import com.android.tools.smali.dexlib2.immutable.ImmutableField
|
||||||
|
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||||
|
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
|
||||||
|
|
||||||
|
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||||
|
"Lapp/revanced/extension/youtube/patches/ForceOriginalAudioPatch;"
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
val forceOriginalAudioPatch = bytecodePatch(
|
||||||
|
name = "Force original audio",
|
||||||
|
description = "Adds an option to always use the original audio track.",
|
||||||
|
) {
|
||||||
|
dependsOn(
|
||||||
|
sharedExtensionPatch,
|
||||||
|
settingsPatch,
|
||||||
|
addResourcesPatch,
|
||||||
|
)
|
||||||
|
|
||||||
|
compatibleWith(
|
||||||
|
"com.google.android.youtube"(
|
||||||
|
"18.38.44",
|
||||||
|
"18.49.37",
|
||||||
|
"19.16.39",
|
||||||
|
"19.25.37",
|
||||||
|
"19.34.42",
|
||||||
|
"19.43.41",
|
||||||
|
"19.45.38",
|
||||||
|
"19.46.42",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
execute {
|
||||||
|
addResources("youtube", "video.audio.forceOriginalAudioPatch")
|
||||||
|
|
||||||
|
PreferenceScreen.VIDEO.addPreferences(
|
||||||
|
SwitchPreference("revanced_force_original_audio")
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Method.firstFormatStreamingModelCall(
|
||||||
|
returnType: String = "Ljava/lang/String;"
|
||||||
|
): MutableMethod {
|
||||||
|
val audioTrackIdIndex = indexOfFirstInstructionOrThrow {
|
||||||
|
val reference = getReference<MethodReference>()
|
||||||
|
reference?.definingClass == "Lcom/google/android/libraries/youtube/innertube/model/media/FormatStreamModel;"
|
||||||
|
&& reference.returnType == returnType
|
||||||
|
}
|
||||||
|
|
||||||
|
return navigate(this).to(audioTrackIdIndex).stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accessor methods of FormatStreamModel have no string constants and
|
||||||
|
// opcodes are identical to other methods in the same class,
|
||||||
|
// so must walk from another class that use the methods.
|
||||||
|
val isDefaultMethod = streamingModelBuilderFingerprint.originalMethod.firstFormatStreamingModelCall("Z")
|
||||||
|
val audioTrackIdMethod = menuItemAudioTrackFingerprint.originalMethod.firstFormatStreamingModelCall()
|
||||||
|
val audioTrackDisplayNameMethod = audioStreamingTypeSelector.originalMethod.firstFormatStreamingModelCall()
|
||||||
|
val formatStreamModelClass = proxy(classes.first {
|
||||||
|
it.type == audioTrackIdMethod.definingClass
|
||||||
|
}).mutableClass
|
||||||
|
|
||||||
|
formatStreamModelClass.apply {
|
||||||
|
// Add a new field to store the override.
|
||||||
|
val helperFieldName = "isDefaultAudioTrackOverride"
|
||||||
|
fields.add(
|
||||||
|
ImmutableField(
|
||||||
|
type,
|
||||||
|
helperFieldName,
|
||||||
|
"Ljava/lang/Boolean;",
|
||||||
|
// Boolean is a 100% immutable class (all fields are final)
|
||||||
|
// and safe to write to a shared field without volatile/synchronization,
|
||||||
|
// but without volatile the field can show stale data
|
||||||
|
// and the same field is calculated more than once by different threads.
|
||||||
|
AccessFlags.PRIVATE.value or AccessFlags.VOLATILE.value,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
).toMutable()
|
||||||
|
)
|
||||||
|
|
||||||
|
// Add a helper method because the isDefaultAudioTrack() has only 2 registers and 3 are needed.
|
||||||
|
val helperMethodClass = type
|
||||||
|
val helperMethodName = "extension_isDefaultAudioTrack"
|
||||||
|
val helperMethod = ImmutableMethod(
|
||||||
|
helperMethodClass,
|
||||||
|
helperMethodName,
|
||||||
|
listOf(ImmutableMethodParameter("Z", null, null)),
|
||||||
|
"Z",
|
||||||
|
AccessFlags.PRIVATE.value,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
MutableMethodImplementation(6),
|
||||||
|
).toMutable().apply {
|
||||||
|
addInstructionsWithLabels(
|
||||||
|
0,
|
||||||
|
"""
|
||||||
|
iget-object v0, p0, $helperMethodClass->$helperFieldName:Ljava/lang/Boolean;
|
||||||
|
if-eqz v0, :call_extension
|
||||||
|
invoke-virtual { v0 }, Ljava/lang/Boolean;->booleanValue()Z
|
||||||
|
move-result v3
|
||||||
|
return v3
|
||||||
|
|
||||||
|
:call_extension
|
||||||
|
invoke-virtual { p0 }, $audioTrackIdMethod
|
||||||
|
move-result-object v1
|
||||||
|
|
||||||
|
invoke-virtual { p0 }, $audioTrackDisplayNameMethod
|
||||||
|
move-result-object v2
|
||||||
|
|
||||||
|
invoke-static { p1, v1, v2 }, $EXTENSION_CLASS_DESCRIPTOR->isDefaultAudioStream(ZLjava/lang/String;Ljava/lang/String;)Z
|
||||||
|
move-result v3
|
||||||
|
|
||||||
|
invoke-static { v3 }, Ljava/lang/Boolean;->valueOf(Z)Ljava/lang/Boolean;
|
||||||
|
move-result-object v0
|
||||||
|
iput-object v0, p0, $helperMethodClass->$helperFieldName:Ljava/lang/Boolean;
|
||||||
|
return v3
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
methods.add(helperMethod)
|
||||||
|
|
||||||
|
// Modify isDefaultAudioTrack() to call extension helper method.
|
||||||
|
isDefaultMethod.apply {
|
||||||
|
val index = indexOfFirstInstructionOrThrow(Opcode.RETURN)
|
||||||
|
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
||||||
|
|
||||||
|
addInstructions(
|
||||||
|
index,
|
||||||
|
"""
|
||||||
|
invoke-direct { p0, v$register }, $helperMethodClass->$helperMethodName(Z)Z
|
||||||
|
move-result v$register
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -158,9 +158,6 @@ You will not be notified of any unexpected events."</string>
|
|||||||
<string name="revanced_hide_timed_reactions_title">Hide timed reactions</string>
|
<string name="revanced_hide_timed_reactions_title">Hide timed reactions</string>
|
||||||
<string name="revanced_hide_timed_reactions_summary_on">Timed reactions are hidden</string>
|
<string name="revanced_hide_timed_reactions_summary_on">Timed reactions are hidden</string>
|
||||||
<string name="revanced_hide_timed_reactions_summary_off">Timed reactions are shown</string>
|
<string name="revanced_hide_timed_reactions_summary_off">Timed reactions are shown</string>
|
||||||
<string name="revanced_hide_search_result_shelf_header_title">Hide search result shelf header</string>
|
|
||||||
<string name="revanced_hide_search_result_shelf_header_summary_on">Shelf header is hidden</string>
|
|
||||||
<string name="revanced_hide_search_result_shelf_header_summary_off">Shelf header is shown</string>
|
|
||||||
<string name="revanced_hide_channel_guidelines_title">Hide channel guidelines</string>
|
<string name="revanced_hide_channel_guidelines_title">Hide channel guidelines</string>
|
||||||
<string name="revanced_hide_channel_guidelines_summary_on">Channel guidelines are hidden</string>
|
<string name="revanced_hide_channel_guidelines_summary_on">Channel guidelines are hidden</string>
|
||||||
<string name="revanced_hide_channel_guidelines_summary_off">Channel guidelines are shown</string>
|
<string name="revanced_hide_channel_guidelines_summary_off">Channel guidelines are shown</string>
|
||||||
@@ -1226,6 +1223,11 @@ Enabling this can unlock higher video qualities"</string>
|
|||||||
<string name="revanced_disable_zoom_haptics_summary_on">Haptics are disabled</string>
|
<string name="revanced_disable_zoom_haptics_summary_on">Haptics are disabled</string>
|
||||||
<string name="revanced_disable_zoom_haptics_summary_off">Haptics are enabled</string>
|
<string name="revanced_disable_zoom_haptics_summary_off">Haptics are enabled</string>
|
||||||
</patch>
|
</patch>
|
||||||
|
<patch id="video.audio.forceOriginalAudioPatch">
|
||||||
|
<string name="revanced_force_original_audio_title">Force original audio</string>
|
||||||
|
<string name="revanced_force_original_audio_summary_on">Using original audio</string>
|
||||||
|
<string name="revanced_force_original_audio_summary_off">Using default audio</string>
|
||||||
|
</patch>
|
||||||
<patch id="video.quality.rememberVideoQualityPatch">
|
<patch id="video.quality.rememberVideoQualityPatch">
|
||||||
<!-- Translations should use the same text as revanced_custom_playback_speeds_auto -->
|
<!-- Translations should use the same text as revanced_custom_playback_speeds_auto -->
|
||||||
<string name="revanced_video_quality_default_entry_1">Auto</string>
|
<string name="revanced_video_quality_default_entry_1">Auto</string>
|
||||||
@@ -1292,8 +1294,8 @@ AVC has a maximum resolution of 1080p, Opus audio codec is not available, and vi
|
|||||||
• Videos end 1 second early"</string>
|
• Videos end 1 second early"</string>
|
||||||
<string name="revanced_spoof_video_streams_about_android_vr_title">Android VR spoofing side effects</string>
|
<string name="revanced_spoof_video_streams_about_android_vr_title">Android VR spoofing side effects</string>
|
||||||
<string name="revanced_spoof_video_streams_about_android_vr_summary">"• Kids videos may not play
|
<string name="revanced_spoof_video_streams_about_android_vr_summary">"• Kids videos may not play
|
||||||
• Livestreams start from the beginning
|
• Audio track menu is missing
|
||||||
• Videos end 1 second early"</string>
|
• Stable volume is not available"</string>
|
||||||
<string name="revanced_spoof_video_streams_language_title">Default audio stream language</string>
|
<string name="revanced_spoof_video_streams_language_title">Default audio stream language</string>
|
||||||
<string name="revanced_spoof_video_streams_language_DEFAULT">App language</string>
|
<string name="revanced_spoof_video_streams_language_DEFAULT">App language</string>
|
||||||
<string name="revanced_spoof_video_streams_language_AR">Arabic</string>
|
<string name="revanced_spoof_video_streams_language_AR">Arabic</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user