mirror of
https://github.com/ReVanced/revanced-patches.git
synced 2026-01-17 16:23:56 +00:00
Compare commits
4 Commits
v5.40.0-de
...
fix/youtub
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
debe93d87a | ||
|
|
3910d9c9b9 | ||
|
|
e0edfdbd97 | ||
|
|
95580f84ec |
105
CHANGELOG.md
105
CHANGELOG.md
@@ -1,108 +1,3 @@
|
||||
# [5.40.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.9...v5.40.0-dev.10) (2025-09-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Spoof video streams:** Add "Force original audio" disclaimer for Android Studio client ([f97d332](https://github.com/ReVanced/revanced-patches/commit/f97d33206b4c97244f0bd0c672c4b91eaf477b0b))
|
||||
|
||||
# [5.40.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.8...v5.40.0-dev.9) (2025-09-20)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube Music:** Support version `8.10.52` ([#5941](https://github.com/ReVanced/revanced-patches/issues/5941)) ([01c0f1b](https://github.com/ReVanced/revanced-patches/commit/01c0f1bd1ac6edb8aea758f88ffffcdea74a29b7))
|
||||
|
||||
# [5.40.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.7...v5.40.0-dev.8) (2025-09-20)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube:** Support version `20.14.43` ([#5940](https://github.com/ReVanced/revanced-patches/issues/5940)) ([f7f4a1b](https://github.com/ReVanced/revanced-patches/commit/f7f4a1b0f0186598266b41a2c6a781fdee49e440))
|
||||
|
||||
# [5.40.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.6...v5.40.0-dev.7) (2025-09-20)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Hide video action buttons:** Add "Hide comments" button ([db796fb](https://github.com/ReVanced/revanced-patches/commit/db796fb8830b813e1ed626d491c4a797171e69e7))
|
||||
|
||||
# [5.40.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.5...v5.40.0-dev.6) (2025-09-20)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube Music:** Add `Enable debugging` patch ([#5939](https://github.com/ReVanced/revanced-patches/issues/5939)) ([418f594](https://github.com/ReVanced/revanced-patches/commit/418f5945c213313f9a77cac9a5c326d89c754dfd))
|
||||
|
||||
# [5.40.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.4...v5.40.0-dev.5) (2025-09-20)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube Music:** Add `Hide cast button` and `Navigation bar` patches ([#5934](https://github.com/ReVanced/revanced-patches/issues/5934)) ([651d358](https://github.com/ReVanced/revanced-patches/commit/651d3580967a252b57cbf4afbba02d6a4601ccfe))
|
||||
|
||||
# [5.40.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.3...v5.40.0-dev.4) (2025-09-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Spoof video streams:** Resolve occasional playback stuttering ([5c7c8b5](https://github.com/ReVanced/revanced-patches/commit/5c7c8b536416ec53cd98f7d59d11850aa1b70f11))
|
||||
|
||||
# [5.40.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.2...v5.40.0-dev.3) (2025-09-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Instagram - Limit feed to followed profiles:** Change patch to default off ([767f1e3](https://github.com/ReVanced/revanced-patches/commit/767f1e3695327bdbc4daea8b50a80d4c0a38456a))
|
||||
|
||||
# [5.40.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.1...v5.40.0-dev.2) (2025-09-18)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Instagram:** Add `Limit feed to followed profiles` patch ([#5908](https://github.com/ReVanced/revanced-patches/issues/5908)) ([8ba9a19](https://github.com/ReVanced/revanced-patches/commit/8ba9a19ade24c5fe9bd6d4e49772b7663522780e))
|
||||
|
||||
# [5.40.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.39.1-dev.1...v5.40.0-dev.1) (2025-09-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Viber - Hide ads:** Support latest app target ([#5863](https://github.com/ReVanced/revanced-patches/issues/5863)) ([e6cce85](https://github.com/ReVanced/revanced-patches/commit/e6cce8554116df3c0ea6dbb7440c59c9e73d8334))
|
||||
|
||||
## [5.39.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.39.0...v5.39.1-dev.1) (2025-09-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Force original audio:** Show UI setting summary if spoofing to Android Studio ([b7026b7](https://github.com/ReVanced/revanced-patches/commit/b7026b70865bc44de07b30f84ba8b8b608930d5b))
|
||||
|
||||
# [5.39.0](https://github.com/ReVanced/revanced-patches/compare/v5.38.0...v5.39.0) (2025-09-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Spoof video streams:** Do not use Android Creator for livestreams ([cbe576b](https://github.com/ReVanced/revanced-patches/commit/cbe576bc384ef5f5ee2fa341147925ed0dff568b))
|
||||
* **YouTube - Spoof video streams:** Show Android Studio in spoof stream menu ([c9f741e](https://github.com/ReVanced/revanced-patches/commit/c9f741e616c7acab0cd4558e02b0c4ec18392c10))
|
||||
* **YouTube Music - Spoof video streams:** Remove iPadOS client ([7eeffd3](https://github.com/ReVanced/revanced-patches/commit/7eeffd3392c57555342173103d3a417c038d0970))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Hide video action buttons:** Add "Hide Shop button" setting ([a84db7b](https://github.com/ReVanced/revanced-patches/commit/a84db7be7fde2e9bb3ac41aec709a1681e845fe1))
|
||||
|
||||
# [5.39.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.39.0-dev.1...v5.39.0-dev.2) (2025-09-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Spoof video streams:** Show Android Studio in spoof stream menu ([c9f741e](https://github.com/ReVanced/revanced-patches/commit/c9f741e616c7acab0cd4558e02b0c4ec18392c10))
|
||||
|
||||
# [5.39.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.38.1-dev.2...v5.39.0-dev.1) (2025-09-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Hide video action buttons:** Add "Hide Shop button" setting ([a84db7b](https://github.com/ReVanced/revanced-patches/commit/a84db7be7fde2e9bb3ac41aec709a1681e845fe1))
|
||||
|
||||
## [5.38.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.38.1-dev.1...v5.38.1-dev.2) (2025-09-16)
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
dependencies {
|
||||
compileOnly(project(":extensions:shared:library"))
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<manifest/>
|
||||
@@ -1,18 +0,0 @@
|
||||
package app.revanced.extension.instagram.feed;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class LimitFeedToFollowedProfiles {
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static Map<String, String> setFollowingHeader(Map<String, String> requestHeaderMap) {
|
||||
// Create new map as original is unmodifiable.
|
||||
Map<String, String> patchedRequestHeaderMap = new HashMap<>(requestHeaderMap);
|
||||
patchedRequestHeaderMap.put("pagination_source", "following");
|
||||
return patchedRequestHeaderMap;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package app.revanced.extension.music.patches;
|
||||
|
||||
import static app.revanced.extension.shared.Utils.hideViewBy0dpUnderCondition;
|
||||
|
||||
import android.view.View;
|
||||
import app.revanced.extension.music.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class HideCastButtonPatch {
|
||||
|
||||
/**
|
||||
* Injection point
|
||||
*/
|
||||
public static int hideCastButton(int original) {
|
||||
return Settings.HIDE_CAST_BUTTON.get() ? View.GONE : original;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point
|
||||
*/
|
||||
public static void hideCastButton(View view) {
|
||||
hideViewBy0dpUnderCondition(Settings.HIDE_CAST_BUTTON.get(), view);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package app.revanced.extension.music.patches;
|
||||
|
||||
import app.revanced.extension.music.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class HideUpgradeButtonPatch {
|
||||
|
||||
/**
|
||||
* Injection point
|
||||
*/
|
||||
public static boolean hideUpgradeButton() {
|
||||
return Settings.HIDE_UPGRADE_BUTTON.get();
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package app.revanced.extension.music.patches;
|
||||
|
||||
import static app.revanced.extension.shared.Utils.hideViewUnderCondition;
|
||||
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import app.revanced.extension.music.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class NavigationBarPatch {
|
||||
@NonNull
|
||||
private static String lastYTNavigationEnumName = "";
|
||||
|
||||
public static void setLastAppNavigationEnum(@Nullable Enum<?> ytNavigationEnumName) {
|
||||
if (ytNavigationEnumName != null) {
|
||||
lastYTNavigationEnumName = ytNavigationEnumName.name();
|
||||
}
|
||||
}
|
||||
|
||||
public static void hideNavigationLabel(TextView textview) {
|
||||
hideViewUnderCondition(Settings.HIDE_NAVIGATION_BAR_LABEL.get(), textview);
|
||||
}
|
||||
|
||||
public static void hideNavigationButton(@NonNull View view) {
|
||||
// Hide entire navigation bar.
|
||||
if (Settings.HIDE_NAVIGATION_BAR.get() && view.getParent() != null) {
|
||||
hideViewUnderCondition(true, (View) view.getParent());
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide navigation buttons based on their type.
|
||||
for (NavigationButton button : NavigationButton.values()) {
|
||||
if (button.ytEnumNames.equals(lastYTNavigationEnumName)) {
|
||||
hideViewUnderCondition(button.hidden, view);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum NavigationButton {
|
||||
HOME(
|
||||
"TAB_HOME",
|
||||
Settings.HIDE_NAVIGATION_BAR_HOME_BUTTON.get()
|
||||
),
|
||||
SAMPLES(
|
||||
"TAB_SAMPLES",
|
||||
Settings.HIDE_NAVIGATION_BAR_SAMPLES_BUTTON.get()
|
||||
),
|
||||
EXPLORE(
|
||||
"TAB_EXPLORE",
|
||||
Settings.HIDE_NAVIGATION_BAR_EXPLORE_BUTTON.get()
|
||||
),
|
||||
LIBRARY(
|
||||
"LIBRARY_MUSIC",
|
||||
Settings.HIDE_NAVIGATION_BAR_LIBRARY_BUTTON.get()
|
||||
),
|
||||
UPGRADE(
|
||||
"TAB_MUSIC_PREMIUM",
|
||||
Settings.HIDE_NAVIGATION_BAR_UPGRADE_BUTTON.get()
|
||||
);
|
||||
|
||||
private final String ytEnumNames;
|
||||
private final boolean hidden;
|
||||
|
||||
NavigationButton(@NonNull String ytEnumNames, boolean hidden) {
|
||||
this.ytEnumNames = ytEnumNames;
|
||||
this.hidden = hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import static app.revanced.extension.shared.spoof.ClientType.VISIONOS;
|
||||
import java.util.List;
|
||||
|
||||
import app.revanced.extension.shared.spoof.ClientType;
|
||||
import app.revanced.extension.shared.spoof.requests.StreamingDataRequest;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class SpoofVideoStreamsPatch {
|
||||
@@ -22,7 +23,8 @@ public class SpoofVideoStreamsPatch {
|
||||
VISIONOS
|
||||
);
|
||||
|
||||
app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.setClientsToUse(
|
||||
availableClients, SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get());
|
||||
ClientType client = SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get();
|
||||
app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.setPreferredClient(client);
|
||||
StreamingDataRequest.setClientOrderToUse(availableClients, client);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,17 +15,10 @@ public class Settings extends BaseSettings {
|
||||
// Ads
|
||||
public static final BooleanSetting HIDE_VIDEO_ADS = new BooleanSetting("revanced_music_hide_video_ads", TRUE, true);
|
||||
public static final BooleanSetting HIDE_GET_PREMIUM_LABEL = new BooleanSetting("revanced_music_hide_get_premium_label", TRUE, true);
|
||||
public static final BooleanSetting HIDE_UPGRADE_BUTTON = new BooleanSetting("revanced_music_hide_upgrade_button", TRUE, true);
|
||||
|
||||
// General
|
||||
public static final BooleanSetting HIDE_CAST_BUTTON = new BooleanSetting("revanced_music_hide_cast_button", TRUE, false);
|
||||
public static final BooleanSetting HIDE_CATEGORY_BAR = new BooleanSetting("revanced_music_hide_category_bar", FALSE, true);
|
||||
public static final BooleanSetting HIDE_NAVIGATION_BAR_HOME_BUTTON = new BooleanSetting("revanced_music_hide_navigation_bar_home_button", FALSE, true);
|
||||
public static final BooleanSetting HIDE_NAVIGATION_BAR_SAMPLES_BUTTON = new BooleanSetting("revanced_music_hide_navigation_bar_samples_button", FALSE, true);
|
||||
public static final BooleanSetting HIDE_NAVIGATION_BAR_EXPLORE_BUTTON = new BooleanSetting("revanced_music_hide_navigation_bar_explore_button", FALSE, true);
|
||||
public static final BooleanSetting HIDE_NAVIGATION_BAR_LIBRARY_BUTTON = new BooleanSetting("revanced_music_hide_navigation_bar_library_button", FALSE, true);
|
||||
public static final BooleanSetting HIDE_NAVIGATION_BAR_UPGRADE_BUTTON = new BooleanSetting("revanced_music_hide_navigation_bar_upgrade_button", TRUE, true);
|
||||
public static final BooleanSetting HIDE_NAVIGATION_BAR = new BooleanSetting("revanced_music_hide_navigation_bar", FALSE, true);
|
||||
public static final BooleanSetting HIDE_NAVIGATION_BAR_LABEL = new BooleanSetting("revanced_music_hide_navigation_bar_labels", FALSE, true);
|
||||
|
||||
// Player
|
||||
public static final BooleanSetting PERMANENT_REPEAT = new BooleanSetting("revanced_music_play_permanent_repeat", FALSE, true);
|
||||
|
||||
5
extensions/proguard-rules.pro
vendored
5
extensions/proguard-rules.pro
vendored
@@ -7,3 +7,8 @@
|
||||
-keep class com.google.** {
|
||||
*;
|
||||
}
|
||||
-keep class org.mozilla.javascript.** { *; }
|
||||
-dontwarn org.mozilla.javascript.tools.**
|
||||
-dontwarn java.beans.**
|
||||
-dontwarn jdk.dynalink.**
|
||||
-dontwarn javax.script.**
|
||||
@@ -7,8 +7,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Searches for a group of different patterns using a trie (prefix tree).
|
||||
/**Searches for a group of different patterns using a trie (prefix tree).
|
||||
* Can significantly speed up searching for multiple patterns.
|
||||
*/
|
||||
public abstract class TrieSearch<T> {
|
||||
@@ -56,13 +55,11 @@ public abstract class TrieSearch<T> {
|
||||
if (searchTextLength - searchTextIndex < patternLength - patternStartIndex) {
|
||||
return false; // Remaining search text is shorter than the remaining leaf pattern and they cannot match.
|
||||
}
|
||||
|
||||
for (int i = searchTextIndex, j = patternStartIndex; j < patternLength; i++, j++) {
|
||||
if (enclosingNode.getCharValue(searchText, i) != enclosingNode.getCharValue(pattern, j)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return callback == null || callback.patternMatched(searchText,
|
||||
searchTextIndex - patternStartIndex, patternLength, callbackParameter);
|
||||
}
|
||||
@@ -146,7 +143,6 @@ public abstract class TrieSearch<T> {
|
||||
endOfPatternCallback.add(callback);
|
||||
return;
|
||||
}
|
||||
|
||||
if (leaf != null) {
|
||||
// Reached end of the graph and a leaf exist.
|
||||
// Recursively call back into this method and push the existing leaf down 1 level.
|
||||
@@ -161,7 +157,6 @@ public abstract class TrieSearch<T> {
|
||||
leaf = new TrieCompressedPath<>(pattern, patternIndex, patternLength, callback);
|
||||
return;
|
||||
}
|
||||
|
||||
final char character = getCharValue(pattern, patternIndex);
|
||||
final int arrayIndex = hashIndexForTableSize(children.length, character);
|
||||
TrieNode<T> child = children[arrayIndex];
|
||||
@@ -186,7 +181,6 @@ public abstract class TrieSearch<T> {
|
||||
//noinspection unchecked
|
||||
TrieNode<T>[] replacement = new TrieNode[replacementArraySize];
|
||||
addNodeToArray(replacement, child);
|
||||
|
||||
boolean collision = false;
|
||||
for (TrieNode<T> existingChild : children) {
|
||||
if (existingChild != null) {
|
||||
@@ -199,7 +193,6 @@ public abstract class TrieSearch<T> {
|
||||
if (collision) {
|
||||
continue;
|
||||
}
|
||||
|
||||
children = replacement;
|
||||
return;
|
||||
}
|
||||
@@ -239,7 +232,6 @@ public abstract class TrieSearch<T> {
|
||||
if (leaf != null && leaf.matches(startNode, searchText, searchTextEndIndex, searchTextIndex, callbackParameter)) {
|
||||
return true; // Leaf exists and it matched the search text.
|
||||
}
|
||||
|
||||
List<TriePatternMatchedCallback<T>> endOfPatternCallback = node.endOfPatternCallback;
|
||||
if (endOfPatternCallback != null) {
|
||||
final int matchStartIndex = searchTextIndex - currentMatchLength;
|
||||
@@ -252,7 +244,6 @@ public abstract class TrieSearch<T> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TrieNode<T>[] children = node.children;
|
||||
if (children == null) {
|
||||
return false; // Reached a graph end point and there's no further patterns to search.
|
||||
@@ -285,11 +276,9 @@ public abstract class TrieSearch<T> {
|
||||
if (leaf != null) {
|
||||
numberOfPointers += 4; // Number of fields in leaf node.
|
||||
}
|
||||
|
||||
if (endOfPatternCallback != null) {
|
||||
numberOfPointers += endOfPatternCallback.size();
|
||||
}
|
||||
|
||||
if (children != null) {
|
||||
numberOfPointers += children.length;
|
||||
for (TrieNode<T> child : children) {
|
||||
|
||||
@@ -116,7 +116,7 @@ public class Utils {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The version name of the app, such as 20.13.41
|
||||
* @return The version name of the app, such as 19.11.43
|
||||
*/
|
||||
public static String getAppVersionName() {
|
||||
if (versionName == null) {
|
||||
|
||||
@@ -69,7 +69,7 @@ public enum ClientType {
|
||||
"132.0.6779.0",
|
||||
"23.47.101",
|
||||
true,
|
||||
"Android Studio"
|
||||
"Android Creator"
|
||||
),
|
||||
/**
|
||||
* Internal YT client for an unreleased YT client. May stop working at any time.
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.text.TextUtils;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -44,7 +43,7 @@ public class SpoofVideoStreamsPatch {
|
||||
/**
|
||||
* @return If this patch was included during patching.
|
||||
*/
|
||||
public static boolean isPatchIncluded() {
|
||||
private static boolean isPatchIncluded() {
|
||||
return false; // Modified during patching.
|
||||
}
|
||||
|
||||
@@ -61,9 +60,8 @@ public class SpoofVideoStreamsPatch {
|
||||
languageOverride = language;
|
||||
}
|
||||
|
||||
public static void setClientsToUse(List<ClientType> availableClients, ClientType client) {
|
||||
public static void setPreferredClient(ClientType client) {
|
||||
preferredClient = Objects.requireNonNull(client);
|
||||
StreamingDataRequest.setClientOrderToUse(availableClients, client);
|
||||
}
|
||||
|
||||
public static boolean spoofingToClientWithNoMultiAudioStreams() {
|
||||
@@ -97,35 +95,6 @@ public class SpoofVideoStreamsPatch {
|
||||
return playerRequestUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*
|
||||
* Blocks /get_watch requests by returning an unreachable URI.
|
||||
* /att/get requests are used to obtain a PoToken challenge.
|
||||
* See: <a href="https://github.com/FreeTubeApp/FreeTube/blob/4b7208430bc1032019a35a35eb7c8a84987ddbd7/src/botGuardScript.js#L15">botGuardScript.js#L15</a>
|
||||
* <p>
|
||||
* Since the Spoof streaming data patch was implemented because a valid PoToken cannot be obtained,
|
||||
* Blocking /att/get requests are not a problem.
|
||||
*/
|
||||
public static String blockGetAttRequest(String originalUrlString) {
|
||||
if (SPOOF_STREAMING_DATA) {
|
||||
try {
|
||||
var originalUri = Uri.parse(originalUrlString);
|
||||
String path = originalUri.getPath();
|
||||
|
||||
if (path != null && path.contains("att/get")) {
|
||||
Logger.printDebug(() -> "Blocking 'att/get' by returning internet connection check uri");
|
||||
|
||||
return INTERNET_CONNECTION_CHECK_URI_STRING;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "blockGetAttRequest failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
return originalUrlString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
* <p>
|
||||
@@ -159,7 +128,7 @@ public class SpoofVideoStreamsPatch {
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
* Only invoked when playing a livestream on an Apple client.
|
||||
* Only invoked when playing a livestream on an iOS client.
|
||||
*/
|
||||
public static boolean fixHLSCurrentTime(boolean original) {
|
||||
if (!SPOOF_STREAMING_DATA) {
|
||||
@@ -168,14 +137,6 @@ public class SpoofVideoStreamsPatch {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Injection point.
|
||||
* Fix audio stuttering in YouTube Music.
|
||||
*/
|
||||
public static boolean disableSABR() {
|
||||
return SPOOF_STREAMING_DATA;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
* Turns off a feature flag that interferes with spoofing.
|
||||
|
||||
@@ -2,6 +2,19 @@ dependencies {
|
||||
compileOnly(project(":extensions:shared:library"))
|
||||
compileOnly(project(":extensions:youtube:stub"))
|
||||
compileOnly(libs.annotation)
|
||||
implementation("com.github.teamnewpipe:NewPipeExtractor:0.24.8")
|
||||
implementation("io.reactivex.rxjava3:rxjava:3.1.8")
|
||||
implementation("com.squareup.okio:okio:3.7.0") // Newer okio use Kotlin 2.0 which Patches does not yet use.
|
||||
implementation("com.github.TeamNewPipe:nanojson:e9d656ddb49a412a5a0a5d5ef20ca7ef09549996")
|
||||
implementation("io.reactivex.rxjava3:rxandroid:3.0.2")
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
google()
|
||||
maven {
|
||||
url = uri("https://jitpack.io")
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package app.revanced.extension.shared.patches;
|
||||
package app.revanced.extension.youtube.patches;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
@@ -7,10 +7,6 @@ final class ButtonsFilter extends Filter {
|
||||
private static final String COMPACT_CHANNEL_BAR_PATH_PREFIX = "compact_channel_bar.eml";
|
||||
private static final String VIDEO_ACTION_BAR_PATH_PREFIX = "video_action_bar.eml";
|
||||
private static final String VIDEO_ACTION_BAR_PATH = "video_action_bar.eml";
|
||||
/**
|
||||
* Video bar path when the video information is collapsed. Seems to shown only with 20.14+
|
||||
*/
|
||||
private static final String COMPACTIFY_VIDEO_ACTION_BAR_PATH = "compactify_video_action_bar.eml";
|
||||
private static final String ANIMATED_VECTOR_TYPE_PATH = "AnimatedVectorType";
|
||||
|
||||
private final StringFilterGroup likeSubscribeGlow;
|
||||
@@ -78,18 +74,10 @@ final class ButtonsFilter extends Filter {
|
||||
Settings.HIDE_ASK_BUTTON,
|
||||
"yt_fill_spark"
|
||||
),
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_SHOP_BUTTON,
|
||||
"yt_outline_bag"
|
||||
),
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_STOP_ADS_BUTTON,
|
||||
"yt_outline_slash_circle_left"
|
||||
),
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_COMMENTS_BUTTON,
|
||||
"yt_outline_message_bubble_right"
|
||||
),
|
||||
// Check for clip button both here and using a path filter,
|
||||
// as there's a chance the path is a generic action button and won't contain 'clip_button'
|
||||
new ByteArrayFilterGroup(
|
||||
@@ -132,8 +120,9 @@ final class ButtonsFilter extends Filter {
|
||||
}
|
||||
|
||||
if (matchedGroup == bufferFilterPathGroup) {
|
||||
// Make sure the current path is the right one to avoid false positives.
|
||||
return (path.startsWith(VIDEO_ACTION_BAR_PATH) || path.startsWith(COMPACTIFY_VIDEO_ACTION_BAR_PATH))
|
||||
// Make sure the current path is the right one
|
||||
// to avoid false positives.
|
||||
return path.startsWith(VIDEO_ACTION_BAR_PATH)
|
||||
&& bufferButtonsGroupList.check(buffer).isFiltered();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,695 @@
|
||||
package app.revanced.extension.youtube.patches.spoof
|
||||
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import android.webkit.ConsoleMessage
|
||||
import android.webkit.JavascriptInterface
|
||||
import android.webkit.WebChromeClient
|
||||
import android.webkit.WebView
|
||||
import app.revanced.extension.shared.Utils
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.core.SingleEmitter
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.schabi.newpipe.extractor.NewPipe
|
||||
import org.schabi.newpipe.extractor.services.youtube.InnertubeClientRequestInfo
|
||||
import org.schabi.newpipe.extractor.services.youtube.PoTokenProvider
|
||||
import org.schabi.newpipe.extractor.services.youtube.PoTokenResult
|
||||
import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper
|
||||
import java.io.Closeable
|
||||
import java.time.Instant
|
||||
|
||||
import com.grack.nanojson.JsonObject
|
||||
import com.grack.nanojson.JsonParser
|
||||
import com.grack.nanojson.JsonWriter
|
||||
import okio.ByteString.Companion.decodeBase64
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
|
||||
// TODO: Remove
|
||||
fun test(){
|
||||
YoutubeStreamExtractor.setPoTokenProvider(PoTokenProviderImpl);
|
||||
with(StreamInfo.getInfo("https://youtube.com/watch?v=dQw4w9WgXcQ")) {
|
||||
this.videoStreams.first().content
|
||||
this.videoOnlyStreams.first().content
|
||||
this.audioStreams.first().content
|
||||
this.subtitles.first().isAutoGenerated
|
||||
}
|
||||
}
|
||||
|
||||
object BuildConfig {
|
||||
const val DEBUG = true
|
||||
}
|
||||
|
||||
fun parseChallengeData(rawChallengeData: String): String {
|
||||
val scrambled = JsonParser.array().from(rawChallengeData)
|
||||
|
||||
val challengeData = if (scrambled.size > 1 && scrambled.isString(1)) {
|
||||
val descrambled = descramble(scrambled.getString(1))
|
||||
JsonParser.array().from(descrambled)
|
||||
} else {
|
||||
scrambled.getArray(0)
|
||||
}
|
||||
|
||||
val messageId = challengeData.getString(0)
|
||||
val interpreterHash = challengeData.getString(3)
|
||||
val program = challengeData.getString(4)
|
||||
val globalName = challengeData.getString(5)
|
||||
val clientExperimentsStateBlob = challengeData.getString(7)
|
||||
|
||||
val privateDoNotAccessOrElseSafeScriptWrappedValue = challengeData.getArray(1, null)?.find { it is String }
|
||||
val privateDoNotAccessOrElseTrustedResourceUrlWrappedValue = challengeData.getArray(2, null)?.find { it is String }
|
||||
|
||||
return JsonWriter.string(
|
||||
JsonObject.builder()
|
||||
.value("messageId", messageId)
|
||||
.`object`("interpreterJavascript")
|
||||
.value("privateDoNotAccessOrElseSafeScriptWrappedValue", privateDoNotAccessOrElseSafeScriptWrappedValue)
|
||||
.value("privateDoNotAccessOrElseTrustedResourceUrlWrappedValue", privateDoNotAccessOrElseTrustedResourceUrlWrappedValue)
|
||||
.end()
|
||||
.value("interpreterHash", interpreterHash)
|
||||
.value("program", program)
|
||||
.value("globalName", globalName)
|
||||
.value("clientExperimentsStateBlob", clientExperimentsStateBlob)
|
||||
.done()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the raw integrity token data obtained from the GenerateIT endpoint to a JavaScript
|
||||
* `Uint8Array` that can be embedded directly in JavaScript code, and an [Int] representing the
|
||||
* duration of this token in seconds.
|
||||
*/
|
||||
fun parseIntegrityTokenData(rawIntegrityTokenData: String): Pair<String, Long> {
|
||||
val integrityTokenData = JsonParser.array().from(rawIntegrityTokenData)
|
||||
return base64ToU8(integrityTokenData.getString(0)) to integrityTokenData.getLong(1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a string (usually the identifier used as input to `obtainPoToken`) to a JavaScript
|
||||
* `Uint8Array` that can be embedded directly in JavaScript code.
|
||||
*/
|
||||
fun stringToU8(identifier: String): String {
|
||||
return newUint8Array(identifier.toByteArray())
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a poToken encoded as a sequence of bytes represented as integers separated by commas
|
||||
* (e.g. "97,98,99" would be "abc"), which is the output of `Uint8Array::toString()` in JavaScript,
|
||||
* and converts it to the specific base64 representation for poTokens.
|
||||
*/
|
||||
fun u8ToBase64(poToken: String): String {
|
||||
return poToken.split(",")
|
||||
.map { it.toUByte().toByte() }
|
||||
.toByteArray()
|
||||
.toByteString()
|
||||
.base64()
|
||||
.replace("+", "-")
|
||||
.replace("/", "_")
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the scrambled challenge, decodes it from base64, adds 97 to each byte.
|
||||
*/
|
||||
private fun descramble(scrambledChallenge: String): String {
|
||||
return base64ToByteString(scrambledChallenge)
|
||||
.map { (it + 97).toByte() }
|
||||
.toByteArray()
|
||||
.decodeToString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a base64 string encoded in the specific base64 representation used by YouTube, and
|
||||
* returns a JavaScript `Uint8Array` that can be embedded directly in JavaScript code.
|
||||
*/
|
||||
private fun base64ToU8(base64: String): String {
|
||||
return newUint8Array(base64ToByteString(base64))
|
||||
}
|
||||
|
||||
private fun newUint8Array(contents: ByteArray): String {
|
||||
return "new Uint8Array([" + contents.joinToString(separator = ",") { it.toUByte().toString() } + "])"
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a base64 string encoded in the specific base64 representation used by YouTube.
|
||||
*/
|
||||
private fun base64ToByteString(base64: String): ByteArray {
|
||||
val base64Mod = base64
|
||||
.replace('-', '+')
|
||||
.replace('_', '/')
|
||||
.replace('.', '=')
|
||||
|
||||
return (base64Mod.decodeBase64() ?: throw PoTokenException("Cannot base64 decode"))
|
||||
.toByteArray()
|
||||
}
|
||||
|
||||
|
||||
class PoTokenException(message: String) : Exception(message)
|
||||
|
||||
// to be thrown if the WebView provided by the system is broken
|
||||
class BadWebViewException(message: String) : Exception(message)
|
||||
|
||||
fun buildExceptionForJsError(error: String): Exception {
|
||||
return if (error.contains("SyntaxError"))
|
||||
BadWebViewException(error)
|
||||
else
|
||||
PoTokenException(error)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This interface was created to allow for multiple methods to generate poTokens in the future (e.g.
|
||||
* via WebView and via a local DOM implementation)
|
||||
*/
|
||||
interface PoTokenGenerator : Closeable {
|
||||
/**
|
||||
* Generates a poToken for the provided identifier, using the `integrityToken` and
|
||||
* `webPoSignalOutput` previously obtained in the initialization of [PoTokenWebView]. Can be
|
||||
* called multiple times.
|
||||
*/
|
||||
fun generatePoToken(identifier: String): Single<String>
|
||||
|
||||
/**
|
||||
* @return whether the `integrityToken` is expired, in which case all tokens generated by
|
||||
* [generatePoToken] will be invalid
|
||||
*/
|
||||
fun isExpired(): Boolean
|
||||
|
||||
interface Factory {
|
||||
/**
|
||||
* Initializes a [PoTokenGenerator] by loading the BotGuard VM, running it, and obtaining
|
||||
* an `integrityToken`. Can then be used multiple times to generate multiple poTokens with
|
||||
* [generatePoToken].
|
||||
*
|
||||
* @param context used e.g. to load the HTML asset or to instantiate a WebView
|
||||
*/
|
||||
fun newPoTokenGenerator(context: Context): Single<PoTokenGenerator>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
object PoTokenProviderImpl : PoTokenProvider {
|
||||
val TAG = PoTokenProviderImpl::class.simpleName
|
||||
private var webViewBadImpl = false // whether the system has a bad WebView implementation
|
||||
|
||||
private object WebPoTokenGenLock
|
||||
private var webPoTokenVisitorData: String? = null
|
||||
private var webPoTokenStreamingPot: String? = null
|
||||
private var webPoTokenGenerator: PoTokenGenerator? = null
|
||||
|
||||
override fun getWebClientPoToken(videoId: String): PoTokenResult? {
|
||||
if (!webViewBadImpl) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
return getWebClientPoToken(videoId = videoId, forceRecreate = false)
|
||||
} catch (e: RuntimeException) {
|
||||
// RxJava's Single wraps exceptions into RuntimeErrors, so we need to unwrap them here
|
||||
when (val cause = e.cause) {
|
||||
is BadWebViewException -> {
|
||||
Log.e(TAG, "Could not obtain poToken because WebView is broken", e)
|
||||
webViewBadImpl = true
|
||||
return null
|
||||
}
|
||||
null -> throw e
|
||||
else -> throw cause // includes PoTokenException
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param forceRecreate whether to force the recreation of [webPoTokenGenerator], to be used in
|
||||
* case the current [webPoTokenGenerator] threw an error last time
|
||||
* [PoTokenGenerator.generatePoToken] was called
|
||||
*/
|
||||
private fun getWebClientPoToken(videoId: String, forceRecreate: Boolean): PoTokenResult {
|
||||
// just a helper class since Kotlin does not have builtin support for 4-tuples
|
||||
data class Quadruple<T1, T2, T3, T4>(val t1: T1, val t2: T2, val t3: T3, val t4: T4)
|
||||
|
||||
val (poTokenGenerator, visitorData, streamingPot, hasBeenRecreated) =
|
||||
synchronized(WebPoTokenGenLock) {
|
||||
val shouldRecreate = webPoTokenGenerator == null || forceRecreate ||
|
||||
webPoTokenGenerator!!.isExpired()
|
||||
|
||||
if (shouldRecreate) {
|
||||
|
||||
val innertubeClientRequestInfo = InnertubeClientRequestInfo.ofWebClient()
|
||||
innertubeClientRequestInfo.clientInfo.clientVersion =
|
||||
YoutubeParsingHelper.getClientVersion()
|
||||
|
||||
webPoTokenVisitorData = YoutubeParsingHelper.getVisitorDataFromInnertube(
|
||||
innertubeClientRequestInfo,
|
||||
NewPipe.getPreferredLocalization(),
|
||||
NewPipe.getPreferredContentCountry(),
|
||||
YoutubeParsingHelper.getYouTubeHeaders(),
|
||||
YoutubeParsingHelper.YOUTUBEI_V1_URL,
|
||||
null,
|
||||
false
|
||||
)
|
||||
// close the current webPoTokenGenerator on the main thread
|
||||
webPoTokenGenerator?.let { Handler(Looper.getMainLooper()).post { it.close() } }
|
||||
|
||||
// create a new webPoTokenGenerator
|
||||
webPoTokenGenerator = PoTokenWebView
|
||||
.newPoTokenGenerator(Utils.getContext()).blockingGet()
|
||||
|
||||
// The streaming poToken needs to be generated exactly once before generating
|
||||
// any other (player) tokens.
|
||||
webPoTokenStreamingPot = webPoTokenGenerator!!
|
||||
.generatePoToken(webPoTokenVisitorData!!).blockingGet()
|
||||
}
|
||||
|
||||
return@synchronized Quadruple(
|
||||
webPoTokenGenerator!!,
|
||||
webPoTokenVisitorData!!,
|
||||
webPoTokenStreamingPot!!,
|
||||
shouldRecreate
|
||||
)
|
||||
}
|
||||
|
||||
val playerPot = try {
|
||||
// Not using synchronized here, since poTokenGenerator would be able to generate
|
||||
// multiple poTokens in parallel if needed. The only important thing is for exactly one
|
||||
// visitorData/streaming poToken to be generated before anything else.
|
||||
poTokenGenerator.generatePoToken(videoId).blockingGet()
|
||||
} catch (throwable: Throwable) {
|
||||
if (hasBeenRecreated) {
|
||||
// the poTokenGenerator has just been recreated (and possibly this is already the
|
||||
// second time we try), so there is likely nothing we can do
|
||||
throw throwable
|
||||
} else {
|
||||
// retry, this time recreating the [webPoTokenGenerator] from scratch;
|
||||
// this might happen for example if NewPipe goes in the background and the WebView
|
||||
// content is lost
|
||||
Log.e(TAG, "Failed to obtain poToken, retrying", throwable)
|
||||
return getWebClientPoToken(videoId = videoId, forceRecreate = true)
|
||||
}
|
||||
}
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(
|
||||
TAG,
|
||||
"poToken for $videoId: playerPot=$playerPot, " +
|
||||
"streamingPot=$streamingPot, visitor_data=$visitorData"
|
||||
)
|
||||
}
|
||||
|
||||
return PoTokenResult(visitorData, playerPot, streamingPot)
|
||||
}
|
||||
|
||||
override fun getWebEmbedClientPoToken(videoId: String): PoTokenResult? = null
|
||||
|
||||
override fun getAndroidClientPoToken(videoId: String): PoTokenResult? = null
|
||||
|
||||
override fun getIosClientPoToken(videoId: String): PoTokenResult? = null
|
||||
}
|
||||
|
||||
|
||||
class PoTokenWebView private constructor(
|
||||
context: Context,
|
||||
// to be used exactly once only during initialization!
|
||||
private val generatorEmitter: SingleEmitter<PoTokenGenerator>,
|
||||
) : PoTokenGenerator {
|
||||
private val webView = WebView(context)
|
||||
private val disposables = CompositeDisposable() // used only during initialization
|
||||
private val poTokenEmitters = mutableListOf<Pair<String, SingleEmitter<String>>>()
|
||||
private lateinit var expirationInstant: Instant
|
||||
|
||||
//region Initialization
|
||||
init {
|
||||
val webViewSettings = webView.settings
|
||||
//noinspection SetJavaScriptEnabled we want to use JavaScript!
|
||||
webViewSettings.javaScriptEnabled = true
|
||||
webViewSettings.safeBrowsingEnabled = false
|
||||
webViewSettings.userAgentString = USER_AGENT
|
||||
webViewSettings.blockNetworkLoads = true // the WebView does not need internet access
|
||||
|
||||
// so that we can run async functions and get back the result
|
||||
webView.addJavascriptInterface(this, JS_INTERFACE)
|
||||
|
||||
webView.webChromeClient = object : WebChromeClient() {
|
||||
override fun onConsoleMessage(m: ConsoleMessage): Boolean {
|
||||
if (m.message().contains("Uncaught")) {
|
||||
// There should not be any uncaught errors while executing the code, because
|
||||
// everything that can fail is guarded by try-catch. Therefore, this likely
|
||||
// indicates that there was a syntax error in the code, i.e. the WebView only
|
||||
// supports a really old version of JS.
|
||||
|
||||
val fmt = "\"${m.message()}\", source: ${m.sourceId()} (${m.lineNumber()})"
|
||||
val exception = BadWebViewException(fmt)
|
||||
Log.e(TAG, "This WebView implementation is broken: $fmt")
|
||||
|
||||
onInitializationErrorCloseAndCancel(exception)
|
||||
popAllPoTokenEmitters().forEach { (_, emitter) -> emitter.onError(exception) }
|
||||
}
|
||||
return super.onConsoleMessage(m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Must be called right after instantiating [PoTokenWebView] to perform the actual
|
||||
* initialization. This will asynchronously go through all the steps needed to load BotGuard,
|
||||
* run it, and obtain an `integrityToken`.
|
||||
*/
|
||||
private fun loadHtmlAndObtainBotguard(context: Context) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(TAG, "loadHtmlAndObtainBotguard() called")
|
||||
}
|
||||
|
||||
disposables.add(
|
||||
Single.fromCallable {
|
||||
val html = context.assets.open("po_token.html").bufferedReader()
|
||||
.use { it.readText() }
|
||||
return@fromCallable html
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{ html ->
|
||||
webView.loadDataWithBaseURL(
|
||||
"https://www.youtube.com",
|
||||
html.replaceFirst(
|
||||
"</script>",
|
||||
// calls downloadAndRunBotguard() when the page has finished loading
|
||||
"\n$JS_INTERFACE.downloadAndRunBotguard()</script>"
|
||||
),
|
||||
"text/html",
|
||||
"utf-8",
|
||||
null,
|
||||
)
|
||||
},
|
||||
this::onInitializationErrorCloseAndCancel
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called during initialization by the JavaScript snippet appended to the HTML page content in
|
||||
* [loadHtmlAndObtainBotguard] after the WebView content has been loaded.
|
||||
*/
|
||||
@JavascriptInterface
|
||||
fun downloadAndRunBotguard() {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(TAG, "downloadAndRunBotguard() called")
|
||||
}
|
||||
|
||||
makeBotguardServiceRequest(
|
||||
"https://www.youtube.com/api/jnn/v1/Create",
|
||||
"[ \"$REQUEST_KEY\" ]",
|
||||
) { responseBody ->
|
||||
val parsedChallengeData = parseChallengeData(responseBody)
|
||||
webView.evaluateJavascript(
|
||||
"""try {
|
||||
data = $parsedChallengeData
|
||||
runBotGuard(data).then(function (result) {
|
||||
this.webPoSignalOutput = result.webPoSignalOutput
|
||||
$JS_INTERFACE.onRunBotguardResult(result.botguardResponse)
|
||||
}, function (error) {
|
||||
$JS_INTERFACE.onJsInitializationError(error + "\n" + error.stack)
|
||||
})
|
||||
} catch (error) {
|
||||
$JS_INTERFACE.onJsInitializationError(error + "\n" + error.stack)
|
||||
}""",
|
||||
null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called during initialization by the JavaScript snippets from either
|
||||
* [downloadAndRunBotguard] or [onRunBotguardResult].
|
||||
*/
|
||||
@JavascriptInterface
|
||||
fun onJsInitializationError(error: String) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.e(TAG, "Initialization error from JavaScript: $error")
|
||||
}
|
||||
onInitializationErrorCloseAndCancel(buildExceptionForJsError(error))
|
||||
}
|
||||
|
||||
/**
|
||||
* Called during initialization by the JavaScript snippet from [downloadAndRunBotguard] after
|
||||
* obtaining the BotGuard execution output [botguardResponse].
|
||||
*/
|
||||
@JavascriptInterface
|
||||
fun onRunBotguardResult(botguardResponse: String) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(TAG, "botguardResponse: $botguardResponse")
|
||||
}
|
||||
makeBotguardServiceRequest(
|
||||
"https://www.youtube.com/api/jnn/v1/GenerateIT",
|
||||
"[ \"$REQUEST_KEY\", \"$botguardResponse\" ]",
|
||||
) { responseBody ->
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(TAG, "GenerateIT response: $responseBody")
|
||||
}
|
||||
val (integrityToken, expirationTimeInSeconds) = parseIntegrityTokenData(responseBody)
|
||||
|
||||
// leave 10 minutes of margin just to be sure
|
||||
expirationInstant = Instant.now().plusSeconds(expirationTimeInSeconds - 600)
|
||||
|
||||
webView.evaluateJavascript(
|
||||
"this.integrityToken = $integrityToken"
|
||||
) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(TAG, "initialization finished, expiration=${expirationTimeInSeconds}s")
|
||||
}
|
||||
generatorEmitter.onSuccess(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region Obtaining poTokens
|
||||
override fun generatePoToken(identifier: String): Single<String> =
|
||||
Single.create { emitter ->
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(TAG, "generatePoToken() called with identifier $identifier")
|
||||
}
|
||||
runOnMainThread(emitter) {
|
||||
addPoTokenEmitter(identifier, emitter)
|
||||
val u8Identifier = stringToU8(identifier)
|
||||
webView.evaluateJavascript(
|
||||
"""try {
|
||||
identifier = "$identifier"
|
||||
u8Identifier = $u8Identifier
|
||||
poTokenU8 = obtainPoToken(webPoSignalOutput, integrityToken, u8Identifier)
|
||||
poTokenU8String = ""
|
||||
for (i = 0; i < poTokenU8.length; i++) {
|
||||
if (i != 0) poTokenU8String += ","
|
||||
poTokenU8String += poTokenU8[i]
|
||||
}
|
||||
$JS_INTERFACE.onObtainPoTokenResult(identifier, poTokenU8String)
|
||||
} catch (error) {
|
||||
$JS_INTERFACE.onObtainPoTokenError(identifier, error + "\n" + error.stack)
|
||||
}""",
|
||||
) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the JavaScript snippet from [generatePoToken] when an error occurs in calling the
|
||||
* JavaScript `obtainPoToken()` function.
|
||||
*/
|
||||
@JavascriptInterface
|
||||
fun onObtainPoTokenError(identifier: String, error: String) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.e(TAG, "obtainPoToken error from JavaScript: $error")
|
||||
}
|
||||
popPoTokenEmitter(identifier)?.onError(buildExceptionForJsError(error))
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the JavaScript snippet from [generatePoToken] with the original identifier and the
|
||||
* result of the JavaScript `obtainPoToken()` function.
|
||||
*/
|
||||
@JavascriptInterface
|
||||
fun onObtainPoTokenResult(identifier: String, poTokenU8: String) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(TAG, "Generated poToken (before decoding): identifier=$identifier poTokenU8=$poTokenU8")
|
||||
}
|
||||
val poToken = try {
|
||||
u8ToBase64(poTokenU8)
|
||||
} catch (t: Throwable) {
|
||||
popPoTokenEmitter(identifier)?.onError(t)
|
||||
return
|
||||
}
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(TAG, "Generated poToken: identifier=$identifier poToken=$poToken")
|
||||
}
|
||||
popPoTokenEmitter(identifier)?.onSuccess(poToken)
|
||||
}
|
||||
|
||||
override fun isExpired(): Boolean {
|
||||
return Instant.now().isAfter(expirationInstant)
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region Handling multiple emitters
|
||||
/**
|
||||
* Adds the ([identifier], [emitter]) pair to the [poTokenEmitters] list. This makes it so that
|
||||
* multiple poToken requests can be generated invparallel, and the results will be notified to
|
||||
* the right emitters.
|
||||
*/
|
||||
private fun addPoTokenEmitter(identifier: String, emitter: SingleEmitter<String>) {
|
||||
synchronized(poTokenEmitters) {
|
||||
poTokenEmitters.add(Pair(identifier, emitter))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts and removes from the [poTokenEmitters] list a [SingleEmitter] based on its
|
||||
* [identifier]. The emitter is supposed to be used immediately after to either signal a success
|
||||
* or an error.
|
||||
*/
|
||||
private fun popPoTokenEmitter(identifier: String): SingleEmitter<String>? {
|
||||
return synchronized(poTokenEmitters) {
|
||||
poTokenEmitters.indexOfFirst { it.first == identifier }.takeIf { it >= 0 }?.let {
|
||||
poTokenEmitters.removeAt(it).second
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears [poTokenEmitters] and returns its previous contents. The emitters are supposed to be
|
||||
* used immediately after to either signal a success or an error.
|
||||
*/
|
||||
private fun popAllPoTokenEmitters(): List<Pair<String, SingleEmitter<String>>> {
|
||||
return synchronized(poTokenEmitters) {
|
||||
val result = poTokenEmitters.toList()
|
||||
poTokenEmitters.clear()
|
||||
result
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region Utils
|
||||
/**
|
||||
* Makes a POST request to [url] with the given [data] by setting the correct headers. Calls
|
||||
* [onInitializationErrorCloseAndCancel] in case of any network errors and also if the response
|
||||
* does not have HTTP code 200, therefore this is supposed to be used only during
|
||||
* initialization. Calls [handleResponseBody] with the response body if the response is
|
||||
* successful. The request is performed in the background and a disposable is added to
|
||||
* [disposables].
|
||||
*/
|
||||
private fun makeBotguardServiceRequest(
|
||||
url: String,
|
||||
data: String,
|
||||
handleResponseBody: (String) -> Unit,
|
||||
) {
|
||||
disposables.add(
|
||||
Single.fromCallable {
|
||||
val connection = URL(url).openConnection() as HttpURLConnection
|
||||
connection.requestMethod = "POST"
|
||||
connection.doOutput = true
|
||||
|
||||
// headers
|
||||
connection.setRequestProperty("User-Agent", USER_AGENT)
|
||||
connection.setRequestProperty("Accept", "application/json")
|
||||
connection.setRequestProperty("Content-Type", "application/json+protobuf")
|
||||
connection.setRequestProperty("x-goog-api-key", GOOGLE_API_KEY)
|
||||
connection.setRequestProperty("x-user-agent", "grpc-web-javascript/0.1")
|
||||
|
||||
// body
|
||||
connection.outputStream.use { os ->
|
||||
os.writer().write(data);
|
||||
}
|
||||
|
||||
// response
|
||||
|
||||
return@fromCallable connection
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{ connection ->
|
||||
val httpCode = connection.responseCode
|
||||
if (httpCode != 200) {
|
||||
onInitializationErrorCloseAndCancel(
|
||||
PoTokenException("Invalid response code: $httpCode")
|
||||
)
|
||||
return@subscribe
|
||||
}
|
||||
|
||||
val responseBody = connection.inputStream.bufferedReader().use { it.readText() }
|
||||
connection.disconnect()
|
||||
|
||||
handleResponseBody(responseBody)
|
||||
},
|
||||
this::onInitializationErrorCloseAndCancel
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles any error happening during initialization, releasing resources and sending the error
|
||||
* to [generatorEmitter].
|
||||
*/
|
||||
private fun onInitializationErrorCloseAndCancel(error: Throwable) {
|
||||
runOnMainThread(generatorEmitter) {
|
||||
close()
|
||||
generatorEmitter.onError(error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases all [webView] and [disposables] resources.
|
||||
*/
|
||||
override fun close() {
|
||||
disposables.dispose()
|
||||
|
||||
webView.clearHistory()
|
||||
// clears RAM cache and disk cache (globally for all WebViews)
|
||||
webView.clearCache(true)
|
||||
|
||||
// ensures that the WebView isn't doing anything when destroying it
|
||||
webView.loadUrl("about:blank")
|
||||
|
||||
webView.onPause()
|
||||
webView.removeAllViews()
|
||||
webView.destroy()
|
||||
}
|
||||
//endregion
|
||||
|
||||
companion object : PoTokenGenerator.Factory {
|
||||
private val TAG = PoTokenWebView::class.simpleName
|
||||
// Public API key used by BotGuard, which has been got by looking at BotGuard requests
|
||||
private const val GOOGLE_API_KEY = "AIzaSyDyT5W0Jh49F30Pqqtyfdf7pDLFKLJoAnw" // NOSONAR
|
||||
private const val REQUEST_KEY = "O43z0dpjhgX20SCx4KAo"
|
||||
private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.3"
|
||||
private const val JS_INTERFACE = "PoTokenWebView"
|
||||
|
||||
override fun newPoTokenGenerator(context: Context): Single<PoTokenGenerator> =
|
||||
Single.create { emitter ->
|
||||
runOnMainThread(emitter) {
|
||||
val potWv = PoTokenWebView(context, emitter)
|
||||
potWv.loadHtmlAndObtainBotguard(context)
|
||||
emitter.setDisposable(potWv.disposables)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs [runnable] on the main thread using `Handler(Looper.getMainLooper()).post()`, and
|
||||
* if the `post` fails emits an error on [emitterIfPostFails].
|
||||
*/
|
||||
private fun runOnMainThread(
|
||||
emitterIfPostFails: SingleEmitter<out Any>,
|
||||
runnable: Runnable,
|
||||
) {
|
||||
if (!Handler(Looper.getMainLooper()).post(runnable)) {
|
||||
emitterIfPostFails.onError(PoTokenException("Could not run on main thread"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import static app.revanced.extension.shared.spoof.ClientType.VISIONOS;
|
||||
import java.util.List;
|
||||
|
||||
import app.revanced.extension.shared.spoof.ClientType;
|
||||
import app.revanced.extension.shared.spoof.requests.StreamingDataRequest;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@@ -26,7 +27,8 @@ public class SpoofVideoStreamsPatch {
|
||||
IPADOS
|
||||
);
|
||||
|
||||
app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.setClientsToUse(
|
||||
availableClients, Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get());
|
||||
ClientType client = Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get();
|
||||
app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.setPreferredClient(client);
|
||||
StreamingDataRequest.setClientOrderToUse(availableClients, client);
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,6 @@ import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehavi
|
||||
import android.graphics.Color;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
import app.revanced.extension.shared.settings.BooleanSetting;
|
||||
import app.revanced.extension.shared.settings.EnumSetting;
|
||||
@@ -223,7 +222,6 @@ public class Settings extends BaseSettings {
|
||||
public static final BooleanSetting DISABLE_LIKE_SUBSCRIBE_GLOW = new BooleanSetting("revanced_disable_like_subscribe_glow", FALSE);
|
||||
public static final BooleanSetting HIDE_ASK_BUTTON = new BooleanSetting("revanced_hide_ask_button", FALSE);
|
||||
public static final BooleanSetting HIDE_CLIP_BUTTON = new BooleanSetting("revanced_hide_clip_button", TRUE);
|
||||
public static final BooleanSetting HIDE_COMMENTS_BUTTON = new BooleanSetting("revanced_hide_comments_button", TRUE);
|
||||
public static final BooleanSetting HIDE_DOWNLOAD_BUTTON = new BooleanSetting("revanced_hide_download_button", FALSE);
|
||||
public static final BooleanSetting HIDE_HYPE_BUTTON = new BooleanSetting("revanced_hide_hype_button", FALSE);
|
||||
public static final BooleanSetting HIDE_LIKE_DISLIKE_BUTTON = new BooleanSetting("revanced_hide_like_dislike_button", FALSE);
|
||||
@@ -232,7 +230,6 @@ public class Settings extends BaseSettings {
|
||||
public static final BooleanSetting HIDE_REPORT_BUTTON = new BooleanSetting("revanced_hide_report_button", FALSE);
|
||||
public static final BooleanSetting HIDE_SAVE_BUTTON = new BooleanSetting("revanced_hide_save_button", FALSE);
|
||||
public static final BooleanSetting HIDE_SHARE_BUTTON = new BooleanSetting("revanced_hide_share_button", FALSE);
|
||||
public static final BooleanSetting HIDE_SHOP_BUTTON = new BooleanSetting("revanced_hide_shop_button", FALSE);
|
||||
public static final BooleanSetting HIDE_STOP_ADS_BUTTON = new BooleanSetting("revanced_hide_stop_ads_button", TRUE);
|
||||
public static final BooleanSetting HIDE_THANKS_BUTTON = new BooleanSetting("revanced_hide_thanks_button", TRUE);
|
||||
|
||||
@@ -515,14 +512,10 @@ public class Settings extends BaseSettings {
|
||||
DEPRECATED_SWIPE_OVERLAY_BACKGROUND_ALPHA.resetToDefault();
|
||||
}
|
||||
|
||||
// Old spoof versions that no longer work,
|
||||
// or is spoofing to a version the same or newer than this app.
|
||||
if (!SPOOF_APP_VERSION_TARGET.isSetToDefault() &&
|
||||
(SPOOF_APP_VERSION_TARGET.get().compareTo(SPOOF_APP_VERSION_TARGET.defaultValue) < 0
|
||||
|| (Utils.getAppVersionName().compareTo(SPOOF_APP_VERSION_TARGET.get()) <= 0))) {
|
||||
Logger.printInfo(() -> "Resetting spoof app version");
|
||||
// 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();
|
||||
SPOOF_APP_VERSION.resetToDefault();
|
||||
}
|
||||
|
||||
// RYD requires manually migrating old settings since the lack of
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
package app.revanced.extension.youtube.settings.preference;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
|
||||
import android.content.Context;
|
||||
import android.preference.SwitchPreference;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import app.revanced.extension.shared.spoof.ClientType;
|
||||
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
@SuppressWarnings({"deprecation", "unused"})
|
||||
public class ForceOriginalAudioSwitchPreference extends SwitchPreference {
|
||||
|
||||
// Spoof stream patch is not included, or is not currently spoofing to Android Studio.
|
||||
private static final boolean available = !SpoofVideoStreamsPatch.isPatchIncluded()
|
||||
|| !(Settings.SPOOF_VIDEO_STREAMS.get()
|
||||
&& Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.ANDROID_CREATOR);
|
||||
|
||||
{
|
||||
if (!available) {
|
||||
// Show why force audio is not available.
|
||||
String summary = str("revanced_force_original_audio_not_available");
|
||||
super.setSummary(summary);
|
||||
super.setSummaryOn(summary);
|
||||
super.setSummaryOff(summary);
|
||||
super.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
public ForceOriginalAudioSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
public ForceOriginalAudioSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
public ForceOriginalAudioSwitchPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
public ForceOriginalAudioSwitchPreference(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
if (!available) {
|
||||
return;
|
||||
}
|
||||
|
||||
super.setEnabled(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSummary(CharSequence summary) {
|
||||
if (!available) {
|
||||
return;
|
||||
}
|
||||
|
||||
super.setSummary(summary);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,20 +83,19 @@ public class SpoofStreamingDataSideEffectsPreference extends Preference {
|
||||
|
||||
String summary = str(clientType == ClientType.IPADOS
|
||||
? "revanced_spoof_video_streams_about_ipados_summary"
|
||||
// Same base side effects for Android VR, Android Studio, and visionOS.
|
||||
// visionOS has same base side effects as Android VR.
|
||||
: "revanced_spoof_video_streams_about_android_summary");
|
||||
|
||||
if (clientType == ClientType.IPADOS) {
|
||||
summary += '\n' + str("revanced_spoof_video_streams_about_no_av1");
|
||||
summary = str("revanced_spoof_video_streams_about_no_av1")
|
||||
+ '\n' + summary;
|
||||
} else if (clientType == ClientType.VISIONOS) {
|
||||
summary = str("revanced_spoof_video_streams_about_experimental")
|
||||
+ '\n' + summary
|
||||
+ '\n' + str("revanced_spoof_video_streams_about_no_av1")
|
||||
+ '\n' + str("revanced_spoof_video_streams_about_kids_videos");
|
||||
} else if (clientType == ClientType.ANDROID_CREATOR) {
|
||||
summary += '\n' + str("revanced_spoof_video_streams_about_no_av1")
|
||||
+ '\n' + str("revanced_spoof_video_streams_about_no_force_original_audio")
|
||||
+ '\n' + str("revanced_spoof_video_streams_about_kids_videos");
|
||||
} else {
|
||||
summary += '\n' + str("revanced_spoof_video_streams_about_kids_videos");
|
||||
}
|
||||
|
||||
setSummary(summary);
|
||||
|
||||
@@ -3,4 +3,4 @@ org.gradle.jvmargs = -Xms512M -Xmx2048M
|
||||
org.gradle.parallel = true
|
||||
android.useAndroidX = true
|
||||
kotlin.code.style = official
|
||||
version = 5.40.0-dev.10
|
||||
version = 5.38.1-dev.2
|
||||
|
||||
@@ -264,10 +264,6 @@ public final class app/revanced/patches/instagram/ads/HideAdsPatchKt {
|
||||
public static final fun getHideAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/instagram/feed/LimitFeedToFollowedProfilesKt {
|
||||
public static final fun getLimitFeedToFollowedProfiles ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/instagram/hide/explore/HideExploreFeedKt {
|
||||
public static final fun getHideExportFeedPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
@@ -280,10 +276,6 @@ public final class app/revanced/patches/instagram/hide/stories/HideStoriesKt {
|
||||
public static final fun getHideStoriesPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/instagram/misc/extension/SharedExtensionPatchKt {
|
||||
public static final fun getSharedExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/instagram/misc/signature/SignatureCheckPatchKt {
|
||||
public static final fun getSignatureCheckPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
@@ -372,18 +364,10 @@ public final class app/revanced/patches/music/interaction/permanentshuffle/Perma
|
||||
public static final fun getPermanentShufflePatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/music/layout/castbutton/HideCastButtonKt {
|
||||
public static final fun getHideCastButton ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/music/layout/compactheader/HideCategoryBarKt {
|
||||
public static final fun getHideCategoryBar ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/music/layout/navigationbar/NavigationBarPatchKt {
|
||||
public static final fun getNavigationBarPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/music/layout/premium/HideGetPremiumPatchKt {
|
||||
public static final fun getHideGetPremiumPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
@@ -401,10 +385,6 @@ public final class app/revanced/patches/music/misc/backgroundplayback/Background
|
||||
public static final fun getBackgroundPlaybackPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/music/misc/debugging/EnableDebuggingPatchKt {
|
||||
public static final fun getEnableDebuggingPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/music/misc/extension/SharedExtensionPatchKt {
|
||||
public static final fun getSharedExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
package app.revanced.patches.instagram.feed
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import app.revanced.patcher.patch.BytecodePatchContext
|
||||
|
||||
internal val mainFeedRequestClassFingerprint = fingerprint {
|
||||
strings("Request{mReason=", ", mInstanceNumber=")
|
||||
}
|
||||
|
||||
context(BytecodePatchContext)
|
||||
internal val initMainFeedRequestFingerprint get() = fingerprint {
|
||||
custom { method, classDef ->
|
||||
method.name == "<init>" &&
|
||||
classDef == mainFeedRequestClassFingerprint.classDef
|
||||
}
|
||||
}
|
||||
|
||||
internal val mainFeedHeaderMapFinderFingerprint = fingerprint {
|
||||
strings("pagination_source", "FEED_REQUEST_SENT")
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package app.revanced.patches.instagram.feed
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.instagram.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
|
||||
internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/instagram/feed/LimitFeedToFollowedProfiles;"
|
||||
|
||||
@Suppress("unused")
|
||||
val limitFeedToFollowedProfiles = bytecodePatch(
|
||||
name = "Limit feed to followed profiles",
|
||||
description = "Filters the home feed to display only content from profiles you follow.",
|
||||
use = false
|
||||
) {
|
||||
compatibleWith("com.instagram.android")
|
||||
|
||||
dependsOn(sharedExtensionPatch)
|
||||
|
||||
execute {
|
||||
/**
|
||||
* Since the header field is obfuscated and there is no easy way to identify it among all the class fields,
|
||||
* an additional method is fingerprinted.
|
||||
* This method uses the map, so we can get the field name of the map field using this.
|
||||
*/
|
||||
val mainFeedRequestHeaderFieldName: String
|
||||
|
||||
with(mainFeedHeaderMapFinderFingerprint.method) {
|
||||
mainFeedRequestHeaderFieldName = indexOfFirstInstructionOrThrow {
|
||||
getReference<FieldReference>().let { ref ->
|
||||
ref?.type == "Ljava/util/Map;" &&
|
||||
ref.definingClass == mainFeedRequestClassFingerprint.classDef.toString()
|
||||
|
||||
}
|
||||
}.let { instructionIndex ->
|
||||
getInstruction(instructionIndex).getReference<FieldReference>()!!.name
|
||||
}
|
||||
}
|
||||
|
||||
initMainFeedRequestFingerprint.method.apply {
|
||||
// Finds the instruction where the map is being initialized in the constructor
|
||||
val getHeaderIndex = indexOfFirstInstructionOrThrow {
|
||||
getReference<FieldReference>().let {
|
||||
it?.name == mainFeedRequestHeaderFieldName
|
||||
}
|
||||
}
|
||||
|
||||
val paramHeaderRegister = getInstruction<TwoRegisterInstruction>(getHeaderIndex).registerA
|
||||
|
||||
// Replace the `pagination_source` header value with `following` in the feed/timeline request.
|
||||
addInstructions(
|
||||
getHeaderIndex,
|
||||
"""
|
||||
invoke-static { v$paramHeaderRegister }, $EXTENSION_CLASS_DESCRIPTOR->setFollowingHeader(Ljava/util/Map;)Ljava/util/Map;
|
||||
move-result-object v$paramHeaderRegister
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ val hideNavigationButtonsPatch = bytecodePatch(
|
||||
val freeRegister = findFreeRegister(insertIndex, loopIndexRegister)
|
||||
val instruction = getInstruction(endIndex - 1)
|
||||
|
||||
val instructions = buildString {
|
||||
var instructions = buildString {
|
||||
if (hideCreate!!) {
|
||||
appendLine(
|
||||
"""
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package app.revanced.patches.instagram.misc.extension
|
||||
|
||||
import app.revanced.patches.instagram.misc.extension.hooks.applicationInitHook
|
||||
import app.revanced.patches.shared.misc.extension.sharedExtensionPatch
|
||||
|
||||
val sharedExtensionPatch = sharedExtensionPatch(
|
||||
"instagram",
|
||||
applicationInitHook,
|
||||
)
|
||||
@@ -1,9 +0,0 @@
|
||||
package app.revanced.patches.instagram.misc.extension.hooks
|
||||
|
||||
import app.revanced.patches.shared.misc.extension.extensionHook
|
||||
|
||||
internal val applicationInitHook = extensionHook {
|
||||
custom { method, classDef ->
|
||||
method.name == "onCreate" && classDef.endsWith("/InstagramAppShell;")
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,7 @@ val hideVideoAdsPatch = bytecodePatch(
|
||||
|
||||
compatibleWith(
|
||||
"com.google.android.apps.youtube.music"(
|
||||
"7.29.52",
|
||||
"8.10.52"
|
||||
"7.29.52"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -17,8 +17,7 @@ val enableExclusiveAudioPlaybackPatch = bytecodePatch(
|
||||
|
||||
compatibleWith(
|
||||
"com.google.android.apps.youtube.music"(
|
||||
"7.29.52",
|
||||
"8.10.52"
|
||||
"7.29.52"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -27,8 +27,7 @@ val permanentRepeatPatch = bytecodePatch(
|
||||
|
||||
compatibleWith(
|
||||
"com.google.android.apps.youtube.music"(
|
||||
"7.29.52",
|
||||
"8.10.52"
|
||||
"7.29.52"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -11,8 +11,7 @@ val permanentShufflePatch = bytecodePatch(
|
||||
) {
|
||||
compatibleWith(
|
||||
"com.google.android.apps.youtube.music"(
|
||||
"7.29.52",
|
||||
"8.10.52"
|
||||
"7.29.52"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package app.revanced.patches.music.layout.castbutton
|
||||
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import app.revanced.patcher.fingerprint
|
||||
import app.revanced.util.literal
|
||||
|
||||
internal val mediaRouteButtonFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PRIVATE, AccessFlags.FINAL)
|
||||
returns("Z")
|
||||
strings("MediaRouteButton")
|
||||
}
|
||||
|
||||
internal val playerOverlayChipFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returns("L")
|
||||
literal { playerOverlayChip }
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package app.revanced.patches.music.layout.castbutton
|
||||
|
||||
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.patch.bytecodePatch
|
||||
import app.revanced.patches.all.misc.resources.addResources
|
||||
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.music.misc.settings.PreferenceScreen
|
||||
import app.revanced.patches.music.misc.settings.settingsPatch
|
||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
internal var playerOverlayChip = -1L
|
||||
private set
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/music/patches/HideCastButtonPatch;"
|
||||
|
||||
@Suppress("unused")
|
||||
val hideCastButton = bytecodePatch(
|
||||
name = "Hide cast button",
|
||||
description = "Adds an option to hide the cast button."
|
||||
) {
|
||||
dependsOn(
|
||||
sharedExtensionPatch,
|
||||
settingsPatch,
|
||||
addResourcesPatch,
|
||||
)
|
||||
|
||||
compatibleWith(
|
||||
"com.google.android.apps.youtube.music"(
|
||||
"7.29.52",
|
||||
"8.10.52"
|
||||
)
|
||||
)
|
||||
|
||||
execute {
|
||||
addResources("music", "layout.castbutton.hideCastButton")
|
||||
|
||||
PreferenceScreen.GENERAL.addPreferences(
|
||||
SwitchPreference("revanced_music_hide_cast_button"),
|
||||
)
|
||||
|
||||
mediaRouteButtonFingerprint.classDef.apply {
|
||||
val setVisibilityMethod = methods.first { method -> method.name == "setVisibility" }
|
||||
|
||||
setVisibilityMethod.addInstructions(
|
||||
0,
|
||||
"""
|
||||
invoke-static { p1 }, $EXTENSION_CLASS_DESCRIPTOR->hideCastButton(I)I
|
||||
move-result p1
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
playerOverlayChipFingerprint.method.apply {
|
||||
val resourceIndex = indexOfFirstLiteralInstructionOrThrow(playerOverlayChip)
|
||||
val targetIndex = indexOfFirstInstructionOrThrow(resourceIndex, Opcode.MOVE_RESULT)
|
||||
val targetRegister = getInstruction<OneRegisterInstruction>(targetIndex).registerA
|
||||
|
||||
addInstruction(
|
||||
targetIndex + 1,
|
||||
"invoke-static { v$targetRegister }, $EXTENSION_CLASS_DESCRIPTOR->hideCastButton(Landroid/view/View;)V"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package app.revanced.patches.music.layout.compactheader
|
||||
|
||||
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
|
||||
@@ -9,6 +10,7 @@ import app.revanced.patches.music.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.music.misc.settings.PreferenceScreen
|
||||
import app.revanced.patches.music.misc.settings.settingsPatch
|
||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.util.addInstructionsAtControlFlowLabel
|
||||
import app.revanced.util.findFreeRegister
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
@@ -27,8 +29,7 @@ val hideCategoryBar = bytecodePatch(
|
||||
|
||||
compatibleWith(
|
||||
"com.google.android.apps.youtube.music"(
|
||||
"7.29.52",
|
||||
"8.10.52"
|
||||
"7.29.52"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
package app.revanced.patches.music.layout.navigationbar
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import app.revanced.util.containsLiteralInstruction
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstruction
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.Method
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
internal val tabLayoutTextFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returns("V")
|
||||
parameters("L")
|
||||
opcodes(
|
||||
Opcode.IGET,
|
||||
Opcode.INVOKE_STATIC,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.IF_NEZ,
|
||||
Opcode.SGET_OBJECT,
|
||||
Opcode.INVOKE_INTERFACE,
|
||||
Opcode.MOVE_RESULT
|
||||
)
|
||||
strings("FEmusic_search")
|
||||
custom { method, _ ->
|
||||
method.containsLiteralInstruction(text1)
|
||||
&& indexOfGetVisibilityInstruction(method) >= 0
|
||||
}
|
||||
}
|
||||
|
||||
internal fun indexOfGetVisibilityInstruction(method: Method) =
|
||||
method.indexOfFirstInstruction {
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
getReference<MethodReference>()?.name == "getVisibility"
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
package app.revanced.patches.music.layout.navigationbar
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.PatchException
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.all.misc.resources.addResources
|
||||
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.music.misc.settings.PreferenceScreen
|
||||
import app.revanced.patches.music.misc.settings.settingsPatch
|
||||
import app.revanced.patches.shared.misc.mapping.get
|
||||
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
|
||||
import app.revanced.patches.shared.misc.mapping.resourceMappings
|
||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
|
||||
internal var text1 = -1L
|
||||
private set
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/music/patches/NavigationBarPatch;"
|
||||
|
||||
@Suppress("unused")
|
||||
val navigationBarPatch = bytecodePatch(
|
||||
name = "Navigation bar",
|
||||
description = "Adds options to hide navigation bar, labels and buttons."
|
||||
) {
|
||||
dependsOn(
|
||||
resourceMappingPatch,
|
||||
sharedExtensionPatch,
|
||||
settingsPatch,
|
||||
addResourcesPatch
|
||||
)
|
||||
|
||||
compatibleWith(
|
||||
"com.google.android.apps.youtube.music"(
|
||||
"7.29.52",
|
||||
"8.10.52"
|
||||
)
|
||||
)
|
||||
|
||||
execute {
|
||||
text1 = resourceMappings[
|
||||
"id",
|
||||
"text1",
|
||||
]
|
||||
|
||||
addResources("music", "layout.navigationbar.navigationBarPatch")
|
||||
|
||||
PreferenceScreen.GENERAL.addPreferences(
|
||||
PreferenceScreenPreference(
|
||||
key = "revanced_music_navigation_bar_screen",
|
||||
sorting = PreferenceScreenPreference.Sorting.UNSORTED,
|
||||
preferences = setOf(
|
||||
SwitchPreference("revanced_music_hide_navigation_bar_home_button"),
|
||||
SwitchPreference("revanced_music_hide_navigation_bar_samples_button"),
|
||||
SwitchPreference("revanced_music_hide_navigation_bar_explore_button"),
|
||||
SwitchPreference("revanced_music_hide_navigation_bar_library_button"),
|
||||
SwitchPreference("revanced_music_hide_navigation_bar_upgrade_button"),
|
||||
|
||||
SwitchPreference("revanced_music_hide_navigation_bar"),
|
||||
SwitchPreference("revanced_music_hide_navigation_bar_labels"),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
tabLayoutTextFingerprint.method.apply {
|
||||
/**
|
||||
* Hide navigation labels.
|
||||
*/
|
||||
val constIndex = indexOfFirstLiteralInstructionOrThrow(text1)
|
||||
val targetIndex = indexOfFirstInstructionOrThrow(constIndex, Opcode.CHECK_CAST)
|
||||
val targetParameter = getInstruction<ReferenceInstruction>(targetIndex).reference
|
||||
val targetRegister = getInstruction<OneRegisterInstruction>(targetIndex).registerA
|
||||
|
||||
if (!targetParameter.toString().endsWith("Landroid/widget/TextView;"))
|
||||
throw PatchException("Method signature parameter did not match: $targetParameter")
|
||||
|
||||
addInstruction(
|
||||
targetIndex + 1,
|
||||
"invoke-static { v$targetRegister }, $EXTENSION_CLASS_DESCRIPTOR->hideNavigationLabel(Landroid/widget/TextView;)V"
|
||||
)
|
||||
|
||||
/**
|
||||
* Set navigation enum and hide navigation buttons.
|
||||
*/
|
||||
val enumIndex = tabLayoutTextFingerprint.patternMatch!!.startIndex + 3
|
||||
val enumRegister = getInstruction<OneRegisterInstruction>(enumIndex).registerA
|
||||
val insertEnumIndex = indexOfFirstInstructionOrThrow(Opcode.AND_INT_LIT8) - 2
|
||||
|
||||
val pivotTabIndex = indexOfGetVisibilityInstruction(this)
|
||||
val pivotTabRegister = getInstruction<FiveRegisterInstruction>(pivotTabIndex).registerC
|
||||
|
||||
addInstruction(
|
||||
pivotTabIndex,
|
||||
"invoke-static { v$pivotTabRegister }, $EXTENSION_CLASS_DESCRIPTOR->hideNavigationButton(Landroid/view/View;)V"
|
||||
)
|
||||
|
||||
addInstruction(
|
||||
insertEnumIndex,
|
||||
"invoke-static { v$enumRegister }, $EXTENSION_CLASS_DESCRIPTOR->setLastAppNavigationEnum(Ljava/lang/Enum;)V"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,8 +28,7 @@ val hideGetPremiumPatch = bytecodePatch(
|
||||
|
||||
compatibleWith(
|
||||
"com.google.android.apps.youtube.music"(
|
||||
"7.29.52",
|
||||
"8.10.52"
|
||||
"7.29.52"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package app.revanced.patches.music.layout.upgradebutton
|
||||
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val pivotBarConstructorFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
|
||||
returns("V")
|
||||
parameters("L", "Z")
|
||||
opcodes(
|
||||
Opcode.CHECK_CAST,
|
||||
Opcode.INVOKE_INTERFACE,
|
||||
Opcode.GOTO,
|
||||
Opcode.IPUT_OBJECT,
|
||||
Opcode.RETURN_VOID
|
||||
)
|
||||
}
|
||||
@@ -1,12 +1,104 @@
|
||||
package app.revanced.patches.music.layout.upgradebutton
|
||||
|
||||
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.replaceInstruction
|
||||
import app.revanced.patcher.extensions.newLabel
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.music.layout.navigationbar.navigationBarPatch
|
||||
import app.revanced.patcher.util.smali.toInstructions
|
||||
import app.revanced.patches.all.misc.resources.addResources
|
||||
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.music.misc.settings.PreferenceScreen
|
||||
import app.revanced.patches.music.misc.settings.settingsPatch
|
||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.util.getReference
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction22t
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/music/patches/HideUpgradeButtonPatch;"
|
||||
|
||||
@Deprecated("Patch is obsolete and was replaced by navigation bar patch", ReplaceWith("navigationBarPatch"))
|
||||
@Suppress("unused")
|
||||
val hideUpgradeButton = bytecodePatch{
|
||||
dependsOn(navigationBarPatch)
|
||||
val hideUpgradeButton = bytecodePatch(
|
||||
name = "Hide upgrade button",
|
||||
description = "Hides the upgrade tab from the pivot bar.",
|
||||
) {
|
||||
dependsOn(
|
||||
sharedExtensionPatch,
|
||||
settingsPatch,
|
||||
addResourcesPatch,
|
||||
)
|
||||
|
||||
compatibleWith(
|
||||
"com.google.android.apps.youtube.music"(
|
||||
"7.29.52"
|
||||
)
|
||||
)
|
||||
|
||||
execute {
|
||||
addResources("music", "layout.upgradebutton.hideUpgradeButtonPatch")
|
||||
|
||||
// TODO: Add an extension patch to allow this to be enabled/disabled in app.
|
||||
if (false) {
|
||||
PreferenceScreen.ADS.addPreferences(
|
||||
SwitchPreference("revanced_music_hide_upgrade_button")
|
||||
)
|
||||
}
|
||||
|
||||
pivotBarConstructorFingerprint.method.apply {
|
||||
val pivotBarElementFieldReference =
|
||||
getInstruction(pivotBarConstructorFingerprint.patternMatch!!.endIndex - 1)
|
||||
.getReference<FieldReference>()
|
||||
|
||||
val register = getInstruction<FiveRegisterInstruction>(0).registerC
|
||||
|
||||
// First compile all the needed instructions.
|
||||
val instructionList = """
|
||||
invoke-interface { v0 }, Ljava/util/List;->size()I
|
||||
move-result v1
|
||||
const/4 v2, 0x4
|
||||
invoke-interface {v0, v2}, Ljava/util/List;->remove(I)Ljava/lang/Object;
|
||||
iput-object v0, v$register, $pivotBarElementFieldReference
|
||||
""".toInstructions().toMutableList()
|
||||
|
||||
val endIndex = pivotBarConstructorFingerprint.patternMatch!!.endIndex
|
||||
|
||||
// Replace the instruction to retain the label at given index.
|
||||
replaceInstruction(
|
||||
endIndex - 1,
|
||||
instructionList[0], // invoke-interface.
|
||||
)
|
||||
// Do not forget to remove this instruction since we added it already.
|
||||
instructionList.removeFirst()
|
||||
|
||||
val exitInstruction = instructionList.last() // iput-object
|
||||
addInstruction(
|
||||
endIndex,
|
||||
exitInstruction,
|
||||
)
|
||||
// Do not forget to remove this instruction since we added it already.
|
||||
instructionList.removeLast()
|
||||
|
||||
// Add the necessary if statement to remove the upgrade tab button in case it exists.
|
||||
instructionList.add(
|
||||
2, // if-le.
|
||||
BuilderInstruction22t(
|
||||
Opcode.IF_LE,
|
||||
1,
|
||||
2,
|
||||
newLabel(endIndex),
|
||||
),
|
||||
)
|
||||
|
||||
addInstructions(
|
||||
endIndex,
|
||||
instructionList,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Patch was renamed", ReplaceWith("hideUpgradeButton"))
|
||||
|
||||
@@ -17,8 +17,7 @@ val bypassCertificateChecksPatch = bytecodePatch(
|
||||
|
||||
compatibleWith(
|
||||
"com.google.android.apps.youtube.music"(
|
||||
"7.29.52",
|
||||
"8.10.52"
|
||||
"7.29.52"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -17,8 +17,7 @@ val backgroundPlaybackPatch = bytecodePatch(
|
||||
|
||||
compatibleWith(
|
||||
"com.google.android.apps.youtube.music"(
|
||||
"7.29.52",
|
||||
"8.10.52"
|
||||
"7.29.52"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
package app.revanced.patches.music.misc.debugging
|
||||
|
||||
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.music.misc.settings.PreferenceScreen
|
||||
import app.revanced.patches.music.misc.settings.settingsPatch
|
||||
import app.revanced.patches.shared.misc.debugging.enableDebuggingPatch
|
||||
|
||||
@Suppress("unused")
|
||||
val enableDebuggingPatch = enableDebuggingPatch(
|
||||
block = {
|
||||
dependsOn(
|
||||
sharedExtensionPatch,
|
||||
settingsPatch,
|
||||
)
|
||||
|
||||
compatibleWith(
|
||||
"com.google.android.apps.youtube.music"(
|
||||
"7.29.52"
|
||||
)
|
||||
)
|
||||
},
|
||||
// String feature flag does not appear to be present with YT Music.
|
||||
hookStringFeatureFlag = false,
|
||||
preferenceScreen = PreferenceScreen.MISC
|
||||
)
|
||||
@@ -8,16 +8,12 @@ import app.revanced.patches.all.misc.resources.addResources
|
||||
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
|
||||
import app.revanced.patches.shared.misc.settings.preference.BasePreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.BasePreferenceScreen
|
||||
import app.revanced.patches.shared.misc.settings.preference.IntentPreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.NonInteractivePreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.*
|
||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting
|
||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.patches.shared.misc.settings.settingsPatch
|
||||
import app.revanced.util.ResourceGroup
|
||||
import app.revanced.util.copyResources
|
||||
import app.revanced.util.copyXmlNode
|
||||
import app.revanced.util.inputStreamFromBundledResource
|
||||
import app.revanced.util.*
|
||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||
|
||||
private const val BASE_ACTIVITY_HOOK_CLASS_DESCRIPTOR =
|
||||
@@ -27,6 +23,7 @@ private const val GOOGLE_API_ACTIVITY_HOOK_CLASS_DESCRIPTOR =
|
||||
|
||||
private val preferences = mutableSetOf<BasePreference>()
|
||||
|
||||
|
||||
private val settingsResourcePatch = resourcePatch {
|
||||
dependsOn(
|
||||
resourceMappingPatch,
|
||||
@@ -90,6 +87,27 @@ val settingsPatch = bytecodePatch(
|
||||
addResources("music", "misc.settings.settingsPatch")
|
||||
addResources("shared", "misc.debugging.enableDebuggingPatch")
|
||||
|
||||
// Should make a separate debugging patch, but for now include it with all installations.
|
||||
PreferenceScreen.MISC.addPreferences(
|
||||
PreferenceScreenPreference(
|
||||
key = "revanced_debug_screen",
|
||||
sorting = Sorting.UNSORTED,
|
||||
preferences = setOf(
|
||||
SwitchPreference("revanced_debug"),
|
||||
NonInteractivePreference(
|
||||
"revanced_debug_export_logs_to_clipboard",
|
||||
tag = "app.revanced.extension.shared.settings.preference.ExportLogToClipboardPreference",
|
||||
selectable = true
|
||||
),
|
||||
NonInteractivePreference(
|
||||
"revanced_debug_logs_clear_buffer",
|
||||
tag = "app.revanced.extension.shared.settings.preference.ClearLogBufferPreference",
|
||||
selectable = true
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
// Add an "About" preference to the top.
|
||||
preferences += NonInteractivePreference(
|
||||
key = "revanced_settings_music_screen_0_about",
|
||||
@@ -136,19 +154,19 @@ fun newIntent(settingsName: String) = IntentPreference.Intent(
|
||||
|
||||
object PreferenceScreen : BasePreferenceScreen() {
|
||||
val ADS = Screen(
|
||||
key = "revanced_settings_music_screen_1_ads",
|
||||
"revanced_settings_music_screen_1_ads",
|
||||
summaryKey = null
|
||||
)
|
||||
val GENERAL = Screen(
|
||||
key = "revanced_settings_music_screen_2_general",
|
||||
"revanced_settings_music_screen_2_general",
|
||||
summaryKey = null
|
||||
)
|
||||
val PLAYER = Screen(
|
||||
key = "revanced_settings_music_screen_3_player",
|
||||
"revanced_settings_music_screen_3_player",
|
||||
summaryKey = null
|
||||
)
|
||||
val MISC = Screen(
|
||||
key = "revanced_settings_music_screen_4_misc",
|
||||
"revanced_settings_music_screen_4_misc",
|
||||
summaryKey = null
|
||||
)
|
||||
|
||||
|
||||
@@ -33,8 +33,7 @@ val spoofVideoStreamsPatch = spoofVideoStreamsPatch(
|
||||
|
||||
compatibleWith(
|
||||
"com.google.android.apps.youtube.music"(
|
||||
"7.29.52",
|
||||
"8.10.52"
|
||||
"7.29.52"
|
||||
)
|
||||
)
|
||||
},
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
package app.revanced.patches.shared.misc.debugging
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.BytecodePatchBuilder
|
||||
import app.revanced.patcher.patch.BytecodePatchContext
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.all.misc.resources.addResources
|
||||
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||
import app.revanced.patches.shared.misc.settings.preference.BasePreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.BasePreferenceScreen
|
||||
import app.revanced.patches.shared.misc.settings.preference.NonInteractivePreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting
|
||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.util.findInstructionIndicesReversedOrThrow
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/shared/patches/EnableDebuggingPatch;"
|
||||
|
||||
/**
|
||||
* Patch shared with YouTube and YT Music.
|
||||
*/
|
||||
internal fun enableDebuggingPatch(
|
||||
block: BytecodePatchBuilder.() -> Unit = {},
|
||||
executeBlock: BytecodePatchContext.() -> Unit = {},
|
||||
hookStringFeatureFlag: Boolean,
|
||||
preferenceScreen: BasePreferenceScreen.Screen,
|
||||
additionalDebugPreferences: List<BasePreference> = emptyList()
|
||||
) = bytecodePatch(
|
||||
name = "Enable debugging",
|
||||
description = "Adds options for debugging and exporting ReVanced logs to the clipboard.",
|
||||
) {
|
||||
|
||||
dependsOn(addResourcesPatch)
|
||||
|
||||
block()
|
||||
|
||||
execute {
|
||||
executeBlock()
|
||||
|
||||
addResources("shared", "misc.debugging.enableDebuggingPatch")
|
||||
|
||||
val preferences = mutableSetOf<BasePreference>(
|
||||
SwitchPreference("revanced_debug"),
|
||||
)
|
||||
|
||||
preferences.addAll(additionalDebugPreferences)
|
||||
|
||||
preferences.addAll(
|
||||
listOf(
|
||||
SwitchPreference("revanced_debug_stacktrace"),
|
||||
SwitchPreference("revanced_debug_toast_on_error"),
|
||||
NonInteractivePreference(
|
||||
"revanced_debug_export_logs_to_clipboard",
|
||||
tag = "app.revanced.extension.shared.settings.preference.ExportLogToClipboardPreference",
|
||||
selectable = true
|
||||
),
|
||||
NonInteractivePreference(
|
||||
"revanced_debug_logs_clear_buffer",
|
||||
tag = "app.revanced.extension.shared.settings.preference.ClearLogBufferPreference",
|
||||
selectable = true
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
preferenceScreen.addPreferences(
|
||||
PreferenceScreenPreference(
|
||||
key = "revanced_debug_screen",
|
||||
sorting = Sorting.UNSORTED,
|
||||
preferences = preferences,
|
||||
)
|
||||
)
|
||||
|
||||
// Hook the methods that look up if a feature flag is active.
|
||||
experimentalBooleanFeatureFlagFingerprint.match(
|
||||
experimentalFeatureFlagParentFingerprint.originalClassDef
|
||||
).method.apply {
|
||||
findInstructionIndicesReversedOrThrow(Opcode.RETURN).forEach { index ->
|
||||
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
||||
|
||||
addInstructions(
|
||||
index,
|
||||
"""
|
||||
invoke-static { v$register, p1 }, $EXTENSION_CLASS_DESCRIPTOR->isBooleanFeatureFlagEnabled(ZLjava/lang/Long;)Z
|
||||
move-result v$register
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
experimentalDoubleFeatureFlagFingerprint.match(
|
||||
experimentalFeatureFlagParentFingerprint.originalClassDef
|
||||
).method.apply {
|
||||
val insertIndex = indexOfFirstInstructionOrThrow(Opcode.MOVE_RESULT_WIDE)
|
||||
|
||||
addInstructions(
|
||||
insertIndex,
|
||||
"""
|
||||
move-result-wide v0 # Also clobbers v1 (p0) since result is wide.
|
||||
invoke-static/range { v0 .. v5 }, $EXTENSION_CLASS_DESCRIPTOR->isDoubleFeatureFlagEnabled(DJD)D
|
||||
move-result-wide v0
|
||||
return-wide v0
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
experimentalLongFeatureFlagFingerprint.match(
|
||||
experimentalFeatureFlagParentFingerprint.originalClassDef
|
||||
).method.apply {
|
||||
val insertIndex = indexOfFirstInstructionOrThrow(Opcode.MOVE_RESULT_WIDE)
|
||||
|
||||
addInstructions(
|
||||
insertIndex,
|
||||
"""
|
||||
move-result-wide v0
|
||||
invoke-static/range { v0 .. v5 }, $EXTENSION_CLASS_DESCRIPTOR->isLongFeatureFlagEnabled(JJJ)J
|
||||
move-result-wide v0
|
||||
return-wide v0
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
if (hookStringFeatureFlag) experimentalStringFeatureFlagFingerprint.match(
|
||||
experimentalFeatureFlagParentFingerprint.originalClassDef
|
||||
).method.apply {
|
||||
val insertIndex = indexOfFirstInstructionReversedOrThrow(Opcode.MOVE_RESULT_OBJECT)
|
||||
|
||||
addInstructions(
|
||||
insertIndex,
|
||||
"""
|
||||
move-result-object v0
|
||||
invoke-static { v0, p1, p2, p3 }, $EXTENSION_CLASS_DESCRIPTOR->isStringFeatureFlagEnabled(Ljava/lang/String;JLjava/lang/String;)Ljava/lang/String;
|
||||
move-result-object v0
|
||||
return-object v0
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
// There exists other experimental accessor methods for byte[]
|
||||
// and wrappers for obfuscated classes, but currently none of those are hooked.
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package app.revanced.patches.shared.misc.debugging
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
internal val experimentalFeatureFlagParentFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
||||
returns("L")
|
||||
parameters("L", "J", "[B")
|
||||
strings("Unable to parse proto typed experiment flag: ")
|
||||
}
|
||||
|
||||
internal val experimentalBooleanFeatureFlagFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
||||
returns("Z")
|
||||
parameters("L", "J", "Z")
|
||||
}
|
||||
|
||||
internal val experimentalDoubleFeatureFlagFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returns("D")
|
||||
parameters("J", "D")
|
||||
}
|
||||
|
||||
internal val experimentalLongFeatureFlagFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returns("J")
|
||||
parameters("J", "J")
|
||||
}
|
||||
|
||||
internal val experimentalStringFeatureFlagFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returns("Ljava/lang/String;")
|
||||
parameters("J", "Ljava/lang/String;")
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import app.revanced.util.indexOfFirstInstruction
|
||||
import app.revanced.util.literal
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.Method
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
internal val buildInitPlaybackRequestFingerprint = fingerprint {
|
||||
@@ -41,6 +40,13 @@ internal val buildRequestFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
||||
returns("Lorg/chromium/net/UrlRequest") // UrlRequest; or UrlRequest$Builder;
|
||||
custom { methodDef, _ ->
|
||||
if (methodDef.indexOfFirstInstruction {
|
||||
val reference = getReference<MethodReference>()
|
||||
reference?.name == "newUrlRequestBuilder"
|
||||
} < 0) {
|
||||
return@custom false
|
||||
}
|
||||
|
||||
// Different targets have slightly different parameters
|
||||
|
||||
// Earlier targets have parameters:
|
||||
@@ -74,10 +80,10 @@ internal val buildRequestFingerprint = fingerprint {
|
||||
val parameterTypesSize = parameterTypes.size
|
||||
(parameterTypesSize == 6 || parameterTypesSize == 7 || parameterTypesSize == 8) &&
|
||||
parameterTypes[1] == "Ljava/util/Map;" // URL headers.
|
||||
&& indexOfNewUrlRequestBuilderInstruction(methodDef) >= 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal val protobufClassParseByteBufferFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PROTECTED, AccessFlags.STATIC)
|
||||
returns("L")
|
||||
@@ -136,17 +142,6 @@ internal val hlsCurrentTimeFingerprint = fingerprint {
|
||||
}
|
||||
}
|
||||
|
||||
internal const val DISABLED_BY_SABR_STREAMING_URI_STRING = "DISABLED_BY_SABR_STREAMING_URI"
|
||||
|
||||
internal val mediaFetchEnumConstructorFingerprint = fingerprint {
|
||||
returns("V")
|
||||
strings(
|
||||
"ENABLED",
|
||||
"DISABLED_FOR_PLAYBACK",
|
||||
DISABLED_BY_SABR_STREAMING_URI_STRING
|
||||
)
|
||||
}
|
||||
|
||||
internal val nerdsStatsVideoFormatBuilderFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
||||
returns("Ljava/lang/String;")
|
||||
@@ -155,6 +150,7 @@ internal val nerdsStatsVideoFormatBuilderFingerprint = fingerprint {
|
||||
}
|
||||
|
||||
internal val patchIncludedExtensionMethodFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC)
|
||||
returns("Z")
|
||||
parameters()
|
||||
custom { method, classDef ->
|
||||
@@ -191,13 +187,3 @@ internal val playbackStartDescriptorFeatureFlagFingerprint = fingerprint {
|
||||
returns("Z")
|
||||
literal { PLAYBACK_START_CHECK_ENDPOINT_USED_FEATURE_FLAG }
|
||||
}
|
||||
|
||||
internal fun indexOfNewUrlRequestBuilderInstruction(method: Method) = method.indexOfFirstInstruction {
|
||||
val reference = getReference<MethodReference>()
|
||||
opcode == Opcode.INVOKE_VIRTUAL && reference?.definingClass == "Lorg/chromium/net/CronetEngine;"
|
||||
&& reference.name == "newUrlRequestBuilder"
|
||||
&& reference.parameterTypes.size == 3
|
||||
&& reference.parameterTypes[0] == "Ljava/lang/String;"
|
||||
&& reference.parameterTypes[1] == "Lorg/chromium/net/UrlRequest\$Callback;"
|
||||
&& reference.parameterTypes[2] == "Ljava/util/concurrent/Executor;"
|
||||
}
|
||||
|
||||
@@ -5,11 +5,9 @@ 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.fingerprint
|
||||
import app.revanced.patcher.patch.BytecodePatchBuilder
|
||||
import app.revanced.patcher.patch.BytecodePatchContext
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
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
|
||||
@@ -24,18 +22,15 @@ import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
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/shared/spoof/SpoofVideoStreamsPatch;"
|
||||
|
||||
private lateinit var buildRequestMethod: MutableMethod
|
||||
private var buildRequestMethodUrlRegister = -1
|
||||
|
||||
fun spoofVideoStreamsPatch(
|
||||
block: BytecodePatchBuilder.() -> Unit = {},
|
||||
fixMediaFetchHotConfigChanges: BytecodePatchBuilder.() -> Boolean = { false },
|
||||
@@ -96,17 +91,18 @@ fun spoofVideoStreamsPatch(
|
||||
// region Get replacement streams at player requests.
|
||||
|
||||
buildRequestFingerprint.method.apply {
|
||||
buildRequestMethod = this
|
||||
|
||||
val newRequestBuilderIndex = indexOfNewUrlRequestBuilderInstruction(this)
|
||||
buildRequestMethodUrlRegister = getInstruction<FiveRegisterInstruction>(newRequestBuilderIndex).registerD
|
||||
val freeRegister = findFreeRegister(newRequestBuilderIndex, buildRequestMethodUrlRegister)
|
||||
val newRequestBuilderIndex = indexOfFirstInstructionOrThrow {
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
getReference<MethodReference>()?.name == "newUrlRequestBuilder"
|
||||
}
|
||||
val urlRegister = getInstruction<FiveRegisterInstruction>(newRequestBuilderIndex).registerD
|
||||
val freeRegister = findFreeRegister(newRequestBuilderIndex, urlRegister)
|
||||
|
||||
addInstructions(
|
||||
newRequestBuilderIndex,
|
||||
"""
|
||||
move-object v$freeRegister, p1
|
||||
invoke-static { v$buildRequestMethodUrlRegister, v$freeRegister }, $EXTENSION_CLASS_DESCRIPTOR->fetchStreams(Ljava/lang/String;Ljava/util/Map;)V
|
||||
invoke-static { v$urlRegister, v$freeRegister }, $EXTENSION_CLASS_DESCRIPTOR->fetchStreams(Ljava/lang/String;Ljava/util/Map;)V
|
||||
"""
|
||||
)
|
||||
}
|
||||
@@ -191,21 +187,6 @@ fun spoofVideoStreamsPatch(
|
||||
|
||||
// endregion
|
||||
|
||||
// region block getAtt request
|
||||
|
||||
buildRequestMethod.apply {
|
||||
val insertIndex = indexOfNewUrlRequestBuilderInstruction(this)
|
||||
|
||||
addInstructions(
|
||||
insertIndex, """
|
||||
invoke-static { v$buildRequestMethodUrlRegister }, $EXTENSION_CLASS_DESCRIPTOR->blockGetAttRequest(Ljava/lang/String;)Ljava/lang/String;
|
||||
move-result-object v$buildRequestMethodUrlRegister
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Remove /videoplayback request body to fix playback.
|
||||
// It is assumed, YouTube makes a request with a body tuned for Android.
|
||||
// Requesting streams intended for other platforms with a body tuned for Android could be the cause of 400 errors.
|
||||
@@ -262,50 +243,6 @@ fun spoofVideoStreamsPatch(
|
||||
|
||||
// endregion
|
||||
|
||||
// region Disable SABR playback.
|
||||
// If SABR is disabled, it seems 'MediaFetchHotConfig' may no longer need an override (not confirmed).
|
||||
|
||||
val (mediaFetchEnumClass, sabrFieldReference) = with(mediaFetchEnumConstructorFingerprint.method) {
|
||||
val stringIndex = mediaFetchEnumConstructorFingerprint.stringMatches!!.first {
|
||||
it.string == DISABLED_BY_SABR_STREAMING_URI_STRING
|
||||
}.index
|
||||
|
||||
val mediaFetchEnumClass = definingClass
|
||||
val sabrFieldIndex = indexOfFirstInstructionOrThrow(stringIndex) {
|
||||
opcode == Opcode.SPUT_OBJECT &&
|
||||
getReference<FieldReference>()?.type == mediaFetchEnumClass
|
||||
}
|
||||
|
||||
Pair(
|
||||
mediaFetchEnumClass,
|
||||
getInstruction<ReferenceInstruction>(sabrFieldIndex).reference
|
||||
)
|
||||
}
|
||||
|
||||
fingerprint {
|
||||
returns(mediaFetchEnumClass)
|
||||
opcodes(
|
||||
Opcode.SGET_OBJECT,
|
||||
Opcode.RETURN_OBJECT,
|
||||
)
|
||||
custom { method, _ ->
|
||||
!method.parameterTypes.isEmpty()
|
||||
}
|
||||
}.method.addInstructionsWithLabels(
|
||||
0,
|
||||
"""
|
||||
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->disableSABR()Z
|
||||
move-result v0
|
||||
if-eqz v0, :ignore
|
||||
sget-object v0, $sabrFieldReference
|
||||
return-object v0
|
||||
:ignore
|
||||
nop
|
||||
"""
|
||||
)
|
||||
|
||||
// endregion
|
||||
|
||||
// region turn off stream config replacement feature flag.
|
||||
|
||||
if (fixMediaFetchHotConfigChanges()) {
|
||||
|
||||
@@ -2,6 +2,12 @@ package app.revanced.patches.viber.ads
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val findAdStringFingerprint = fingerprint {
|
||||
strings("viber_plus_debug_ads_free_flag")
|
||||
internal val adsFreeFingerprint = fingerprint {
|
||||
returns("I")
|
||||
parameters()
|
||||
custom { method, classDef ->
|
||||
classDef.type.contains("com/viber/voip/feature/viberplus") &&
|
||||
classDef.superclass?.contains("com/viber/voip/core/feature") == true && // Must extend com.viber.voip.core.feature.?
|
||||
classDef.methods.count() == 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,17 @@
|
||||
package app.revanced.patches.viber.ads
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.fingerprint
|
||||
import app.revanced.patcher.patch.PatchException
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
|
||||
import app.revanced.util.returnEarly
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
|
||||
|
||||
@Suppress("unused")
|
||||
val hideAdsPatch = bytecodePatch(
|
||||
name = "Hide Ads",
|
||||
description = "Hides ad banners between chats.",
|
||||
) {
|
||||
compatibleWith("com.viber.voip"("25.9.2.0", "26.1.2.0"))
|
||||
compatibleWith("com.viber.voip"("25.9.2.0"))
|
||||
|
||||
execute {
|
||||
val method = findAdStringFingerprint.method
|
||||
|
||||
// Find the ads free string index
|
||||
val stringIndex = findAdStringFingerprint.stringMatches!!.first().index
|
||||
|
||||
// Search backwards from the string to find the `new-instance` (TypeReference) instruction
|
||||
val typeRefIndex = method.indexOfFirstInstructionReversedOrThrow(stringIndex) { this.opcode == Opcode.NEW_INSTANCE }
|
||||
|
||||
// Get the class name from the TypeReference
|
||||
val targetClass = method.getInstruction<ReferenceInstruction>(typeRefIndex).reference as TypeReference
|
||||
|
||||
// Patch the ads-free method to always return true
|
||||
fingerprint {
|
||||
returns("I")
|
||||
parameters()
|
||||
custom { method, classDef ->
|
||||
classDef == targetClass
|
||||
}
|
||||
}.method.returnEarly(1)
|
||||
// Return 1 (true) indicating ads should be disabled.
|
||||
adsFreeFingerprint.method.returnEarly(1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,9 +78,10 @@ val hideAdsPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -27,9 +27,10 @@ val hideGetPremiumPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -25,9 +25,10 @@ val videoAdsPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -55,9 +55,10 @@ val copyVideoUrlPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -26,9 +26,10 @@ val removeViewerDiscretionDialogPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -29,8 +29,8 @@ val disableDoubleTapActionsPatch = bytecodePatch(
|
||||
compatibleWith(
|
||||
"com.google.android.youtube"(
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -76,9 +76,10 @@ val downloadsPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -22,9 +22,10 @@ val seekbarPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -90,9 +90,10 @@ val swipeControlsPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -26,9 +26,10 @@ val autoCaptionsPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -45,9 +45,10 @@ val customBrandingPatch = resourcePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -95,9 +95,10 @@ val changeHeaderPatch = resourcePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -24,9 +24,10 @@ val hideButtonsPatch = resourcePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
@@ -40,7 +41,6 @@ val hideButtonsPatch = resourcePatch(
|
||||
SwitchPreference("revanced_disable_like_subscribe_glow"),
|
||||
SwitchPreference("revanced_hide_ask_button"),
|
||||
SwitchPreference("revanced_hide_clip_button"),
|
||||
SwitchPreference("revanced_hide_comments_button"),
|
||||
SwitchPreference("revanced_hide_download_button"),
|
||||
SwitchPreference("revanced_hide_hype_button"),
|
||||
SwitchPreference("revanced_hide_like_dislike_button"),
|
||||
@@ -49,7 +49,6 @@ val hideButtonsPatch = resourcePatch(
|
||||
SwitchPreference("revanced_hide_report_button"),
|
||||
SwitchPreference("revanced_hide_save_button"),
|
||||
SwitchPreference("revanced_hide_share_button"),
|
||||
SwitchPreference("revanced_hide_shop_button"),
|
||||
SwitchPreference("revanced_hide_stop_ads_button"),
|
||||
SwitchPreference("revanced_hide_thanks_button"),
|
||||
)
|
||||
|
||||
@@ -42,9 +42,10 @@ val navigationButtonsPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -60,9 +60,10 @@ val hidePlayerOverlayButtonsPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -35,9 +35,10 @@ val changeFormFactorPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -61,9 +61,10 @@ val hideEndscreenCardsPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -33,9 +33,10 @@ val hideEndScreenSuggestedVideoPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -31,9 +31,10 @@ val disableFullscreenAmbientModePatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -128,9 +128,10 @@ val hideLayoutComponentsPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -59,9 +59,10 @@ val hideInfoCardsPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -26,9 +26,10 @@ val hidePlayerFlyoutMenuPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -50,9 +50,10 @@ val hideRelatedVideoOverlayPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -31,9 +31,10 @@ val disableRollingNumberAnimationPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -174,9 +174,10 @@ val hideShortsComponentsPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -33,9 +33,10 @@ val disableSignInToTvPopupPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -23,9 +23,10 @@ val hideTimestampPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -155,9 +155,10 @@ val miniplayerPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -23,9 +23,10 @@ val playerPopupPanelsPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -23,9 +23,10 @@ internal val exitFullscreenPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -25,9 +25,10 @@ val openVideosFullscreenPatch = bytecodePatch(
|
||||
|
||||
compatibleWith(
|
||||
"com.google.android.youtube"(
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -54,9 +54,10 @@ val customPlayerOverlayOpacityPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -63,9 +63,10 @@ val returnYouTubeDislikePatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -68,9 +68,10 @@ val wideSearchbarPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -46,9 +46,10 @@ val shortsAutoplayPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -66,9 +66,10 @@ val openShortsInRegularPlayerPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -128,9 +128,10 @@ val sponsorBlockPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPref
|
||||
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_43_or_greater
|
||||
import app.revanced.patches.youtube.misc.playservice.is_20_14_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
|
||||
@@ -61,9 +60,10 @@ val spoofAppVersionPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
@@ -79,21 +79,17 @@ val spoofAppVersionPatch = bytecodePatch(
|
||||
tag = "app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory",
|
||||
preferences = setOf(
|
||||
SwitchPreference("revanced_spoof_app_version"),
|
||||
if (is_20_14_or_greater) {
|
||||
ListPreference("revanced_spoof_app_version_target")
|
||||
} else if (is_19_43_or_greater) {
|
||||
if (is_19_43_or_greater) {
|
||||
ListPreference(
|
||||
key = "revanced_spoof_app_version_target",
|
||||
summaryKey = null,
|
||||
entriesKey = "revanced_spoof_app_version_target_legacy_20_13_entries",
|
||||
entryValuesKey = "revanced_spoof_app_version_target_legacy_20_13_entry_values"
|
||||
summaryKey = null
|
||||
)
|
||||
} else {
|
||||
ListPreference(
|
||||
key = "revanced_spoof_app_version_target",
|
||||
summaryKey = null,
|
||||
entriesKey = "revanced_spoof_app_version_target_legacy_19_34_entries",
|
||||
entryValuesKey = "revanced_spoof_app_version_target_legacy_19_34_entry_values"
|
||||
entriesKey = "revanced_spoof_app_version_target_legacy_entries",
|
||||
entryValuesKey = "revanced_spoof_app_version_target_legacy_entry_values"
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -34,9 +34,10 @@ val changeStartPagePatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -36,9 +36,10 @@ val disableResumingShortsOnStartupPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -206,9 +206,10 @@ val themePatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -35,9 +35,10 @@ val alternativeThumbnailsPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -29,9 +29,10 @@ val bypassImageRegionRestrictionsPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -25,9 +25,10 @@ val announcementsPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -26,9 +26,10 @@ val autoRepeatPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -53,9 +53,10 @@ val backgroundPlaybackPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -1,34 +1,144 @@
|
||||
package app.revanced.patches.youtube.misc.debugging
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.all.misc.resources.addResources
|
||||
import app.revanced.patches.shared.misc.debugging.enableDebuggingPatch
|
||||
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||
import app.revanced.patches.shared.misc.settings.preference.NonInteractivePreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
|
||||
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.versionCheckPatch
|
||||
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||
import app.revanced.util.findInstructionIndicesReversedOrThrow
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
@Suppress("unused")
|
||||
val enableDebuggingPatch = enableDebuggingPatch(
|
||||
block = {
|
||||
dependsOn(
|
||||
sharedExtensionPatch,
|
||||
settingsPatch,
|
||||
)
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/youtube/patches/EnableDebuggingPatch;"
|
||||
|
||||
compatibleWith(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"20.07.39",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
// TODO: Refactor this into a shared patch that can be used by both YT and YT Music.
|
||||
// Almost all of the feature flag hooks are the same between both apps.
|
||||
val enableDebuggingPatch = bytecodePatch(
|
||||
name = "Enable debugging",
|
||||
description = "Adds options for debugging and exporting ReVanced logs to the clipboard.",
|
||||
) {
|
||||
dependsOn(
|
||||
sharedExtensionPatch,
|
||||
settingsPatch,
|
||||
addResourcesPatch,
|
||||
versionCheckPatch
|
||||
)
|
||||
|
||||
compatibleWith(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
)
|
||||
},
|
||||
executeBlock = {
|
||||
)
|
||||
|
||||
execute {
|
||||
addResources("shared", "misc.debugging.enableDebuggingPatch")
|
||||
addResources("youtube", "misc.debugging.enableDebuggingPatch")
|
||||
},
|
||||
hookStringFeatureFlag = true,
|
||||
preferenceScreen = PreferenceScreen.MISC,
|
||||
additionalDebugPreferences = listOf(SwitchPreference("revanced_debug_protobuffer"))
|
||||
)
|
||||
|
||||
PreferenceScreen.MISC.addPreferences(
|
||||
PreferenceScreenPreference(
|
||||
key = "revanced_debug_screen",
|
||||
sorting = Sorting.UNSORTED,
|
||||
preferences = setOf(
|
||||
SwitchPreference("revanced_debug"),
|
||||
SwitchPreference("revanced_debug_protobuffer"),
|
||||
SwitchPreference("revanced_debug_stacktrace"),
|
||||
SwitchPreference("revanced_debug_toast_on_error"),
|
||||
NonInteractivePreference(
|
||||
"revanced_debug_export_logs_to_clipboard",
|
||||
tag = "app.revanced.extension.shared.settings.preference.ExportLogToClipboardPreference",
|
||||
selectable = true
|
||||
),
|
||||
NonInteractivePreference(
|
||||
"revanced_debug_logs_clear_buffer",
|
||||
tag = "app.revanced.extension.shared.settings.preference.ClearLogBufferPreference",
|
||||
selectable = true
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
// Hook the methods that look up if a feature flag is active.
|
||||
experimentalBooleanFeatureFlagFingerprint.match(
|
||||
experimentalFeatureFlagParentFingerprint.originalClassDef
|
||||
).method.apply {
|
||||
findInstructionIndicesReversedOrThrow(Opcode.RETURN).forEach { index ->
|
||||
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
||||
|
||||
addInstructions(
|
||||
index,
|
||||
"""
|
||||
invoke-static { v$register, p1 }, $EXTENSION_CLASS_DESCRIPTOR->isBooleanFeatureFlagEnabled(ZLjava/lang/Long;)Z
|
||||
move-result v$register
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
experimentalDoubleFeatureFlagFingerprint.match(
|
||||
experimentalFeatureFlagParentFingerprint.originalClassDef
|
||||
).method.apply {
|
||||
val insertIndex = indexOfFirstInstructionOrThrow(Opcode.MOVE_RESULT_WIDE)
|
||||
|
||||
addInstructions(
|
||||
insertIndex,
|
||||
"""
|
||||
move-result-wide v0 # Also clobbers v1 (p0) since result is wide.
|
||||
invoke-static/range { v0 .. v5 }, $EXTENSION_CLASS_DESCRIPTOR->isDoubleFeatureFlagEnabled(DJD)D
|
||||
move-result-wide v0
|
||||
return-wide v0
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
experimentalLongFeatureFlagFingerprint.match(
|
||||
experimentalFeatureFlagParentFingerprint.originalClassDef
|
||||
).method.apply {
|
||||
val insertIndex = indexOfFirstInstructionOrThrow(Opcode.MOVE_RESULT_WIDE)
|
||||
|
||||
addInstructions(
|
||||
insertIndex,
|
||||
"""
|
||||
move-result-wide v0
|
||||
invoke-static/range { v0 .. v5 }, $EXTENSION_CLASS_DESCRIPTOR->isLongFeatureFlagEnabled(JJJ)J
|
||||
move-result-wide v0
|
||||
return-wide v0
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
experimentalStringFeatureFlagFingerprint.match(
|
||||
experimentalFeatureFlagParentFingerprint.originalClassDef
|
||||
).method.apply {
|
||||
val insertIndex = indexOfFirstInstructionReversedOrThrow(Opcode.MOVE_RESULT_OBJECT)
|
||||
|
||||
addInstructions(
|
||||
insertIndex,
|
||||
"""
|
||||
move-result-object v0
|
||||
invoke-static { v0, p1, p2, p3 }, $EXTENSION_CLASS_DESCRIPTOR->isStringFeatureFlagEnabled(Ljava/lang/String;JLjava/lang/String;)Ljava/lang/String;
|
||||
move-result-object v0
|
||||
return-object v0
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
// There exists other experimental accessor methods for byte[]
|
||||
// and wrappers for obfuscated classes, but currently none of those are hooked.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,9 +26,10 @@ val spoofDeviceDimensionsPatch = bytecodePatch(
|
||||
"com.google.android.youtube"(
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.47.53",
|
||||
"20.07.39",
|
||||
"20.12.46",
|
||||
"20.13.41",
|
||||
"20.14.43",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user