Merge branch 'dev' of https://github.com/lbux/revanced-patches into dev
This commit is contained in:
commit
ff16079369
167 changed files with 61381 additions and 59814 deletions
|
|
@ -4,12 +4,12 @@ import static java.lang.Boolean.FALSE;
|
|||
import static java.lang.Boolean.TRUE;
|
||||
import static app.revanced.extension.shared.settings.Setting.parent;
|
||||
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
import app.revanced.extension.shared.settings.YouTubeAndMusicSettings;
|
||||
import app.revanced.extension.shared.settings.BooleanSetting;
|
||||
import app.revanced.extension.shared.settings.EnumSetting;
|
||||
import app.revanced.extension.shared.spoof.ClientType;
|
||||
|
||||
public class Settings extends BaseSettings {
|
||||
public class Settings extends YouTubeAndMusicSettings {
|
||||
|
||||
// Ads
|
||||
public static final BooleanSetting HIDE_VIDEO_ADS = new BooleanSetting("revanced_music_hide_video_ads", TRUE, true);
|
||||
|
|
|
|||
15
extensions/nothingx/build.gradle.kts
Normal file
15
extensions/nothingx/build.gradle.kts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
dependencies {
|
||||
compileOnly(project(":extensions:shared:library"))
|
||||
compileOnly(project(":extensions:nothingx:stub"))
|
||||
}
|
||||
|
||||
android {
|
||||
defaultConfig {
|
||||
minSdk = 26
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
}
|
||||
1
extensions/nothingx/src/main/AndroidManifest.xml
Normal file
1
extensions/nothingx/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1 @@
|
|||
<manifest/>
|
||||
|
|
@ -0,0 +1,590 @@
|
|||
package app.revanced.extension.nothingx.patches;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Application;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Patches to expose the K1 token for Nothing X app to enable pairing with GadgetBridge.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class ShowK1TokensPatch {
|
||||
|
||||
private static final String TAG = "ReVanced";
|
||||
private static final String PACKAGE_NAME = "com.nothing.smartcenter";
|
||||
private static final String EMPTY_MD5 = "d41d8cd98f00b204e9800998ecf8427e";
|
||||
private static final String PREFS_NAME = "revanced_nothingx_prefs";
|
||||
private static final String KEY_DONT_SHOW_DIALOG = "dont_show_k1_dialog";
|
||||
|
||||
// Colors
|
||||
private static final int COLOR_BG = 0xFF1E1E1E;
|
||||
private static final int COLOR_CARD = 0xFF2D2D2D;
|
||||
private static final int COLOR_TEXT_PRIMARY = 0xFFFFFFFF;
|
||||
private static final int COLOR_TEXT_SECONDARY = 0xFFB0B0B0;
|
||||
private static final int COLOR_ACCENT = 0xFFFF9500;
|
||||
private static final int COLOR_TOKEN_BG = 0xFF3A3A3A;
|
||||
private static final int COLOR_BUTTON_POSITIVE = 0xFFFF9500;
|
||||
private static final int COLOR_BUTTON_NEGATIVE = 0xFFFF6B6B;
|
||||
|
||||
// Match standalone K1: k1:, K1:, k1>, etc.
|
||||
private static final Pattern K1_STANDALONE_PATTERN = Pattern.compile("(?i)(?:k1\\s*[:>]\\s*)([0-9a-f]{32})");
|
||||
// Match combined r3+k1: format (64 chars = r3(32) + k1(32))
|
||||
private static final Pattern K1_COMBINED_PATTERN = Pattern.compile("(?i)r3\\+k1\\s*:\\s*([0-9a-f]{64})");
|
||||
|
||||
private static volatile boolean k1Logged = false;
|
||||
private static volatile boolean lifecycleCallbacksRegistered = false;
|
||||
private static Context appContext;
|
||||
|
||||
/**
|
||||
* Get K1 tokens from database and log files.
|
||||
* Call this after the app initializes.
|
||||
*
|
||||
* @param context Application context
|
||||
*/
|
||||
public static void showK1Tokens(Context context) {
|
||||
if (k1Logged) {
|
||||
return;
|
||||
}
|
||||
|
||||
appContext = context.getApplicationContext();
|
||||
|
||||
Set<String> allTokens = new LinkedHashSet<>();
|
||||
|
||||
// First try to get from database.
|
||||
String dbToken = getK1TokensFromDatabase();
|
||||
if (dbToken != null) {
|
||||
allTokens.add(dbToken);
|
||||
}
|
||||
|
||||
// Then get from log files.
|
||||
Set<String> logTokens = getK1TokensFromLogFiles();
|
||||
allTokens.addAll(logTokens);
|
||||
|
||||
if (allTokens.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Log all found tokens.
|
||||
int index = 1;
|
||||
for (String token : allTokens) {
|
||||
Log.i(TAG, "#" + index++ + ": " + token.toUpperCase());
|
||||
}
|
||||
|
||||
// Register lifecycle callbacks to show dialog when an Activity is ready.
|
||||
registerLifecycleCallbacks(allTokens);
|
||||
|
||||
k1Logged = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register ActivityLifecycleCallbacks to show dialog when first Activity resumes.
|
||||
*
|
||||
* @param tokens Set of K1 tokens to display
|
||||
*/
|
||||
private static void registerLifecycleCallbacks(Set<String> tokens) {
|
||||
if (lifecycleCallbacksRegistered || !(appContext instanceof Application)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Application application = (Application) appContext;
|
||||
application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
|
||||
@Override
|
||||
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityStarted(Activity activity) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResumed(Activity activity) {
|
||||
// Check if user chose not to show dialog.
|
||||
SharedPreferences prefs = activity.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||
if (prefs.getBoolean(KEY_DONT_SHOW_DIALOG, false)) {
|
||||
application.unregisterActivityLifecycleCallbacks(this);
|
||||
lifecycleCallbacksRegistered = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Show dialog on first Activity resume.
|
||||
if (tokens != null && !tokens.isEmpty()) {
|
||||
activity.runOnUiThread(() -> showK1TokensDialog(activity, tokens));
|
||||
// Unregister after showing
|
||||
application.unregisterActivityLifecycleCallbacks(this);
|
||||
lifecycleCallbacksRegistered = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityPaused(Activity activity) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityStopped(Activity activity) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityDestroyed(Activity activity) {
|
||||
}
|
||||
});
|
||||
|
||||
lifecycleCallbacksRegistered = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show dialog with K1 tokens.
|
||||
*
|
||||
* @param activity Activity context
|
||||
* @param tokens Set of K1 tokens
|
||||
*/
|
||||
private static void showK1TokensDialog(Activity activity, Set<String> tokens) {
|
||||
try {
|
||||
// Create main container.
|
||||
LinearLayout mainLayout = new LinearLayout(activity);
|
||||
mainLayout.setOrientation(LinearLayout.VERTICAL);
|
||||
mainLayout.setBackgroundColor(COLOR_BG);
|
||||
mainLayout.setPadding(dpToPx(activity, 24), dpToPx(activity, 16),
|
||||
dpToPx(activity, 24), dpToPx(activity, 16));
|
||||
|
||||
// Title.
|
||||
TextView titleView = new TextView(activity);
|
||||
titleView.setText("K1 Token(s) Found");
|
||||
titleView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
|
||||
titleView.setTypeface(Typeface.DEFAULT_BOLD);
|
||||
titleView.setTextColor(COLOR_TEXT_PRIMARY);
|
||||
titleView.setGravity(Gravity.CENTER);
|
||||
mainLayout.addView(titleView, new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
));
|
||||
|
||||
// Subtitle.
|
||||
TextView subtitleView = new TextView(activity);
|
||||
subtitleView.setText(tokens.size() == 1 ? "1 token found • Tap to copy" : tokens.size() + " tokens found • Tap to copy");
|
||||
subtitleView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
|
||||
subtitleView.setTextColor(COLOR_TEXT_SECONDARY);
|
||||
subtitleView.setGravity(Gravity.CENTER);
|
||||
LinearLayout.LayoutParams subtitleParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
);
|
||||
subtitleParams.topMargin = dpToPx(activity, 4);
|
||||
subtitleParams.bottomMargin = dpToPx(activity, 16);
|
||||
mainLayout.addView(subtitleView, subtitleParams);
|
||||
|
||||
// Scrollable content.
|
||||
ScrollView scrollView = new ScrollView(activity);
|
||||
scrollView.setVerticalScrollBarEnabled(false);
|
||||
LinearLayout.LayoutParams scrollParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
0,
|
||||
1.0f
|
||||
);
|
||||
scrollParams.topMargin = dpToPx(activity, 8);
|
||||
scrollParams.bottomMargin = dpToPx(activity, 16);
|
||||
mainLayout.addView(scrollView, scrollParams);
|
||||
|
||||
LinearLayout tokensContainer = new LinearLayout(activity);
|
||||
tokensContainer.setOrientation(LinearLayout.VERTICAL);
|
||||
scrollView.addView(tokensContainer);
|
||||
|
||||
// Add each token as a card.
|
||||
boolean singleToken = tokens.size() == 1;
|
||||
int index = 1;
|
||||
for (String token : tokens) {
|
||||
LinearLayout tokenCard = createTokenCard(activity, token, index++, singleToken);
|
||||
LinearLayout.LayoutParams cardParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
);
|
||||
cardParams.bottomMargin = dpToPx(activity, 12);
|
||||
tokensContainer.addView(tokenCard, cardParams);
|
||||
}
|
||||
|
||||
// Info text.
|
||||
TextView infoView = new TextView(activity);
|
||||
infoView.setText(tokens.size() == 1 ? "Tap the token to copy it" : "Tap any token to copy it");
|
||||
infoView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12);
|
||||
infoView.setTextColor(COLOR_TEXT_SECONDARY);
|
||||
infoView.setGravity(Gravity.CENTER);
|
||||
infoView.setAlpha(0.7f);
|
||||
LinearLayout.LayoutParams infoParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
);
|
||||
infoParams.topMargin = dpToPx(activity, 8);
|
||||
mainLayout.addView(infoView, infoParams);
|
||||
|
||||
// Button row.
|
||||
LinearLayout buttonRow = new LinearLayout(activity);
|
||||
buttonRow.setOrientation(LinearLayout.HORIZONTAL);
|
||||
buttonRow.setGravity(Gravity.END);
|
||||
LinearLayout.LayoutParams buttonRowParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
);
|
||||
buttonRowParams.topMargin = dpToPx(activity, 16);
|
||||
mainLayout.addView(buttonRow, buttonRowParams);
|
||||
|
||||
// "Don't show again" button.
|
||||
Button dontShowButton = new Button(activity);
|
||||
dontShowButton.setText("Don't show again");
|
||||
dontShowButton.setTextColor(Color.WHITE);
|
||||
dontShowButton.setBackgroundColor(Color.TRANSPARENT);
|
||||
dontShowButton.setAllCaps(false);
|
||||
dontShowButton.setTypeface(Typeface.DEFAULT);
|
||||
dontShowButton.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
|
||||
dontShowButton.setPadding(dpToPx(activity, 16), dpToPx(activity, 8),
|
||||
dpToPx(activity, 16), dpToPx(activity, 8));
|
||||
LinearLayout.LayoutParams dontShowParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
);
|
||||
dontShowParams.rightMargin = dpToPx(activity, 8);
|
||||
buttonRow.addView(dontShowButton, dontShowParams);
|
||||
|
||||
// "OK" button.
|
||||
Button okButton = new Button(activity);
|
||||
okButton.setText("OK");
|
||||
okButton.setTextColor(Color.BLACK);
|
||||
okButton.setBackgroundColor(COLOR_BUTTON_POSITIVE);
|
||||
okButton.setAllCaps(false);
|
||||
okButton.setTypeface(Typeface.DEFAULT_BOLD);
|
||||
okButton.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
|
||||
okButton.setPadding(dpToPx(activity, 24), dpToPx(activity, 12),
|
||||
dpToPx(activity, 24), dpToPx(activity, 12));
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
okButton.setElevation(dpToPx(activity, 4));
|
||||
}
|
||||
buttonRow.addView(okButton, new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
));
|
||||
|
||||
// Build dialog.
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||
builder.setView(mainLayout);
|
||||
|
||||
final AlertDialog dialog = builder.create();
|
||||
|
||||
// Style the dialog with dark background.
|
||||
if (dialog.getWindow() != null) {
|
||||
dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
|
||||
}
|
||||
|
||||
dialog.show();
|
||||
|
||||
// Set button click listeners after dialog is created.
|
||||
dontShowButton.setOnClickListener(v -> {
|
||||
SharedPreferences prefs = activity.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||
prefs.edit().putBoolean(KEY_DONT_SHOW_DIALOG, true).apply();
|
||||
Toast.makeText(activity, "Dialog disabled. Clear app data to re-enable.",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
dialog.dismiss();
|
||||
});
|
||||
|
||||
okButton.setOnClickListener(v -> {
|
||||
dialog.dismiss();
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Failed to show K1 dialog", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a card view for a single token.
|
||||
*/
|
||||
private static LinearLayout createTokenCard(Activity activity, String token, int index, boolean singleToken) {
|
||||
LinearLayout card = new LinearLayout(activity);
|
||||
card.setOrientation(LinearLayout.VERTICAL);
|
||||
card.setBackgroundColor(COLOR_TOKEN_BG);
|
||||
card.setPadding(dpToPx(activity, 16), dpToPx(activity, 12),
|
||||
dpToPx(activity, 16), dpToPx(activity, 12));
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
card.setElevation(dpToPx(activity, 2));
|
||||
}
|
||||
card.setClickable(true);
|
||||
card.setFocusable(true);
|
||||
|
||||
// Token label (only show if multiple tokens).
|
||||
if (!singleToken) {
|
||||
TextView labelView = new TextView(activity);
|
||||
labelView.setText("Token #" + index);
|
||||
labelView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12);
|
||||
labelView.setTextColor(COLOR_ACCENT);
|
||||
labelView.setTypeface(Typeface.DEFAULT_BOLD);
|
||||
card.addView(labelView);
|
||||
}
|
||||
|
||||
// Token value.
|
||||
TextView tokenView = new TextView(activity);
|
||||
tokenView.setText(token.toUpperCase());
|
||||
tokenView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
|
||||
tokenView.setTextColor(COLOR_TEXT_PRIMARY);
|
||||
tokenView.setTypeface(Typeface.MONOSPACE);
|
||||
tokenView.setLetterSpacing(0.05f);
|
||||
LinearLayout.LayoutParams tokenParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||
);
|
||||
if (!singleToken) {
|
||||
tokenParams.topMargin = dpToPx(activity, 8);
|
||||
}
|
||||
card.addView(tokenView, tokenParams);
|
||||
|
||||
// Click to copy.
|
||||
card.setOnClickListener(v -> {
|
||||
ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
if (clipboard != null) {
|
||||
clipboard.setText(token.toUpperCase());
|
||||
Toast.makeText(activity, "Token copied!", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert dp to pixels.
|
||||
*/
|
||||
private static int dpToPx(Context context, float dp) {
|
||||
return (int) TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
dp,
|
||||
context.getResources().getDisplayMetrics()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get K1 tokens from log files.
|
||||
* Prioritizes pairing K1 tokens over reconnect tokens.
|
||||
*/
|
||||
private static Set<String> getK1TokensFromLogFiles() {
|
||||
Set<String> pairingTokens = new LinkedHashSet<>();
|
||||
Set<String> reconnectTokens = new LinkedHashSet<>();
|
||||
try {
|
||||
File logDir = new File("/data/data/" + PACKAGE_NAME + "/files/log");
|
||||
if (!logDir.exists() || !logDir.isDirectory()) {
|
||||
return pairingTokens;
|
||||
}
|
||||
|
||||
File[] logFiles = logDir.listFiles((dir, name) ->
|
||||
name.endsWith(".log") || name.endsWith(".log.") || name.matches(".*\\.log\\.\\d+"));
|
||||
|
||||
if (logFiles == null || logFiles.length == 0) {
|
||||
return pairingTokens;
|
||||
}
|
||||
|
||||
for (File logFile : logFiles) {
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader(logFile))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
// Determine if this is a pairing or reconnect context.
|
||||
boolean isPairingContext = line.toLowerCase().contains("watchbind");
|
||||
boolean isReconnectContext = line.toLowerCase().contains("watchreconnect");
|
||||
|
||||
String k1Token = null;
|
||||
|
||||
// First check for combined r3+k1 format (priority).
|
||||
Matcher combinedMatcher = K1_COMBINED_PATTERN.matcher(line);
|
||||
if (combinedMatcher.find()) {
|
||||
String combined = combinedMatcher.group(1);
|
||||
if (combined.length() == 64) {
|
||||
// Second half is the actual K1
|
||||
k1Token = combined.substring(32).toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
// Then check for standalone K1 format (only if not found in combined).
|
||||
if (k1Token == null) {
|
||||
Matcher standaloneMatcher = K1_STANDALONE_PATTERN.matcher(line);
|
||||
if (standaloneMatcher.find()) {
|
||||
String token = standaloneMatcher.group(1);
|
||||
if (token != null && token.length() == 32) {
|
||||
k1Token = token.toLowerCase();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add to appropriate set.
|
||||
if (k1Token != null) {
|
||||
if (isPairingContext && !isReconnectContext) {
|
||||
pairingTokens.add(k1Token);
|
||||
} else {
|
||||
reconnectTokens.add(k1Token);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Skip unreadable files.
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
// Fail silently.
|
||||
}
|
||||
|
||||
// Return pairing tokens first, add reconnect tokens if no pairing tokens found.
|
||||
if (!pairingTokens.isEmpty()) {
|
||||
Log.i(TAG, "Found " + pairingTokens.size() + " pairing K1 token(s)");
|
||||
return pairingTokens;
|
||||
}
|
||||
|
||||
if (!reconnectTokens.isEmpty()) {
|
||||
Log.i(TAG, "Found " + reconnectTokens.size() + " reconnect K1 token(s) (may not work for initial pairing)");
|
||||
}
|
||||
return reconnectTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to get K1 tokens from the database.
|
||||
*/
|
||||
private static String getK1TokensFromDatabase() {
|
||||
try {
|
||||
File dbDir = new File("/data/data/" + PACKAGE_NAME + "/databases");
|
||||
if (!dbDir.exists() || !dbDir.isDirectory()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
File[] dbFiles = dbDir.listFiles((dir, name) ->
|
||||
name.endsWith(".db") && !name.startsWith("google_app_measurement") && !name.contains("firebase"));
|
||||
|
||||
if (dbFiles == null || dbFiles.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (File dbFile : dbFiles) {
|
||||
String token = getK1TokensFromDatabase(dbFile);
|
||||
if (token != null) {
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (Exception ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract K1 tokens from a database file.
|
||||
*/
|
||||
private static String getK1TokensFromDatabase(File dbFile) {
|
||||
SQLiteDatabase db = null;
|
||||
try {
|
||||
db = SQLiteDatabase.openDatabase(dbFile.getPath(), null, SQLiteDatabase.OPEN_READONLY);
|
||||
|
||||
// Get all tables.
|
||||
Cursor cursor = db.rawQuery(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'",
|
||||
null
|
||||
);
|
||||
|
||||
List<String> tables = new ArrayList<>();
|
||||
while (cursor.moveToNext()) {
|
||||
tables.add(cursor.getString(0));
|
||||
}
|
||||
cursor.close();
|
||||
|
||||
// Scan all columns for 32-char hex strings.
|
||||
for (String table : tables) {
|
||||
Cursor schemaCursor = null;
|
||||
try {
|
||||
schemaCursor = db.rawQuery("PRAGMA table_info(" + table + ")", null);
|
||||
List<String> columns = new ArrayList<>();
|
||||
while (schemaCursor.moveToNext()) {
|
||||
columns.add(schemaCursor.getString(1));
|
||||
}
|
||||
schemaCursor.close();
|
||||
|
||||
for (String column : columns) {
|
||||
Cursor dataCursor = null;
|
||||
try {
|
||||
dataCursor = db.query(table, new String[]{column}, null, null, null, null, null);
|
||||
while (dataCursor.moveToNext()) {
|
||||
String value = dataCursor.getString(0);
|
||||
if (value != null && value.length() == 32 && value.matches("[0-9a-fA-F]{32}")) {
|
||||
// Skip obviously fake tokens (MD5 of empty string).
|
||||
if (!value.equalsIgnoreCase(EMPTY_MD5)) {
|
||||
dataCursor.close();
|
||||
db.close();
|
||||
return value.toLowerCase();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Skip non-string columns.
|
||||
} finally {
|
||||
if (dataCursor != null) {
|
||||
dataCursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Continue to next table.
|
||||
} finally {
|
||||
if (schemaCursor != null && !schemaCursor.isClosed()) {
|
||||
schemaCursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (Exception ex) {
|
||||
return null;
|
||||
} finally {
|
||||
if (db != null && db.isOpen()) {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the logged flag (useful for testing or re-pairing).
|
||||
*/
|
||||
public static void resetK1Logged() {
|
||||
k1Logged = false;
|
||||
lifecycleCallbacksRegistered = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the "don't show again" preference.
|
||||
*/
|
||||
public static void resetDontShowPreference() {
|
||||
if (appContext != null) {
|
||||
SharedPreferences prefs = appContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||
prefs.edit().putBoolean(KEY_DONT_SHOW_DIALOG, false).apply();
|
||||
}
|
||||
}
|
||||
}
|
||||
17
extensions/nothingx/stub/build.gradle.kts
Normal file
17
extensions/nothingx/stub/build.gradle.kts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
plugins {
|
||||
alias(libs.plugins.android.library)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "app.revanced.extension"
|
||||
compileSdk = 34
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 26
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
}
|
||||
1
extensions/nothingx/stub/src/main/AndroidManifest.xml
Normal file
1
extensions/nothingx/stub/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1 @@
|
|||
<manifest/>
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package app.revanced.extension.youtube.patches.components;
|
||||
package app.revanced.extension.shared.patches.components;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
|
||||
|
|
@ -15,13 +15,15 @@ import java.util.regex.Pattern;
|
|||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.ByteTrieSearch;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
|
||||
import app.revanced.extension.shared.settings.YouTubeAndMusicSettings;
|
||||
import app.revanced.extension.shared.patches.litho.Filter;
|
||||
|
||||
/**
|
||||
* Allows custom filtering using a path and optionally a proto buffer string.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
final class CustomFilter extends Filter {
|
||||
public final class CustomFilter extends Filter {
|
||||
|
||||
private static void showInvalidSyntaxToast(@NonNull String expression) {
|
||||
Utils.showToastLong(str("revanced_custom_filter_toast_invalid_syntax", expression));
|
||||
|
|
@ -45,7 +47,7 @@ final class CustomFilter extends Filter {
|
|||
@NonNull
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
static Collection<CustomFilterGroup> parseCustomFilterGroups() {
|
||||
String rawCustomFilterText = Settings.CUSTOM_FILTER_STRINGS.get();
|
||||
String rawCustomFilterText = YouTubeAndMusicSettings.CUSTOM_FILTER_STRINGS.get();
|
||||
if (rawCustomFilterText.isBlank()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
|
@ -100,7 +102,7 @@ final class CustomFilter extends Filter {
|
|||
ByteTrieSearch bufferSearch;
|
||||
|
||||
CustomFilterGroup(boolean startsWith, @NonNull String path) {
|
||||
super(Settings.CUSTOM_FILTER, path);
|
||||
super(YouTubeAndMusicSettings.CUSTOM_FILTER, path);
|
||||
this.startsWith = startsWith;
|
||||
}
|
||||
|
||||
|
|
@ -145,7 +147,7 @@ final class CustomFilter extends Filter {
|
|||
}
|
||||
|
||||
@Override
|
||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
// All callbacks are custom filter groups.
|
||||
CustomFilterGroup custom = (CustomFilterGroup) matchedGroup;
|
||||
|
|
@ -159,4 +161,4 @@ final class CustomFilter extends Filter {
|
|||
|
||||
return custom.bufferSearch.matches(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,12 @@
|
|||
package app.revanced.extension.youtube.patches.components;
|
||||
package app.revanced.extension.shared.patches.litho;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
|
||||
import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup;
|
||||
|
||||
/**
|
||||
* Filters litho based components.
|
||||
*
|
||||
|
|
@ -14,11 +17,11 @@ import java.util.List;
|
|||
* either an identifier or a path.
|
||||
* Then inside {@link #isFiltered(String, String, byte[], StringFilterGroup, FilterContentType, int)}
|
||||
* search for the buffer content using either a {@link ByteArrayFilterGroup} (if searching for 1 pattern)
|
||||
* or a {@link ByteArrayFilterGroupList} (if searching for more than 1 pattern).
|
||||
* or a {@link FilterGroupList.ByteArrayFilterGroupList} (if searching for more than 1 pattern).
|
||||
*
|
||||
* All callbacks must be registered before the constructor completes.
|
||||
*/
|
||||
abstract class Filter {
|
||||
public abstract class Filter {
|
||||
|
||||
public enum FilterContentType {
|
||||
IDENTIFIER,
|
||||
|
|
@ -65,7 +68,7 @@ abstract class Filter {
|
|||
* @param contentIndex Matched index of the identifier or path.
|
||||
* @return True if the litho component should be filtered out.
|
||||
*/
|
||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -0,0 +1,213 @@
|
|||
package app.revanced.extension.shared.patches.litho;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import app.revanced.extension.shared.ByteTrieSearch;
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.settings.BooleanSetting;
|
||||
|
||||
public abstract class FilterGroup<T> {
|
||||
public final static class FilterGroupResult {
|
||||
private BooleanSetting setting;
|
||||
private int matchedIndex;
|
||||
private int matchedLength;
|
||||
// In the future it might be useful to include which pattern matched,
|
||||
// but for now that is not needed.
|
||||
|
||||
FilterGroupResult() {
|
||||
this(null, -1, 0);
|
||||
}
|
||||
|
||||
FilterGroupResult(BooleanSetting setting, int matchedIndex, int matchedLength) {
|
||||
setValues(setting, matchedIndex, matchedLength);
|
||||
}
|
||||
|
||||
public void setValues(BooleanSetting setting, int matchedIndex, int matchedLength) {
|
||||
this.setting = setting;
|
||||
this.matchedIndex = matchedIndex;
|
||||
this.matchedLength = matchedLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* A null value if the group has no setting,
|
||||
* or if no match is returned from {@link FilterGroupList#check(Object)}.
|
||||
*/
|
||||
public BooleanSetting getSetting() {
|
||||
return setting;
|
||||
}
|
||||
|
||||
public boolean isFiltered() {
|
||||
return matchedIndex >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Matched index of first pattern that matched, or -1 if nothing matched.
|
||||
*/
|
||||
public int getMatchedIndex() {
|
||||
return matchedIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Length of the matched filter pattern.
|
||||
*/
|
||||
public int getMatchedLength() {
|
||||
return matchedLength;
|
||||
}
|
||||
}
|
||||
|
||||
protected final BooleanSetting setting;
|
||||
protected final T[] filters;
|
||||
|
||||
/**
|
||||
* Initialize a new filter group.
|
||||
*
|
||||
* @param setting The associated setting.
|
||||
* @param filters The filters.
|
||||
*/
|
||||
@SafeVarargs
|
||||
public FilterGroup(final BooleanSetting setting, final T... filters) {
|
||||
this.setting = setting;
|
||||
this.filters = filters;
|
||||
if (filters.length == 0) {
|
||||
throw new IllegalArgumentException("Must use one or more filter patterns (zero specified)");
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return setting == null || setting.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If {@link FilterGroupList} should include this group when searching.
|
||||
* By default, all filters are included except non enabled settings that require reboot.
|
||||
*/
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
public boolean includeInSearch() {
|
||||
return isEnabled() || !setting.rebootApp;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + ": " + (setting == null ? "(null setting)" : setting);
|
||||
}
|
||||
|
||||
public abstract FilterGroupResult check(final T stack);
|
||||
|
||||
|
||||
public static class StringFilterGroup extends FilterGroup<String> {
|
||||
|
||||
public StringFilterGroup(final BooleanSetting setting, final String... filters) {
|
||||
super(setting, filters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilterGroupResult check(final String string) {
|
||||
int matchedIndex = -1;
|
||||
int matchedLength = 0;
|
||||
if (isEnabled()) {
|
||||
for (String pattern : filters) {
|
||||
if (!string.isEmpty()) {
|
||||
final int indexOf = string.indexOf(pattern);
|
||||
if (indexOf >= 0) {
|
||||
matchedIndex = indexOf;
|
||||
matchedLength = pattern.length();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return new FilterGroupResult(setting, matchedIndex, matchedLength);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If you have more than 1 filter patterns, then all instances of
|
||||
* this class should filtered using {@link FilterGroupList.ByteArrayFilterGroupList#check(byte[])},
|
||||
* which uses a prefix tree to give better performance.
|
||||
*/
|
||||
public static class ByteArrayFilterGroup extends FilterGroup<byte[]> {
|
||||
|
||||
private volatile int[][] failurePatterns;
|
||||
|
||||
// Modified implementation from https://stackoverflow.com/a/1507813
|
||||
private static int indexOf(final byte[] data, final byte[] pattern, final int[] failure) {
|
||||
// Finds the first occurrence of the pattern in the byte array using
|
||||
// KMP matching algorithm.
|
||||
int patternLength = pattern.length;
|
||||
for (int i = 0, j = 0, dataLength = data.length; i < dataLength; i++) {
|
||||
while (j > 0 && pattern[j] != data[i]) {
|
||||
j = failure[j - 1];
|
||||
}
|
||||
if (pattern[j] == data[i]) {
|
||||
j++;
|
||||
}
|
||||
if (j == patternLength) {
|
||||
return i - patternLength + 1;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static int[] createFailurePattern(byte[] pattern) {
|
||||
// Computes the failure function using a boot-strapping process,
|
||||
// where the pattern is matched against itself.
|
||||
final int patternLength = pattern.length;
|
||||
final int[] failure = new int[patternLength];
|
||||
|
||||
for (int i = 1, j = 0; i < patternLength; i++) {
|
||||
while (j > 0 && pattern[j] != pattern[i]) {
|
||||
j = failure[j - 1];
|
||||
}
|
||||
if (pattern[j] == pattern[i]) {
|
||||
j++;
|
||||
}
|
||||
failure[i] = j;
|
||||
}
|
||||
return failure;
|
||||
}
|
||||
|
||||
public ByteArrayFilterGroup(BooleanSetting setting, byte[]... filters) {
|
||||
super(setting, filters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the Strings into byte arrays. Used to search for text in binary data.
|
||||
*/
|
||||
public ByteArrayFilterGroup(BooleanSetting setting, String... filters) {
|
||||
super(setting, ByteTrieSearch.convertStringsToBytes(filters));
|
||||
}
|
||||
|
||||
private synchronized void buildFailurePatterns() {
|
||||
if (failurePatterns != null) return; // Thread race and another thread already initialized the search.
|
||||
Logger.printDebug(() -> "Building failure array for: " + this);
|
||||
int[][] failurePatterns = new int[filters.length][];
|
||||
int i = 0;
|
||||
for (byte[] pattern : filters) {
|
||||
failurePatterns[i++] = createFailurePattern(pattern);
|
||||
}
|
||||
this.failurePatterns = failurePatterns; // Must set after initialization finishes.
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilterGroupResult check(final byte[] bytes) {
|
||||
int matchedLength = 0;
|
||||
int matchedIndex = -1;
|
||||
if (isEnabled()) {
|
||||
int[][] failures = failurePatterns;
|
||||
if (failures == null) {
|
||||
buildFailurePatterns(); // Lazy load.
|
||||
failures = failurePatterns;
|
||||
}
|
||||
for (int i = 0, length = filters.length; i < length; i++) {
|
||||
byte[] filter = filters[i];
|
||||
matchedIndex = indexOf(bytes, filter, failures[i]);
|
||||
if (matchedIndex >= 0) {
|
||||
matchedLength = filter.length;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return new FilterGroupResult(setting, matchedIndex, matchedLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +1,22 @@
|
|||
package app.revanced.extension.youtube.patches.components;
|
||||
package app.revanced.extension.shared.patches.litho;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import app.revanced.extension.shared.ByteTrieSearch;
|
||||
import app.revanced.extension.shared.StringTrieSearch;
|
||||
import app.revanced.extension.shared.TrieSearch;
|
||||
import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup;
|
||||
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
|
||||
|
||||
abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<T> {
|
||||
public abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<T> {
|
||||
|
||||
private final List<T> filterGroups = new ArrayList<>();
|
||||
private final TrieSearch<V> search = createSearchGraph();
|
||||
|
||||
@SafeVarargs
|
||||
protected final void addAll(final T... groups) {
|
||||
public final void addAll(final T... groups) {
|
||||
filterGroups.addAll(Arrays.asList(groups));
|
||||
|
||||
for (T group : groups) {
|
||||
|
|
@ -41,18 +42,7 @@ abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<
|
|||
return filterGroups.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(@NonNull Consumer<? super T> action) {
|
||||
filterGroups.forEach(action);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Spliterator<T> spliterator() {
|
||||
return filterGroups.spliterator();
|
||||
}
|
||||
|
||||
protected FilterGroup.FilterGroupResult check(V stack) {
|
||||
public FilterGroup.FilterGroupResult check(V stack) {
|
||||
FilterGroup.FilterGroupResult result = new FilterGroup.FilterGroupResult();
|
||||
search.matches(stack, result);
|
||||
return result;
|
||||
|
|
@ -60,21 +50,21 @@ abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<
|
|||
}
|
||||
|
||||
protected abstract TrieSearch<V> createSearchGraph();
|
||||
}
|
||||
|
||||
final class StringFilterGroupList extends FilterGroupList<String, StringFilterGroup> {
|
||||
protected StringTrieSearch createSearchGraph() {
|
||||
return new StringTrieSearch();
|
||||
public static final class StringFilterGroupList extends FilterGroupList<String, StringFilterGroup> {
|
||||
protected StringTrieSearch createSearchGraph() {
|
||||
return new StringTrieSearch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If searching for a single byte pattern, then it is slightly better to use
|
||||
* {@link ByteArrayFilterGroup#check(byte[])} as it uses KMP which is faster
|
||||
* than a prefix tree to search for only 1 pattern.
|
||||
*/
|
||||
final class ByteArrayFilterGroupList extends FilterGroupList<byte[], ByteArrayFilterGroup> {
|
||||
protected ByteTrieSearch createSearchGraph() {
|
||||
return new ByteTrieSearch();
|
||||
/**
|
||||
* If searching for a single byte pattern, then it is slightly better to use
|
||||
* {@link ByteArrayFilterGroup#check(byte[])} as it uses KMP which is faster
|
||||
* than a prefix tree to search for only 1 pattern.
|
||||
*/
|
||||
public static final class ByteArrayFilterGroupList extends FilterGroupList<byte[], ByteArrayFilterGroup> {
|
||||
protected ByteTrieSearch createSearchGraph() {
|
||||
return new ByteTrieSearch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package app.revanced.extension.youtube.patches.components;
|
||||
package app.revanced.extension.shared.patches.litho;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
|
@ -7,9 +7,11 @@ import java.nio.ByteBuffer;
|
|||
import java.util.List;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
import app.revanced.extension.shared.StringTrieSearch;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
import app.revanced.extension.shared.settings.YouTubeAndMusicSettings;
|
||||
|
||||
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class LithoFilterPatch {
|
||||
|
|
@ -36,7 +38,7 @@ public final class LithoFilterPatch {
|
|||
builder.append(identifier);
|
||||
builder.append(" Path: ");
|
||||
builder.append(path);
|
||||
if (Settings.DEBUG_PROTOBUFFER.get()) {
|
||||
if (YouTubeAndMusicSettings.DEBUG_PROTOBUFFER.get()) {
|
||||
builder.append(" BufferStrings: ");
|
||||
findAsciiStrings(builder, buffer);
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package app.revanced.extension.shared.settings;
|
||||
|
||||
import static app.revanced.extension.shared.settings.Setting.parent;
|
||||
import static java.lang.Boolean.FALSE;
|
||||
|
||||
public class YouTubeAndMusicSettings extends BaseSettings {
|
||||
// Custom filter
|
||||
public static final BooleanSetting CUSTOM_FILTER = new BooleanSetting("revanced_custom_filter", FALSE);
|
||||
public static final StringSetting CUSTOM_FILTER_STRINGS = new StringSetting("revanced_custom_filter_strings", "", true, parent(CUSTOM_FILTER));
|
||||
|
||||
// Miscellaneous
|
||||
public static final BooleanSetting DEBUG_PROTOBUFFER = new BooleanSetting("revanced_debug_protobuffer", FALSE, false,
|
||||
"revanced_debug_protobuffer_user_dialog_message", parent(BaseSettings.DEBUG));
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ import android.os.Environment;
|
|||
import android.provider.MediaStore;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import com.strava.core.data.MediaType;
|
||||
import com.strava.mediamodels.data.MediaType;
|
||||
import com.strava.photos.data.Media;
|
||||
|
||||
import okhttp3.*;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,227 @@
|
|||
package app.revanced.extension.strava;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
|
||||
import com.strava.modularframework.data.Destination;
|
||||
import com.strava.modularframework.data.GenericLayoutModule;
|
||||
import com.strava.modularframework.data.GenericModuleField;
|
||||
import com.strava.modularframework.data.ListField;
|
||||
import com.strava.modularframework.data.ListProperties;
|
||||
import com.strava.modularframework.data.ModularComponent;
|
||||
import com.strava.modularframework.data.ModularEntry;
|
||||
import com.strava.modularframework.data.ModularEntryContainer;
|
||||
import com.strava.modularframework.data.ModularMenuItem;
|
||||
import com.strava.modularframework.data.Module;
|
||||
import com.strava.modularframework.data.MultiStateFieldDescriptor;
|
||||
import com.strava.modularframeworknetwork.ModularEntryNetworkContainer;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
public class HideDistractionsPatch {
|
||||
public static boolean upselling;
|
||||
public static boolean promo;
|
||||
public static boolean followSuggestions;
|
||||
public static boolean challengeSuggestions;
|
||||
public static boolean joinChallenge;
|
||||
public static boolean joinClub;
|
||||
public static boolean activityLookback;
|
||||
|
||||
public static List<ModularEntry> filterChildrenEntries(ModularEntry modularEntry) {
|
||||
if (hideModularEntry(modularEntry)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return modularEntry.getChildrenEntries$original().stream()
|
||||
.filter(childrenEntry -> !hideModularEntry(childrenEntry))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static List<ModularEntry> filterEntries(ModularEntryContainer modularEntryContainer) {
|
||||
if (hideModularEntryContainer(modularEntryContainer)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return modularEntryContainer.getEntries$original().stream()
|
||||
.filter(entry -> !hideModularEntry(entry))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static List<ModularEntry> filterEntries(ModularEntryNetworkContainer modularEntryNetworkContainer) {
|
||||
if (hideModularEntryNetworkContainer(modularEntryNetworkContainer)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return modularEntryNetworkContainer.getEntries$original().stream()
|
||||
.filter(entry -> !hideModularEntry(entry))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static List<ModularMenuItem> filterMenuItems(ModularEntryContainer modularEntryContainer) {
|
||||
if (hideModularEntryContainer(modularEntryContainer)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return modularEntryContainer.getMenuItems$original().stream()
|
||||
.filter(menuItem -> !hideModularMenuItem(menuItem))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static ListProperties filterProperties(ModularEntryContainer modularEntryContainer) {
|
||||
if (hideModularEntryContainer(modularEntryContainer)) {
|
||||
return null;
|
||||
}
|
||||
return modularEntryContainer.getProperties$original();
|
||||
}
|
||||
|
||||
public static ListProperties filterProperties(ModularEntryNetworkContainer modularEntryNetworkContainer) {
|
||||
if (hideModularEntryNetworkContainer(modularEntryNetworkContainer)) {
|
||||
return null;
|
||||
}
|
||||
return modularEntryNetworkContainer.getProperties$original();
|
||||
}
|
||||
|
||||
public static ListField filterField(ListProperties listProperties, String key) {
|
||||
ListField listField = listProperties.getField$original(key);
|
||||
if (hideListField(listField)) {
|
||||
return null;
|
||||
}
|
||||
return listField;
|
||||
}
|
||||
|
||||
public static List<ListField> filterFields(ListField listField) {
|
||||
if (hideListField(listField)) {
|
||||
return null;
|
||||
}
|
||||
return listField.getFields$original().stream()
|
||||
.filter(field -> !hideListField(field))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static List<Module> filterModules(ModularEntry modularEntry) {
|
||||
if (hideModularEntry(modularEntry)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return modularEntry.getModules$original().stream()
|
||||
.filter(module -> !hideModule(module))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static GenericModuleField filterField(GenericLayoutModule genericLayoutModule, String key) {
|
||||
if (hideGenericLayoutModule(genericLayoutModule)) {
|
||||
return null;
|
||||
}
|
||||
GenericModuleField field = genericLayoutModule.getField$original(key);
|
||||
if (hideGenericModuleField(field)) {
|
||||
return null;
|
||||
}
|
||||
return field;
|
||||
}
|
||||
|
||||
public static GenericModuleField[] filterFields(GenericLayoutModule genericLayoutModule) {
|
||||
if (hideGenericLayoutModule(genericLayoutModule)) {
|
||||
return new GenericModuleField[0];
|
||||
}
|
||||
return Arrays.stream(genericLayoutModule.getFields$original())
|
||||
.filter(field -> !hideGenericModuleField(field))
|
||||
.toArray(GenericModuleField[]::new);
|
||||
}
|
||||
|
||||
public static GenericLayoutModule[] filterSubmodules(GenericLayoutModule genericLayoutModule) {
|
||||
if (hideGenericLayoutModule(genericLayoutModule)) {
|
||||
return new GenericLayoutModule[0];
|
||||
}
|
||||
return Arrays.stream(genericLayoutModule.getSubmodules$original())
|
||||
.filter(submodule -> !hideGenericLayoutModule(submodule))
|
||||
.toArray(GenericLayoutModule[]::new);
|
||||
}
|
||||
|
||||
public static List<Module> filterSubmodules(ModularComponent modularComponent) {
|
||||
if (hideByName(modularComponent.getPage()) || hideByName(modularComponent.getElement())) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return modularComponent.getSubmodules$original().stream()
|
||||
.filter(submodule -> !hideModule(submodule))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static Map<String, GenericModuleField> filterStateMap(MultiStateFieldDescriptor multiStateFieldDescriptor) {
|
||||
return multiStateFieldDescriptor.getStateMap$original().entrySet().stream()
|
||||
.filter(entry -> !hideGenericModuleField(entry.getValue()))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
}
|
||||
|
||||
private static boolean hideModule(Module module) {
|
||||
return module == null ||
|
||||
hideByName(module.getPage()) ||
|
||||
hideByName(module.getElement());
|
||||
}
|
||||
|
||||
private static boolean hideModularEntry(ModularEntry modularEntry) {
|
||||
return modularEntry == null ||
|
||||
hideByName(modularEntry.getPage()) ||
|
||||
hideByName(modularEntry.getElement()) ||
|
||||
hideByDestination(modularEntry.getDestination());
|
||||
}
|
||||
|
||||
private static boolean hideGenericLayoutModule(GenericLayoutModule genericLayoutModule) {
|
||||
try {
|
||||
return genericLayoutModule == null ||
|
||||
hideByName(genericLayoutModule.getPage()) ||
|
||||
hideByName(genericLayoutModule.getElement()) ||
|
||||
hideByDestination(genericLayoutModule.getDestination());
|
||||
} catch (RuntimeException getParentEntryOrThrowException) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hideListField(ListField listField) {
|
||||
return listField == null ||
|
||||
hideByName(listField.getElement()) ||
|
||||
hideByDestination(listField.getDestination());
|
||||
}
|
||||
|
||||
private static boolean hideGenericModuleField(GenericModuleField genericModuleField) {
|
||||
return genericModuleField == null ||
|
||||
hideByName(genericModuleField.getElement()) ||
|
||||
hideByDestination(genericModuleField.getDestination());
|
||||
}
|
||||
|
||||
private static boolean hideModularEntryContainer(ModularEntryContainer modularEntryContainer) {
|
||||
return modularEntryContainer == null ||
|
||||
hideByName(modularEntryContainer.getPage());
|
||||
}
|
||||
|
||||
private static boolean hideModularEntryNetworkContainer(ModularEntryNetworkContainer modularEntryNetworkContainer) {
|
||||
return modularEntryNetworkContainer == null ||
|
||||
hideByName(modularEntryNetworkContainer.getPage());
|
||||
}
|
||||
|
||||
private static boolean hideModularMenuItem(ModularMenuItem modularMenuItem) {
|
||||
return modularMenuItem == null ||
|
||||
hideByName(modularMenuItem.getElementName()) ||
|
||||
hideByDestination(modularMenuItem.getDestination());
|
||||
}
|
||||
|
||||
private static boolean hideByName(String name) {
|
||||
return name != null && (
|
||||
upselling && name.contains("_upsell") ||
|
||||
promo && (name.equals("promo") || name.equals("top_of_tab_promo")) ||
|
||||
followSuggestions && name.equals("suggested_follows") ||
|
||||
challengeSuggestions && name.equals("suggested_challenges") ||
|
||||
joinChallenge && name.equals("challenge") ||
|
||||
joinClub && name.equals("club") ||
|
||||
activityLookback && name.equals("highlighted_activity_lookback")
|
||||
);
|
||||
}
|
||||
|
||||
private static boolean hideByDestination(Destination destination) {
|
||||
if (destination == null) {
|
||||
return false;
|
||||
}
|
||||
String url = destination.getUrl();
|
||||
return url != null && (
|
||||
upselling && url.startsWith("strava://subscription/checkout")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.strava.core.data;
|
||||
package com.strava.mediamodels.data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.strava.core.data;
|
||||
package com.strava.mediamodels.data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.strava.core.data;
|
||||
package com.strava.mediamodels.data;
|
||||
|
||||
public enum MediaType {
|
||||
PHOTO(1),
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.strava.core.data;
|
||||
package com.strava.mediamodels.data;
|
||||
|
||||
import java.util.SortedMap;
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.strava.core.data;
|
||||
package com.strava.mediamodels.data;
|
||||
|
||||
public enum RemoteMediaStatus {
|
||||
NEW,
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package com.strava.modularframework.data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public abstract class Destination implements Serializable {
|
||||
public abstract String getUrl();
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.strava.modularframework.data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public abstract class GenericLayoutModule implements Serializable, Module {
|
||||
public abstract Destination getDestination();
|
||||
|
||||
@Override
|
||||
public abstract String getElement();
|
||||
|
||||
public abstract GenericModuleField getField(String key);
|
||||
|
||||
// Added by patch.
|
||||
public abstract GenericModuleField getField$original(String key);
|
||||
|
||||
public abstract GenericModuleField[] getFields();
|
||||
|
||||
// Added by patch.
|
||||
public abstract GenericModuleField[] getFields$original();
|
||||
|
||||
@Override
|
||||
public abstract String getPage();
|
||||
|
||||
public abstract GenericLayoutModule[] getSubmodules();
|
||||
|
||||
// Added by patch.
|
||||
public abstract GenericLayoutModule[] getSubmodules$original();
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.strava.modularframework.data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public abstract class GenericModuleField implements Serializable {
|
||||
public abstract Destination getDestination();
|
||||
|
||||
public abstract String getElement();
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.strava.modularframework.data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public abstract class ListField {
|
||||
public abstract Destination getDestination();
|
||||
|
||||
public abstract String getElement();
|
||||
|
||||
public abstract List<ListField> getFields();
|
||||
|
||||
// Added by patch.
|
||||
public abstract List<ListField> getFields$original();
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.strava.modularframework.data;
|
||||
|
||||
public abstract class ListProperties {
|
||||
public abstract ListField getField(String key);
|
||||
|
||||
// Added by patch.
|
||||
public abstract ListField getField$original(String key);
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.strava.modularframework.data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public abstract class ModularComponent implements Module {
|
||||
@Override
|
||||
public abstract String getElement();
|
||||
|
||||
@Override
|
||||
public abstract String getPage();
|
||||
|
||||
public abstract List<Module> getSubmodules();
|
||||
|
||||
// Added by patch.
|
||||
public abstract List<Module> getSubmodules$original();
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.strava.modularframework.data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ModularEntry {
|
||||
List<ModularEntry> getChildrenEntries();
|
||||
|
||||
// Added by patch.
|
||||
List<ModularEntry> getChildrenEntries$original();
|
||||
|
||||
Destination getDestination();
|
||||
|
||||
String getElement();
|
||||
|
||||
List<Module> getModules();
|
||||
|
||||
// Added by patch.
|
||||
List<Module> getModules$original();
|
||||
|
||||
String getPage();
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.strava.modularframework.data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public abstract class ModularEntryContainer {
|
||||
public abstract List<ModularEntry> getEntries();
|
||||
|
||||
// Added by patch.
|
||||
public abstract List<ModularEntry> getEntries$original();
|
||||
|
||||
public abstract List<ModularMenuItem> getMenuItems();
|
||||
|
||||
// Added by patch.
|
||||
public abstract List<ModularMenuItem> getMenuItems$original();
|
||||
|
||||
public abstract String getPage();
|
||||
|
||||
public abstract ListProperties getProperties();
|
||||
|
||||
// Added by patch.
|
||||
public abstract ListProperties getProperties$original();
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package com.strava.modularframework.data;
|
||||
|
||||
public abstract class ModularMenuItem {
|
||||
public abstract Destination getDestination();
|
||||
|
||||
public abstract String getElementName();
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package com.strava.modularframework.data;
|
||||
|
||||
public interface Module {
|
||||
String getElement();
|
||||
|
||||
String getPage();
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.strava.modularframework.data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class MultiStateFieldDescriptor {
|
||||
public abstract Map<String, GenericModuleField> getStateMap();
|
||||
|
||||
// Added by patch.
|
||||
public abstract Map<String, GenericModuleField> getStateMap$original();
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.strava.modularframeworknetwork;
|
||||
|
||||
import com.strava.modularframework.data.ListProperties;
|
||||
import com.strava.modularframework.data.ModularEntry;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class ModularEntryNetworkContainer {
|
||||
public abstract List<ModularEntry> getEntries();
|
||||
|
||||
// Added by patch.
|
||||
public abstract List<ModularEntry> getEntries$original();
|
||||
|
||||
public abstract String getPage();
|
||||
|
||||
public abstract ListProperties getProperties();
|
||||
|
||||
// Added by patch.
|
||||
public abstract ListProperties getProperties$original();
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
package com.strava.photos.data;
|
||||
|
||||
import com.strava.core.data.MediaDimension;
|
||||
import com.strava.core.data.MediaType;
|
||||
import com.strava.core.data.RemoteMediaContent;
|
||||
import com.strava.core.data.RemoteMediaStatus;
|
||||
import com.strava.mediamodels.data.MediaDimension;
|
||||
import com.strava.mediamodels.data.MediaType;
|
||||
import com.strava.mediamodels.data.RemoteMediaContent;
|
||||
import com.strava.mediamodels.data.RemoteMediaStatus;
|
||||
|
||||
import java.util.SortedMap;
|
||||
|
||||
public abstract class Media implements RemoteMediaContent {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ import java.util.List;
|
|||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.StringTrieSearch;
|
||||
import app.revanced.extension.shared.patches.litho.Filter;
|
||||
import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup;
|
||||
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
|
|
@ -153,8 +156,8 @@ public final class AdsFilter extends Filter {
|
|||
}
|
||||
|
||||
@Override
|
||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
if (matchedGroup == playerShoppingShelf) {
|
||||
return contentIndex == 0 && playerShoppingShelfBuffer.check(buffer).isFiltered();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package app.revanced.extension.youtube.patches.components;
|
||||
|
||||
import app.revanced.extension.shared.patches.litho.Filter;
|
||||
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
|
||||
import app.revanced.extension.youtube.patches.playback.quality.AdvancedVideoQualityMenuPatch;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
|
|
@ -19,7 +21,7 @@ public final class AdvancedVideoQualityMenuFilter extends Filter {
|
|||
}
|
||||
|
||||
@Override
|
||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
isVideoQualityMenuVisible = true;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
package app.revanced.extension.youtube.patches.components;
|
||||
|
||||
import app.revanced.extension.shared.patches.litho.FilterGroupList.ByteArrayFilterGroupList;
|
||||
import app.revanced.extension.shared.patches.litho.Filter;
|
||||
import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup;
|
||||
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
final class ButtonsFilter extends Filter {
|
||||
public final class ButtonsFilter extends Filter {
|
||||
private static final String COMPACT_CHANNEL_BAR_PATH_PREFIX = "compact_channel_bar.e";
|
||||
private static final String VIDEO_ACTION_BAR_PATH_PREFIX = "video_action_bar.e";
|
||||
private static final String VIDEO_ACTION_BAR_PATH = "video_action_bar.e";
|
||||
|
|
@ -118,7 +122,7 @@ final class ButtonsFilter extends Filter {
|
|||
}
|
||||
|
||||
@Override
|
||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
if (matchedGroup == likeSubscribeGlow) {
|
||||
return (path.startsWith(VIDEO_ACTION_BAR_PATH_PREFIX) || path.startsWith(COMPACT_CHANNEL_BAR_PATH_PREFIX))
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
package app.revanced.extension.youtube.patches.components;
|
||||
|
||||
import app.revanced.extension.shared.patches.litho.Filter;
|
||||
import app.revanced.extension.shared.patches.litho.FilterGroup.*;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.shared.PlayerType;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
final class CommentsFilter extends Filter {
|
||||
public final class CommentsFilter extends Filter {
|
||||
|
||||
private static final String COMMENT_COMPOSER_PATH = "comment_composer.e";
|
||||
|
||||
|
|
@ -88,8 +90,8 @@ final class CommentsFilter extends Filter {
|
|||
}
|
||||
|
||||
@Override
|
||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
if (matchedGroup == chipBar) {
|
||||
// Playlist sort button uses same components and must only filter if the player is opened.
|
||||
return PlayerType.getCurrent().isMaximizedOrFullscreen()
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
package app.revanced.extension.youtube.patches.components;
|
||||
|
||||
import app.revanced.extension.shared.StringTrieSearch;
|
||||
import app.revanced.extension.shared.patches.litho.Filter;
|
||||
import app.revanced.extension.shared.patches.litho.FilterGroup.*;
|
||||
import app.revanced.extension.shared.patches.litho.FilterGroupList.*;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.shared.PlayerType;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
final class DescriptionComponentsFilter extends Filter {
|
||||
public final class DescriptionComponentsFilter extends Filter {
|
||||
|
||||
private static final String INFOCARDS_SECTION_PATH = "infocards_section.e";
|
||||
|
||||
|
|
@ -128,8 +131,8 @@ final class DescriptionComponentsFilter extends Filter {
|
|||
}
|
||||
|
||||
@Override
|
||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
|
||||
if (matchedGroup == aiGeneratedVideoSummarySection || matchedGroup == hypePoints) {
|
||||
// Only hide if player is open, in case this component is used somewhere else.
|
||||
|
|
|
|||
|
|
@ -1,214 +0,0 @@
|
|||
package app.revanced.extension.youtube.patches.components;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.settings.BooleanSetting;
|
||||
import app.revanced.extension.shared.ByteTrieSearch;
|
||||
|
||||
abstract class FilterGroup<T> {
|
||||
final static class FilterGroupResult {
|
||||
private BooleanSetting setting;
|
||||
private int matchedIndex;
|
||||
private int matchedLength;
|
||||
// In the future it might be useful to include which pattern matched,
|
||||
// but for now that is not needed.
|
||||
|
||||
FilterGroupResult() {
|
||||
this(null, -1, 0);
|
||||
}
|
||||
|
||||
FilterGroupResult(BooleanSetting setting, int matchedIndex, int matchedLength) {
|
||||
setValues(setting, matchedIndex, matchedLength);
|
||||
}
|
||||
|
||||
public void setValues(BooleanSetting setting, int matchedIndex, int matchedLength) {
|
||||
this.setting = setting;
|
||||
this.matchedIndex = matchedIndex;
|
||||
this.matchedLength = matchedLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* A null value if the group has no setting,
|
||||
* or if no match is returned from {@link FilterGroupList#check(Object)}.
|
||||
*/
|
||||
public BooleanSetting getSetting() {
|
||||
return setting;
|
||||
}
|
||||
|
||||
public boolean isFiltered() {
|
||||
return matchedIndex >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Matched index of first pattern that matched, or -1 if nothing matched.
|
||||
*/
|
||||
public int getMatchedIndex() {
|
||||
return matchedIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Length of the matched filter pattern.
|
||||
*/
|
||||
public int getMatchedLength() {
|
||||
return matchedLength;
|
||||
}
|
||||
}
|
||||
|
||||
protected final BooleanSetting setting;
|
||||
protected final T[] filters;
|
||||
|
||||
/**
|
||||
* Initialize a new filter group.
|
||||
*
|
||||
* @param setting The associated setting.
|
||||
* @param filters The filters.
|
||||
*/
|
||||
@SafeVarargs
|
||||
public FilterGroup(final BooleanSetting setting, final T... filters) {
|
||||
this.setting = setting;
|
||||
this.filters = filters;
|
||||
if (filters.length == 0) {
|
||||
throw new IllegalArgumentException("Must use one or more filter patterns (zero specified)");
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return setting == null || setting.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If {@link FilterGroupList} should include this group when searching.
|
||||
* By default, all filters are included except non enabled settings that require reboot.
|
||||
*/
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
public boolean includeInSearch() {
|
||||
return isEnabled() || !setting.rebootApp;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + ": " + (setting == null ? "(null setting)" : setting);
|
||||
}
|
||||
|
||||
public abstract FilterGroupResult check(final T stack);
|
||||
}
|
||||
|
||||
class StringFilterGroup extends FilterGroup<String> {
|
||||
|
||||
public StringFilterGroup(final BooleanSetting setting, final String... filters) {
|
||||
super(setting, filters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilterGroupResult check(final String string) {
|
||||
int matchedIndex = -1;
|
||||
int matchedLength = 0;
|
||||
if (isEnabled()) {
|
||||
for (String pattern : filters) {
|
||||
if (!string.isEmpty()) {
|
||||
final int indexOf = string.indexOf(pattern);
|
||||
if (indexOf >= 0) {
|
||||
matchedIndex = indexOf;
|
||||
matchedLength = pattern.length();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return new FilterGroupResult(setting, matchedIndex, matchedLength);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If you have more than 1 filter patterns, then all instances of
|
||||
* this class should filtered using {@link ByteArrayFilterGroupList#check(byte[])},
|
||||
* which uses a prefix tree to give better performance.
|
||||
*/
|
||||
class ByteArrayFilterGroup extends FilterGroup<byte[]> {
|
||||
|
||||
private volatile int[][] failurePatterns;
|
||||
|
||||
// Modified implementation from https://stackoverflow.com/a/1507813
|
||||
private static int indexOf(final byte[] data, final byte[] pattern, final int[] failure) {
|
||||
// Finds the first occurrence of the pattern in the byte array using
|
||||
// KMP matching algorithm.
|
||||
int patternLength = pattern.length;
|
||||
for (int i = 0, j = 0, dataLength = data.length; i < dataLength; i++) {
|
||||
while (j > 0 && pattern[j] != data[i]) {
|
||||
j = failure[j - 1];
|
||||
}
|
||||
if (pattern[j] == data[i]) {
|
||||
j++;
|
||||
}
|
||||
if (j == patternLength) {
|
||||
return i - patternLength + 1;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static int[] createFailurePattern(byte[] pattern) {
|
||||
// Computes the failure function using a boot-strapping process,
|
||||
// where the pattern is matched against itself.
|
||||
final int patternLength = pattern.length;
|
||||
final int[] failure = new int[patternLength];
|
||||
|
||||
for (int i = 1, j = 0; i < patternLength; i++) {
|
||||
while (j > 0 && pattern[j] != pattern[i]) {
|
||||
j = failure[j - 1];
|
||||
}
|
||||
if (pattern[j] == pattern[i]) {
|
||||
j++;
|
||||
}
|
||||
failure[i] = j;
|
||||
}
|
||||
return failure;
|
||||
}
|
||||
|
||||
public ByteArrayFilterGroup(BooleanSetting setting, byte[]... filters) {
|
||||
super(setting, filters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the Strings into byte arrays. Used to search for text in binary data.
|
||||
*/
|
||||
public ByteArrayFilterGroup(BooleanSetting setting, String... filters) {
|
||||
super(setting, ByteTrieSearch.convertStringsToBytes(filters));
|
||||
}
|
||||
|
||||
private synchronized void buildFailurePatterns() {
|
||||
if (failurePatterns != null) return; // Thread race and another thread already initialized the search.
|
||||
Logger.printDebug(() -> "Building failure array for: " + this);
|
||||
int[][] failurePatterns = new int[filters.length][];
|
||||
int i = 0;
|
||||
for (byte[] pattern : filters) {
|
||||
failurePatterns[i++] = createFailurePattern(pattern);
|
||||
}
|
||||
this.failurePatterns = failurePatterns; // Must set after initialization finishes.
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilterGroupResult check(final byte[] bytes) {
|
||||
int matchedLength = 0;
|
||||
int matchedIndex = -1;
|
||||
if (isEnabled()) {
|
||||
int[][] failures = failurePatterns;
|
||||
if (failures == null) {
|
||||
buildFailurePatterns(); // Lazy load.
|
||||
failures = failurePatterns;
|
||||
}
|
||||
for (int i = 0, length = filters.length; i < length; i++) {
|
||||
byte[] filter = filters[i];
|
||||
matchedIndex = indexOf(bytes, filter, failures[i]);
|
||||
if (matchedIndex >= 0) {
|
||||
matchedLength = filter.length;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return new FilterGroupResult(setting, matchedIndex, matchedLength);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
package app.revanced.extension.youtube.patches.components;
|
||||
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.shared.patches.litho.Filter;
|
||||
import app.revanced.extension.shared.patches.litho.FilterGroup.*;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class HideInfoCardsFilter extends Filter {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ import app.revanced.extension.shared.Utils;
|
|||
import app.revanced.extension.shared.ByteTrieSearch;
|
||||
import app.revanced.extension.shared.StringTrieSearch;
|
||||
import app.revanced.extension.shared.TrieSearch;
|
||||
import app.revanced.extension.shared.patches.litho.Filter;
|
||||
import app.revanced.extension.shared.patches.litho.FilterGroup.*;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.shared.NavigationBar;
|
||||
import app.revanced.extension.youtube.shared.PlayerType;
|
||||
|
|
@ -41,7 +43,7 @@ import app.revanced.extension.youtube.shared.PlayerType;
|
|||
* - When using whole word syntax, some keywords may need additional pluralized variations.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
final class KeywordContentFilter extends Filter {
|
||||
public final class KeywordContentFilter extends Filter {
|
||||
|
||||
/**
|
||||
* Strings found in the buffer for every videos. Full strings should be specified.
|
||||
|
|
@ -554,8 +556,8 @@ final class KeywordContentFilter extends Filter {
|
|||
}
|
||||
|
||||
@Override
|
||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
if (contentIndex != 0 && matchedGroup == startsWithFilter) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ import androidx.annotation.Nullable;
|
|||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.StringTrieSearch;
|
||||
import app.revanced.extension.shared.patches.litho.Filter;
|
||||
import app.revanced.extension.shared.patches.litho.FilterGroup.*;
|
||||
import app.revanced.extension.shared.patches.litho.FilterGroupList.*;
|
||||
import app.revanced.extension.youtube.patches.ChangeHeaderPatch;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.shared.NavigationBar;
|
||||
|
|
@ -342,7 +345,7 @@ public final class LayoutComponentsFilter extends Filter {
|
|||
}
|
||||
|
||||
@Override
|
||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
// This identifier is used not only in players but also in search results:
|
||||
// https://github.com/ReVanced/revanced-patches/issues/3245
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package app.revanced.extension.youtube.patches.components;
|
||||
|
||||
import app.revanced.extension.shared.patches.litho.Filter;
|
||||
import app.revanced.extension.shared.patches.litho.FilterGroup.*;
|
||||
import app.revanced.extension.youtube.patches.playback.speed.CustomPlaybackSpeedPatch;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
|
|
@ -36,7 +38,7 @@ public final class PlaybackSpeedMenuFilter extends Filter {
|
|||
}
|
||||
|
||||
@Override
|
||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
if (matchedGroup == oldPlaybackMenuGroup) {
|
||||
isOldPlaybackSpeedMenuVisible = true;
|
||||
|
|
|
|||
|
|
@ -3,13 +3,16 @@ package app.revanced.extension.youtube.patches.components;
|
|||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
import app.revanced.extension.shared.settings.Setting;
|
||||
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
|
||||
import app.revanced.extension.shared.patches.litho.Filter;
|
||||
import app.revanced.extension.shared.patches.litho.FilterGroup.*;
|
||||
import app.revanced.extension.shared.patches.litho.FilterGroupList.*;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.shared.ShortsPlayerState;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class PlayerFlyoutMenuItemsFilter extends Filter {
|
||||
public final class PlayerFlyoutMenuItemsFilter extends Filter {
|
||||
|
||||
public static final class HideAudioFlyoutMenuAvailability implements Setting.Availability {
|
||||
@Override
|
||||
|
|
@ -94,7 +97,7 @@ public class PlayerFlyoutMenuItemsFilter extends Filter {
|
|||
}
|
||||
|
||||
@Override
|
||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
if (matchedGroup == videoQualityMenuFooter) {
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@ import app.revanced.extension.youtube.patches.VideoInformation;
|
|||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.TrieSearch;
|
||||
import app.revanced.extension.shared.patches.litho.Filter;
|
||||
import app.revanced.extension.shared.patches.litho.FilterGroup.*;
|
||||
import app.revanced.extension.shared.patches.litho.FilterGroupList.*;
|
||||
|
||||
/**
|
||||
* Searches for video id's in the proto buffer of Shorts dislike.
|
||||
|
|
@ -84,13 +87,13 @@ public final class ReturnYouTubeDislikeFilter extends Filter {
|
|||
}
|
||||
|
||||
@Override
|
||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
if (!Settings.RYD_ENABLED.get() || !Settings.RYD_SHORTS.get()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
FilterGroup.FilterGroupResult result = videoIdFilterGroup.check(buffer);
|
||||
FilterGroupResult result = videoIdFilterGroup.check(buffer);
|
||||
if (result.isFiltered()) {
|
||||
String matchedVideoId = findVideoId(buffer);
|
||||
// Matched video will be null if in incognito mode.
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ import java.util.Arrays;
|
|||
import java.util.List;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.patches.litho.Filter;
|
||||
import app.revanced.extension.shared.patches.litho.FilterGroup.*;
|
||||
import app.revanced.extension.shared.patches.litho.FilterGroupList.*;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.shared.NavigationBar;
|
||||
import app.revanced.extension.youtube.shared.PlayerType;
|
||||
|
|
@ -339,7 +342,7 @@ public final class ShortsFilter extends Filter {
|
|||
}
|
||||
|
||||
@Override
|
||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
if (contentType == FilterContentType.PATH) {
|
||||
if (matchedGroup == subscribeButton || matchedGroup == joinButton
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import android.graphics.Color;
|
|||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.YouTubeAndMusicSettings;
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
import app.revanced.extension.shared.settings.BooleanSetting;
|
||||
import app.revanced.extension.shared.settings.EnumSetting;
|
||||
|
|
@ -49,7 +50,7 @@ import app.revanced.extension.youtube.patches.MiniplayerPatch;
|
|||
import app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings;
|
||||
import app.revanced.extension.youtube.swipecontrols.SwipeControlsConfigurationProvider.SwipeOverlayStyle;
|
||||
|
||||
public class Settings extends BaseSettings {
|
||||
public class Settings extends YouTubeAndMusicSettings {
|
||||
// Video
|
||||
public static final BooleanSetting ADVANCED_VIDEO_QUALITY_MENU = new BooleanSetting("revanced_advanced_video_quality_menu", TRUE);
|
||||
public static final BooleanSetting DISABLE_HDR_VIDEO = new BooleanSetting("revanced_disable_hdr_video", FALSE);
|
||||
|
|
@ -274,11 +275,6 @@ public class Settings extends BaseSettings {
|
|||
public static final BooleanSetting CHANGE_START_PAGE_ALWAYS = new BooleanSetting("revanced_change_start_page_always", FALSE, true,
|
||||
new ChangeStartPageTypeAvailability());
|
||||
public static final StringSetting SPOOF_APP_VERSION_TARGET = new StringSetting("revanced_spoof_app_version_target", "19.01.34", true, parent(SPOOF_APP_VERSION));
|
||||
|
||||
// Custom filter
|
||||
public static final BooleanSetting CUSTOM_FILTER = new BooleanSetting("revanced_custom_filter", FALSE);
|
||||
public static final StringSetting CUSTOM_FILTER_STRINGS = new StringSetting("revanced_custom_filter_strings", "", true, parent(CUSTOM_FILTER));
|
||||
|
||||
// Navigation buttons
|
||||
public static final BooleanSetting HIDE_HOME_BUTTON = new BooleanSetting("revanced_hide_home_button", FALSE, true);
|
||||
public static final BooleanSetting HIDE_CREATE_BUTTON = new BooleanSetting("revanced_hide_create_button", TRUE, true);
|
||||
|
|
@ -368,8 +364,6 @@ public class Settings extends BaseSettings {
|
|||
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client_type", ClientType.ANDROID_VR_1_43_32, true, parent(SPOOF_VIDEO_STREAMS));
|
||||
public static final BooleanSetting SPOOF_VIDEO_STREAMS_AV1 = new BooleanSetting("revanced_spoof_video_streams_av1", FALSE, true,
|
||||
"revanced_spoof_video_streams_av1_user_dialog_message", new SpoofClientAv1Availability());
|
||||
public static final BooleanSetting DEBUG_PROTOBUFFER = new BooleanSetting("revanced_debug_protobuffer", FALSE, false,
|
||||
"revanced_debug_protobuffer_user_dialog_message", parent(BaseSettings.DEBUG));
|
||||
|
||||
// Swipe controls
|
||||
public static final BooleanSetting SWIPE_CHANGE_VIDEO = new BooleanSetting("revanced_swipe_change_video", FALSE, true);
|
||||
|
|
@ -382,7 +376,7 @@ public class Settings extends BaseSettings {
|
|||
public static final IntegerSetting SWIPE_MAGNITUDE_THRESHOLD = new IntegerSetting("revanced_swipe_threshold", 30, true,
|
||||
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
|
||||
public static final IntegerSetting SWIPE_VOLUME_SENSITIVITY = new IntegerSetting("revanced_swipe_volume_sensitivity", 1, true, parent(SWIPE_VOLUME));
|
||||
public static final EnumSetting<SwipeOverlayStyle> SWIPE_OVERLAY_STYLE = new EnumSetting<>("revanced_swipe_overlay_style", SwipeOverlayStyle.HORIZONTAL,true,
|
||||
public static final EnumSetting<SwipeOverlayStyle> SWIPE_OVERLAY_STYLE = new EnumSetting<>("revanced_swipe_overlay_style", SwipeOverlayStyle.HORIZONTAL, true,
|
||||
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
|
||||
public static final IntegerSetting SWIPE_OVERLAY_TEXT_SIZE = new IntegerSetting("revanced_swipe_text_overlay_size", 14, true,
|
||||
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
|
||||
|
|
@ -411,7 +405,9 @@ public class Settings extends BaseSettings {
|
|||
|
||||
// SponsorBlock
|
||||
public static final BooleanSetting SB_ENABLED = new BooleanSetting("sb_enabled", TRUE);
|
||||
/** Do not use id setting directly. Instead use {@link SponsorBlockSettings}. */
|
||||
/**
|
||||
* Do not use id setting directly. Instead use {@link SponsorBlockSettings}.
|
||||
*/
|
||||
public static final StringSetting SB_PRIVATE_USER_ID = new StringSetting("sb_private_user_id_Do_Not_Share", "", parent(SB_ENABLED));
|
||||
public static final IntegerSetting SB_CREATE_NEW_SEGMENT_STEP = new IntegerSetting("sb_create_new_segment_step", 150, parent(SB_ENABLED));
|
||||
public static final BooleanSetting SB_VOTING_BUTTON = new BooleanSetting("sb_voting_button", FALSE, parent(SB_ENABLED));
|
||||
|
|
@ -460,7 +456,7 @@ public class Settings extends BaseSettings {
|
|||
public static final StringSetting SB_CATEGORY_UNSUBMITTED_COLOR = new StringSetting("sb_unsubmitted_color", "#FFFFFFFF", false, false);
|
||||
|
||||
// Deprecated migrations
|
||||
private static final StringSetting DEPRECATED_SEEKBAR_CUSTOM_COLOR_PRIMARY = new StringSetting("revanced_seekbar_custom_color_value", "#FF0033");
|
||||
private static final StringSetting DEPRECATED_SEEKBAR_CUSTOM_COLOR_PRIMARY = new StringSetting("revanced_seekbar_custom_color_value", "#FF0033");
|
||||
|
||||
private static final FloatSetting DEPRECATED_SB_CATEGORY_SPONSOR_OPACITY = new FloatSetting("sb_sponsor_opacity", 0.8f, false, false);
|
||||
private static final FloatSetting DEPRECATED_SB_CATEGORY_SELF_PROMO_OPACITY = new FloatSetting("sb_selfpromo_opacity", 0.8f, false, false);
|
||||
|
|
@ -512,7 +508,7 @@ public class Settings extends BaseSettings {
|
|||
// 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))) {
|
||||
|| (Utils.getAppVersionName().compareTo(SPOOF_APP_VERSION_TARGET.get()) <= 0))) {
|
||||
Logger.printInfo(() -> "Resetting spoof app version");
|
||||
SPOOF_APP_VERSION_TARGET.resetToDefault();
|
||||
SPOOF_APP_VERSION.resetToDefault();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue