mirror of
https://github.com/ReVanced/revanced-patches.git
synced 2026-01-25 19:51:03 +00:00
Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters
# Conflicts: # extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/requests/StreamingDataRequest.java # extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/LithoFilterPatch.java # extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/ReturnYouTubeDislikeFilter.java # patches/src/main/kotlin/app/revanced/patches/shared/misc/spoof/Fingerprints.kt # patches/src/main/kotlin/app/revanced/patches/viber/ads/Fingerprints.kt # patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/action/HideButtonsPatch.kt
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
package app.revanced.extension.shared;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public final class ByteTrieSearch extends TrieSearch<byte[]> {
|
||||
|
||||
private static final class ByteTrieNode extends TrieNode<byte[]> {
|
||||
ByteTrieNode() {
|
||||
super();
|
||||
}
|
||||
ByteTrieNode(char nodeCharacterValue) {
|
||||
super(nodeCharacterValue);
|
||||
}
|
||||
@Override
|
||||
TrieNode<byte[]> createNode(char nodeCharacterValue) {
|
||||
return new ByteTrieNode(nodeCharacterValue);
|
||||
}
|
||||
@Override
|
||||
char getCharValue(byte[] text, int index) {
|
||||
return (char) text[index];
|
||||
}
|
||||
@Override
|
||||
int getTextLength(byte[] text) {
|
||||
return text.length;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for the common usage of converting Strings to raw UTF-8 bytes.
|
||||
*/
|
||||
public static byte[][] convertStringsToBytes(String... strings) {
|
||||
final int length = strings.length;
|
||||
byte[][] replacement = new byte[length][];
|
||||
for (int i = 0; i < length; i++) {
|
||||
replacement[i] = strings[i].getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
return replacement;
|
||||
}
|
||||
|
||||
public ByteTrieSearch(byte[]... patterns) {
|
||||
super(new ByteTrieNode(), patterns);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package app.revanced.extension.shared;
|
||||
|
||||
/**
|
||||
* Text pattern searching using a prefix tree (trie).
|
||||
*/
|
||||
public final class StringTrieSearch extends TrieSearch<String> {
|
||||
|
||||
private static final class StringTrieNode extends TrieNode<String> {
|
||||
StringTrieNode() {
|
||||
super();
|
||||
}
|
||||
StringTrieNode(char nodeCharacterValue) {
|
||||
super(nodeCharacterValue);
|
||||
}
|
||||
@Override
|
||||
TrieNode<String> createNode(char nodeValue) {
|
||||
return new StringTrieNode(nodeValue);
|
||||
}
|
||||
@Override
|
||||
char getCharValue(String text, int index) {
|
||||
return text.charAt(index);
|
||||
}
|
||||
@Override
|
||||
int getTextLength(String text) {
|
||||
return text.length();
|
||||
}
|
||||
}
|
||||
|
||||
public StringTrieSearch(String... patterns) {
|
||||
super(new StringTrieNode(), patterns);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,421 @@
|
||||
package app.revanced.extension.shared;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 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> {
|
||||
|
||||
public interface TriePatternMatchedCallback<T> {
|
||||
/**
|
||||
* Called when a pattern is matched.
|
||||
*
|
||||
* @param textSearched Text that was searched.
|
||||
* @param matchedStartIndex Start index of the search text, where the pattern was matched.
|
||||
* @param matchedLength Length of the match.
|
||||
* @param callbackParameter Optional parameter passed into {@link TrieSearch#matches(Object, Object)}.
|
||||
* @return True, if the search should stop here.
|
||||
* If false, searching will continue to look for other matches.
|
||||
*/
|
||||
boolean patternMatched(T textSearched, int matchedStartIndex, int matchedLength, Object callbackParameter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a compressed tree path for a single pattern that shares no sibling nodes.
|
||||
*
|
||||
* For example, if a tree contains the patterns: "foobar", "football", "feet",
|
||||
* it would contain 3 compressed paths of: "bar", "tball", "eet".
|
||||
*
|
||||
* And the tree would contain children arrays only for the first level containing 'f',
|
||||
* the second level containing 'o',
|
||||
* and the third level containing 'o'.
|
||||
*
|
||||
* This is done to reduce memory usage, which can be substantial if many long patterns are used.
|
||||
*/
|
||||
private static final class TrieCompressedPath<T> {
|
||||
final T pattern;
|
||||
final int patternStartIndex;
|
||||
final int patternLength;
|
||||
final TriePatternMatchedCallback<T> callback;
|
||||
|
||||
TrieCompressedPath(T pattern, int patternStartIndex, int patternLength, TriePatternMatchedCallback<T> callback) {
|
||||
this.pattern = pattern;
|
||||
this.patternStartIndex = patternStartIndex;
|
||||
this.patternLength = patternLength;
|
||||
this.callback = callback;
|
||||
}
|
||||
boolean matches(TrieNode<T> enclosingNode, // Used only for the get character method.
|
||||
T searchText, int searchTextLength, int searchTextIndex, Object callbackParameter) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
static abstract class TrieNode<T> {
|
||||
/**
|
||||
* Dummy value used for root node. Value can be anything as it's never referenced.
|
||||
*/
|
||||
private static final char ROOT_NODE_CHARACTER_VALUE = 0; // ASCII null character.
|
||||
|
||||
/**
|
||||
* How much to expand the children array when resizing.
|
||||
*/
|
||||
private static final int CHILDREN_ARRAY_INCREASE_SIZE_INCREMENT = 2;
|
||||
|
||||
/**
|
||||
* Character this node represents.
|
||||
* This field is ignored for the root node (which does not represent any character).
|
||||
*/
|
||||
private final char nodeValue;
|
||||
|
||||
/**
|
||||
* A compressed graph path that represents the remaining pattern characters of a single child node.
|
||||
*
|
||||
* If present then child array is always null, although callbacks for other
|
||||
* end of patterns can also exist on this same node.
|
||||
*/
|
||||
@Nullable
|
||||
private TrieCompressedPath<T> leaf;
|
||||
|
||||
/**
|
||||
* All child nodes. Only present if no compressed leaf exist.
|
||||
*
|
||||
* Array is dynamically increased in size as needed,
|
||||
* and uses perfect hashing for the elements it contains.
|
||||
*
|
||||
* So if the array contains a given character,
|
||||
* the character will always map to the node with index: (character % arraySize).
|
||||
*
|
||||
* Elements not contained can collide with elements the array does contain,
|
||||
* so must compare the nodes character value.
|
||||
*
|
||||
* Alternatively this array could be a sorted and densely packed array,
|
||||
* and lookup is done using binary search.
|
||||
* That would save a small amount of memory because there's no null children entries,
|
||||
* but would give a worst case search of O(nlog(m)) where n is the number of
|
||||
* characters in the searched text and m is the maximum size of the sorted character arrays.
|
||||
* Using a hash table array always gives O(n) search time.
|
||||
* The memory usage here is very small (all Litho filters use ~10KB of memory),
|
||||
* so the more performant hash implementation is chosen.
|
||||
*/
|
||||
@Nullable
|
||||
private TrieNode<T>[] children;
|
||||
|
||||
/**
|
||||
* Callbacks for all patterns that end at this node.
|
||||
*/
|
||||
@Nullable
|
||||
private List<TriePatternMatchedCallback<T>> endOfPatternCallback;
|
||||
|
||||
TrieNode() {
|
||||
this.nodeValue = ROOT_NODE_CHARACTER_VALUE;
|
||||
}
|
||||
TrieNode(char nodeCharacterValue) {
|
||||
this.nodeValue = nodeCharacterValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param pattern Pattern to add.
|
||||
* @param patternIndex Current recursive index of the pattern.
|
||||
* @param patternLength Length of the pattern.
|
||||
* @param callback Callback, where a value of NULL indicates to always accept a pattern match.
|
||||
*/
|
||||
private void addPattern(T pattern, int patternIndex, int patternLength,
|
||||
@Nullable TriePatternMatchedCallback<T> callback) {
|
||||
if (patternIndex == patternLength) { // Reached the end of the pattern.
|
||||
if (endOfPatternCallback == null) {
|
||||
endOfPatternCallback = new ArrayList<>(1);
|
||||
}
|
||||
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.
|
||||
if (children != null) throw new IllegalStateException();
|
||||
//noinspection unchecked
|
||||
children = new TrieNode[1];
|
||||
TrieCompressedPath<T> temp = leaf;
|
||||
leaf = null;
|
||||
addPattern(temp.pattern, temp.patternStartIndex, temp.patternLength, temp.callback);
|
||||
// Continue onward and add the parameter pattern.
|
||||
} else if (children == null) {
|
||||
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];
|
||||
if (child == null) {
|
||||
child = createNode(character);
|
||||
children[arrayIndex] = child;
|
||||
} else if (child.nodeValue != character) {
|
||||
// Hash collision. Resize the table until perfect hashing is found.
|
||||
child = createNode(character);
|
||||
expandChildArray(child);
|
||||
}
|
||||
child.addPattern(pattern, patternIndex + 1, patternLength, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes the children table until all nodes hash to exactly one array index.
|
||||
*/
|
||||
private void expandChildArray(TrieNode<T> child) {
|
||||
int replacementArraySize = Objects.requireNonNull(children).length;
|
||||
while (true) {
|
||||
replacementArraySize += CHILDREN_ARRAY_INCREASE_SIZE_INCREMENT;
|
||||
//noinspection unchecked
|
||||
TrieNode<T>[] replacement = new TrieNode[replacementArraySize];
|
||||
addNodeToArray(replacement, child);
|
||||
|
||||
boolean collision = false;
|
||||
for (TrieNode<T> existingChild : children) {
|
||||
if (existingChild != null) {
|
||||
if (!addNodeToArray(replacement, existingChild)) {
|
||||
collision = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (collision) {
|
||||
continue;
|
||||
}
|
||||
|
||||
children = replacement;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> boolean addNodeToArray(TrieNode<T>[] array, TrieNode<T> childToAdd) {
|
||||
final int insertIndex = hashIndexForTableSize(array.length, childToAdd.nodeValue);
|
||||
if (array[insertIndex] != null ) {
|
||||
return false; // Collision.
|
||||
}
|
||||
array[insertIndex] = childToAdd;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int hashIndexForTableSize(int arraySize, char nodeValue) {
|
||||
return nodeValue % arraySize;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is static and uses a loop to avoid all recursion.
|
||||
* This is done for performance since the JVM does not optimize tail recursion.
|
||||
*
|
||||
* @param startNode Node to start the search from.
|
||||
* @param searchText Text to search for patterns in.
|
||||
* @param searchTextIndex Start index, inclusive.
|
||||
* @param searchTextEndIndex End index, exclusive.
|
||||
* @return If any pattern matches, and it's associated callback halted the search.
|
||||
*/
|
||||
private static <T> boolean matches(final TrieNode<T> startNode, final T searchText,
|
||||
int searchTextIndex, final int searchTextEndIndex,
|
||||
final Object callbackParameter) {
|
||||
TrieNode<T> node = startNode;
|
||||
int currentMatchLength = 0;
|
||||
|
||||
while (true) {
|
||||
TrieCompressedPath<T> leaf = node.leaf;
|
||||
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;
|
||||
for (@Nullable TriePatternMatchedCallback<T> callback : endOfPatternCallback) {
|
||||
if (callback == null) {
|
||||
return true; // No callback and all matches are valid.
|
||||
}
|
||||
if (callback.patternMatched(searchText, matchStartIndex, currentMatchLength, callbackParameter)) {
|
||||
return true; // Callback confirmed the match.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TrieNode<T>[] children = node.children;
|
||||
if (children == null) {
|
||||
return false; // Reached a graph end point and there's no further patterns to search.
|
||||
}
|
||||
if (searchTextIndex == searchTextEndIndex) {
|
||||
return false; // Reached end of the search text and found no matches.
|
||||
}
|
||||
|
||||
// Use the start node to reduce VM method lookup, since all nodes are the same class type.
|
||||
final char character = startNode.getCharValue(searchText, searchTextIndex);
|
||||
final int arrayIndex = hashIndexForTableSize(children.length, character);
|
||||
TrieNode<T> child = children[arrayIndex];
|
||||
if (child == null || child.nodeValue != character) {
|
||||
return false;
|
||||
}
|
||||
|
||||
node = child;
|
||||
searchTextIndex++;
|
||||
currentMatchLength++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives an approximate memory usage.
|
||||
*
|
||||
* @return Estimated number of memory pointers used, starting from this node and including all children.
|
||||
*/
|
||||
private int estimatedNumberOfPointersUsed() {
|
||||
int numberOfPointers = 4; // Number of fields in this class.
|
||||
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) {
|
||||
if (child != null) {
|
||||
numberOfPointers += child.estimatedNumberOfPointersUsed();
|
||||
}
|
||||
}
|
||||
}
|
||||
return numberOfPointers;
|
||||
}
|
||||
|
||||
abstract TrieNode<T> createNode(char nodeValue);
|
||||
abstract char getCharValue(T text, int index);
|
||||
abstract int getTextLength(T text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Root node, and it's children represent the first pattern characters.
|
||||
*/
|
||||
private final TrieNode<T> root;
|
||||
|
||||
/**
|
||||
* Patterns to match.
|
||||
*/
|
||||
private final List<T> patterns = new ArrayList<>();
|
||||
|
||||
@SafeVarargs
|
||||
TrieSearch(TrieNode<T> root, T... patterns) {
|
||||
this.root = Objects.requireNonNull(root);
|
||||
addPatterns(patterns);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public final void addPatterns(T... patterns) {
|
||||
for (T pattern : patterns) {
|
||||
addPattern(pattern);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a pattern that will always return a positive match if found.
|
||||
*
|
||||
* @param pattern Pattern to add. Calling this with a zero length pattern does nothing.
|
||||
*/
|
||||
public void addPattern(T pattern) {
|
||||
addPattern(pattern, root.getTextLength(pattern), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param pattern Pattern to add. Calling this with a zero length pattern does nothing.
|
||||
* @param callback Callback to determine if searching should halt when a match is found.
|
||||
*/
|
||||
public void addPattern(T pattern, TriePatternMatchedCallback<T> callback) {
|
||||
addPattern(pattern, root.getTextLength(pattern), Objects.requireNonNull(callback));
|
||||
}
|
||||
|
||||
void addPattern(T pattern, int patternLength, @Nullable TriePatternMatchedCallback<T> callback) {
|
||||
if (patternLength == 0) return; // Nothing to match
|
||||
|
||||
patterns.add(pattern);
|
||||
root.addPattern(pattern, 0, patternLength, callback);
|
||||
}
|
||||
|
||||
public final boolean matches(T textToSearch) {
|
||||
return matches(textToSearch, 0);
|
||||
}
|
||||
|
||||
public boolean matches(T textToSearch, Object callbackParameter) {
|
||||
return matches(textToSearch, 0, root.getTextLength(textToSearch),
|
||||
Objects.requireNonNull(callbackParameter));
|
||||
}
|
||||
|
||||
public boolean matches(T textToSearch, int startIndex) {
|
||||
return matches(textToSearch, startIndex, root.getTextLength(textToSearch));
|
||||
}
|
||||
|
||||
public final boolean matches(T textToSearch, int startIndex, int endIndex) {
|
||||
return matches(textToSearch, startIndex, endIndex, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches through text, looking for any substring that matches any pattern in this tree.
|
||||
*
|
||||
* @param textToSearch Text to search through.
|
||||
* @param startIndex Index to start searching, inclusive value.
|
||||
* @param endIndex Index to stop matching, exclusive value.
|
||||
* @param callbackParameter Optional parameter passed to the callbacks.
|
||||
* @return If any pattern matched, and it's callback halted searching.
|
||||
*/
|
||||
public boolean matches(T textToSearch, int startIndex, int endIndex, @Nullable Object callbackParameter) {
|
||||
return matches(textToSearch, root.getTextLength(textToSearch), startIndex, endIndex, callbackParameter);
|
||||
}
|
||||
|
||||
private boolean matches(T textToSearch, int textToSearchLength, int startIndex, int endIndex,
|
||||
@Nullable Object callbackParameter) {
|
||||
if (endIndex > textToSearchLength) {
|
||||
throw new IllegalArgumentException("endIndex: " + endIndex
|
||||
+ " is greater than texToSearchLength: " + textToSearchLength);
|
||||
}
|
||||
if (patterns.isEmpty()) {
|
||||
return false; // No patterns were added.
|
||||
}
|
||||
for (int i = startIndex; i < endIndex; i++) {
|
||||
if (TrieNode.matches(root, textToSearch, i, endIndex, callbackParameter)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Estimated memory size (in kilobytes) of this instance.
|
||||
*/
|
||||
public int getEstimatedMemorySize() {
|
||||
if (patterns.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
// Assume the device has less than 32GB of ram (and can use pointer compression),
|
||||
// or the device is 32-bit.
|
||||
final int numberOfBytesPerPointer = 4;
|
||||
return (int) Math.ceil((numberOfBytesPerPointer * root.estimatedNumberOfPointersUsed()) / 1024.0);
|
||||
}
|
||||
|
||||
public int numberOfPatterns() {
|
||||
return patterns.size();
|
||||
}
|
||||
|
||||
public List<T> getPatterns() {
|
||||
return Collections.unmodifiableList(patterns);
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,6 @@ import static java.lang.Boolean.TRUE;
|
||||
import static app.revanced.extension.shared.settings.Setting.parent;
|
||||
import static app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.AudioStreamLanguageOverrideAvailability;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.spoof.ClientType;
|
||||
|
||||
/**
|
||||
* Settings shared across multiple apps.
|
||||
* <p>
|
||||
@@ -31,13 +28,4 @@ public class BaseSettings {
|
||||
public static final BooleanSetting SPOOF_VIDEO_STREAMS = new BooleanSetting("revanced_spoof_video_streams", TRUE, true, "revanced_spoof_video_streams_user_dialog_message");
|
||||
public static final EnumSetting<AppLanguage> SPOOF_VIDEO_STREAMS_LANGUAGE = new EnumSetting<>("revanced_spoof_video_streams_language", AppLanguage.DEFAULT, new AudioStreamLanguageOverrideAvailability());
|
||||
public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_streaming_data_stats_for_nerds", TRUE, parent(SPOOF_VIDEO_STREAMS));
|
||||
// Client type must be last spoof setting due to cyclic references.
|
||||
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client_type", ClientType.ANDROID_VR_1_61_48, true, parent(SPOOF_VIDEO_STREAMS));
|
||||
|
||||
static {
|
||||
if (SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.IOS_UNPLUGGED) {
|
||||
Logger.printInfo(() -> "Migrating from iOS Unplugged to iPadOS");
|
||||
SPOOF_VIDEO_STREAMS_CLIENT_TYPE.save(ClientType.IPADOS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ public enum ClientType {
|
||||
"132.0.6808.3",
|
||||
"1.61.48",
|
||||
false,
|
||||
false,
|
||||
"Android VR 1.61"
|
||||
),
|
||||
/**
|
||||
@@ -50,7 +49,6 @@ public enum ClientType {
|
||||
Objects.requireNonNull(ANDROID_VR_1_61_48.buildId),
|
||||
"107.0.5284.2",
|
||||
"1.43.32",
|
||||
ANDROID_VR_1_61_48.requiresAuth,
|
||||
ANDROID_VR_1_61_48.useAuth,
|
||||
"Android VR 1.43"
|
||||
),
|
||||
@@ -71,8 +69,7 @@ public enum ClientType {
|
||||
"132.0.6779.0",
|
||||
"23.47.101",
|
||||
true,
|
||||
true,
|
||||
"Android Creator"
|
||||
"Android Studio"
|
||||
),
|
||||
/**
|
||||
* Internal YT client for an unreleased YT client. May stop working at any time.
|
||||
@@ -86,7 +83,6 @@ public enum ClientType {
|
||||
"0.1",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15",
|
||||
false,
|
||||
false,
|
||||
"visionOS"
|
||||
),
|
||||
/**
|
||||
@@ -111,25 +107,7 @@ public enum ClientType {
|
||||
"19.22.3",
|
||||
"com.google.ios.youtube/19.22.3 (iPad7,6; U; CPU iPadOS 17_7_10 like Mac OS X; " + Locale.getDefault() + ")",
|
||||
false,
|
||||
false,
|
||||
"iPadOS"
|
||||
),
|
||||
/**
|
||||
* Obsolete and broken client. Here only to migrate data.
|
||||
*/
|
||||
@Deprecated
|
||||
IOS_UNPLUGGED(
|
||||
33,
|
||||
"IOS_UNPLUGGED",
|
||||
"Apple",
|
||||
"iPhone16,2",
|
||||
"iOS",
|
||||
"18.2.22C152",
|
||||
"8.49",
|
||||
"dummy user-agent",
|
||||
true,
|
||||
true,
|
||||
"iOS TV"
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -197,12 +175,6 @@ public enum ClientType {
|
||||
*/
|
||||
public final String clientVersion;
|
||||
|
||||
/**
|
||||
* If this client requires authentication and does not work
|
||||
* if logged out or in incognito mode.
|
||||
*/
|
||||
public final boolean requiresAuth;
|
||||
|
||||
/**
|
||||
* If the client should use authentication if available.
|
||||
*/
|
||||
@@ -227,7 +199,6 @@ public enum ClientType {
|
||||
@NonNull String buildId,
|
||||
@NonNull String cronetVersion,
|
||||
String clientVersion,
|
||||
boolean requiresAuth,
|
||||
boolean useAuth,
|
||||
String friendlyName) {
|
||||
this.id = id;
|
||||
@@ -241,7 +212,6 @@ public enum ClientType {
|
||||
this.buildId = buildId;
|
||||
this.cronetVersion = cronetVersion;
|
||||
this.clientVersion = clientVersion;
|
||||
this.requiresAuth = requiresAuth;
|
||||
this.useAuth = useAuth;
|
||||
this.friendlyName = friendlyName;
|
||||
|
||||
@@ -267,7 +237,6 @@ public enum ClientType {
|
||||
String osVersion,
|
||||
String clientVersion,
|
||||
String userAgent,
|
||||
boolean requiresAuth,
|
||||
boolean useAuth,
|
||||
String friendlyName) {
|
||||
this.id = id;
|
||||
@@ -278,7 +247,6 @@ public enum ClientType {
|
||||
this.osVersion = osVersion;
|
||||
this.clientVersion = clientVersion;
|
||||
this.userAgent = userAgent;
|
||||
this.requiresAuth = requiresAuth;
|
||||
this.useAuth = useAuth;
|
||||
this.friendlyName = friendlyName;
|
||||
this.packageName = null;
|
||||
|
||||
@@ -6,7 +6,9 @@ import android.text.TextUtils;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
@@ -17,14 +19,6 @@ import app.revanced.extension.shared.spoof.requests.StreamingDataRequest;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class SpoofVideoStreamsPatch {
|
||||
private static final boolean SPOOF_STREAMING_DATA = BaseSettings.SPOOF_VIDEO_STREAMS.get();
|
||||
|
||||
private static final boolean FIX_HLS_CURRENT_TIME = SPOOF_STREAMING_DATA
|
||||
&& BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.VISIONOS;
|
||||
|
||||
@Nullable
|
||||
private static volatile AppLanguage languageOverride;
|
||||
|
||||
/**
|
||||
* Domain used for internet connectivity verification.
|
||||
* It has an empty response body and is only used to check for a 204 response code.
|
||||
@@ -40,17 +34,23 @@ public class SpoofVideoStreamsPatch {
|
||||
private static final String INTERNET_CONNECTION_CHECK_URI_STRING = "https://www.google.com/gen_204";
|
||||
private static final Uri INTERNET_CONNECTION_CHECK_URI = Uri.parse(INTERNET_CONNECTION_CHECK_URI_STRING);
|
||||
|
||||
private static final boolean SPOOF_STREAMING_DATA = BaseSettings.SPOOF_VIDEO_STREAMS.get();
|
||||
|
||||
@Nullable
|
||||
private static volatile AppLanguage languageOverride;
|
||||
|
||||
private static volatile ClientType preferredClient = ClientType.ANDROID_VR_1_61_48;
|
||||
|
||||
/**
|
||||
* @return If this patch was included during patching.
|
||||
*/
|
||||
private static boolean isPatchIncluded() {
|
||||
public static boolean isPatchIncluded() {
|
||||
return false; // Modified during patching.
|
||||
}
|
||||
|
||||
public static boolean spoofingToClientWithNoMultiAudioStreams() {
|
||||
return isPatchIncluded()
|
||||
&& BaseSettings.SPOOF_VIDEO_STREAMS.get()
|
||||
&& BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() != ClientType.IPADOS;
|
||||
@Nullable
|
||||
public static AppLanguage getLanguageOverride() {
|
||||
return languageOverride;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,9 +61,15 @@ public class SpoofVideoStreamsPatch {
|
||||
languageOverride = language;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static AppLanguage getLanguageOverride() {
|
||||
return languageOverride;
|
||||
public static void setClientsToUse(List<ClientType> availableClients, ClientType client) {
|
||||
preferredClient = Objects.requireNonNull(client);
|
||||
StreamingDataRequest.setClientOrderToUse(availableClients, client);
|
||||
}
|
||||
|
||||
public static boolean spoofingToClientWithNoMultiAudioStreams() {
|
||||
return isPatchIncluded()
|
||||
&& SPOOF_STREAMING_DATA
|
||||
&& preferredClient != ClientType.IPADOS;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -278,8 +284,7 @@ public class SpoofVideoStreamsPatch {
|
||||
public static final class AudioStreamLanguageOverrideAvailability implements Setting.Availability {
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
// Since all current clients are un-authenticated, this works for all spoof clients.
|
||||
return BaseSettings.SPOOF_VIDEO_STREAMS.get();
|
||||
return BaseSettings.SPOOF_VIDEO_STREAMS.get() && !preferredClient.useAuth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,10 +42,10 @@ final class PlayerRoutes {
|
||||
JSONObject context = new JSONObject();
|
||||
|
||||
AppLanguage language = SpoofVideoStreamsPatch.getLanguageOverride();
|
||||
if (language == null || BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ANDROID_VR_1_43_32) {
|
||||
if (language == null || clientType == ANDROID_VR_1_43_32) {
|
||||
// Force original audio has not overrode the language.
|
||||
// Or if YT has fallen over to the very last client (VR 1.43), then always
|
||||
// use the app language because forcing an audio stream of specific languages
|
||||
// Or if YT has fallen over to the last unauthenticated client (VR 1.43), then
|
||||
// always use the app language because forcing an audio stream of specific languages
|
||||
// can sometimes fail so it's better to try and load something rather than nothing.
|
||||
language = BaseSettings.SPOOF_VIDEO_STREAMS_LANGUAGE.get();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package app.revanced.extension.shared.spoof.requests;
|
||||
|
||||
import static app.revanced.extension.shared.ByteTrieSearch.convertStringsToBytes;
|
||||
import static app.revanced.extension.shared.spoof.requests.PlayerRoutes.GET_STREAMING_DATA;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -23,6 +24,7 @@ import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import app.revanced.extension.shared.ByteTrieSearch;
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
@@ -90,6 +92,16 @@ public class StreamingDataRequest {
|
||||
private static final Map<String, StreamingDataRequest> cache = Collections.synchronizedMap(
|
||||
Utils.createSizeRestrictedMap(50));
|
||||
|
||||
/**
|
||||
* Strings found in the response if the video is a livestream.
|
||||
*/
|
||||
private static final ByteTrieSearch liveStreamBufferSearch = new ByteTrieSearch(
|
||||
convertStringsToBytes(
|
||||
"yt_live_broadcast",
|
||||
"yt_premiere_broadcast"
|
||||
)
|
||||
);
|
||||
|
||||
private static volatile ClientType lastSpoofedClientType;
|
||||
|
||||
public static String getLastSpoofedClientName() {
|
||||
@@ -157,7 +169,7 @@ public class StreamingDataRequest {
|
||||
}
|
||||
}
|
||||
|
||||
if (!authHeadersIncludes && clientType.requiresAuth) {
|
||||
if (!authHeadersIncludes && clientType.useAuth) {
|
||||
Logger.printDebug(() -> "Skipping client since user is not logged in: " + clientType
|
||||
+ " videoId: " + videoId);
|
||||
return null;
|
||||
@@ -218,9 +230,13 @@ public class StreamingDataRequest {
|
||||
while ((bytesRead = inputStream.read(buffer)) >= 0) {
|
||||
baos.write(buffer, 0, bytesRead);
|
||||
}
|
||||
lastSpoofedClientType = clientType;
|
||||
if (clientType == ClientType.ANDROID_CREATOR && liveStreamBufferSearch.matches(buffer)) {
|
||||
Logger.printDebug(() -> "Skipping Android Studio as video is a livestream: " + videoId);
|
||||
} else {
|
||||
lastSpoofedClientType = clientType;
|
||||
|
||||
return ByteBuffer.wrap(baos.toByteArray());
|
||||
return ByteBuffer.wrap(baos.toByteArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
|
||||
Reference in New Issue
Block a user