chore: Merge branch dev to main (#6512)
This commit is contained in:
@@ -2,6 +2,10 @@ name: Build pull request
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
pr:
|
||||||
|
description: "PR to build"
|
||||||
|
required: true
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- dev
|
- dev
|
||||||
@@ -10,9 +14,14 @@ jobs:
|
|||||||
release:
|
release:
|
||||||
name: Build
|
name: Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
ref: ${{ inputs.pr && format('refs/pull/{0}/merge', inputs.pr) || github.ref }}
|
||||||
|
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v5
|
uses: actions/setup-java@v5
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
name: Pull strings
|
name: Pull strings
|
||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
|
||||||
- cron: "0 0 * * 0"
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -17,7 +15,7 @@ jobs:
|
|||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: dev
|
ref: dev
|
||||||
clean: true
|
persist-credentials: true
|
||||||
|
|
||||||
- name: Pull strings
|
- name: Pull strings
|
||||||
uses: crowdin/github-action@v2
|
uses: crowdin/github-action@v2
|
||||||
@@ -25,16 +23,28 @@ jobs:
|
|||||||
config: crowdin.yml
|
config: crowdin.yml
|
||||||
upload_sources: false
|
upload_sources: false
|
||||||
download_translations: true
|
download_translations: true
|
||||||
|
push_translations: false
|
||||||
skip_ref_checkout: true
|
skip_ref_checkout: true
|
||||||
localization_branch_name: feat/translations
|
|
||||||
create_pull_request: false
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||||
|
|
||||||
|
- name: Process strings
|
||||||
|
run: |
|
||||||
|
gradlew processStringsFromCrowdin
|
||||||
|
env:
|
||||||
|
ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ github.actor }}
|
||||||
|
ORG_GRADLE_PROJECT_githubPackagesPassword: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Commit changes
|
||||||
|
uses: stefanzweifel/git-auto-commit-action@v7
|
||||||
|
with:
|
||||||
|
commit_message: "chore: Sync translations from Crowdin"
|
||||||
|
push_options: '--force'
|
||||||
|
branch: feat/translations
|
||||||
|
|
||||||
- name: Open pull request
|
- name: Open pull request
|
||||||
if: github.event_name == 'workflow_dispatch'
|
|
||||||
uses: repo-sync/pull-request@v2
|
uses: repo-sync/pull-request@v2
|
||||||
with:
|
with:
|
||||||
source_branch: feat/translations
|
source_branch: feat/translations
|
||||||
|
|||||||
@@ -16,10 +16,11 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Preprocess strings
|
- name: Process strings
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ github.actor }}
|
||||||
run: ./gradlew clean preprocessCrowdinStrings
|
ORG_GRADLE_PROJECT_githubPackagesPassword: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: ./gradlew processStringsForCrowdin
|
||||||
|
|
||||||
- name: Push strings
|
- name: Push strings
|
||||||
uses: crowdin/github-action@v2
|
uses: crowdin/github-action@v2
|
||||||
|
|||||||
+100
@@ -1,3 +1,103 @@
|
|||||||
|
# [5.50.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.50.0-dev.7...v5.50.0-dev.8) (2026-02-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **GmsCore support:** Reduce amount of necessary changes and add update check ([#6582](https://github.com/ReVanced/revanced-patches/issues/6582)) ([650e6a2](https://github.com/ReVanced/revanced-patches/commit/650e6a271075b57368432cd9d4294fd1ce26cceb))
|
||||||
|
|
||||||
|
# [5.50.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.50.0-dev.6...v5.50.0-dev.7) (2026-02-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Instagram:** Make `Change link sharing domain` and `Sanitize sharing links` work with latest versions again ([#6518](https://github.com/ReVanced/revanced-patches/issues/6518)) ([85a9079](https://github.com/ReVanced/revanced-patches/commit/85a9079c25760d0329e518e379eeefe3beeea143))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Instagram:** Add `Disable analytics` patch ([#6531](https://github.com/ReVanced/revanced-patches/issues/6531)) ([ad92864](https://github.com/ReVanced/revanced-patches/commit/ad92864483a21d7eae7952c8f8429cde3d44e848))
|
||||||
|
* **Kleinanzeigen:** Add `Hide PUR` patch ([#6558](https://github.com/ReVanced/revanced-patches/issues/6558)) ([4958ecf](https://github.com/ReVanced/revanced-patches/commit/4958ecf10c880e9e7f15dd2e58ebaefbf49e417a))
|
||||||
|
* **Microsoft Lens:** Remove migration to OneDrive ([#6551](https://github.com/ReVanced/revanced-patches/issues/6551)) ([e389632](https://github.com/ReVanced/revanced-patches/commit/e389632afd52403aba26b6981d098b93cea45e00))
|
||||||
|
|
||||||
|
# [5.50.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.50.0-dev.5...v5.50.0-dev.6) (2026-02-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **FotMob:** Add `Hide ads` patch ([#6566](https://github.com/ReVanced/revanced-patches/issues/6566)) ([4b0b737](https://github.com/ReVanced/revanced-patches/commit/4b0b7374f21d13599ef2f1e2f5880e7589b0874e))
|
||||||
|
|
||||||
|
# [5.50.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.50.0-dev.4...v5.50.0-dev.5) (2026-02-01)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Process strings from Crowdin to strip the app/patch prefixes again ([e566efc](https://github.com/ReVanced/revanced-patches/commit/e566efc51fca45c6284406245a360685a8e90d74))
|
||||||
|
|
||||||
|
# [5.50.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.50.0-dev.3...v5.50.0-dev.4) (2026-01-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Instagram - Open links externally:** Fix patch by handling >4-bit register ([#6538](https://github.com/ReVanced/revanced-patches/issues/6538)) ([f681a6f](https://github.com/ReVanced/revanced-patches/commit/f681a6ffd45f05a61743e7d272cd68c4b743be42))
|
||||||
|
|
||||||
|
# [5.50.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.50.0-dev.2...v5.50.0-dev.3) (2026-01-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Kleinanzeigen:** Add `Hide ads` patch ([#6533](https://github.com/ReVanced/revanced-patches/issues/6533)) ([bd6e544](https://github.com/ReVanced/revanced-patches/commit/bd6e544007d539ac2eb890d9bdcb6850435f96cb))
|
||||||
|
|
||||||
|
# [5.50.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.50.0-dev.1...v5.50.0-dev.2) (2026-01-25)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Letterboxd - Hide ads:** Fix patch by returning the correct return type ([#6527](https://github.com/ReVanced/revanced-patches/issues/6527)) ([80c34b9](https://github.com/ReVanced/revanced-patches/commit/80c34b9d74a42018a0cd52b4a584ee71206bf963))
|
||||||
|
|
||||||
|
# [5.50.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.49.0-dev.1...v5.50.0-dev.1) (2026-01-25)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Strava:** Fix `Add media download` patch ([#6526](https://github.com/ReVanced/revanced-patches/issues/6526)) ([dc9e68b](https://github.com/ReVanced/revanced-patches/commit/dc9e68ba574dd9f35cd742cb63193c5d875addde))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Nothing X:** Add `Show K1 token(s)` patch ([#6490](https://github.com/ReVanced/revanced-patches/issues/6490)) ([421cb28](https://github.com/ReVanced/revanced-patches/commit/421cb2899ef5c0f100fb8007bae8b89137d0e41c))
|
||||||
|
* **Strava:** Add `Hide distractions` patch ([#6479](https://github.com/ReVanced/revanced-patches/issues/6479)) ([66b0852](https://github.com/ReVanced/revanced-patches/commit/66b0852f8fa57c82b09997337a304374883d8ba5))
|
||||||
|
* **YouTube Music:** Add `Unlock Android Auto Media Browser` patch ([#6477](https://github.com/ReVanced/revanced-patches/issues/6477)) ([5edd9dc](https://github.com/ReVanced/revanced-patches/commit/5edd9dccae3b1ab4edf19771a771812e3c9ccf80))
|
||||||
|
|
||||||
|
# [5.50.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.49.0-dev.1...v5.50.0-dev.1) (2026-01-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Nothing X:** Add `Show K1 token(s)` patch ([#6490](https://github.com/ReVanced/revanced-patches/issues/6490)) ([421cb28](https://github.com/ReVanced/revanced-patches/commit/421cb2899ef5c0f100fb8007bae8b89137d0e41c))
|
||||||
|
* **Strava:** Add `Hide distractions` patch ([#6479](https://github.com/ReVanced/revanced-patches/issues/6479)) ([66b0852](https://github.com/ReVanced/revanced-patches/commit/66b0852f8fa57c82b09997337a304374883d8ba5))
|
||||||
|
* **YouTube Music:** Add `Unlock Android Auto Media Browser` patch ([#6477](https://github.com/ReVanced/revanced-patches/issues/6477)) ([5edd9dc](https://github.com/ReVanced/revanced-patches/commit/5edd9dccae3b1ab4edf19771a771812e3c9ccf80))
|
||||||
|
|
||||||
|
# [5.50.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.48.0...v5.50.0-dev.1) (2026-01-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube Music:** Add `Unlock Android Auto Media Browser` patch ([#6477](https://github.com/ReVanced/revanced-patches/issues/6477)) ([89645dc](https://github.com/ReVanced/revanced-patches/commit/89645dcc2e13603b8f2fedb5e16231cb396e5965))
|
||||||
|
|
||||||
|
# [5.49.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.48.1-dev.1...v5.49.0-dev.1) (2026-01-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube Music:** Add `Hide layout components` patch ([#6365](https://github.com/ReVanced/revanced-patches/issues/6365)) ([71ce823](https://github.com/ReVanced/revanced-patches/commit/71ce8230a959dcaf2d8cd5dad1a4f21b88819aa0))
|
||||||
|
|
||||||
|
## [5.48.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.48.0...v5.48.1-dev.1) (2026-01-21)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Disable `Prevent screenshot detection` by default ([#6511](https://github.com/ReVanced/revanced-patches/issues/6511)) ([5b5c502](https://github.com/ReVanced/revanced-patches/commit/5b5c50254d533faa0e04d542f4859cbef610713e))
|
||||||
|
|
||||||
# [5.48.0](https://github.com/ReVanced/revanced-patches/compare/v5.47.0...v5.48.0) (2026-01-19)
|
# [5.48.0](https://github.com/ReVanced/revanced-patches/compare/v5.47.0...v5.48.0) (2026-01-19)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import static java.lang.Boolean.FALSE;
|
|||||||
import static java.lang.Boolean.TRUE;
|
import static java.lang.Boolean.TRUE;
|
||||||
import static app.revanced.extension.shared.settings.Setting.parent;
|
import static app.revanced.extension.shared.settings.Setting.parent;
|
||||||
|
|
||||||
import 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.BooleanSetting;
|
||||||
import app.revanced.extension.shared.settings.EnumSetting;
|
import app.revanced.extension.shared.settings.EnumSetting;
|
||||||
import app.revanced.extension.shared.spoof.ClientType;
|
import app.revanced.extension.shared.spoof.ClientType;
|
||||||
|
|
||||||
public class Settings extends BaseSettings {
|
public class Settings extends YouTubeAndMusicSettings {
|
||||||
|
|
||||||
// Ads
|
// Ads
|
||||||
public static final BooleanSetting HIDE_VIDEO_ADS = new BooleanSetting("revanced_music_hide_video_ads", TRUE, true);
|
public static final BooleanSetting HIDE_VIDEO_ADS = new BooleanSetting("revanced_music_hide_video_ads", TRUE, true);
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<manifest/>
|
||||||
+590
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<manifest/>
|
||||||
+349
-212
@@ -1,8 +1,5 @@
|
|||||||
package app.revanced.extension.shared;
|
package app.revanced.extension.shared;
|
||||||
|
|
||||||
import static app.revanced.extension.shared.StringRef.str;
|
|
||||||
import static app.revanced.extension.shared.requests.Route.Method.GET;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
@@ -17,46 +14,53 @@ import android.os.PowerManager;
|
|||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import app.revanced.extension.shared.requests.Requester;
|
||||||
|
import app.revanced.extension.shared.requests.Route;
|
||||||
|
import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
|
import app.revanced.extension.shared.ui.CustomDialog;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import app.revanced.extension.shared.requests.Requester;
|
import static app.revanced.extension.shared.StringRef.str;
|
||||||
import app.revanced.extension.shared.requests.Route;
|
import static app.revanced.extension.shared.requests.Route.Method.GET;
|
||||||
import app.revanced.extension.shared.ui.CustomDialog;
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class GmsCoreSupport {
|
public class GmsCoreSupport {
|
||||||
private static final String GMS_CORE_PACKAGE_NAME
|
private static GmsCore gmsCore = GmsCore.UNKNOWN;
|
||||||
= getGmsCoreVendorGroupId() + ".android.gms";
|
|
||||||
private static final Uri GMS_CORE_PROVIDER
|
|
||||||
= Uri.parse("content://" + getGmsCoreVendorGroupId() + ".android.gsf.gservices/prefix");
|
|
||||||
private static final String DONT_KILL_MY_APP_URL
|
|
||||||
= "https://dontkillmyapp.com/";
|
|
||||||
private static final Route DONT_KILL_MY_APP_MANUFACTURER_API
|
|
||||||
= new Route(GET, "/api/v2/{manufacturer}.json");
|
|
||||||
private static final String DONT_KILL_MY_APP_NAME_PARAMETER
|
|
||||||
= "?app=MicroG";
|
|
||||||
private static final String BUILD_MANUFACTURER
|
|
||||||
= Build.MANUFACTURER.toLowerCase(Locale.ROOT).replace(" ", "-");
|
|
||||||
|
|
||||||
/**
|
static {
|
||||||
* If a manufacturer specific page exists on DontKillMyApp.
|
for (GmsCore core : GmsCore.values()) {
|
||||||
*/
|
if (core.getGroupId().equals(getGmsCoreVendorGroupId())) {
|
||||||
@Nullable
|
GmsCoreSupport.gmsCore = core;
|
||||||
private static volatile Boolean DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED;
|
break;
|
||||||
|
}
|
||||||
private static String getOriginalPackageName() {
|
}
|
||||||
return null; // Modified during patching.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void checkGmsCore(Activity context) {
|
||||||
|
gmsCore.check(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getOriginalPackageName() {
|
||||||
|
return null; // Modified during patching.
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getGmsCoreVendorGroupId() {
|
||||||
|
return "app.revanced"; // Modified during patching.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return If the current package name is the same as the original unpatched app.
|
* @return If the current package name is the same as the original unpatched app.
|
||||||
* If `GmsCore support` was not included during patching, this returns true;
|
* If `GmsCore support` was not included during patching, this returns true;
|
||||||
*/
|
*/
|
||||||
public static boolean isPackageNameOriginal() {
|
public static boolean isPackageNameOriginal() {
|
||||||
String originalPackageName = getOriginalPackageName();
|
String originalPackageName = getOriginalPackageName();
|
||||||
@@ -64,203 +68,336 @@ public class GmsCoreSupport {
|
|||||||
|| originalPackageName.equals(Utils.getContext().getPackageName());
|
|| originalPackageName.equals(Utils.getContext().getPackageName());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void open(String queryOrLink) {
|
private enum GmsCore {
|
||||||
Logger.printInfo(() -> "Opening link: " + queryOrLink);
|
REVANCED("app.revanced", "https://github.com/revanced/gmscore/releases/latest", () -> {
|
||||||
|
|
||||||
Intent intent;
|
|
||||||
try {
|
|
||||||
// Check if queryOrLink is a valid URL.
|
|
||||||
new URL(queryOrLink);
|
|
||||||
|
|
||||||
intent = new Intent(Intent.ACTION_VIEW, Uri.parse(queryOrLink));
|
|
||||||
} catch (MalformedURLException e) {
|
|
||||||
intent = new Intent(Intent.ACTION_WEB_SEARCH);
|
|
||||||
intent.putExtra(SearchManager.QUERY, queryOrLink);
|
|
||||||
}
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
||||||
Utils.getContext().startActivity(intent);
|
|
||||||
|
|
||||||
// Gracefully exit, otherwise the broken app will continue to run.
|
|
||||||
System.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void showBatteryOptimizationDialog(Activity context,
|
|
||||||
String dialogMessageRef,
|
|
||||||
String positiveButtonTextRef,
|
|
||||||
DialogInterface.OnClickListener onPositiveClickListener) {
|
|
||||||
// Use a delay to allow the activity to finish initializing.
|
|
||||||
// Otherwise, if device is in dark mode the dialog is shown with wrong color scheme.
|
|
||||||
Utils.runOnMainThreadDelayed(() -> {
|
|
||||||
// Create the custom dialog.
|
|
||||||
Pair<Dialog, LinearLayout> dialogPair = CustomDialog.create(
|
|
||||||
context,
|
|
||||||
str("gms_core_dialog_title"), // Title.
|
|
||||||
str(dialogMessageRef), // Message.
|
|
||||||
null, // No EditText.
|
|
||||||
str(positiveButtonTextRef), // OK button text.
|
|
||||||
() -> onPositiveClickListener.onClick(null, 0), // Convert DialogInterface.OnClickListener to Runnable.
|
|
||||||
null, // No Cancel button action.
|
|
||||||
null, // No Neutral button text.
|
|
||||||
null, // No Neutral button action.
|
|
||||||
true // Dismiss dialog when onNeutralClick.
|
|
||||||
);
|
|
||||||
|
|
||||||
Dialog dialog = dialogPair.first;
|
|
||||||
|
|
||||||
// Do not set cancelable to false, to allow using back button to skip the action,
|
|
||||||
// just in case the battery change can never be satisfied.
|
|
||||||
dialog.setCancelable(true);
|
|
||||||
|
|
||||||
// Show the dialog
|
|
||||||
Utils.showDialog(context, dialog);
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection point.
|
|
||||||
*/
|
|
||||||
public static void checkGmsCore(Activity context) {
|
|
||||||
try {
|
|
||||||
// Verify the user has not included GmsCore for a root installation.
|
|
||||||
// GmsCore Support changes the package name, but with a mounted installation
|
|
||||||
// all manifest changes are ignored and the original package name is used.
|
|
||||||
if (isPackageNameOriginal()) {
|
|
||||||
Logger.printInfo(() -> "App is mounted with root, but GmsCore patch was included");
|
|
||||||
// Cannot use localize text here, since the app will load resources
|
|
||||||
// from the unpatched app and all patch strings are missing.
|
|
||||||
Utils.showToastLong("The 'GmsCore support' patch breaks mount installations");
|
|
||||||
|
|
||||||
// Do not exit. If the app exits before launch completes (and without
|
|
||||||
// opening another activity), then on some devices such as Pixel phone Android 10
|
|
||||||
// no toast will be shown and the app will continually relaunch
|
|
||||||
// with the appearance of a hung app.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify GmsCore is installed.
|
|
||||||
try {
|
try {
|
||||||
PackageManager manager = context.getPackageManager();
|
|
||||||
manager.getPackageInfo(GMS_CORE_PACKAGE_NAME, PackageManager.GET_ACTIVITIES);
|
|
||||||
} catch (PackageManager.NameNotFoundException exception) {
|
|
||||||
Logger.printInfo(() -> "GmsCore was not found");
|
|
||||||
// Cannot show a dialog and must show a toast,
|
|
||||||
// because on some installations the app crashes before a dialog can be displayed.
|
|
||||||
Utils.showToastLong(str("gms_core_toast_not_installed_message"));
|
|
||||||
open(getGmsCoreDownload());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if GmsCore is whitelisted from battery optimizations.
|
|
||||||
if (isAndroidAutomotive(context)) {
|
|
||||||
// Ignore Android Automotive devices (Google built-in),
|
|
||||||
// as there is no way to disable battery optimizations.
|
|
||||||
Logger.printDebug(() -> "Device is Android Automotive");
|
|
||||||
} else if (batteryOptimizationsEnabled(context)) {
|
|
||||||
Logger.printInfo(() -> "GmsCore is not whitelisted from battery optimizations");
|
|
||||||
|
|
||||||
showBatteryOptimizationDialog(context,
|
|
||||||
"gms_core_dialog_not_whitelisted_using_battery_optimizations_message",
|
|
||||||
"gms_core_dialog_continue_text",
|
|
||||||
(dialog, id) -> openGmsCoreDisableBatteryOptimizationsIntent(context));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if GmsCore is currently running in the background.
|
|
||||||
var client = context.getContentResolver().acquireContentProviderClient(GMS_CORE_PROVIDER);
|
|
||||||
//noinspection TryFinallyCanBeTryWithResources
|
|
||||||
try {
|
|
||||||
if (client == null) {
|
|
||||||
Logger.printInfo(() -> "GmsCore is not running in the background");
|
|
||||||
checkIfDontKillMyAppSupportsManufacturer();
|
|
||||||
|
|
||||||
showBatteryOptimizationDialog(context,
|
|
||||||
"gms_core_dialog_not_whitelisted_not_allowed_in_background_message",
|
|
||||||
"gms_core_dialog_open_website_text",
|
|
||||||
(dialog, id) -> openDontKillMyApp());
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (client != null) client.close();
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Logger.printException(() -> "checkGmsCore failure", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("BatteryLife") // Permission is part of GmsCore
|
|
||||||
private static void openGmsCoreDisableBatteryOptimizationsIntent(Activity activity) {
|
|
||||||
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
|
|
||||||
intent.setData(Uri.fromParts("package", GMS_CORE_PACKAGE_NAME, null));
|
|
||||||
activity.startActivityForResult(intent, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void checkIfDontKillMyAppSupportsManufacturer() {
|
|
||||||
Utils.runOnBackgroundThread(() -> {
|
|
||||||
try {
|
|
||||||
final long start = System.currentTimeMillis();
|
|
||||||
HttpURLConnection connection = Requester.getConnectionFromRoute(
|
HttpURLConnection connection = Requester.getConnectionFromRoute(
|
||||||
DONT_KILL_MY_APP_URL, DONT_KILL_MY_APP_MANUFACTURER_API, BUILD_MANUFACTURER);
|
"https://api.github.com",
|
||||||
|
new Route(GET, "/repos/revanced/gmscore/releases/latest")
|
||||||
|
);
|
||||||
connection.setConnectTimeout(5000);
|
connection.setConnectTimeout(5000);
|
||||||
connection.setReadTimeout(5000);
|
connection.setReadTimeout(5000);
|
||||||
|
|
||||||
final boolean supported = connection.getResponseCode() == 200;
|
int responseCode = connection.getResponseCode();
|
||||||
Logger.printInfo(() -> "Manufacturer is " + (supported ? "" : "NOT ")
|
if (responseCode != 200) {
|
||||||
+ "listed on DontKillMyApp: " + BUILD_MANUFACTURER
|
Logger.printDebug(() -> "GitHub API returned status code: " + responseCode);
|
||||||
+ " fetch took: " + (System.currentTimeMillis() - start) + "ms");
|
return null;
|
||||||
DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED = supported;
|
}
|
||||||
|
|
||||||
|
// Parse the response
|
||||||
|
JSONObject releaseData = Requester.parseJSONObject(connection);
|
||||||
|
String tagName = releaseData.optString("tag_name", "");
|
||||||
|
connection.disconnect();
|
||||||
|
|
||||||
|
if (tagName.isEmpty()) {
|
||||||
|
Logger.printDebug(() -> "No tag_name found in GitHub release data");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tagName.startsWith("v")) tagName = tagName.substring(1);
|
||||||
|
|
||||||
|
return tagName;
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printInfo(() -> "Could not check if manufacturer is listed on DontKillMyApp: "
|
Logger.printInfo(() -> "Failed to fetch latest GmsCore version from GitHub", ex);
|
||||||
+ BUILD_MANUFACTURER, ex);
|
return null;
|
||||||
DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED = null;
|
|
||||||
}
|
}
|
||||||
});
|
}),
|
||||||
}
|
UNKNOWN(getGmsCoreVendorGroupId(), getGmsCoreVendorGroupId() + "android.gms", () -> null);
|
||||||
|
|
||||||
private static void openDontKillMyApp() {
|
private static final String DONT_KILL_MY_APP_URL
|
||||||
final Boolean manufacturerSupported = DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED;
|
= "https://dontkillmyapp.com/";
|
||||||
|
private static final Route DONT_KILL_MY_APP_MANUFACTURER_API
|
||||||
|
= new Route(GET, "/api/v2/{manufacturer}.json");
|
||||||
|
private static final String DONT_KILL_MY_APP_NAME_PARAMETER
|
||||||
|
= "?app=MicroG";
|
||||||
|
private static final String BUILD_MANUFACTURER
|
||||||
|
= Build.MANUFACTURER.toLowerCase(Locale.ROOT).replace(" ", "-");
|
||||||
|
|
||||||
String manufacturerPageToOpen;
|
/**
|
||||||
if (manufacturerSupported == null) {
|
* If a manufacturer specific page exists on DontKillMyApp.
|
||||||
// Fetch has not completed yet. Only happens on extremely slow internet connections
|
*/
|
||||||
// and the user spends less than 1 second reading what's on screen.
|
@Nullable
|
||||||
// Instead of waiting for the fetch (which may timeout),
|
private volatile Boolean dontKillMyAppManufacturerSupported;
|
||||||
// open the website without a vendor.
|
|
||||||
manufacturerPageToOpen = "";
|
private final String groupId;
|
||||||
} else if (manufacturerSupported) {
|
private final String packageName;
|
||||||
manufacturerPageToOpen = BUILD_MANUFACTURER;
|
private final String downloadQuery;
|
||||||
} else {
|
private final GetLatestVersion getLatestVersion;
|
||||||
// No manufacturer specific page exists. Open the general page.
|
private final Uri gmsCoreProvider;
|
||||||
manufacturerPageToOpen = "general";
|
|
||||||
|
GmsCore(String groupId, String downloadQuery, GetLatestVersion getLatestVersion) {
|
||||||
|
this.groupId = groupId;
|
||||||
|
this.packageName = groupId + ".android.gms";
|
||||||
|
this.gmsCoreProvider = Uri.parse("content://" + groupId + ".android.gsf.gservices/prefix");
|
||||||
|
|
||||||
|
this.downloadQuery = downloadQuery;
|
||||||
|
this.getLatestVersion = getLatestVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
open(DONT_KILL_MY_APP_URL + manufacturerPageToOpen + DONT_KILL_MY_APP_NAME_PARAMETER);
|
String getGroupId() {
|
||||||
}
|
return groupId;
|
||||||
|
}
|
||||||
/**
|
|
||||||
* @return If GmsCore is not whitelisted from battery optimizations.
|
void check(Activity context) {
|
||||||
*/
|
checkInstallation(context);
|
||||||
private static boolean batteryOptimizationsEnabled(Context context) {
|
checkUpdates(context);
|
||||||
//noinspection ObsoleteSdkInt
|
}
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
|
||||||
// Android 5.0 does not have battery optimization settings.
|
private void checkInstallation(Activity context) {
|
||||||
return false;
|
try {
|
||||||
|
// Verify the user has not included GmsCore for a root installation.
|
||||||
|
// GmsCore Support changes the package name, but with a mounted installation
|
||||||
|
// all manifest changes are ignored and the original package name is used.
|
||||||
|
if (isPackageNameOriginal()) {
|
||||||
|
Logger.printInfo(() -> "App is mounted with root, but GmsCore patch was included");
|
||||||
|
// Cannot use localize text here, since the app will load resources
|
||||||
|
// from the unpatched app and all patch strings are missing.
|
||||||
|
Utils.showToastLong("The 'GmsCore support' patch breaks mount installations");
|
||||||
|
|
||||||
|
// Do not exit. If the app exits before launch completes (and without
|
||||||
|
// opening another activity), then on some devices such as Pixel phone Android 10
|
||||||
|
// no toast will be shown and the app will continually relaunch
|
||||||
|
// with the appearance of a hung app.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify GmsCore is installed.
|
||||||
|
try {
|
||||||
|
PackageManager manager = context.getPackageManager();
|
||||||
|
manager.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES);
|
||||||
|
} catch (PackageManager.NameNotFoundException exception) {
|
||||||
|
Logger.printInfo(() -> "GmsCore was not found");
|
||||||
|
// Cannot show a dialog and must show a toast,
|
||||||
|
// because on some installations the app crashes before a dialog can be displayed.
|
||||||
|
Utils.showToastLong(str("revanced_gms_core_toast_not_installed_message"));
|
||||||
|
|
||||||
|
open(downloadQuery);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if GmsCore is whitelisted from battery optimizations.
|
||||||
|
if (isAndroidAutomotive(context)) {
|
||||||
|
// Ignore Android Automotive devices (Google built-in),
|
||||||
|
// as there is no way to disable battery optimizations.
|
||||||
|
Logger.printDebug(() -> "Device is Android Automotive");
|
||||||
|
} else if (batteryOptimizationsEnabled(context)) {
|
||||||
|
Logger.printInfo(() -> "GmsCore is not whitelisted from battery optimizations");
|
||||||
|
|
||||||
|
showBatteryOptimizationDialog(context,
|
||||||
|
"revanced_gms_core_dialog_not_whitelisted_using_battery_optimizations_message",
|
||||||
|
"revanced_gms_core_dialog_continue_text",
|
||||||
|
(dialog, id) -> openGmsCoreDisableBatteryOptimizationsIntent(context));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if GmsCore is currently running in the background.
|
||||||
|
var client = context.getContentResolver().acquireContentProviderClient(gmsCoreProvider);
|
||||||
|
//noinspection TryFinallyCanBeTryWithResources
|
||||||
|
try {
|
||||||
|
if (client == null) {
|
||||||
|
Logger.printInfo(() -> "GmsCore is not running in the background");
|
||||||
|
checkIfDontKillMyAppSupportsManufacturer();
|
||||||
|
|
||||||
|
showBatteryOptimizationDialog(context,
|
||||||
|
"revanced_gms_core_dialog_not_whitelisted_not_allowed_in_background_message",
|
||||||
|
"gmsrevanced_gms_core_log_open_website_text",
|
||||||
|
(dialog, id) -> openDontKillMyApp());
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (client != null) client.close();
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "checkGmsCore failure", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkUpdates(Activity context) {
|
||||||
|
if (!BaseSettings.GMS_CORE_CHECK_UPDATES.get()) {
|
||||||
|
Logger.printDebug(() -> "GmsCore update check is disabled in settings");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Utils.runOnBackgroundThread(() -> {
|
||||||
|
try {
|
||||||
|
PackageManager manager = context.getPackageManager();
|
||||||
|
String installedVersion = manager.getPackageInfo(packageName, 0).versionName;
|
||||||
|
|
||||||
|
Logger.printDebug(() -> "Installed GmsCore version: " + installedVersion);
|
||||||
|
|
||||||
|
String latestVersion = getLatestVersion.get();
|
||||||
|
|
||||||
|
if (latestVersion == null || latestVersion.isEmpty()) {
|
||||||
|
Logger.printDebug(() -> "Could not get latest GmsCore version");
|
||||||
|
Utils.showToastLong(str("revanced_gms_core_toast_update_check_failed_message"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.printDebug(() -> "Latest GmsCore version on GitHub: " + latestVersion);
|
||||||
|
|
||||||
|
// Compare versions
|
||||||
|
if (!installedVersion.equals(latestVersion)) {
|
||||||
|
Logger.printInfo(() -> "GmsCore update available. Installed: " + installedVersion
|
||||||
|
+ ", Latest: " + latestVersion);
|
||||||
|
|
||||||
|
showUpdateDialog(context, installedVersion, latestVersion);
|
||||||
|
} else {
|
||||||
|
Logger.printDebug(() -> "GmsCore is up to date");
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printInfo(() -> "Could not check GmsCore updates", ex);
|
||||||
|
Utils.showToastLong(str("revanced_gms_core_toast_update_check_failed_message"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void open(String queryOrLink) {
|
||||||
|
Logger.printInfo(() -> "Opening link: " + queryOrLink);
|
||||||
|
|
||||||
|
Intent intent;
|
||||||
|
try {
|
||||||
|
// Check if queryOrLink is a valid URL.
|
||||||
|
new URL(queryOrLink);
|
||||||
|
|
||||||
|
intent = new Intent(Intent.ACTION_VIEW, Uri.parse(queryOrLink));
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
intent = new Intent(Intent.ACTION_WEB_SEARCH);
|
||||||
|
intent.putExtra(SearchManager.QUERY, queryOrLink);
|
||||||
|
}
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
Utils.getContext().startActivity(intent);
|
||||||
|
|
||||||
|
// Gracefully exit, otherwise the broken app will continue to run.
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showUpdateDialog(Activity context, String installedVersion, String latestVersion) {
|
||||||
|
// Use a delay to allow the activity to finish initializing.
|
||||||
|
// Otherwise, if device is in dark mode the dialog is shown with wrong color scheme.
|
||||||
|
Utils.runOnMainThreadDelayed(() -> {
|
||||||
|
try {
|
||||||
|
Pair<Dialog, LinearLayout> dialogPair = CustomDialog.create(
|
||||||
|
context,
|
||||||
|
str("revanced_gms_core_dialog_title"),
|
||||||
|
String.format(str("revanced_gms_core_update_available_message"), latestVersion, installedVersion),
|
||||||
|
null,
|
||||||
|
str("revanced_gms_core_dialog_open_website_text"),
|
||||||
|
() -> open(downloadQuery),
|
||||||
|
() -> {
|
||||||
|
},
|
||||||
|
str("revanced_gms_core_dialog_cancel_text"),
|
||||||
|
null,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
Dialog dialog = dialogPair.first;
|
||||||
|
dialog.setCancelable(true);
|
||||||
|
Utils.showDialog(context, dialog);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "Failed to show GmsCore update dialog", ex);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void showBatteryOptimizationDialog(Activity context,
|
||||||
|
String dialogMessageRef,
|
||||||
|
String positiveButtonTextRef,
|
||||||
|
DialogInterface.OnClickListener onPositiveClickListener) {
|
||||||
|
// Use a delay to allow the activity to finish initializing.
|
||||||
|
// Otherwise, if device is in dark mode the dialog is shown with wrong color scheme.
|
||||||
|
Utils.runOnMainThreadDelayed(() -> {
|
||||||
|
// Create the custom dialog.
|
||||||
|
Pair<Dialog, LinearLayout> dialogPair = CustomDialog.create(
|
||||||
|
context,
|
||||||
|
str("revanced_gms_core_dialog_title"), // Title.
|
||||||
|
str(dialogMessageRef), // Message.
|
||||||
|
null, // No EditText.
|
||||||
|
str(positiveButtonTextRef), // OK button text.
|
||||||
|
() -> onPositiveClickListener.onClick(null, 0), // Convert DialogInterface.OnClickListener to Runnable.
|
||||||
|
null, // No Cancel button action.
|
||||||
|
null, // No Neutral button text.
|
||||||
|
null, // No Neutral button action.
|
||||||
|
true // Dismiss dialog when onNeutralClick.
|
||||||
|
);
|
||||||
|
|
||||||
|
Dialog dialog = dialogPair.first;
|
||||||
|
|
||||||
|
// Do not set cancelable to false to allow using back button to skip the action,
|
||||||
|
// just in case the battery change can never be satisfied.
|
||||||
|
dialog.setCancelable(true);
|
||||||
|
|
||||||
|
// Show the dialog
|
||||||
|
Utils.showDialog(context, dialog);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("BatteryLife") // Permission is part of GmsCore
|
||||||
|
private void openGmsCoreDisableBatteryOptimizationsIntent(Activity activity) {
|
||||||
|
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
|
||||||
|
intent.setData(Uri.fromParts("package", packageName, null));
|
||||||
|
activity.startActivityForResult(intent, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkIfDontKillMyAppSupportsManufacturer() {
|
||||||
|
Utils.runOnBackgroundThread(() -> {
|
||||||
|
try {
|
||||||
|
final long start = System.currentTimeMillis();
|
||||||
|
HttpURLConnection connection = Requester.getConnectionFromRoute(
|
||||||
|
DONT_KILL_MY_APP_URL, DONT_KILL_MY_APP_MANUFACTURER_API, BUILD_MANUFACTURER);
|
||||||
|
connection.setConnectTimeout(5000);
|
||||||
|
connection.setReadTimeout(5000);
|
||||||
|
|
||||||
|
final boolean supported = connection.getResponseCode() == 200;
|
||||||
|
Logger.printInfo(() -> "Manufacturer is " + (supported ? "" : "NOT ")
|
||||||
|
+ "listed on DontKillMyApp: " + BUILD_MANUFACTURER
|
||||||
|
+ " fetch took: " + (System.currentTimeMillis() - start) + "ms");
|
||||||
|
dontKillMyAppManufacturerSupported = supported;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printInfo(() -> "Could not check if manufacturer is listed on DontKillMyApp: "
|
||||||
|
+ BUILD_MANUFACTURER, ex);
|
||||||
|
dontKillMyAppManufacturerSupported = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openDontKillMyApp() {
|
||||||
|
final Boolean manufacturerSupported = dontKillMyAppManufacturerSupported;
|
||||||
|
|
||||||
|
String manufacturerPageToOpen;
|
||||||
|
if (manufacturerSupported == null) {
|
||||||
|
// Fetch has not completed yet. Only happens on extremely slow internet connections
|
||||||
|
// and the user spends less than 1 second reading what's on screen.
|
||||||
|
// Instead of waiting for the fetch (which may timeout),
|
||||||
|
// open the website without a vendor.
|
||||||
|
manufacturerPageToOpen = "";
|
||||||
|
} else if (manufacturerSupported) {
|
||||||
|
manufacturerPageToOpen = BUILD_MANUFACTURER;
|
||||||
|
} else {
|
||||||
|
// No manufacturer specific page exists. Open the general page.
|
||||||
|
manufacturerPageToOpen = "general";
|
||||||
|
}
|
||||||
|
|
||||||
|
open(DONT_KILL_MY_APP_URL + manufacturerPageToOpen + DONT_KILL_MY_APP_NAME_PARAMETER);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return If GmsCore is not whitelisted from battery optimizations.
|
||||||
|
*/
|
||||||
|
private boolean batteryOptimizationsEnabled(Context context) {
|
||||||
|
//noinspection ObsoleteSdkInt
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||||
|
// Android 5.0 does not have battery optimization settings.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||||||
|
return !powerManager.isIgnoringBatteryOptimizations(packageName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isAndroidAutomotive(Context context) {
|
||||||
|
return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
|
||||||
}
|
}
|
||||||
var powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
|
||||||
return !powerManager.isIgnoringBatteryOptimizations(GMS_CORE_PACKAGE_NAME);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isAndroidAutomotive(Context context) {
|
@FunctionalInterface
|
||||||
return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
|
private interface GetLatestVersion {
|
||||||
}
|
String get();
|
||||||
|
|
||||||
private static String getGmsCoreDownload() {
|
|
||||||
final var vendorGroupId = getGmsCoreVendorGroupId();
|
|
||||||
//noinspection SwitchStatementWithTooFewBranches
|
|
||||||
return switch (vendorGroupId) {
|
|
||||||
case "app.revanced" -> "https://github.com/revanced/gmscore/releases/latest";
|
|
||||||
default -> vendorGroupId + ".android.gms";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getGmsCoreVendorGroupId() {
|
|
||||||
return "app.revanced"; // Modified during patching.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-6
@@ -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;
|
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.Logger;
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.shared.ByteTrieSearch;
|
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.
|
* Allows custom filtering using a path and optionally a proto buffer string.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
final class CustomFilter extends Filter {
|
public final class CustomFilter extends Filter {
|
||||||
|
|
||||||
private static void showInvalidSyntaxToast(@NonNull String expression) {
|
private static void showInvalidSyntaxToast(@NonNull String expression) {
|
||||||
Utils.showToastLong(str("revanced_custom_filter_toast_invalid_syntax", expression));
|
Utils.showToastLong(str("revanced_custom_filter_toast_invalid_syntax", expression));
|
||||||
@@ -45,7 +47,7 @@ final class CustomFilter extends Filter {
|
|||||||
@NonNull
|
@NonNull
|
||||||
@SuppressWarnings("ConstantConditions")
|
@SuppressWarnings("ConstantConditions")
|
||||||
static Collection<CustomFilterGroup> parseCustomFilterGroups() {
|
static Collection<CustomFilterGroup> parseCustomFilterGroups() {
|
||||||
String rawCustomFilterText = Settings.CUSTOM_FILTER_STRINGS.get();
|
String rawCustomFilterText = YouTubeAndMusicSettings.CUSTOM_FILTER_STRINGS.get();
|
||||||
if (rawCustomFilterText.isBlank()) {
|
if (rawCustomFilterText.isBlank()) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
@@ -100,7 +102,7 @@ final class CustomFilter extends Filter {
|
|||||||
ByteTrieSearch bufferSearch;
|
ByteTrieSearch bufferSearch;
|
||||||
|
|
||||||
CustomFilterGroup(boolean startsWith, @NonNull String path) {
|
CustomFilterGroup(boolean startsWith, @NonNull String path) {
|
||||||
super(Settings.CUSTOM_FILTER, path);
|
super(YouTubeAndMusicSettings.CUSTOM_FILTER, path);
|
||||||
this.startsWith = startsWith;
|
this.startsWith = startsWith;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +147,7 @@ final class CustomFilter extends Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
// All callbacks are custom filter groups.
|
// All callbacks are custom filter groups.
|
||||||
CustomFilterGroup custom = (CustomFilterGroup) matchedGroup;
|
CustomFilterGroup custom = (CustomFilterGroup) matchedGroup;
|
||||||
+7
-4
@@ -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.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
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.
|
* Filters litho based components.
|
||||||
*
|
*
|
||||||
@@ -14,11 +17,11 @@ import java.util.List;
|
|||||||
* either an identifier or a path.
|
* either an identifier or a path.
|
||||||
* Then inside {@link #isFiltered(String, String, byte[], StringFilterGroup, FilterContentType, int)}
|
* 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)
|
* 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.
|
* All callbacks must be registered before the constructor completes.
|
||||||
*/
|
*/
|
||||||
abstract class Filter {
|
public abstract class Filter {
|
||||||
|
|
||||||
public enum FilterContentType {
|
public enum FilterContentType {
|
||||||
IDENTIFIER,
|
IDENTIFIER,
|
||||||
@@ -65,7 +68,7 @@ abstract class Filter {
|
|||||||
* @param contentIndex Matched index of the identifier or path.
|
* @param contentIndex Matched index of the identifier or path.
|
||||||
* @return True if the litho component should be filtered out.
|
* @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) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
+213
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+19
-29
@@ -1,21 +1,22 @@
|
|||||||
package app.revanced.extension.youtube.patches.components;
|
package app.revanced.extension.shared.patches.litho;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
import app.revanced.extension.shared.ByteTrieSearch;
|
import app.revanced.extension.shared.ByteTrieSearch;
|
||||||
import app.revanced.extension.shared.StringTrieSearch;
|
import app.revanced.extension.shared.StringTrieSearch;
|
||||||
import app.revanced.extension.shared.TrieSearch;
|
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 List<T> filterGroups = new ArrayList<>();
|
||||||
private final TrieSearch<V> search = createSearchGraph();
|
private final TrieSearch<V> search = createSearchGraph();
|
||||||
|
|
||||||
@SafeVarargs
|
@SafeVarargs
|
||||||
protected final void addAll(final T... groups) {
|
public final void addAll(final T... groups) {
|
||||||
filterGroups.addAll(Arrays.asList(groups));
|
filterGroups.addAll(Arrays.asList(groups));
|
||||||
|
|
||||||
for (T group : groups) {
|
for (T group : groups) {
|
||||||
@@ -41,18 +42,7 @@ abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<
|
|||||||
return filterGroups.iterator();
|
return filterGroups.iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public FilterGroup.FilterGroupResult check(V stack) {
|
||||||
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) {
|
|
||||||
FilterGroup.FilterGroupResult result = new FilterGroup.FilterGroupResult();
|
FilterGroup.FilterGroupResult result = new FilterGroup.FilterGroupResult();
|
||||||
search.matches(stack, result);
|
search.matches(stack, result);
|
||||||
return result;
|
return result;
|
||||||
@@ -60,21 +50,21 @@ abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected abstract TrieSearch<V> createSearchGraph();
|
protected abstract TrieSearch<V> createSearchGraph();
|
||||||
}
|
|
||||||
|
|
||||||
final class StringFilterGroupList extends FilterGroupList<String, StringFilterGroup> {
|
public static final class StringFilterGroupList extends FilterGroupList<String, StringFilterGroup> {
|
||||||
protected StringTrieSearch createSearchGraph() {
|
protected StringTrieSearch createSearchGraph() {
|
||||||
return new StringTrieSearch();
|
return new StringTrieSearch();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If searching for a single byte pattern, then it is slightly better to use
|
* 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
|
* {@link ByteArrayFilterGroup#check(byte[])} as it uses KMP which is faster
|
||||||
* than a prefix tree to search for only 1 pattern.
|
* than a prefix tree to search for only 1 pattern.
|
||||||
*/
|
*/
|
||||||
final class ByteArrayFilterGroupList extends FilterGroupList<byte[], ByteArrayFilterGroup> {
|
public static final class ByteArrayFilterGroupList extends FilterGroupList<byte[], ByteArrayFilterGroup> {
|
||||||
protected ByteTrieSearch createSearchGraph() {
|
protected ByteTrieSearch createSearchGraph() {
|
||||||
return new ByteTrieSearch();
|
return new ByteTrieSearch();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+6
-4
@@ -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.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@@ -7,9 +7,11 @@ import java.nio.ByteBuffer;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.settings.BaseSettings;
|
|
||||||
import app.revanced.extension.shared.StringTrieSearch;
|
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")
|
@SuppressWarnings("unused")
|
||||||
public final class LithoFilterPatch {
|
public final class LithoFilterPatch {
|
||||||
@@ -36,7 +38,7 @@ public final class LithoFilterPatch {
|
|||||||
builder.append(identifier);
|
builder.append(identifier);
|
||||||
builder.append(" Path: ");
|
builder.append(" Path: ");
|
||||||
builder.append(path);
|
builder.append(path);
|
||||||
if (Settings.DEBUG_PROTOBUFFER.get()) {
|
if (YouTubeAndMusicSettings.DEBUG_PROTOBUFFER.get()) {
|
||||||
builder.append(" BufferStrings: ");
|
builder.append(" BufferStrings: ");
|
||||||
findAsciiStrings(builder, buffer);
|
findAsciiStrings(builder, buffer);
|
||||||
}
|
}
|
||||||
+2
@@ -28,6 +28,8 @@ public class BaseSettings {
|
|||||||
public static final BooleanSetting SETTINGS_SEARCH_HISTORY = new BooleanSetting("revanced_settings_search_history", TRUE, true);
|
public static final BooleanSetting SETTINGS_SEARCH_HISTORY = new BooleanSetting("revanced_settings_search_history", TRUE, true);
|
||||||
public static final StringSetting SETTINGS_SEARCH_ENTRIES = new StringSetting("revanced_settings_search_entries", "");
|
public static final StringSetting SETTINGS_SEARCH_ENTRIES = new StringSetting("revanced_settings_search_entries", "");
|
||||||
|
|
||||||
|
public static final BooleanSetting GMS_CORE_CHECK_UPDATES = new BooleanSetting("revanced_gms_core_check_updates", true, true);
|
||||||
|
|
||||||
//
|
//
|
||||||
// Settings shared by YouTube and YouTube Music.
|
// Settings shared by YouTube and YouTube Music.
|
||||||
//
|
//
|
||||||
|
|||||||
+14
@@ -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));
|
||||||
|
}
|
||||||
+1
-1
@@ -10,7 +10,7 @@ import android.os.Environment;
|
|||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
import android.webkit.MimeTypeMap;
|
import android.webkit.MimeTypeMap;
|
||||||
|
|
||||||
import com.strava.core.data.MediaType;
|
import com.strava.mediamodels.data.MediaType;
|
||||||
import com.strava.photos.data.Media;
|
import com.strava.photos.data.Media;
|
||||||
|
|
||||||
import okhttp3.*;
|
import okhttp3.*;
|
||||||
|
|||||||
+227
@@ -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
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.strava.core.data;
|
package com.strava.mediamodels.data;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.strava.core.data;
|
package com.strava.mediamodels.data;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.strava.core.data;
|
package com.strava.mediamodels.data;
|
||||||
|
|
||||||
public enum MediaType {
|
public enum MediaType {
|
||||||
PHOTO(1),
|
PHOTO(1),
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.strava.core.data;
|
package com.strava.mediamodels.data;
|
||||||
|
|
||||||
import java.util.SortedMap;
|
import java.util.SortedMap;
|
||||||
|
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.strava.core.data;
|
package com.strava.mediamodels.data;
|
||||||
|
|
||||||
public enum RemoteMediaStatus {
|
public enum RemoteMediaStatus {
|
||||||
NEW,
|
NEW,
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.strava.modularframework.data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
public abstract class Destination implements Serializable {
|
||||||
|
public abstract String getUrl();
|
||||||
|
}
|
||||||
+28
@@ -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();
|
||||||
|
}
|
||||||
+9
@@ -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();
|
||||||
|
}
|
||||||
+8
@@ -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);
|
||||||
|
}
|
||||||
+16
@@ -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();
|
||||||
|
}
|
||||||
+21
@@ -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();
|
||||||
|
}
|
||||||
+22
@@ -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();
|
||||||
|
}
|
||||||
+7
@@ -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();
|
||||||
|
}
|
||||||
+10
@@ -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();
|
||||||
|
}
|
||||||
+19
@@ -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;
|
package com.strava.photos.data;
|
||||||
|
|
||||||
import com.strava.core.data.MediaDimension;
|
import com.strava.mediamodels.data.MediaDimension;
|
||||||
import com.strava.core.data.MediaType;
|
import com.strava.mediamodels.data.MediaType;
|
||||||
import com.strava.core.data.RemoteMediaContent;
|
import com.strava.mediamodels.data.RemoteMediaContent;
|
||||||
import com.strava.core.data.RemoteMediaStatus;
|
import com.strava.mediamodels.data.RemoteMediaStatus;
|
||||||
|
|
||||||
import java.util.SortedMap;
|
import java.util.SortedMap;
|
||||||
|
|
||||||
public abstract class Media implements RemoteMediaContent {
|
public abstract class Media implements RemoteMediaContent {
|
||||||
|
|||||||
+5
-2
@@ -11,6 +11,9 @@ import java.util.List;
|
|||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.shared.StringTrieSearch;
|
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;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
@@ -153,8 +156,8 @@ public final class AdsFilter extends Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
if (matchedGroup == playerShoppingShelf) {
|
if (matchedGroup == playerShoppingShelf) {
|
||||||
return contentIndex == 0 && playerShoppingShelfBuffer.check(buffer).isFiltered();
|
return contentIndex == 0 && playerShoppingShelfBuffer.check(buffer).isFiltered();
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -1,5 +1,7 @@
|
|||||||
package app.revanced.extension.youtube.patches.components;
|
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.patches.playback.quality.AdvancedVideoQualityMenuPatch;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
@@ -19,7 +21,7 @@ public final class AdvancedVideoQualityMenuFilter extends Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
isVideoQualityMenuVisible = true;
|
isVideoQualityMenuVisible = true;
|
||||||
|
|
||||||
|
|||||||
+6
-2
@@ -1,9 +1,13 @@
|
|||||||
package app.revanced.extension.youtube.patches.components;
|
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;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@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 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_PREFIX = "video_action_bar.e";
|
||||||
private static final String VIDEO_ACTION_BAR_PATH = "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
|
@Override
|
||||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
if (matchedGroup == likeSubscribeGlow) {
|
if (matchedGroup == likeSubscribeGlow) {
|
||||||
return (path.startsWith(VIDEO_ACTION_BAR_PATH_PREFIX) || path.startsWith(COMPACT_CHANNEL_BAR_PATH_PREFIX))
|
return (path.startsWith(VIDEO_ACTION_BAR_PATH_PREFIX) || path.startsWith(COMPACT_CHANNEL_BAR_PATH_PREFIX))
|
||||||
|
|||||||
+5
-3
@@ -1,10 +1,12 @@
|
|||||||
package app.revanced.extension.youtube.patches.components;
|
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.settings.Settings;
|
||||||
import app.revanced.extension.youtube.shared.PlayerType;
|
import app.revanced.extension.youtube.shared.PlayerType;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
final class CommentsFilter extends Filter {
|
public final class CommentsFilter extends Filter {
|
||||||
|
|
||||||
private static final String COMMENT_COMPOSER_PATH = "comment_composer.e";
|
private static final String COMMENT_COMPOSER_PATH = "comment_composer.e";
|
||||||
|
|
||||||
@@ -88,8 +90,8 @@ final class CommentsFilter extends Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
if (matchedGroup == chipBar) {
|
if (matchedGroup == chipBar) {
|
||||||
// Playlist sort button uses same components and must only filter if the player is opened.
|
// Playlist sort button uses same components and must only filter if the player is opened.
|
||||||
return PlayerType.getCurrent().isMaximizedOrFullscreen()
|
return PlayerType.getCurrent().isMaximizedOrFullscreen()
|
||||||
|
|||||||
+6
-3
@@ -1,11 +1,14 @@
|
|||||||
package app.revanced.extension.youtube.patches.components;
|
package app.revanced.extension.youtube.patches.components;
|
||||||
|
|
||||||
import app.revanced.extension.shared.StringTrieSearch;
|
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.settings.Settings;
|
||||||
import app.revanced.extension.youtube.shared.PlayerType;
|
import app.revanced.extension.youtube.shared.PlayerType;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
final class DescriptionComponentsFilter extends Filter {
|
public final class DescriptionComponentsFilter extends Filter {
|
||||||
|
|
||||||
private static final String INFOCARDS_SECTION_PATH = "infocards_section.e";
|
private static final String INFOCARDS_SECTION_PATH = "infocards_section.e";
|
||||||
|
|
||||||
@@ -128,8 +131,8 @@ final class DescriptionComponentsFilter extends Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
|
|
||||||
if (matchedGroup == aiGeneratedVideoSummarySection || matchedGroup == hypePoints) {
|
if (matchedGroup == aiGeneratedVideoSummarySection || matchedGroup == hypePoints) {
|
||||||
// Only hide if player is open, in case this component is used somewhere else.
|
// Only hide if player is open, in case this component is used somewhere else.
|
||||||
|
|||||||
-214
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
+2
@@ -1,6 +1,8 @@
|
|||||||
package app.revanced.extension.youtube.patches.components;
|
package app.revanced.extension.youtube.patches.components;
|
||||||
|
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
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")
|
@SuppressWarnings("unused")
|
||||||
public final class HideInfoCardsFilter extends Filter {
|
public final class HideInfoCardsFilter extends Filter {
|
||||||
|
|||||||
+5
-3
@@ -17,6 +17,8 @@ import app.revanced.extension.shared.Utils;
|
|||||||
import app.revanced.extension.shared.ByteTrieSearch;
|
import app.revanced.extension.shared.ByteTrieSearch;
|
||||||
import app.revanced.extension.shared.StringTrieSearch;
|
import app.revanced.extension.shared.StringTrieSearch;
|
||||||
import app.revanced.extension.shared.TrieSearch;
|
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.settings.Settings;
|
||||||
import app.revanced.extension.youtube.shared.NavigationBar;
|
import app.revanced.extension.youtube.shared.NavigationBar;
|
||||||
import app.revanced.extension.youtube.shared.PlayerType;
|
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.
|
* - When using whole word syntax, some keywords may need additional pluralized variations.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@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.
|
* Strings found in the buffer for every videos. Full strings should be specified.
|
||||||
@@ -554,8 +556,8 @@ final class KeywordContentFilter extends Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
if (contentIndex != 0 && matchedGroup == startsWithFilter) {
|
if (contentIndex != 0 && matchedGroup == startsWithFilter) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -14,6 +14,9 @@ import androidx.annotation.Nullable;
|
|||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.shared.StringTrieSearch;
|
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.patches.ChangeHeaderPatch;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
import app.revanced.extension.youtube.shared.NavigationBar;
|
import app.revanced.extension.youtube.shared.NavigationBar;
|
||||||
@@ -342,7 +345,7 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
// This identifier is used not only in players but also in search results:
|
// This identifier is used not only in players but also in search results:
|
||||||
// https://github.com/ReVanced/revanced-patches/issues/3245
|
// https://github.com/ReVanced/revanced-patches/issues/3245
|
||||||
|
|||||||
+3
-1
@@ -1,5 +1,7 @@
|
|||||||
package app.revanced.extension.youtube.patches.components;
|
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.patches.playback.speed.CustomPlaybackSpeedPatch;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
@@ -36,7 +38,7 @@ public final class PlaybackSpeedMenuFilter extends Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
if (matchedGroup == oldPlaybackMenuGroup) {
|
if (matchedGroup == oldPlaybackMenuGroup) {
|
||||||
isOldPlaybackSpeedMenuVisible = true;
|
isOldPlaybackSpeedMenuVisible = true;
|
||||||
|
|||||||
+5
-2
@@ -3,13 +3,16 @@ package app.revanced.extension.youtube.patches.components;
|
|||||||
import app.revanced.extension.shared.settings.BaseSettings;
|
import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
import app.revanced.extension.shared.settings.Setting;
|
import app.revanced.extension.shared.settings.Setting;
|
||||||
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
|
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.settings.Settings;
|
||||||
import app.revanced.extension.youtube.shared.ShortsPlayerState;
|
import app.revanced.extension.youtube.shared.ShortsPlayerState;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class PlayerFlyoutMenuItemsFilter extends Filter {
|
public final class PlayerFlyoutMenuItemsFilter extends Filter {
|
||||||
|
|
||||||
public static final class HideAudioFlyoutMenuAvailability implements Setting.Availability {
|
public static final class HideAudioFlyoutMenuAvailability implements Setting.Availability {
|
||||||
@Override
|
@Override
|
||||||
@@ -94,7 +97,7 @@ public class PlayerFlyoutMenuItemsFilter extends Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
if (matchedGroup == videoQualityMenuFooter) {
|
if (matchedGroup == videoQualityMenuFooter) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
+5
-2
@@ -13,6 +13,9 @@ import app.revanced.extension.youtube.patches.VideoInformation;
|
|||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.TrieSearch;
|
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.
|
* Searches for video id's in the proto buffer of Shorts dislike.
|
||||||
@@ -84,13 +87,13 @@ public final class ReturnYouTubeDislikeFilter extends Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
if (!Settings.RYD_ENABLED.get() || !Settings.RYD_SHORTS.get()) {
|
if (!Settings.RYD_ENABLED.get() || !Settings.RYD_SHORTS.get()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
FilterGroup.FilterGroupResult result = videoIdFilterGroup.check(buffer);
|
FilterGroupResult result = videoIdFilterGroup.check(buffer);
|
||||||
if (result.isFiltered()) {
|
if (result.isFiltered()) {
|
||||||
String matchedVideoId = findVideoId(buffer);
|
String matchedVideoId = findVideoId(buffer);
|
||||||
// Matched video will be null if in incognito mode.
|
// Matched video will be null if in incognito mode.
|
||||||
|
|||||||
+4
-1
@@ -11,6 +11,9 @@ import java.util.Arrays;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
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.settings.Settings;
|
||||||
import app.revanced.extension.youtube.shared.NavigationBar;
|
import app.revanced.extension.youtube.shared.NavigationBar;
|
||||||
import app.revanced.extension.youtube.shared.PlayerType;
|
import app.revanced.extension.youtube.shared.PlayerType;
|
||||||
@@ -339,7 +342,7 @@ public final class ShortsFilter extends Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean isFiltered(String identifier, String path, byte[] buffer,
|
public boolean isFiltered(String identifier, String path, byte[] buffer,
|
||||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
if (contentType == FilterContentType.PATH) {
|
if (contentType == FilterContentType.PATH) {
|
||||||
if (matchedGroup == subscribeButton || matchedGroup == joinButton
|
if (matchedGroup == subscribeButton || matchedGroup == joinButton
|
||||||
|
|||||||
+8
-12
@@ -32,6 +32,7 @@ import android.graphics.Color;
|
|||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.Utils;
|
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.BaseSettings;
|
||||||
import app.revanced.extension.shared.settings.BooleanSetting;
|
import app.revanced.extension.shared.settings.BooleanSetting;
|
||||||
import app.revanced.extension.shared.settings.EnumSetting;
|
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.sponsorblock.SponsorBlockSettings;
|
||||||
import app.revanced.extension.youtube.swipecontrols.SwipeControlsConfigurationProvider.SwipeOverlayStyle;
|
import app.revanced.extension.youtube.swipecontrols.SwipeControlsConfigurationProvider.SwipeOverlayStyle;
|
||||||
|
|
||||||
public class Settings extends BaseSettings {
|
public class Settings extends YouTubeAndMusicSettings {
|
||||||
// Video
|
// Video
|
||||||
public static final BooleanSetting ADVANCED_VIDEO_QUALITY_MENU = new BooleanSetting("revanced_advanced_video_quality_menu", TRUE);
|
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);
|
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,
|
public static final BooleanSetting CHANGE_START_PAGE_ALWAYS = new BooleanSetting("revanced_change_start_page_always", FALSE, true,
|
||||||
new ChangeStartPageTypeAvailability());
|
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));
|
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
|
// Navigation buttons
|
||||||
public static final BooleanSetting HIDE_HOME_BUTTON = new BooleanSetting("revanced_hide_home_button", FALSE, true);
|
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);
|
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 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,
|
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());
|
"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
|
// Swipe controls
|
||||||
public static final BooleanSetting SWIPE_CHANGE_VIDEO = new BooleanSetting("revanced_swipe_change_video", FALSE, true);
|
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,
|
public static final IntegerSetting SWIPE_MAGNITUDE_THRESHOLD = new IntegerSetting("revanced_swipe_threshold", 30, true,
|
||||||
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
|
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 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));
|
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
|
||||||
public static final IntegerSetting SWIPE_OVERLAY_TEXT_SIZE = new IntegerSetting("revanced_swipe_text_overlay_size", 14, true,
|
public static final IntegerSetting SWIPE_OVERLAY_TEXT_SIZE = new IntegerSetting("revanced_swipe_text_overlay_size", 14, true,
|
||||||
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
|
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
|
||||||
@@ -411,7 +405,9 @@ public class Settings extends BaseSettings {
|
|||||||
|
|
||||||
// SponsorBlock
|
// SponsorBlock
|
||||||
public static final BooleanSetting SB_ENABLED = new BooleanSetting("sb_enabled", TRUE);
|
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 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 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));
|
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);
|
public static final StringSetting SB_CATEGORY_UNSUBMITTED_COLOR = new StringSetting("sb_unsubmitted_color", "#FFFFFFFF", false, false);
|
||||||
|
|
||||||
// Deprecated migrations
|
// 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_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);
|
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.
|
// or is spoofing to a version the same or newer than this app.
|
||||||
if (!SPOOF_APP_VERSION_TARGET.isSetToDefault() &&
|
if (!SPOOF_APP_VERSION_TARGET.isSetToDefault() &&
|
||||||
(SPOOF_APP_VERSION_TARGET.get().compareTo(SPOOF_APP_VERSION_TARGET.defaultValue) < 0
|
(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");
|
Logger.printInfo(() -> "Resetting spoof app version");
|
||||||
SPOOF_APP_VERSION_TARGET.resetToDefault();
|
SPOOF_APP_VERSION_TARGET.resetToDefault();
|
||||||
SPOOF_APP_VERSION.resetToDefault();
|
SPOOF_APP_VERSION.resetToDefault();
|
||||||
|
|||||||
+1
-1
@@ -3,4 +3,4 @@ org.gradle.jvmargs = -Xms512M -Xmx2048M
|
|||||||
org.gradle.parallel = true
|
org.gradle.parallel = true
|
||||||
android.useAndroidX = true
|
android.useAndroidX = true
|
||||||
kotlin.code.style = official
|
kotlin.code.style = official
|
||||||
version = 5.48.0
|
version = 5.50.0-dev.8
|
||||||
|
|||||||
@@ -232,6 +232,10 @@ public final class app/revanced/patches/finanzonline/detection/root/RootDetectio
|
|||||||
public static final fun getRootDetectionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getRootDetectionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/fotmob/ads/HideAdsPatchKt {
|
||||||
|
public static final fun getHideAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/googlenews/customtabs/EnableCustomTabsPatchKt {
|
public final class app/revanced/patches/googlenews/customtabs/EnableCustomTabsPatchKt {
|
||||||
public static final fun getEnableCustomTabsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getEnableCustomTabsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
@@ -332,6 +336,10 @@ public final class app/revanced/patches/instagram/misc/devmenu/EnableDeveloperMe
|
|||||||
public static final fun getEnableDeveloperMenuPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getEnableDeveloperMenuPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/instagram/misc/disableAnalytics/DisableAnalyticsPatchKt {
|
||||||
|
public static final fun getDisableAnalyticsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/instagram/misc/extension/SharedExtensionPatchKt {
|
public final class app/revanced/patches/instagram/misc/extension/SharedExtensionPatchKt {
|
||||||
public static final fun getSharedExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getSharedExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
@@ -364,6 +372,14 @@ public final class app/revanced/patches/irplus/ad/RemoveAdsPatchKt {
|
|||||||
public static final fun getRemoveAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getRemoveAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/kleinanzeigen/ads/HideAdsPatchKt {
|
||||||
|
public static final fun getHideAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/kleinanzeigen/hide_pur/HidePurPatchKt {
|
||||||
|
public static final fun getHidePurPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/letterboxd/ads/HideAdsPatchKt {
|
public final class app/revanced/patches/letterboxd/ads/HideAdsPatchKt {
|
||||||
public static final fun getHideAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getHideAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
@@ -432,6 +448,10 @@ public final class app/revanced/patches/meta/ads/HideAdsPatchKt {
|
|||||||
public static final fun getHideAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getHideAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/microsoft/officelens/misc/onedrive/HideOneDriveMigrationPatchKt {
|
||||||
|
public static final fun getHideOneDriveMigrationPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/mifitness/misc/locale/ForceEnglishLocalePatchKt {
|
public final class app/revanced/patches/mifitness/misc/locale/ForceEnglishLocalePatchKt {
|
||||||
public static final fun getForceEnglishLocalePatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getForceEnglishLocalePatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
@@ -472,6 +492,10 @@ public final class app/revanced/patches/music/layout/compactheader/HideCategoryB
|
|||||||
public static final fun getHideCategoryBar ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getHideCategoryBar ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/music/layout/hide/general/HideLayoutComponentsPatchKt {
|
||||||
|
public static final fun getHideLayoutComponentsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/music/layout/miniplayercolor/ChangeMiniplayerColorKt {
|
public final class app/revanced/patches/music/layout/miniplayercolor/ChangeMiniplayerColorKt {
|
||||||
public static final fun getChangeMiniplayerColor ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getChangeMiniplayerColor ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
@@ -497,6 +521,10 @@ public final class app/revanced/patches/music/misc/androidauto/BypassCertificate
|
|||||||
public static final fun getBypassCertificateChecksPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getBypassCertificateChecksPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/music/misc/androidauto/UnlockAndroidAutoMediaBrowserPatchKt {
|
||||||
|
public static final fun getUnlockAndroidAutoMediaBrowserPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/music/misc/backgroundplayback/BackgroundPlaybackPatchKt {
|
public final class app/revanced/patches/music/misc/backgroundplayback/BackgroundPlaybackPatchKt {
|
||||||
public static final fun getBackgroundPlaybackPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getBackgroundPlaybackPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
@@ -521,6 +549,10 @@ public final class app/revanced/patches/music/misc/gms/GmsCoreSupportPatchKt {
|
|||||||
public static final fun getGmsCoreSupportPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getGmsCoreSupportPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/music/misc/litho/filter/LithoFilterPatchKt {
|
||||||
|
public static final fun getLithoFilterPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/music/misc/privacy/SanitizeSharingLinksPatchKt {
|
public final class app/revanced/patches/music/misc/privacy/SanitizeSharingLinksPatchKt {
|
||||||
public static final fun getSanitizeSharingLinksPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getSanitizeSharingLinksPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
@@ -577,6 +609,14 @@ public final class app/revanced/patches/nfctoolsse/misc/pro/UnlockProPatchKt {
|
|||||||
public static final fun getUnlockProPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getUnlockProPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/nothingx/misc/extension/SharedExtensionPatchKt {
|
||||||
|
public static final fun getSharedExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/nothingx/misc/logk1token/ShowK1TokenPatchsKt {
|
||||||
|
public static final fun getShowK1TokensPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/nunl/ads/HideAdsPatchKt {
|
public final class app/revanced/patches/nunl/ads/HideAdsPatchKt {
|
||||||
public static final fun getHideAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getHideAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
@@ -932,6 +972,10 @@ public final class app/revanced/patches/shared/misc/hex/Replacement {
|
|||||||
public final fun getReplacementBytesPadded ()[B
|
public final fun getReplacementBytesPadded ()[B
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/shared/misc/litho/filter/LithoFilterPatchKt {
|
||||||
|
public static final fun getAddLithoFilter ()Lkotlin/jvm/functions/Function1;
|
||||||
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/shared/misc/mapping/ResourceElement {
|
public final class app/revanced/patches/shared/misc/mapping/ResourceElement {
|
||||||
public final fun component1 ()Ljava/lang/String;
|
public final fun component1 ()Ljava/lang/String;
|
||||||
public final fun component2 ()Ljava/lang/String;
|
public final fun component2 ()Ljava/lang/String;
|
||||||
@@ -1212,6 +1256,10 @@ public final class app/revanced/patches/stocard/layout/HideStoryBubblesPatchKt {
|
|||||||
public static final fun getHideStoryBubblesPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
|
public static final fun getHideStoryBubblesPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/patches/strava/distractions/HideDistractionsPatchKt {
|
||||||
|
public static final fun getHideDistractionsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
|
}
|
||||||
|
|
||||||
public final class app/revanced/patches/strava/groupkudos/AddGiveGroupKudosButtonToGroupActivityKt {
|
public final class app/revanced/patches/strava/groupkudos/AddGiveGroupKudosButtonToGroupActivityKt {
|
||||||
public static final fun getAddGiveGroupKudosButtonToGroupActivity ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getAddGiveGroupKudosButtonToGroupActivity ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,25 +22,6 @@ dependencies {
|
|||||||
compileOnly(project(":patches:stub"))
|
compileOnly(project(":patches:stub"))
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks {
|
|
||||||
register<JavaExec>("preprocessCrowdinStrings") {
|
|
||||||
description = "Preprocess strings for Crowdin push"
|
|
||||||
|
|
||||||
dependsOn(compileKotlin)
|
|
||||||
|
|
||||||
classpath = sourceSets["main"].runtimeClasspath
|
|
||||||
mainClass.set("app.revanced.util.CrowdinPreprocessorKt")
|
|
||||||
|
|
||||||
args = listOf(
|
|
||||||
"src/main/resources/addresources/values/strings.xml",
|
|
||||||
// Ideally this would use build/tmp/crowdin/strings.xml
|
|
||||||
// But using that does not work with Crowdin pull because
|
|
||||||
// it does not recognize the strings.xml file belongs to this project.
|
|
||||||
"src/main/resources/addresources/values/strings.xml"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
compilerOptions {
|
compilerOptions {
|
||||||
freeCompilerArgs = listOf("-Xcontext-receivers")
|
freeCompilerArgs = listOf("-Xcontext-receivers")
|
||||||
@@ -56,3 +37,5 @@ publishing {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
apply(from = "strings-processing.gradle.kts")
|
||||||
|
|||||||
+1
@@ -32,6 +32,7 @@ private val unregisterScreenCaptureCallbackMethodReference = ImmutableMethodRefe
|
|||||||
val preventScreenshotDetectionPatch = bytecodePatch(
|
val preventScreenshotDetectionPatch = bytecodePatch(
|
||||||
name = "Prevent screenshot detection",
|
name = "Prevent screenshot detection",
|
||||||
description = "Removes the registration of all screen capture callbacks. This prevents the app from detecting screenshots.",
|
description = "Removes the registration of all screen capture callbacks. This prevents the app from detecting screenshots.",
|
||||||
|
use = false
|
||||||
) {
|
) {
|
||||||
dependsOn(transformInstructionsPatch(
|
dependsOn(transformInstructionsPatch(
|
||||||
filterMap = { _, _, instruction, instructionIndex ->
|
filterMap = { _, _, instruction, instructionIndex ->
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package app.revanced.patches.fotmob.ads
|
||||||
|
|
||||||
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
|
import app.revanced.patcher.fingerprint
|
||||||
|
|
||||||
|
internal val shouldDisplayAdsMethod = fingerprint {
|
||||||
|
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||||
|
returns("Z")
|
||||||
|
custom { method, classDef ->
|
||||||
|
method.name == "shouldDisplayAds" && classDef.type.endsWith("AdsService;")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package app.revanced.patches.fotmob.ads
|
||||||
|
|
||||||
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
|
import app.revanced.util.returnEarly
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
val hideAdsPatch = bytecodePatch(
|
||||||
|
name = "Hide ads",
|
||||||
|
) {
|
||||||
|
compatibleWith("com.mobilefootie.wc2010")
|
||||||
|
|
||||||
|
execute {
|
||||||
|
shouldDisplayAdsMethod.method.returnEarly(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
-1
@@ -27,5 +27,4 @@ private fun gmsCoreSupportResourcePatch(
|
|||||||
toPackageName = REVANCED_MAGAZINES_PACKAGE_NAME,
|
toPackageName = REVANCED_MAGAZINES_PACKAGE_NAME,
|
||||||
spoofedPackageSignature = "24bb24c05e47e0aefa68a58a766179d9b613a666",
|
spoofedPackageSignature = "24bb24c05e47e0aefa68a58a766179d9b613a666",
|
||||||
gmsCoreVendorGroupIdOption = gmsCoreVendorGroupIdOption,
|
gmsCoreVendorGroupIdOption = gmsCoreVendorGroupIdOption,
|
||||||
addStringResources = false,
|
|
||||||
)
|
)
|
||||||
|
|||||||
-1
@@ -22,7 +22,6 @@ private fun gmsCoreSupportResourcePatch(
|
|||||||
) = app.revanced.patches.shared.misc.gms.gmsCoreSupportResourcePatch(
|
) = app.revanced.patches.shared.misc.gms.gmsCoreSupportResourcePatch(
|
||||||
fromPackageName = PHOTOS_PACKAGE_NAME,
|
fromPackageName = PHOTOS_PACKAGE_NAME,
|
||||||
toPackageName = REVANCED_PHOTOS_PACKAGE_NAME,
|
toPackageName = REVANCED_PHOTOS_PACKAGE_NAME,
|
||||||
addStringResources = false,
|
|
||||||
spoofedPackageSignature = "24bb24c05e47e0aefa68a58a766179d9b613a600",
|
spoofedPackageSignature = "24bb24c05e47e0aefa68a58a766179d9b613a600",
|
||||||
gmsCoreVendorGroupIdOption = gmsCoreVendorGroupIdOption,
|
gmsCoreVendorGroupIdOption = gmsCoreVendorGroupIdOption,
|
||||||
)
|
)
|
||||||
|
|||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
package app.revanced.patches.instagram.misc.disableAnalytics
|
||||||
|
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||||
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
|
import app.revanced.patches.instagram.shared.replaceStringWithBogus
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
val disableAnalyticsPatch = bytecodePatch(
|
||||||
|
name = "Disable analytics",
|
||||||
|
description = "Disables analytics that are sent periodically.",
|
||||||
|
) {
|
||||||
|
compatibleWith("com.instagram.android")
|
||||||
|
|
||||||
|
execute {
|
||||||
|
// Returns BOGUS as analytics url.
|
||||||
|
instagramAnalyticsUrlBuilderMethodFingerprint.method.addInstructions(
|
||||||
|
0,
|
||||||
|
"""
|
||||||
|
const-string v0, "BOGUS"
|
||||||
|
return-object v0
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
// Replaces analytics url as BOGUS.
|
||||||
|
facebookAnalyticsUrlInitMethodFingerprint.replaceStringWithBogus(TARGET_URL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
package app.revanced.patches.instagram.misc.disableAnalytics
|
||||||
|
|
||||||
|
import app.revanced.patcher.fingerprint
|
||||||
|
|
||||||
|
internal val instagramAnalyticsUrlBuilderMethodFingerprint = fingerprint {
|
||||||
|
strings("/logging_client_events")
|
||||||
|
}
|
||||||
|
|
||||||
|
internal const val TARGET_URL = "https://graph.facebook.com/logging_client_events"
|
||||||
|
internal val facebookAnalyticsUrlInitMethodFingerprint = fingerprint {
|
||||||
|
strings("analytics_endpoint",TARGET_URL)
|
||||||
|
}
|
||||||
+3
-3
@@ -36,9 +36,9 @@ val openLinksExternallyPatch = bytecodePatch(
|
|||||||
addInstructions(
|
addInstructions(
|
||||||
urlResultObjIndex + 1,
|
urlResultObjIndex + 1,
|
||||||
"""
|
"""
|
||||||
invoke-static { v$urlRegister }, $EXTENSION_CLASS_DESCRIPTOR->openExternally(Ljava/lang/String;)Z
|
invoke-static/range { v$urlRegister .. v$urlRegister }, $EXTENSION_CLASS_DESCRIPTOR->openExternally(Ljava/lang/String;)Z
|
||||||
move-result v$urlRegister
|
move-result v0
|
||||||
return v$urlRegister
|
return v0
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-4
@@ -16,15 +16,14 @@ internal fun editShareLinksPatch(block: MutableMethod.(index: Int, register: Int
|
|||||||
liveUrlResponseJsonParserFingerprint
|
liveUrlResponseJsonParserFingerprint
|
||||||
)
|
)
|
||||||
|
|
||||||
for (fingerprint in fingerprintsToPatch) {
|
fingerprintsToPatch.forEachIndexed { index, fingerprint ->
|
||||||
|
|
||||||
fingerprint.method.apply {
|
fingerprint.method.apply {
|
||||||
val putSharingUrlIndex = indexOfFirstInstruction(
|
val putSharingUrlIndex = indexOfFirstInstruction(
|
||||||
permalinkResponseJsonParserFingerprint.stringMatches!!.first().index,
|
index,
|
||||||
Opcode.IPUT_OBJECT
|
Opcode.IPUT_OBJECT
|
||||||
)
|
)
|
||||||
|
|
||||||
val sharingUrlRegister = getInstruction<TwoRegisterInstruction>(putSharingUrlIndex).registerA
|
val sharingUrlRegister = getInstruction<TwoRegisterInstruction>(putSharingUrlIndex).registerA
|
||||||
|
|
||||||
block(putSharingUrlIndex, sharingUrlRegister)
|
block(putSharingUrlIndex, sharingUrlRegister)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-4
@@ -1,23 +1,32 @@
|
|||||||
package app.revanced.patches.instagram.misc.share
|
package app.revanced.patches.instagram.misc.share
|
||||||
|
|
||||||
import app.revanced.patcher.fingerprint
|
import app.revanced.patcher.fingerprint
|
||||||
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
|
|
||||||
|
internal val TARGET_STRING_ARRAY = arrayOf(
|
||||||
|
"permalink",
|
||||||
|
"story_item_to_share_url",
|
||||||
|
"profile_to_share_url",
|
||||||
|
"live_to_share_url",
|
||||||
|
)
|
||||||
|
|
||||||
internal val permalinkResponseJsonParserFingerprint = fingerprint {
|
internal val permalinkResponseJsonParserFingerprint = fingerprint {
|
||||||
strings("permalink", "PermalinkResponse")
|
strings(TARGET_STRING_ARRAY[0])
|
||||||
|
opcodes(Opcode.NEW_INSTANCE,Opcode.INVOKE_DIRECT,Opcode.INVOKE_VIRTUAL)
|
||||||
custom { method, _ -> method.name == "parseFromJson" }
|
custom { method, _ -> method.name == "parseFromJson" }
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val storyUrlResponseJsonParserFingerprint = fingerprint {
|
internal val storyUrlResponseJsonParserFingerprint = fingerprint {
|
||||||
strings("story_item_to_share_url", "StoryItemUrlResponse")
|
strings(TARGET_STRING_ARRAY[1])
|
||||||
custom { method, _ -> method.name == "parseFromJson" }
|
custom { method, _ -> method.name == "parseFromJson" }
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val profileUrlResponseJsonParserFingerprint = fingerprint {
|
internal val profileUrlResponseJsonParserFingerprint = fingerprint {
|
||||||
strings("profile_to_share_url")
|
strings(TARGET_STRING_ARRAY[2])
|
||||||
custom { method, _ -> method.name == "parseFromJson" }
|
custom { method, _ -> method.name == "parseFromJson" }
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val liveUrlResponseJsonParserFingerprint = fingerprint {
|
internal val liveUrlResponseJsonParserFingerprint = fingerprint {
|
||||||
strings("live_to_share_url", "LiveItemLinkUrlResponse")
|
strings(TARGET_STRING_ARRAY[3])
|
||||||
custom { method, _ -> method.name == "parseFromJson" }
|
custom { method, _ -> method.name == "parseFromJson" }
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package app.revanced.patches.kleinanzeigen.ads
|
||||||
|
|
||||||
|
import app.revanced.patcher.fingerprint
|
||||||
|
|
||||||
|
internal val getLibertyInitFingerprint = fingerprint {
|
||||||
|
custom { method, classDef ->
|
||||||
|
method.name == "init" && classDef.endsWith("/Liberty;")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package app.revanced.patches.kleinanzeigen.ads
|
||||||
|
|
||||||
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
|
import app.revanced.util.returnEarly
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
val hideAdsPatch = bytecodePatch(
|
||||||
|
name = "Hide ads",
|
||||||
|
description = "Hides sponsored ads and Google Ads. Also happens to disable Microsoft Clarity analytics.",
|
||||||
|
) {
|
||||||
|
compatibleWith("com.ebay.kleinanzeigen")
|
||||||
|
|
||||||
|
execute {
|
||||||
|
getLibertyInitFingerprint.method.returnEarly()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package app.revanced.patches.kleinanzeigen.hide_pur
|
||||||
|
|
||||||
|
import app.revanced.patcher.fingerprint
|
||||||
|
|
||||||
|
internal val getShowAdFreeSubscriptionFingerprint = fingerprint {
|
||||||
|
custom { method, classDef ->
|
||||||
|
method.name == "getShowAdFreeSubscription"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package app.revanced.patches.kleinanzeigen.hide_pur
|
||||||
|
|
||||||
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
|
import app.revanced.util.returnEarly
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
val hidePurPatch = bytecodePatch(
|
||||||
|
name = "Hide Pur",
|
||||||
|
description = "Hides Pur (Ad Free Subscription) from Settings Menu.",
|
||||||
|
) {
|
||||||
|
compatibleWith("com.ebay.kleinanzeigen")
|
||||||
|
|
||||||
|
execute {
|
||||||
|
getShowAdFreeSubscriptionFingerprint.method.returnEarly(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ val hideAdsPatch = bytecodePatch(
|
|||||||
execute {
|
execute {
|
||||||
admobHelperSetShowAdsFingerprint.method.addInstruction(0, "const p1, 0x0")
|
admobHelperSetShowAdsFingerprint.method.addInstruction(0, "const p1, 0x0")
|
||||||
listOf(admobHelperShouldShowAdsFingerprint, filmFragmentShowAdsFingerprint, memberExtensionShowAdsFingerprint).forEach {
|
listOf(admobHelperShouldShowAdsFingerprint, filmFragmentShowAdsFingerprint, memberExtensionShowAdsFingerprint).forEach {
|
||||||
it.method.returnEarly(false)
|
it.method.returnEarly()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
package app.revanced.patches.microsoft.officelens.misc.onedrive
|
||||||
|
|
||||||
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
|
import app.revanced.patcher.fingerprint
|
||||||
|
|
||||||
|
internal val hasMigratedToOneDriveFingerprint = fingerprint {
|
||||||
|
custom { method, classDef ->
|
||||||
|
classDef.endsWith("FREManager;") && method.name == "getMigrationStage"
|
||||||
|
}
|
||||||
|
}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
package app.revanced.patches.microsoft.officelens.misc.onedrive
|
||||||
|
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstructions
|
||||||
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
val hideOneDriveMigrationPatch = bytecodePatch(
|
||||||
|
name = "Hide OneDrive migration",
|
||||||
|
description = "Hides the OneDrive migration prompt when opening Microsoft Office Lens.",
|
||||||
|
) {
|
||||||
|
compatibleWith("com.microsoft.office.officelens")
|
||||||
|
|
||||||
|
execute {
|
||||||
|
hasMigratedToOneDriveFingerprint.method.replaceInstructions(
|
||||||
|
0,
|
||||||
|
"""
|
||||||
|
sget-object v0, Lcom/microsoft/office/officelens/scansMigration/LensMigrationStage;->PreMigration:Lcom/microsoft/office/officelens/scansMigration/LensMigrationStage;
|
||||||
|
return-object v0
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
package app.revanced.patches.music.layout.hide.general
|
||||||
|
|
||||||
|
import app.revanced.patches.music.misc.litho.filter.lithoFilterPatch
|
||||||
|
import app.revanced.patches.music.misc.settings.settingsPatch
|
||||||
|
import app.revanced.patches.shared.layout.hide.general.hideLayoutComponentsPatch
|
||||||
|
|
||||||
|
val hideLayoutComponentsPatch = hideLayoutComponentsPatch(
|
||||||
|
lithoFilterPatch = lithoFilterPatch,
|
||||||
|
settingsPatch = settingsPatch,
|
||||||
|
filterClasses = setOf("Lapp/revanced/extension/shared/patches/components/CustomFilter;"),
|
||||||
|
compatibleWithPackages = arrayOf("com.google.android.apps.youtube.music" to setOf("7.29.52", "8.10.52"))
|
||||||
|
)
|
||||||
+3
-16
@@ -5,24 +5,11 @@ import app.revanced.patches.music.misc.extension.sharedExtensionPatch
|
|||||||
import app.revanced.patches.music.misc.settings.settingsPatch
|
import app.revanced.patches.music.misc.settings.settingsPatch
|
||||||
import app.revanced.util.returnEarly
|
import app.revanced.util.returnEarly
|
||||||
|
|
||||||
|
@Deprecated("This patch is useless by itself and has been merged into another patch.", ReplaceWith("unlockAndroidAutoMediaBrowserPatch"))
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
val bypassCertificateChecksPatch = bytecodePatch(
|
val bypassCertificateChecksPatch = bytecodePatch(
|
||||||
name = "Bypass certificate checks",
|
|
||||||
description = "Bypasses certificate checks which prevent YouTube Music from working on Android Auto.",
|
description = "Bypasses certificate checks which prevent YouTube Music from working on Android Auto.",
|
||||||
) {
|
) {
|
||||||
dependsOn(
|
dependsOn(unlockAndroidAutoMediaBrowserPatch)
|
||||||
sharedExtensionPatch,
|
|
||||||
settingsPatch
|
|
||||||
)
|
|
||||||
|
|
||||||
compatibleWith(
|
|
||||||
"com.google.android.apps.youtube.music"(
|
|
||||||
"7.29.52",
|
|
||||||
"8.10.52"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
execute {
|
|
||||||
checkCertificateFingerprint.method.returnEarly(true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package app.revanced.patches.music.misc.androidauto
|
package app.revanced.patches.music.misc.androidauto
|
||||||
|
|
||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
|
||||||
import app.revanced.patcher.fingerprint
|
import app.revanced.patcher.fingerprint
|
||||||
|
|
||||||
internal val checkCertificateFingerprint = fingerprint {
|
internal val checkCertificateFingerprint = fingerprint {
|
||||||
@@ -11,3 +10,12 @@ internal val checkCertificateFingerprint = fingerprint {
|
|||||||
"Failed to get certificate" // Partial String match.
|
"Failed to get certificate" // Partial String match.
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal val searchMediaItemsConstructorFingerprint = fingerprint {
|
||||||
|
returns("V")
|
||||||
|
strings("ytm_media_browser/search_media_items")
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val searchMediaItemsExecuteFingerprint = fingerprint {
|
||||||
|
parameters()
|
||||||
|
}
|
||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
package app.revanced.patches.music.misc.androidauto
|
||||||
|
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.instructions
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||||
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
|
import app.revanced.util.getReference
|
||||||
|
import app.revanced.util.registersUsed
|
||||||
|
import app.revanced.util.returnEarly
|
||||||
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
|
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
val unlockAndroidAutoMediaBrowserPatch = bytecodePatch(
|
||||||
|
name = "Unlock Android Auto Media Browser",
|
||||||
|
description = "Unlocks Android Auto Media Browser which enables the search function including speech to text.",
|
||||||
|
) {
|
||||||
|
compatibleWith(
|
||||||
|
"com.google.android.apps.youtube.music"(
|
||||||
|
"7.29.52",
|
||||||
|
"8.10.52"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
execute {
|
||||||
|
checkCertificateFingerprint.method.returnEarly(true)
|
||||||
|
|
||||||
|
searchMediaItemsExecuteFingerprint
|
||||||
|
.match(searchMediaItemsConstructorFingerprint.classDef)
|
||||||
|
.method.apply {
|
||||||
|
val targetIndex = instructions.indexOfFirst {
|
||||||
|
it.opcode == Opcode.IGET_OBJECT && it.getReference<FieldReference>()?.type == "Ljava/lang/String;"
|
||||||
|
}
|
||||||
|
|
||||||
|
val register = instructions[targetIndex].registersUsed.first()
|
||||||
|
replaceInstruction(targetIndex, "const-string v$register, \"com.google.android.apps.youtube.music\"")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+8
-13
@@ -7,20 +7,15 @@ import app.revanced.patches.shared.misc.debugging.enableDebuggingPatch
|
|||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
val enableDebuggingPatch = enableDebuggingPatch(
|
val enableDebuggingPatch = enableDebuggingPatch(
|
||||||
block = {
|
sharedExtensionPatch = sharedExtensionPatch,
|
||||||
dependsOn(
|
settingsPatch = settingsPatch,
|
||||||
sharedExtensionPatch,
|
compatibleWithPackages = arrayOf(
|
||||||
settingsPatch,
|
"com.google.android.apps.youtube.music" to setOf(
|
||||||
|
"7.29.52",
|
||||||
|
"8.10.52"
|
||||||
)
|
)
|
||||||
|
),
|
||||||
compatibleWith(
|
|
||||||
"com.google.android.apps.youtube.music"(
|
|
||||||
"7.29.52",
|
|
||||||
"8.10.52"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
// String feature flag does not appear to be present with YT Music.
|
// String feature flag does not appear to be present with YT Music.
|
||||||
hookStringFeatureFlag = false,
|
hookStringFeatureFlag = false,
|
||||||
preferenceScreen = PreferenceScreen.MISC
|
preferenceScreen = PreferenceScreen.MISC,
|
||||||
)
|
)
|
||||||
|
|||||||
+20
-12
@@ -4,15 +4,17 @@ import app.revanced.patcher.patch.Option
|
|||||||
import app.revanced.patches.all.misc.resources.addResources
|
import app.revanced.patches.all.misc.resources.addResources
|
||||||
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||||
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
|
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
|
||||||
|
import app.revanced.patches.music.misc.fileprovider.fileProviderPatch
|
||||||
import app.revanced.patches.music.misc.gms.Constants.MUSIC_PACKAGE_NAME
|
import app.revanced.patches.music.misc.gms.Constants.MUSIC_PACKAGE_NAME
|
||||||
import app.revanced.patches.music.misc.gms.Constants.REVANCED_MUSIC_PACKAGE_NAME
|
import app.revanced.patches.music.misc.gms.Constants.REVANCED_MUSIC_PACKAGE_NAME
|
||||||
import app.revanced.patches.music.misc.settings.PreferenceScreen
|
import app.revanced.patches.music.misc.settings.PreferenceScreen
|
||||||
import app.revanced.patches.music.misc.settings.settingsPatch
|
import app.revanced.patches.music.misc.settings.settingsPatch
|
||||||
import app.revanced.patches.music.misc.spoof.spoofVideoStreamsPatch
|
import app.revanced.patches.music.misc.spoof.spoofVideoStreamsPatch
|
||||||
import app.revanced.patches.music.misc.fileprovider.fileProviderPatch
|
|
||||||
import app.revanced.patches.shared.castContextFetchFingerprint
|
import app.revanced.patches.shared.castContextFetchFingerprint
|
||||||
import app.revanced.patches.shared.misc.gms.gmsCoreSupportPatch
|
import app.revanced.patches.shared.misc.gms.gmsCoreSupportPatch
|
||||||
import app.revanced.patches.shared.misc.settings.preference.IntentPreference
|
import app.revanced.patches.shared.misc.settings.preference.IntentPreference
|
||||||
|
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
|
||||||
|
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||||
import app.revanced.patches.shared.primeMethodFingerprint
|
import app.revanced.patches.shared.primeMethodFingerprint
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
@@ -32,8 +34,8 @@ val gmsCoreSupportPatch = gmsCoreSupportPatch(
|
|||||||
compatibleWith(
|
compatibleWith(
|
||||||
MUSIC_PACKAGE_NAME(
|
MUSIC_PACKAGE_NAME(
|
||||||
"7.29.52",
|
"7.29.52",
|
||||||
"8.10.52"
|
"8.10.52",
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,21 +52,27 @@ private fun gmsCoreSupportResourcePatch(
|
|||||||
val gmsCoreVendorGroupId by gmsCoreVendorGroupIdOption
|
val gmsCoreVendorGroupId by gmsCoreVendorGroupIdOption
|
||||||
|
|
||||||
PreferenceScreen.MISC.addPreferences(
|
PreferenceScreen.MISC.addPreferences(
|
||||||
IntentPreference(
|
PreferenceScreenPreference(
|
||||||
"microg_settings",
|
"revanced_gms_core_screen",
|
||||||
intent = IntentPreference.Intent("", "org.microg.gms.ui.SettingsActivity") {
|
preferences = setOf(
|
||||||
"$gmsCoreVendorGroupId.android.gms"
|
SwitchPreference("revanced_gms_core_check_updates"),
|
||||||
}
|
IntentPreference(
|
||||||
)
|
"revanced_gms_core_settings",
|
||||||
|
intent = IntentPreference.Intent("", "org.microg.gms.ui.SettingsActivity") {
|
||||||
|
"$gmsCoreVendorGroupId.android.gms"
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
) {
|
) {
|
||||||
dependsOn(
|
dependsOn(
|
||||||
addResourcesPatch,
|
addResourcesPatch,
|
||||||
settingsPatch,
|
settingsPatch,
|
||||||
fileProviderPatch(
|
fileProviderPatch(
|
||||||
MUSIC_PACKAGE_NAME,
|
MUSIC_PACKAGE_NAME,
|
||||||
REVANCED_MUSIC_PACKAGE_NAME
|
REVANCED_MUSIC_PACKAGE_NAME,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
package app.revanced.patches.music.misc.litho.filter
|
||||||
|
|
||||||
|
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
|
||||||
|
import app.revanced.patches.music.shared.conversionContextFingerprintToString
|
||||||
|
import app.revanced.patches.shared.misc.litho.filter.lithoFilterPatch
|
||||||
|
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||||
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
|
|
||||||
|
val lithoFilterPatch = lithoFilterPatch(
|
||||||
|
componentCreateInsertionIndex = {
|
||||||
|
// No supported version clobbers p2 so we can just do our things before the return instruction.
|
||||||
|
indexOfFirstInstructionOrThrow(Opcode.RETURN_OBJECT)
|
||||||
|
},
|
||||||
|
conversionContextFingerprintToString = conversionContextFingerprintToString,
|
||||||
|
) {
|
||||||
|
dependsOn(sharedExtensionPatch)
|
||||||
|
}
|
||||||
@@ -11,3 +11,19 @@ internal val mainActivityOnCreateFingerprint = fingerprint {
|
|||||||
method.name == "onCreate" && classDef.type == YOUTUBE_MUSIC_MAIN_ACTIVITY_CLASS_TYPE
|
method.name == "onCreate" && classDef.type == YOUTUBE_MUSIC_MAIN_ACTIVITY_CLASS_TYPE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal val conversionContextFingerprintToString = fingerprint {
|
||||||
|
parameters()
|
||||||
|
strings(
|
||||||
|
"ConversionContext{containerInternal=",
|
||||||
|
", gridColumnCount=",
|
||||||
|
", gridColumnIndex=",
|
||||||
|
", templateLoggerFactory=",
|
||||||
|
", rootDisposableContainer=",
|
||||||
|
", elementId=",
|
||||||
|
", identifierProperty="
|
||||||
|
)
|
||||||
|
custom { method, _ ->
|
||||||
|
method.name == "toString"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
package app.revanced.patches.nothingx.misc.extension
|
||||||
|
|
||||||
|
import app.revanced.patches.shared.misc.extension.sharedExtensionPatch
|
||||||
|
import app.revanced.patches.shared.misc.extension.extensionHook
|
||||||
|
|
||||||
|
val sharedExtensionPatch = sharedExtensionPatch(
|
||||||
|
extensionName = "nothingx",
|
||||||
|
extensionHook {
|
||||||
|
custom { method, classDef ->
|
||||||
|
method.name == "onCreate" && classDef.contains("BaseApplication")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package app.revanced.patches.nothingx.misc.logk1token
|
||||||
|
|
||||||
|
import app.revanced.patcher.fingerprint
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fingerprint for the Application onCreate method.
|
||||||
|
* This is used to trigger scanning for existing log files on app startup.
|
||||||
|
*/
|
||||||
|
internal val applicationOnCreateFingerprint = fingerprint {
|
||||||
|
returns("V")
|
||||||
|
parameters()
|
||||||
|
custom { method, classDef ->
|
||||||
|
// Match BaseApplication onCreate specifically
|
||||||
|
method.name == "onCreate" && classDef.endsWith("BaseApplication;")
|
||||||
|
}
|
||||||
|
}
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
package app.revanced.patches.nothingx.misc.logk1token
|
||||||
|
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||||
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
|
import app.revanced.patches.nothingx.misc.extension.sharedExtensionPatch
|
||||||
|
|
||||||
|
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||||
|
"Lapp/revanced/extension/nothingx/patches/ShowK1TokensPatch;"
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
val showK1TokensPatch = bytecodePatch(
|
||||||
|
name = "Show K1 token(s)",
|
||||||
|
description = "Shows the K1 authentication token(s) in a dialog and logs it to logcat " +
|
||||||
|
"for pairing with GadgetBridge without requiring root access. " +
|
||||||
|
"After installing this patch, pair your watch with the Nothing X app and " +
|
||||||
|
"use the token from the dialog or logcat.",
|
||||||
|
) {
|
||||||
|
dependsOn(sharedExtensionPatch)
|
||||||
|
|
||||||
|
compatibleWith("com.nothing.smartcenter"())
|
||||||
|
|
||||||
|
execute {
|
||||||
|
// Hook Application.onCreate to get K1 tokens from database and log files.
|
||||||
|
// This will find K1 tokens that were already written to log files.
|
||||||
|
// p0 is the Application context in onCreate.
|
||||||
|
applicationOnCreateFingerprint.method.addInstruction(
|
||||||
|
0,
|
||||||
|
"invoke-static { p0 }, $EXTENSION_CLASS_DESCRIPTOR->showK1Tokens(Landroid/content/Context;)V",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+55
@@ -0,0 +1,55 @@
|
|||||||
|
package app.revanced.patches.shared.layout.hide.general
|
||||||
|
|
||||||
|
import app.revanced.patcher.patch.BytecodePatchContext
|
||||||
|
import app.revanced.patcher.patch.Patch
|
||||||
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
|
import app.revanced.patches.all.misc.resources.addResources
|
||||||
|
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||||
|
import app.revanced.patches.music.misc.settings.PreferenceScreen
|
||||||
|
import app.revanced.patches.shared.misc.litho.filter.addLithoFilter
|
||||||
|
import app.revanced.patches.shared.misc.settings.preference.InputType
|
||||||
|
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
|
||||||
|
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||||
|
import app.revanced.patches.shared.misc.settings.preference.TextPreference
|
||||||
|
|
||||||
|
internal fun hideLayoutComponentsPatch(
|
||||||
|
lithoFilterPatch: Patch<*>,
|
||||||
|
settingsPatch: Patch<*>,
|
||||||
|
additionalDependencies: Set<Patch<*>> = emptySet(),
|
||||||
|
filterClasses: Set<String>,
|
||||||
|
vararg compatibleWithPackages: Pair<String, Set<String>?>,
|
||||||
|
executeBlock: BytecodePatchContext.() -> Unit = {},
|
||||||
|
) = bytecodePatch(
|
||||||
|
name = "Hide layout components",
|
||||||
|
description = "Adds options to hide general layout components.",
|
||||||
|
) {
|
||||||
|
dependsOn(
|
||||||
|
lithoFilterPatch,
|
||||||
|
settingsPatch,
|
||||||
|
*additionalDependencies.toTypedArray(),
|
||||||
|
addResourcesPatch,
|
||||||
|
)
|
||||||
|
|
||||||
|
compatibleWith(packages = compatibleWithPackages)
|
||||||
|
|
||||||
|
execute {
|
||||||
|
addResources("shared", "layout.hide.general.hideLayoutComponentsPatch")
|
||||||
|
|
||||||
|
PreferenceScreen.GENERAL.addPreferences(
|
||||||
|
PreferenceScreenPreference(
|
||||||
|
key = "revanced_custom_filter_screen",
|
||||||
|
sorting = PreferenceScreenPreference.Sorting.UNSORTED,
|
||||||
|
preferences = setOf(
|
||||||
|
SwitchPreference("revanced_custom_filter"),
|
||||||
|
TextPreference("revanced_custom_filter_strings", inputType = InputType.TEXT_MULTI_LINE),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
filterClasses.forEach { className ->
|
||||||
|
addLithoFilter(className)
|
||||||
|
}
|
||||||
|
|
||||||
|
executeBlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
+29
-43
@@ -2,23 +2,14 @@ package app.revanced.patches.shared.misc.debugging
|
|||||||
|
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||||
import app.revanced.patcher.patch.BytecodePatchBuilder
|
import app.revanced.patcher.patch.Patch
|
||||||
import app.revanced.patcher.patch.BytecodePatchContext
|
|
||||||
import app.revanced.patcher.patch.bytecodePatch
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
import app.revanced.patcher.patch.resourcePatch
|
import app.revanced.patcher.patch.resourcePatch
|
||||||
import app.revanced.patches.all.misc.resources.addResources
|
import app.revanced.patches.all.misc.resources.addResources
|
||||||
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||||
import app.revanced.patches.shared.misc.settings.preference.BasePreference
|
import app.revanced.patches.shared.misc.settings.preference.*
|
||||||
import app.revanced.patches.shared.misc.settings.preference.BasePreferenceScreen
|
|
||||||
import app.revanced.patches.shared.misc.settings.preference.NonInteractivePreference
|
|
||||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
|
|
||||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting
|
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting
|
||||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
import app.revanced.util.*
|
||||||
import app.revanced.util.ResourceGroup
|
|
||||||
import app.revanced.util.copyResources
|
|
||||||
import app.revanced.util.findInstructionIndicesReversedOrThrow
|
|
||||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
|
||||||
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
|
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||||
|
|
||||||
@@ -29,23 +20,27 @@ private const val EXTENSION_CLASS_DESCRIPTOR =
|
|||||||
* Patch shared with YouTube and YT Music.
|
* Patch shared with YouTube and YT Music.
|
||||||
*/
|
*/
|
||||||
internal fun enableDebuggingPatch(
|
internal fun enableDebuggingPatch(
|
||||||
block: BytecodePatchBuilder.() -> Unit = {},
|
sharedExtensionPatch: Patch<*>,
|
||||||
executeBlock: BytecodePatchContext.() -> Unit = {},
|
settingsPatch: Patch<*>,
|
||||||
|
vararg compatibleWithPackages: Pair<String, Set<String>>,
|
||||||
hookStringFeatureFlag: Boolean,
|
hookStringFeatureFlag: Boolean,
|
||||||
preferenceScreen: BasePreferenceScreen.Screen,
|
preferenceScreen: BasePreferenceScreen.Screen,
|
||||||
additionalDebugPreferences: List<BasePreference> = emptyList()
|
|
||||||
) = bytecodePatch(
|
) = bytecodePatch(
|
||||||
name = "Enable debugging",
|
name = "Enable debugging",
|
||||||
description = "Adds options for debugging and exporting ReVanced logs to the clipboard.",
|
description = "Adds options for debugging and exporting ReVanced logs to the clipboard.",
|
||||||
) {
|
) {
|
||||||
|
compatibleWith(packages = compatibleWithPackages)
|
||||||
|
|
||||||
dependsOn(
|
dependsOn(
|
||||||
|
sharedExtensionPatch,
|
||||||
|
settingsPatch,
|
||||||
addResourcesPatch,
|
addResourcesPatch,
|
||||||
resourcePatch {
|
resourcePatch {
|
||||||
execute {
|
execute {
|
||||||
copyResources(
|
copyResources(
|
||||||
"settings",
|
"settings",
|
||||||
ResourceGroup("drawable",
|
ResourceGroup(
|
||||||
|
"drawable",
|
||||||
// Action buttons.
|
// Action buttons.
|
||||||
"revanced_settings_copy_all.xml",
|
"revanced_settings_copy_all.xml",
|
||||||
"revanced_settings_deselect_all.xml",
|
"revanced_settings_deselect_all.xml",
|
||||||
@@ -61,38 +56,29 @@ internal fun enableDebuggingPatch(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
block()
|
|
||||||
|
|
||||||
execute {
|
execute {
|
||||||
executeBlock()
|
|
||||||
|
|
||||||
addResources("shared", "misc.debugging.enableDebuggingPatch")
|
addResources("shared", "misc.debugging.enableDebuggingPatch")
|
||||||
|
|
||||||
val preferences = mutableSetOf<BasePreference>(
|
val preferences = setOf(
|
||||||
SwitchPreference("revanced_debug"),
|
SwitchPreference("revanced_debug"),
|
||||||
)
|
SwitchPreference("revanced_debug_protobuffer"),
|
||||||
|
SwitchPreference("revanced_debug_stacktrace"),
|
||||||
preferences.addAll(additionalDebugPreferences)
|
SwitchPreference("revanced_debug_toast_on_error"),
|
||||||
|
NonInteractivePreference(
|
||||||
preferences.addAll(
|
"revanced_debug_export_logs_to_clipboard",
|
||||||
listOf(
|
tag = "app.revanced.extension.shared.settings.preference.ExportLogToClipboardPreference",
|
||||||
SwitchPreference("revanced_debug_stacktrace"),
|
selectable = true
|
||||||
SwitchPreference("revanced_debug_toast_on_error"),
|
),
|
||||||
NonInteractivePreference(
|
NonInteractivePreference(
|
||||||
"revanced_debug_export_logs_to_clipboard",
|
"revanced_debug_logs_clear_buffer",
|
||||||
tag = "app.revanced.extension.shared.settings.preference.ExportLogToClipboardPreference",
|
tag = "app.revanced.extension.shared.settings.preference.ClearLogBufferPreference",
|
||||||
selectable = true
|
selectable = true
|
||||||
),
|
),
|
||||||
NonInteractivePreference(
|
NonInteractivePreference(
|
||||||
"revanced_debug_logs_clear_buffer",
|
"revanced_debug_feature_flags_manager",
|
||||||
tag = "app.revanced.extension.shared.settings.preference.ClearLogBufferPreference",
|
tag = "app.revanced.extension.shared.settings.preference.FeatureFlagsManagerPreference",
|
||||||
selectable = true
|
selectable = true
|
||||||
),
|
|
||||||
NonInteractivePreference(
|
|
||||||
"revanced_debug_feature_flags_manager",
|
|
||||||
tag = "app.revanced.extension.shared.settings.preference.FeatureFlagsManagerPreference",
|
|
||||||
selectable = true
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ internal val serviceCheckFingerprint = fingerprint {
|
|||||||
strings("Google Play Services not available")
|
strings("Google Play Services not available")
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val gmsCoreSupportFingerprint = fingerprint {
|
internal val getGmsCoreVendorGroupIdFingerprint = fingerprint {
|
||||||
accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC)
|
accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC)
|
||||||
returns("Ljava/lang/String;")
|
returns("Ljava/lang/String;")
|
||||||
parameters()
|
parameters()
|
||||||
|
|||||||
+166
-452
@@ -2,26 +2,18 @@ package app.revanced.patches.shared.misc.gms
|
|||||||
|
|
||||||
import app.revanced.patcher.Fingerprint
|
import app.revanced.patcher.Fingerprint
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.instructions
|
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||||
import app.revanced.patcher.patch.BytecodePatchBuilder
|
import app.revanced.patcher.patch.*
|
||||||
import app.revanced.patcher.patch.BytecodePatchContext
|
|
||||||
import app.revanced.patcher.patch.Option
|
|
||||||
import app.revanced.patcher.patch.Patch
|
|
||||||
import app.revanced.patcher.patch.ResourcePatchBuilder
|
|
||||||
import app.revanced.patcher.patch.ResourcePatchContext
|
|
||||||
import app.revanced.patcher.patch.bytecodePatch
|
|
||||||
import app.revanced.patcher.patch.resourcePatch
|
|
||||||
import app.revanced.patcher.patch.stringOption
|
|
||||||
import app.revanced.patches.all.misc.packagename.changePackageNamePatch
|
import app.revanced.patches.all.misc.packagename.changePackageNamePatch
|
||||||
import app.revanced.patches.all.misc.packagename.setOrGetFallbackPackageName
|
import app.revanced.patches.all.misc.packagename.setOrGetFallbackPackageName
|
||||||
import app.revanced.patches.all.misc.resources.addResources
|
import app.revanced.patches.all.misc.resources.addResources
|
||||||
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||||
import app.revanced.patches.shared.misc.gms.Constants.ACTIONS
|
import app.revanced.patches.shared.misc.gms.Constants.APP_AUTHORITIES
|
||||||
import app.revanced.patches.shared.misc.gms.Constants.AUTHORITIES
|
import app.revanced.patches.shared.misc.gms.Constants.APP_PERMISSIONS
|
||||||
import app.revanced.patches.shared.misc.gms.Constants.PERMISSIONS
|
import app.revanced.patches.shared.misc.gms.Constants.GMS_AUTHORITIES
|
||||||
import app.revanced.util.getReference
|
import app.revanced.patches.shared.misc.gms.Constants.GMS_PERMISSIONS
|
||||||
import app.revanced.util.returnEarly
|
import app.revanced.util.*
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21c
|
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21c
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||||
@@ -29,8 +21,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction21c
|
|||||||
import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
||||||
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableStringReference
|
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableStringReference
|
||||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||||
import org.w3c.dom.Element
|
import java.net.URI
|
||||||
import org.w3c.dom.Node
|
|
||||||
|
|
||||||
internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/shared/GmsCoreSupport;"
|
internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/shared/GmsCoreSupport;"
|
||||||
|
|
||||||
@@ -69,10 +60,7 @@ fun gmsCoreSupportPatch(
|
|||||||
val gmsCoreVendorGroupIdOption = stringOption(
|
val gmsCoreVendorGroupIdOption = stringOption(
|
||||||
key = "gmsCoreVendorGroupId",
|
key = "gmsCoreVendorGroupId",
|
||||||
default = "app.revanced",
|
default = "app.revanced",
|
||||||
values =
|
values = mapOf("ReVanced" to "app.revanced"),
|
||||||
mapOf(
|
|
||||||
"ReVanced" to "app.revanced",
|
|
||||||
),
|
|
||||||
title = "GmsCore vendor group ID",
|
title = "GmsCore vendor group ID",
|
||||||
description = "The vendor's group ID for GmsCore.",
|
description = "The vendor's group ID for GmsCore.",
|
||||||
required = true,
|
required = true,
|
||||||
@@ -84,9 +72,9 @@ fun gmsCoreSupportPatch(
|
|||||||
extensionPatch,
|
extensionPatch,
|
||||||
)
|
)
|
||||||
|
|
||||||
val gmsCoreVendorGroupId by gmsCoreVendorGroupIdOption
|
|
||||||
|
|
||||||
execute {
|
execute {
|
||||||
|
val gmsCoreVendorGroupId = gmsCoreVendorGroupIdOption.value!!
|
||||||
|
|
||||||
fun transformStringReferences(transform: (str: String) -> String?) = classes.forEach {
|
fun transformStringReferences(transform: (str: String) -> String?) = classes.forEach {
|
||||||
val mutableClass by lazy {
|
val mutableClass by lazy {
|
||||||
proxy(it).mutableClass
|
proxy(it).mutableClass
|
||||||
@@ -118,80 +106,54 @@ fun gmsCoreSupportPatch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// region Collection of transformations that are applied to all strings.
|
fun transformPackages(string: String): String? = when (string) {
|
||||||
|
|
||||||
fun commonTransform(referencedString: String): String? = when (referencedString) {
|
|
||||||
"com.google",
|
"com.google",
|
||||||
"com.google.android.gms",
|
"com.google.android.gms",
|
||||||
in PERMISSIONS,
|
in GMS_PERMISSIONS,
|
||||||
in ACTIONS,
|
in GMS_AUTHORITIES,
|
||||||
in AUTHORITIES,
|
-> if (string.startsWith("com.google")) {
|
||||||
-> referencedString.replace("com.google", gmsCoreVendorGroupId!!)
|
string.replace("com.google", gmsCoreVendorGroupId)
|
||||||
|
} else {
|
||||||
|
"$gmsCoreVendorGroupId.$string"
|
||||||
|
}
|
||||||
|
|
||||||
|
in APP_PERMISSIONS,
|
||||||
|
in APP_AUTHORITIES,
|
||||||
|
-> "$toPackageName.$string"
|
||||||
|
|
||||||
// No vendor prefix for whatever reason...
|
|
||||||
"subscribedfeeds" -> "$gmsCoreVendorGroupId.subscribedfeeds"
|
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun contentUrisTransform(str: String): String? {
|
fun transformContentUrlAuthority(string: String) = if (!string.startsWith("content://")) {
|
||||||
// only when content:// uri
|
null
|
||||||
if (str.startsWith("content://")) {
|
} else {
|
||||||
// check if matches any authority
|
runCatching { URI.create(string) }.map {
|
||||||
for (authority in AUTHORITIES) {
|
when (it.authority) {
|
||||||
val uriPrefix = "content://$authority"
|
in GMS_AUTHORITIES ->
|
||||||
if (str.startsWith(uriPrefix)) {
|
if (it.authority.startsWith("com.google")) {
|
||||||
return str.replace(
|
string.replace("com.google", gmsCoreVendorGroupId)
|
||||||
uriPrefix,
|
} else {
|
||||||
"content://${authority.replace("com.google", gmsCoreVendorGroupId!!)}",
|
string.replace(
|
||||||
)
|
it.authority,
|
||||||
}
|
"$gmsCoreVendorGroupId.${it.authority}",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
in APP_AUTHORITIES ->
|
||||||
|
string.replace(it.authority, "$toPackageName.${it.authority}")
|
||||||
|
|
||||||
|
else -> null
|
||||||
}
|
}
|
||||||
|
}.getOrNull()
|
||||||
// gms also has a 'subscribedfeeds' authority, check for that one too
|
|
||||||
val subFeedsUriPrefix = "content://subscribedfeeds"
|
|
||||||
if (str.startsWith(subFeedsUriPrefix)) {
|
|
||||||
return str.replace(subFeedsUriPrefix, "content://$gmsCoreVendorGroupId.subscribedfeeds")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun packageNameTransform(fromPackageName: String, toPackageName: String): (String) -> String? = { string ->
|
|
||||||
when (string) {
|
|
||||||
"$fromPackageName.SuggestionProvider",
|
|
||||||
"$fromPackageName.fileprovider",
|
|
||||||
-> string.replace(fromPackageName, toPackageName)
|
|
||||||
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun transformPrimeMethod(packageName: String) {
|
|
||||||
primeMethodFingerprint!!.method.apply {
|
|
||||||
var register = 2
|
|
||||||
|
|
||||||
val index = instructions.indexOfFirst {
|
|
||||||
if (it.getReference<StringReference>()?.string != fromPackageName) return@indexOfFirst false
|
|
||||||
|
|
||||||
register = (it as OneRegisterInstruction).registerA
|
|
||||||
return@indexOfFirst true
|
|
||||||
}
|
|
||||||
|
|
||||||
replaceInstruction(index, "const-string v$register, \"$packageName\"")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
val packageName = setOrGetFallbackPackageName(toPackageName)
|
val packageName = setOrGetFallbackPackageName(toPackageName)
|
||||||
|
|
||||||
// Transform all strings using all provided transforms, first match wins.
|
|
||||||
val transformations = arrayOf(
|
val transformations = arrayOf(
|
||||||
::commonTransform,
|
::transformPackages,
|
||||||
::contentUrisTransform,
|
::transformContentUrlAuthority,
|
||||||
packageNameTransform(fromPackageName, packageName),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
transformStringReferences transform@{ string ->
|
transformStringReferences transform@{ string ->
|
||||||
transformations.forEach { transform ->
|
transformations.forEach { transform ->
|
||||||
transform(string)?.let { transformedString -> return@transform transformedString }
|
transform(string)?.let { transformedString -> return@transform transformedString }
|
||||||
@@ -201,16 +163,26 @@ fun gmsCoreSupportPatch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Specific method that needs to be patched.
|
// Specific method that needs to be patched.
|
||||||
primeMethodFingerprint?.let { transformPrimeMethod(packageName) }
|
if (primeMethodFingerprint?.methodOrNull != null) {
|
||||||
|
val primeMethod = primeMethodFingerprint.method
|
||||||
|
|
||||||
|
val index = primeMethod.indexOfFirstInstruction {
|
||||||
|
getReference<StringReference>()?.string == fromPackageName
|
||||||
|
}
|
||||||
|
val register = primeMethod.getInstruction<OneRegisterInstruction>(index).registerA
|
||||||
|
|
||||||
|
primeMethod.replaceInstruction(
|
||||||
|
index,
|
||||||
|
"const-string v$register, \"$packageName\"",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Return these methods early to prevent the app from crashing.
|
// Return these methods early to prevent the app from crashing.
|
||||||
earlyReturnFingerprints.forEach { it.method.returnEarly() }
|
earlyReturnFingerprints.forEach { it.method.returnEarly() }
|
||||||
serviceCheckFingerprint.method.returnEarly()
|
serviceCheckFingerprint.method.returnEarly()
|
||||||
|
|
||||||
// Google Play Utility is not present in all apps, so we need to check if it's present.
|
// Google Play Utility is not present in all apps, so we need to check if it's present.
|
||||||
if (googlePlayUtilityFingerprint.methodOrNull != null) {
|
googlePlayUtilityFingerprint.methodOrNull?.returnEarly(0)
|
||||||
googlePlayUtilityFingerprint.method.returnEarly(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set original and patched package names for extension to use.
|
// Set original and patched package names for extension to use.
|
||||||
originalPackageNameExtensionFingerprint.method.returnEarly(fromPackageName)
|
originalPackageNameExtensionFingerprint.method.returnEarly(fromPackageName)
|
||||||
@@ -219,11 +191,11 @@ fun gmsCoreSupportPatch(
|
|||||||
mainActivityOnCreateFingerprint.method.addInstruction(
|
mainActivityOnCreateFingerprint.method.addInstruction(
|
||||||
0,
|
0,
|
||||||
"invoke-static/range { p0 .. p0 }, $EXTENSION_CLASS_DESCRIPTOR->" +
|
"invoke-static/range { p0 .. p0 }, $EXTENSION_CLASS_DESCRIPTOR->" +
|
||||||
"checkGmsCore(Landroid/app/Activity;)V"
|
"checkGmsCore(Landroid/app/Activity;)V",
|
||||||
)
|
)
|
||||||
|
|
||||||
// Change the vendor of GmsCore in the extension.
|
// Change the vendor of GmsCore in the extension.
|
||||||
gmsCoreSupportFingerprint.method.returnEarly(gmsCoreVendorGroupId!!)
|
getGmsCoreVendorGroupIdFingerprint.method.returnEarly(gmsCoreVendorGroupId)
|
||||||
|
|
||||||
executeBlock()
|
executeBlock()
|
||||||
}
|
}
|
||||||
@@ -231,274 +203,6 @@ fun gmsCoreSupportPatch(
|
|||||||
block()
|
block()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A collection of permissions, intents and content provider authorities
|
|
||||||
* that are present in GmsCore which need to be transformed.
|
|
||||||
*/
|
|
||||||
private object Constants {
|
|
||||||
/**
|
|
||||||
* All permissions.
|
|
||||||
*/
|
|
||||||
val PERMISSIONS = setOf(
|
|
||||||
"com.google.android.c2dm.permission.RECEIVE",
|
|
||||||
"com.google.android.c2dm.permission.SEND",
|
|
||||||
"com.google.android.gms.auth.api.phone.permission.SEND",
|
|
||||||
"com.google.android.gms.permission.AD_ID",
|
|
||||||
"com.google.android.gms.permission.AD_ID_NOTIFICATION",
|
|
||||||
"com.google.android.gms.permission.CAR_FUEL",
|
|
||||||
"com.google.android.gms.permission.CAR_INFORMATION",
|
|
||||||
"com.google.android.gms.permission.CAR_MILEAGE",
|
|
||||||
"com.google.android.gms.permission.CAR_SPEED",
|
|
||||||
"com.google.android.gms.permission.CAR_VENDOR_EXTENSION",
|
|
||||||
"com.google.android.googleapps.permission.GOOGLE_AUTH",
|
|
||||||
"com.google.android.googleapps.permission.GOOGLE_AUTH.cp",
|
|
||||||
"com.google.android.googleapps.permission.GOOGLE_AUTH.local",
|
|
||||||
"com.google.android.googleapps.permission.GOOGLE_AUTH.mail",
|
|
||||||
"com.google.android.googleapps.permission.GOOGLE_AUTH.writely",
|
|
||||||
"com.google.android.gtalkservice.permission.GTALK_SERVICE",
|
|
||||||
"com.google.android.providers.gsf.permission.READ_GSERVICES",
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All intent actions.
|
|
||||||
*/
|
|
||||||
val ACTIONS = setOf(
|
|
||||||
"com.google.android.c2dm.intent.RECEIVE",
|
|
||||||
"com.google.android.c2dm.intent.REGISTER",
|
|
||||||
"com.google.android.c2dm.intent.REGISTRATION",
|
|
||||||
"com.google.android.c2dm.intent.UNREGISTER",
|
|
||||||
"com.google.android.contextmanager.service.ContextManagerService.START",
|
|
||||||
"com.google.android.gcm.intent.SEND",
|
|
||||||
"com.google.android.gms.accounts.ACCOUNT_SERVICE",
|
|
||||||
"com.google.android.gms.accountsettings.ACCOUNT_PREFERENCES_SETTINGS",
|
|
||||||
"com.google.android.gms.accountsettings.action.BROWSE_SETTINGS",
|
|
||||||
"com.google.android.gms.accountsettings.action.VIEW_SETTINGS",
|
|
||||||
"com.google.android.gms.accountsettings.MY_ACCOUNT",
|
|
||||||
"com.google.android.gms.accountsettings.PRIVACY_SETTINGS",
|
|
||||||
"com.google.android.gms.accountsettings.SECURITY_SETTINGS",
|
|
||||||
"com.google.android.gms.ads.gservice.START",
|
|
||||||
"com.google.android.gms.ads.identifier.service.EVENT_ATTESTATION",
|
|
||||||
"com.google.android.gms.ads.service.CACHE",
|
|
||||||
"com.google.android.gms.ads.service.CONSENT_LOOKUP",
|
|
||||||
"com.google.android.gms.ads.service.HTTP",
|
|
||||||
"com.google.android.gms.analytics.service.START",
|
|
||||||
"com.google.android.gms.app.settings.GoogleSettingsLink",
|
|
||||||
"com.google.android.gms.appstate.service.START",
|
|
||||||
"com.google.android.gms.appusage.service.START",
|
|
||||||
"com.google.android.gms.asterism.service.START",
|
|
||||||
"com.google.android.gms.audiomodem.service.AudioModemService.START",
|
|
||||||
"com.google.android.gms.audit.service.START",
|
|
||||||
"com.google.android.gms.auth.account.authapi.START",
|
|
||||||
"com.google.android.gms.auth.account.authenticator.auto.service.START",
|
|
||||||
"com.google.android.gms.auth.account.authenticator.chromeos.START",
|
|
||||||
"com.google.android.gms.auth.account.authenticator.tv.service.START",
|
|
||||||
"com.google.android.gms.auth.account.data.service.START",
|
|
||||||
"com.google.android.gms.auth.api.credentials.PICKER",
|
|
||||||
"com.google.android.gms.auth.api.credentials.service.START",
|
|
||||||
"com.google.android.gms.auth.api.identity.service.authorization.START",
|
|
||||||
"com.google.android.gms.auth.api.identity.service.credentialsaving.START",
|
|
||||||
"com.google.android.gms.auth.api.identity.service.signin.START",
|
|
||||||
"com.google.android.gms.auth.api.phone.service.InternalService.START",
|
|
||||||
"com.google.android.gms.auth.api.signin.service.START",
|
|
||||||
"com.google.android.gms.auth.be.appcert.AppCertService",
|
|
||||||
"com.google.android.gms.auth.blockstore.service.START",
|
|
||||||
"com.google.android.gms.auth.config.service.START",
|
|
||||||
"com.google.android.gms.auth.cryptauth.cryptauthservice.START",
|
|
||||||
"com.google.android.gms.auth.GOOGLE_SIGN_IN",
|
|
||||||
"com.google.android.gms.auth.login.LOGIN",
|
|
||||||
"com.google.android.gms.auth.proximity.devicesyncservice.START",
|
|
||||||
"com.google.android.gms.auth.proximity.securechannelservice.START",
|
|
||||||
"com.google.android.gms.auth.proximity.START",
|
|
||||||
"com.google.android.gms.auth.service.START",
|
|
||||||
"com.google.android.gms.backup.ACTION_BACKUP_SETTINGS",
|
|
||||||
"com.google.android.gms.backup.G1_BACKUP",
|
|
||||||
"com.google.android.gms.backup.G1_RESTORE",
|
|
||||||
"com.google.android.gms.backup.GMS_MODULE_RESTORE",
|
|
||||||
"com.google.android.gms.beacon.internal.IBleService.START",
|
|
||||||
"com.google.android.gms.car.service.START",
|
|
||||||
"com.google.android.gms.carrierauth.service.START",
|
|
||||||
"com.google.android.gms.cast.firstparty.START",
|
|
||||||
"com.google.android.gms.cast.remote_display.service.START",
|
|
||||||
"com.google.android.gms.cast.service.BIND_CAST_DEVICE_CONTROLLER_SERVICE",
|
|
||||||
"com.google.android.gms.cast_mirroring.service.START",
|
|
||||||
"com.google.android.gms.checkin.BIND_TO_SERVICE",
|
|
||||||
"com.google.android.gms.chromesync.service.START",
|
|
||||||
"com.google.android.gms.clearcut.service.START",
|
|
||||||
"com.google.android.gms.common.account.CHOOSE_ACCOUNT",
|
|
||||||
"com.google.android.gms.common.download.START",
|
|
||||||
"com.google.android.gms.common.service.START",
|
|
||||||
"com.google.android.gms.common.telemetry.service.START",
|
|
||||||
"com.google.android.gms.config.START",
|
|
||||||
"com.google.android.gms.constellation.service.START",
|
|
||||||
"com.google.android.gms.credential.manager.service.firstparty.START",
|
|
||||||
"com.google.android.gms.deviceconnection.service.START",
|
|
||||||
"com.google.android.gms.drive.ApiService.RESET_AFTER_BOOT",
|
|
||||||
"com.google.android.gms.drive.ApiService.START",
|
|
||||||
"com.google.android.gms.drive.ApiService.STOP",
|
|
||||||
"com.google.android.gms.droidguard.service.INIT",
|
|
||||||
"com.google.android.gms.droidguard.service.PING",
|
|
||||||
"com.google.android.gms.droidguard.service.START",
|
|
||||||
"com.google.android.gms.enterprise.loader.service.START",
|
|
||||||
"com.google.android.gms.facs.cache.service.START",
|
|
||||||
"com.google.android.gms.facs.internal.service.START",
|
|
||||||
"com.google.android.gms.feedback.internal.IFeedbackService",
|
|
||||||
"com.google.android.gms.fido.credentialstore.internal_service.START",
|
|
||||||
"com.google.android.gms.fido.fido2.privileged.START",
|
|
||||||
"com.google.android.gms.fido.fido2.regular.START",
|
|
||||||
"com.google.android.gms.fido.fido2.zeroparty.START",
|
|
||||||
"com.google.android.gms.fido.sourcedevice.service.START",
|
|
||||||
"com.google.android.gms.fido.targetdevice.internal_service.START",
|
|
||||||
"com.google.android.gms.fido.u2f.privileged.START",
|
|
||||||
"com.google.android.gms.fido.u2f.thirdparty.START",
|
|
||||||
"com.google.android.gms.fido.u2f.zeroparty.START",
|
|
||||||
"com.google.android.gms.fitness.BleApi",
|
|
||||||
"com.google.android.gms.fitness.ConfigApi",
|
|
||||||
"com.google.android.gms.fitness.GoalsApi",
|
|
||||||
"com.google.android.gms.fitness.GoogleFitnessService.START",
|
|
||||||
"com.google.android.gms.fitness.HistoryApi",
|
|
||||||
"com.google.android.gms.fitness.InternalApi",
|
|
||||||
"com.google.android.gms.fitness.RecordingApi",
|
|
||||||
"com.google.android.gms.fitness.SensorsApi",
|
|
||||||
"com.google.android.gms.fitness.SessionsApi",
|
|
||||||
"com.google.android.gms.fonts.service.START",
|
|
||||||
"com.google.android.gms.freighter.service.START",
|
|
||||||
"com.google.android.gms.games.internal.connect.service.START",
|
|
||||||
"com.google.android.gms.games.PLAY_GAMES_UPGRADE",
|
|
||||||
"com.google.android.gms.games.service.START",
|
|
||||||
"com.google.android.gms.gass.START",
|
|
||||||
"com.google.android.gms.gmscompliance.service.START",
|
|
||||||
"com.google.android.gms.googlehelp.HELP",
|
|
||||||
"com.google.android.gms.googlehelp.service.GoogleHelpService.START",
|
|
||||||
"com.google.android.gms.growth.service.START",
|
|
||||||
"com.google.android.gms.herrevad.services.LightweightNetworkQualityAndroidService.START",
|
|
||||||
"com.google.android.gms.icing.INDEX_SERVICE",
|
|
||||||
"com.google.android.gms.icing.LIGHTWEIGHT_INDEX_SERVICE",
|
|
||||||
"com.google.android.gms.identity.service.BIND",
|
|
||||||
"com.google.android.gms.inappreach.service.START",
|
|
||||||
"com.google.android.gms.instantapps.START",
|
|
||||||
"com.google.android.gms.kids.service.START",
|
|
||||||
"com.google.android.gms.languageprofile.service.START",
|
|
||||||
"com.google.android.gms.learning.internal.dynamitesupport.START",
|
|
||||||
"com.google.android.gms.learning.intservice.START",
|
|
||||||
"com.google.android.gms.learning.predictor.START",
|
|
||||||
"com.google.android.gms.learning.trainer.START",
|
|
||||||
"com.google.android.gms.learning.training.background.START",
|
|
||||||
"com.google.android.gms.location.places.GeoDataApi",
|
|
||||||
"com.google.android.gms.location.places.PlaceDetectionApi",
|
|
||||||
"com.google.android.gms.location.places.PlacesApi",
|
|
||||||
"com.google.android.gms.location.reporting.service.START",
|
|
||||||
"com.google.android.gms.location.settings.LOCATION_HISTORY",
|
|
||||||
"com.google.android.gms.location.settings.LOCATION_REPORTING_SETTINGS",
|
|
||||||
"com.google.android.gms.locationsharing.api.START",
|
|
||||||
"com.google.android.gms.locationsharingreporter.service.START",
|
|
||||||
"com.google.android.gms.lockbox.service.START",
|
|
||||||
"com.google.android.gms.matchstick.lighter.service.START",
|
|
||||||
"com.google.android.gms.mdm.services.DeviceManagerApiService.START",
|
|
||||||
"com.google.android.gms.mdm.services.START",
|
|
||||||
"com.google.android.gms.mdns.service.START",
|
|
||||||
"com.google.android.gms.measurement.START",
|
|
||||||
"com.google.android.gms.nearby.bootstrap.service.NearbyBootstrapService.START",
|
|
||||||
"com.google.android.gms.nearby.connection.service.START",
|
|
||||||
"com.google.android.gms.nearby.fastpair.START",
|
|
||||||
"com.google.android.gms.nearby.messages.service.NearbyMessagesService.START",
|
|
||||||
"com.google.android.gms.nearby.sharing.service.NearbySharingService.START",
|
|
||||||
"com.google.android.gms.nearby.sharing.START_SERVICE",
|
|
||||||
"com.google.android.gms.notifications.service.START",
|
|
||||||
"com.google.android.gms.ocr.service.internal.START",
|
|
||||||
"com.google.android.gms.ocr.service.START",
|
|
||||||
"com.google.android.gms.oss.licenses.service.START",
|
|
||||||
"com.google.android.gms.payse.service.BIND",
|
|
||||||
"com.google.android.gms.people.contactssync.service.START",
|
|
||||||
"com.google.android.gms.people.service.START",
|
|
||||||
"com.google.android.gms.phenotype.service.START",
|
|
||||||
"com.google.android.gms.photos.autobackup.service.START",
|
|
||||||
"com.google.android.gms.playlog.service.START",
|
|
||||||
"com.google.android.gms.plus.service.default.INTENT",
|
|
||||||
"com.google.android.gms.plus.service.image.INTENT",
|
|
||||||
"com.google.android.gms.plus.service.internal.START",
|
|
||||||
"com.google.android.gms.plus.service.START",
|
|
||||||
"com.google.android.gms.potokens.service.START",
|
|
||||||
"com.google.android.gms.pseudonymous.service.START",
|
|
||||||
"com.google.android.gms.rcs.START",
|
|
||||||
"com.google.android.gms.reminders.service.START",
|
|
||||||
"com.google.android.gms.romanesco.MODULE_BACKUP_AGENT",
|
|
||||||
"com.google.android.gms.romanesco.service.START",
|
|
||||||
"com.google.android.gms.safetynet.service.START",
|
|
||||||
"com.google.android.gms.scheduler.ACTION_PROXY_SCHEDULE",
|
|
||||||
"com.google.android.gms.search.service.SEARCH_AUTH_START",
|
|
||||||
"com.google.android.gms.semanticlocation.service.START_ODLH",
|
|
||||||
"com.google.android.gms.sesame.service.BIND",
|
|
||||||
"com.google.android.gms.settings.EXPOSURE_NOTIFICATION_SETTINGS",
|
|
||||||
"com.google.android.gms.setup.auth.SecondDeviceAuth.START",
|
|
||||||
"com.google.android.gms.signin.service.START",
|
|
||||||
"com.google.android.gms.smartdevice.d2d.SourceDeviceService.START",
|
|
||||||
"com.google.android.gms.smartdevice.d2d.TargetDeviceService.START",
|
|
||||||
"com.google.android.gms.smartdevice.directtransfer.SourceDirectTransferService.START",
|
|
||||||
"com.google.android.gms.smartdevice.directtransfer.TargetDirectTransferService.START",
|
|
||||||
"com.google.android.gms.smartdevice.postsetup.PostSetupService.START",
|
|
||||||
"com.google.android.gms.smartdevice.setup.accounts.AccountsService.START",
|
|
||||||
"com.google.android.gms.smartdevice.wifi.START_WIFI_HELPER_SERVICE",
|
|
||||||
"com.google.android.gms.social.location.activity.service.START",
|
|
||||||
"com.google.android.gms.speech.service.START",
|
|
||||||
"com.google.android.gms.statementservice.EXECUTE",
|
|
||||||
"com.google.android.gms.stats.ACTION_UPLOAD_DROPBOX_ENTRIES",
|
|
||||||
"com.google.android.gms.tapandpay.service.BIND",
|
|
||||||
"com.google.android.gms.telephonyspam.service.START",
|
|
||||||
"com.google.android.gms.testsupport.service.START",
|
|
||||||
"com.google.android.gms.thunderbird.service.START",
|
|
||||||
"com.google.android.gms.trustagent.BridgeApi.START",
|
|
||||||
"com.google.android.gms.trustagent.StateApi.START",
|
|
||||||
"com.google.android.gms.trustagent.trustlet.trustletmanagerservice.BIND",
|
|
||||||
"com.google.android.gms.trustlet.bluetooth.service.BIND",
|
|
||||||
"com.google.android.gms.trustlet.connectionlessble.service.BIND",
|
|
||||||
"com.google.android.gms.trustlet.face.service.BIND",
|
|
||||||
"com.google.android.gms.trustlet.nfc.service.BIND",
|
|
||||||
"com.google.android.gms.trustlet.onbody.service.BIND",
|
|
||||||
"com.google.android.gms.trustlet.place.service.BIND",
|
|
||||||
"com.google.android.gms.trustlet.voiceunlock.service.BIND",
|
|
||||||
"com.google.android.gms.udc.service.START",
|
|
||||||
"com.google.android.gms.update.START_API_SERVICE",
|
|
||||||
"com.google.android.gms.update.START_SERVICE",
|
|
||||||
"com.google.android.gms.update.START_SINGLE_USER_API_SERVICE",
|
|
||||||
"com.google.android.gms.update.START_TV_API_SERVICE",
|
|
||||||
"com.google.android.gms.usagereporting.service.START",
|
|
||||||
"com.google.android.gms.userlocation.service.START",
|
|
||||||
"com.google.android.gms.vehicle.cabin.service.START",
|
|
||||||
"com.google.android.gms.vehicle.climate.service.START",
|
|
||||||
"com.google.android.gms.vehicle.info.service.START",
|
|
||||||
"com.google.android.gms.wallet.service.BIND",
|
|
||||||
"com.google.android.gms.walletp2p.service.firstparty.BIND",
|
|
||||||
"com.google.android.gms.walletp2p.service.zeroparty.BIND",
|
|
||||||
"com.google.android.gms.wearable.BIND",
|
|
||||||
"com.google.android.gms.wearable.BIND_LISTENER",
|
|
||||||
"com.google.android.gms.wearable.DATA_CHANGED",
|
|
||||||
"com.google.android.gms.wearable.MESSAGE_RECEIVED",
|
|
||||||
"com.google.android.gms.wearable.NODE_CHANGED",
|
|
||||||
"com.google.android.gsf.action.GET_GLS",
|
|
||||||
"com.google.android.location.settings.LOCATION_REPORTING_SETTINGS",
|
|
||||||
"com.google.android.mdd.service.START",
|
|
||||||
"com.google.android.mdh.service.listener.START",
|
|
||||||
"com.google.android.mdh.service.START",
|
|
||||||
"com.google.android.mobstore.service.START",
|
|
||||||
"com.google.firebase.auth.api.gms.service.START",
|
|
||||||
"com.google.firebase.dynamiclinks.service.START",
|
|
||||||
"com.google.iid.TOKEN_REQUEST",
|
|
||||||
"com.google.android.gms.location.places.ui.PICK_PLACE",
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All content provider authorities.
|
|
||||||
*/
|
|
||||||
val AUTHORITIES = setOf(
|
|
||||||
"com.google.android.gms.auth.accounts",
|
|
||||||
"com.google.android.gms.chimera",
|
|
||||||
"com.google.android.gms.fonts",
|
|
||||||
"com.google.android.gms.phenotype",
|
|
||||||
"com.google.android.gsf.gservices",
|
|
||||||
"com.google.settings",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract resource patch that allows Google apps to run without root and under a different package name
|
* Abstract resource patch that allows Google apps to run without root and under a different package name
|
||||||
* by using GmsCore instead of Google Play Services.
|
* by using GmsCore instead of Google Play Services.
|
||||||
@@ -510,129 +214,139 @@ private object Constants {
|
|||||||
* @param executeBlock The additional execution block of the patch.
|
* @param executeBlock The additional execution block of the patch.
|
||||||
* @param block The additional block to build the patch.
|
* @param block The additional block to build the patch.
|
||||||
*/
|
*/
|
||||||
fun gmsCoreSupportResourcePatch( // This is here only for binary compatibility.
|
fun gmsCoreSupportResourcePatch(
|
||||||
fromPackageName: String,
|
fromPackageName: String,
|
||||||
toPackageName: String,
|
toPackageName: String,
|
||||||
spoofedPackageSignature: String,
|
spoofedPackageSignature: String,
|
||||||
gmsCoreVendorGroupIdOption: Option<String>,
|
gmsCoreVendorGroupIdOption: Option<String>,
|
||||||
executeBlock: ResourcePatchContext.() -> Unit = {},
|
executeBlock: ResourcePatchContext.() -> Unit = {},
|
||||||
block: ResourcePatchBuilder.() -> Unit = {},
|
block: ResourcePatchBuilder.() -> Unit = {},
|
||||||
) = gmsCoreSupportResourcePatch(
|
|
||||||
fromPackageName,
|
|
||||||
toPackageName,
|
|
||||||
spoofedPackageSignature,
|
|
||||||
gmsCoreVendorGroupIdOption,
|
|
||||||
true,
|
|
||||||
executeBlock,
|
|
||||||
block
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abstract resource patch that allows Google apps to run without root and under a different package name
|
|
||||||
* by using GmsCore instead of Google Play Services.
|
|
||||||
*
|
|
||||||
* @param fromPackageName The package name of the original app.
|
|
||||||
* @param toPackageName The package name to fall back to if no custom package name is specified in patch options.
|
|
||||||
* @param spoofedPackageSignature The signature of the package to spoof to.
|
|
||||||
* @param gmsCoreVendorGroupIdOption The option to get the vendor group ID of GmsCore.
|
|
||||||
* @param addStringResources If the GmsCore shared strings should be added to the patched app.
|
|
||||||
* @param executeBlock The additional execution block of the patch.
|
|
||||||
* @param block The additional block to build the patch.
|
|
||||||
*/
|
|
||||||
// TODO: On the next major release make this public and delete the public overloaded constructor.
|
|
||||||
internal fun gmsCoreSupportResourcePatch(
|
|
||||||
fromPackageName: String,
|
|
||||||
toPackageName: String,
|
|
||||||
spoofedPackageSignature: String,
|
|
||||||
gmsCoreVendorGroupIdOption: Option<String>,
|
|
||||||
addStringResources: Boolean = true,
|
|
||||||
executeBlock: ResourcePatchContext.() -> Unit = {},
|
|
||||||
block: ResourcePatchBuilder.() -> Unit = {},
|
|
||||||
) = resourcePatch {
|
) = resourcePatch {
|
||||||
dependsOn(
|
dependsOn(
|
||||||
changePackageNamePatch,
|
changePackageNamePatch,
|
||||||
addResourcesPatch,
|
addResourcesPatch,
|
||||||
)
|
)
|
||||||
|
|
||||||
val gmsCoreVendorGroupId by gmsCoreVendorGroupIdOption
|
val gmsCoreVendorGroupId = gmsCoreVendorGroupIdOption.value!!
|
||||||
|
|
||||||
execute {
|
execute {
|
||||||
// Some patches don't use shared String resources so there's no need to add them.
|
addResources("shared", "misc.gms.gmsCoreSupportResourcePatch")
|
||||||
if (addStringResources) {
|
|
||||||
addResources("shared", "misc.gms.gmsCoreSupportResourcePatch")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
document("AndroidManifest.xml").use { document ->
|
||||||
* Add metadata to manifest to support spoofing the package name and signature of GmsCore.
|
document.getElementsByTagName("permission").asSequence().forEach { node ->
|
||||||
*/
|
val nameElement = node.attributes.getNamedItem("android:name")
|
||||||
fun addSpoofingMetadata() {
|
nameElement.textContent = toPackageName + nameElement.textContent
|
||||||
fun Node.adoptChild(
|
|
||||||
tagName: String,
|
|
||||||
block: Element.() -> Unit,
|
|
||||||
) {
|
|
||||||
val child = ownerDocument.createElement(tagName)
|
|
||||||
child.block()
|
|
||||||
appendChild(child)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
document("AndroidManifest.xml").use { document ->
|
document.getElementsByTagName("uses-permission").asSequence().forEach { node ->
|
||||||
val applicationNode =
|
val nameElement = node.attributes.getNamedItem("android:name")
|
||||||
document
|
if (nameElement.textContent in GMS_PERMISSIONS) {
|
||||||
.getElementsByTagName("application")
|
nameElement.textContent.replace("com.google", gmsCoreVendorGroupId)
|
||||||
.item(0)
|
|
||||||
|
|
||||||
// Spoof package name and signature.
|
|
||||||
applicationNode.adoptChild("meta-data") {
|
|
||||||
setAttribute("android:name", "$gmsCoreVendorGroupId.android.gms.SPOOFED_PACKAGE_NAME")
|
|
||||||
setAttribute("android:value", fromPackageName)
|
|
||||||
}
|
|
||||||
|
|
||||||
applicationNode.adoptChild("meta-data") {
|
|
||||||
setAttribute("android:name", "$gmsCoreVendorGroupId.android.gms.SPOOFED_PACKAGE_SIGNATURE")
|
|
||||||
setAttribute("android:value", spoofedPackageSignature)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GmsCore presence detection in extension.
|
|
||||||
applicationNode.adoptChild("meta-data") {
|
|
||||||
// TODO: The name of this metadata should be dynamic.
|
|
||||||
setAttribute("android:name", "app.revanced.MICROG_PACKAGE_NAME")
|
|
||||||
setAttribute("android:value", "$gmsCoreVendorGroupId.android.gms")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
document.getElementsByTagName("provider").asSequence().forEach { node ->
|
||||||
* Patch the manifest to support GmsCore.
|
val providerElement = node.attributes.getNamedItem("android:authorities")
|
||||||
*/
|
|
||||||
fun patchManifest() {
|
|
||||||
val packageName = setOrGetFallbackPackageName(toPackageName)
|
|
||||||
|
|
||||||
val transformations = mapOf(
|
providerElement.textContent = providerElement.textContent.split(";")
|
||||||
"package=\"$fromPackageName" to "package=\"$packageName",
|
.joinToString(";") { authority ->
|
||||||
"android:authorities=\"$fromPackageName" to "android:authorities=\"$packageName",
|
if (authority.startsWith("com.google")) {
|
||||||
"$fromPackageName.permission.C2D_MESSAGE" to "$packageName.permission.C2D_MESSAGE",
|
authority.replace("com.google", gmsCoreVendorGroupId)
|
||||||
"$fromPackageName.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION" to "$packageName.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION",
|
} else {
|
||||||
"com.google.android.c2dm" to "$gmsCoreVendorGroupId.android.c2dm",
|
"$gmsCoreVendorGroupId.$authority"
|
||||||
"com.google.android.libraries.photos.api.mars" to "$gmsCoreVendorGroupId.android.apps.photos.api.mars",
|
}
|
||||||
"</queries>" to "<package android:name=\"$gmsCoreVendorGroupId.android.gms\"/></queries>",
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
val manifest = get("AndroidManifest.xml")
|
document.getNode("manifest")
|
||||||
manifest.writeText(
|
.attributes.getNamedItem("package").textContent =
|
||||||
transformations.entries.fold(manifest.readText()) { acc, (from, to) ->
|
setOrGetFallbackPackageName(toPackageName)
|
||||||
acc.replace(
|
|
||||||
from,
|
document.getNode("queries").appendChild(
|
||||||
to,
|
document.createElement("package").apply {
|
||||||
|
attributes.setNamedItem(
|
||||||
|
document.createAttribute("android:name").apply {
|
||||||
|
textContent = "$gmsCoreVendorGroupId.android.gms"
|
||||||
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
patchManifest()
|
val applicationNode = document.getNode("application")
|
||||||
addSpoofingMetadata()
|
|
||||||
|
// Spoof package name and signature.
|
||||||
|
applicationNode.appendChild(
|
||||||
|
document.createElement("meta-data").apply {
|
||||||
|
setAttribute(
|
||||||
|
"android:name",
|
||||||
|
"$gmsCoreVendorGroupId.android.gms.SPOOFED_PACKAGE_NAME",
|
||||||
|
)
|
||||||
|
setAttribute("android:value", fromPackageName)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
applicationNode.appendChild(
|
||||||
|
document.createElement("meta-data").apply {
|
||||||
|
setAttribute(
|
||||||
|
"android:name",
|
||||||
|
"$gmsCoreVendorGroupId.android.gms.SPOOFED_PACKAGE_SIGNATURE",
|
||||||
|
)
|
||||||
|
setAttribute("android:value", spoofedPackageSignature)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// GmsCore presence detection in extension.
|
||||||
|
applicationNode.appendChild(
|
||||||
|
document.createElement("meta-data").apply {
|
||||||
|
// TODO: The name of this metadata should be dynamic.
|
||||||
|
setAttribute("android:name", "app.revanced.MICROG_PACKAGE_NAME")
|
||||||
|
setAttribute("android:value", "$gmsCoreVendorGroupId.android.gms")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
executeBlock()
|
executeBlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
block()
|
block()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private object Constants {
|
||||||
|
val GMS_PERMISSIONS = setOf(
|
||||||
|
"com.google.android.providers.gsf.permission.READ_GSERVICES",
|
||||||
|
"com.google.android.c2dm.permission.RECEIVE",
|
||||||
|
"com.google.android.c2dm.permission.SEND",
|
||||||
|
"com.google.android.gtalkservice.permission.GTALK_SERVICE",
|
||||||
|
"com.google.android.googleapps.permission.GOOGLE_AUTH",
|
||||||
|
"com.google.android.googleapps.permission.GOOGLE_AUTH.cp",
|
||||||
|
"com.google.android.googleapps.permission.GOOGLE_AUTH.local",
|
||||||
|
"com.google.android.googleapps.permission.GOOGLE_AUTH.mail",
|
||||||
|
"com.google.android.googleapps.permission.GOOGLE_AUTH.writely",
|
||||||
|
"com.google.android.gms.permission.ACTIVITY_RECOGNITION",
|
||||||
|
"com.google.android.gms.permission.AD_ID",
|
||||||
|
"com.google.android.gms.permission.AD_ID_NOTIFICATION",
|
||||||
|
"com.google.android.gms.auth.api.phone.permission.SEND",
|
||||||
|
"com.google.android.gms.permission.CAR_INFORMATION",
|
||||||
|
"com.google.android.gms.permission.CAR_SPEED",
|
||||||
|
"com.google.android.gms.permission.CAR_FUEL",
|
||||||
|
"com.google.android.gms.permission.CAR_MILEAGE",
|
||||||
|
"com.google.android.gms.permission.CAR_VENDOR_EXTENSION",
|
||||||
|
"com.google.android.gms.locationsharingreporter.periodic.STATUS_UPDATE",
|
||||||
|
"com.google.android.gms.auth.permission.GOOGLE_ACCOUNT_CHANGE",
|
||||||
|
)
|
||||||
|
|
||||||
|
val GMS_AUTHORITIES = setOf(
|
||||||
|
"google.android.gms.fileprovider",
|
||||||
|
"com.google.android.gms.auth.accounts",
|
||||||
|
"com.google.android.gms.chimera",
|
||||||
|
"com.google.android.gms.fonts",
|
||||||
|
"com.google.android.gms.phenotype",
|
||||||
|
"com.google.android.gsf.gservices",
|
||||||
|
"com.google.settings",
|
||||||
|
"subscribedfeeds",
|
||||||
|
)
|
||||||
|
|
||||||
|
val APP_PERMISSIONS = mutableSetOf<String>()
|
||||||
|
|
||||||
|
val APP_AUTHORITIES = mutableSetOf<String>()
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package app.revanced.patches.shared.misc.litho.filter
|
||||||
|
|
||||||
|
import app.revanced.patcher.fingerprint
|
||||||
|
import app.revanced.util.containsLiteralInstruction
|
||||||
|
import app.revanced.util.literal
|
||||||
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
|
|
||||||
|
internal val lithoFilterFingerprint = fingerprint {
|
||||||
|
accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR)
|
||||||
|
custom { _, classDef ->
|
||||||
|
classDef.endsWith("/LithoFilterPatch;")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matches a method that use the protobuf of our component.
|
||||||
|
*/
|
||||||
|
internal val protobufBufferReferenceFingerprint = fingerprint {
|
||||||
|
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||||
|
returns("V")
|
||||||
|
parameters("I", "Ljava/nio/ByteBuffer;")
|
||||||
|
opcodes(
|
||||||
|
Opcode.IPUT,
|
||||||
|
Opcode.INVOKE_VIRTUAL,
|
||||||
|
Opcode.MOVE_RESULT,
|
||||||
|
Opcode.SUB_INT_2ADDR,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val componentContextParserFingerprint = fingerprint {
|
||||||
|
strings("Number of bits must be positive")
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val emptyComponentFingerprint = fingerprint {
|
||||||
|
accessFlags(AccessFlags.PRIVATE, AccessFlags.CONSTRUCTOR)
|
||||||
|
parameters()
|
||||||
|
strings("EmptyComponent")
|
||||||
|
custom { _, classDef ->
|
||||||
|
classDef.methods.filter { AccessFlags.STATIC.isSet(it.accessFlags) }.size == 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val componentCreateFingerprint = fingerprint {
|
||||||
|
strings(
|
||||||
|
"Element missing correct type extension",
|
||||||
|
"Element missing type"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val lithoThreadExecutorFingerprint = fingerprint {
|
||||||
|
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
|
||||||
|
parameters("I", "I", "I")
|
||||||
|
custom { method, classDef ->
|
||||||
|
classDef.superclass == "Ljava/util/concurrent/ThreadPoolExecutor;" &&
|
||||||
|
method.containsLiteralInstruction(1L) // 1L = default thread timeout.
|
||||||
|
}
|
||||||
|
}
|
||||||
+211
@@ -0,0 +1,211 @@
|
|||||||
|
@file:Suppress("SpellCheckingInspection")
|
||||||
|
|
||||||
|
package app.revanced.patches.shared.misc.litho.filter
|
||||||
|
|
||||||
|
import app.revanced.patcher.Fingerprint
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.removeInstructions
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||||
|
import app.revanced.patcher.patch.BytecodePatchBuilder
|
||||||
|
import app.revanced.patcher.patch.BytecodePatchContext
|
||||||
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
|
import app.revanced.patches.shared.misc.extension.sharedExtensionPatch
|
||||||
|
import app.revanced.util.addInstructionsAtControlFlowLabel
|
||||||
|
import app.revanced.util.findFreeRegister
|
||||||
|
import app.revanced.util.getReference
|
||||||
|
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
|
||||||
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
|
import com.android.tools.smali.dexlib2.iface.Method
|
||||||
|
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||||
|
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to add a hook point to the extension stub.
|
||||||
|
*/
|
||||||
|
lateinit var addLithoFilter: (String) -> Unit
|
||||||
|
private set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts the number of filters added to the static field array.
|
||||||
|
*/
|
||||||
|
private var filterCount = 0
|
||||||
|
|
||||||
|
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/shared/patches/litho/LithoFilterPatch;"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A patch that allows to filter Litho components based on their identifier or path.
|
||||||
|
*
|
||||||
|
* @param componentCreateInsertionIndex The index to insert the filtering code in the component create method.
|
||||||
|
* @param conversionContextFingerprintToString The fingerprint of the conversion context to string method.
|
||||||
|
* @param executeBlock The additional execution block of the patch.
|
||||||
|
* @param block The additional block to build the patch.
|
||||||
|
*/
|
||||||
|
internal fun lithoFilterPatch(
|
||||||
|
componentCreateInsertionIndex: Method.() -> Int,
|
||||||
|
conversionContextFingerprintToString: Fingerprint,
|
||||||
|
executeBlock: BytecodePatchContext.() -> Unit = {},
|
||||||
|
block: BytecodePatchBuilder.() -> Unit = {},
|
||||||
|
) = bytecodePatch(
|
||||||
|
description = "Hooks the method which parses the bytes into a ComponentContext to filter components.",
|
||||||
|
) {
|
||||||
|
dependsOn(
|
||||||
|
sharedExtensionPatch(),
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The following patch inserts a hook into the method that parses the bytes into a ComponentContext.
|
||||||
|
* This method contains a StringBuilder object that represents the pathBuilder of the component.
|
||||||
|
* The pathBuilder is used to filter components by their path.
|
||||||
|
*
|
||||||
|
* Additionally, the method contains a reference to the component's identifier.
|
||||||
|
* The identifier is used to filter components by their identifier.
|
||||||
|
*
|
||||||
|
* The protobuf buffer is passed along from a different injection point before the filtering occurs.
|
||||||
|
* The buffer is a large byte array that represents the component tree.
|
||||||
|
* This byte array is searched for strings that indicate the current component.
|
||||||
|
*
|
||||||
|
* All modifications done here must allow all the original code to still execute
|
||||||
|
* even when filtering, otherwise memory leaks or poor app performance may occur.
|
||||||
|
*
|
||||||
|
* The following pseudocode shows how this patch works:
|
||||||
|
*
|
||||||
|
* class SomeOtherClass {
|
||||||
|
* // Called before ComponentContextParser.parseComponent() method.
|
||||||
|
* public void someOtherMethod(ByteBuffer byteBuffer) {
|
||||||
|
* ExtensionClass.setProtoBuffer(byteBuffer); // Inserted by this patch.
|
||||||
|
* ...
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* class CreateComponentClass {
|
||||||
|
* public Component createComponent() {
|
||||||
|
* ...
|
||||||
|
*
|
||||||
|
* if (extensionClass.shouldFilter(identifier, path)) { // Inserted by this patch.
|
||||||
|
* return emptyComponent;
|
||||||
|
* }
|
||||||
|
* return originalUnpatchedComponent; // Original code.
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
execute {
|
||||||
|
// Remove dummy filter from extenion static field
|
||||||
|
// and add the filters included during patching.
|
||||||
|
lithoFilterFingerprint.method.apply {
|
||||||
|
removeInstructions(2, 4) // Remove dummy filter.
|
||||||
|
|
||||||
|
addLithoFilter = { classDescriptor ->
|
||||||
|
addInstructions(
|
||||||
|
2,
|
||||||
|
"""
|
||||||
|
new-instance v1, $classDescriptor
|
||||||
|
invoke-direct { v1 }, $classDescriptor-><init>()V
|
||||||
|
const/16 v2, ${filterCount++}
|
||||||
|
aput-object v1, v0, v2
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add an interceptor to steal the protobuf of our component.
|
||||||
|
protobufBufferReferenceFingerprint.method.addInstruction(
|
||||||
|
0,
|
||||||
|
"invoke-static { p2 }, $EXTENSION_CLASS_DESCRIPTOR->setProtoBuffer(Ljava/nio/ByteBuffer;)V",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
// Hook the method that parses bytes into a ComponentContext.
|
||||||
|
// Allow the method to run to completion, and override the
|
||||||
|
// return value with an empty component if it should be filtered.
|
||||||
|
// It is important to allow the original code to always run to completion,
|
||||||
|
// otherwise high memory usage and poor app performance can occur.
|
||||||
|
|
||||||
|
// Find the identifier/path fields of the conversion context.
|
||||||
|
val conversionContextIdentifierField = componentContextParserFingerprint.let {
|
||||||
|
// Identifier field is loaded just before the string declaration.
|
||||||
|
val index = it.method.indexOfFirstInstructionReversedOrThrow(
|
||||||
|
it.stringMatches!!.first().index
|
||||||
|
) {
|
||||||
|
// Our instruction reads a String from a field of the ConversionContext class.
|
||||||
|
val reference = getReference<FieldReference>()
|
||||||
|
reference?.definingClass == conversionContextFingerprintToString.originalClassDef.type
|
||||||
|
&& reference.type == "Ljava/lang/String;"
|
||||||
|
}
|
||||||
|
|
||||||
|
it.method.getInstruction<ReferenceInstruction>(index).getReference<FieldReference>()!!
|
||||||
|
}
|
||||||
|
|
||||||
|
val conversionContextPathBuilderField = conversionContextFingerprintToString.originalClassDef
|
||||||
|
.fields.single { field -> field.type == "Ljava/lang/StringBuilder;" }
|
||||||
|
|
||||||
|
// Find class and methods to create an empty component.
|
||||||
|
val builderMethodDescriptor = emptyComponentFingerprint.classDef.methods.single {
|
||||||
|
// The only static method in the class.
|
||||||
|
method ->
|
||||||
|
AccessFlags.STATIC.isSet(method.accessFlags)
|
||||||
|
}
|
||||||
|
|
||||||
|
val emptyComponentField = classBy {
|
||||||
|
// Only one field that matches.
|
||||||
|
it.type == builderMethodDescriptor.returnType
|
||||||
|
}!!.immutableClass.fields.single()
|
||||||
|
|
||||||
|
// Match all component creations methods
|
||||||
|
componentCreateFingerprint.method.apply {
|
||||||
|
val insertIndex = componentCreateInsertionIndex()
|
||||||
|
val freeRegister = findFreeRegister(insertIndex)
|
||||||
|
val identifierRegister = findFreeRegister(insertIndex, freeRegister)
|
||||||
|
val pathRegister = findFreeRegister(insertIndex, freeRegister, identifierRegister)
|
||||||
|
|
||||||
|
addInstructionsAtControlFlowLabel(
|
||||||
|
insertIndex,
|
||||||
|
"""
|
||||||
|
move-object/from16 v$freeRegister, p2 # ConversionContext parameter
|
||||||
|
check-cast v$freeRegister, ${conversionContextFingerprintToString.originalClassDef.type} # Check we got the actual ConversionContext
|
||||||
|
|
||||||
|
# Get identifier and path from ConversionContext
|
||||||
|
iget-object v$identifierRegister, v$freeRegister, $conversionContextIdentifierField
|
||||||
|
iget-object v$pathRegister, v$freeRegister, $conversionContextPathBuilderField
|
||||||
|
|
||||||
|
# Check if the component should be filtered.
|
||||||
|
invoke-static { v$identifierRegister, v$pathRegister }, $EXTENSION_CLASS_DESCRIPTOR->isFiltered(Ljava/lang/String;Ljava/lang/StringBuilder;)Z
|
||||||
|
move-result v$freeRegister
|
||||||
|
if-eqz v$freeRegister, :unfiltered
|
||||||
|
|
||||||
|
# Return an empty component
|
||||||
|
move-object/from16 v$freeRegister, p1
|
||||||
|
invoke-static { v$freeRegister }, $builderMethodDescriptor
|
||||||
|
move-result-object v$freeRegister
|
||||||
|
iget-object v$freeRegister, v$freeRegister, $emptyComponentField
|
||||||
|
return-object v$freeRegister
|
||||||
|
|
||||||
|
:unfiltered
|
||||||
|
nop
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Check if needed in music
|
||||||
|
// Change Litho thread executor to 1 thread to fix layout issue in unpatched YouTube.
|
||||||
|
lithoThreadExecutorFingerprint.method.addInstructions(
|
||||||
|
0,
|
||||||
|
"""
|
||||||
|
invoke-static { p1 }, $EXTENSION_CLASS_DESCRIPTOR->getExecutorCorePoolSize(I)I
|
||||||
|
move-result p1
|
||||||
|
invoke-static { p2 }, $EXTENSION_CLASS_DESCRIPTOR->getExecutorMaxThreads(I)I
|
||||||
|
move-result p2
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
executeBlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
finalize {
|
||||||
|
// Save the number of filters added.
|
||||||
|
lithoFilterFingerprint.method.replaceInstruction(0, "const/16 v0, $filterCount")
|
||||||
|
}
|
||||||
|
|
||||||
|
block()
|
||||||
|
}
|
||||||
+191
@@ -0,0 +1,191 @@
|
|||||||
|
package app.revanced.patches.strava.distractions
|
||||||
|
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||||
|
import app.revanced.patcher.patch.booleanOption
|
||||||
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
|
||||||
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||||
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||||
|
import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableBooleanEncodedValue.Companion.toMutable
|
||||||
|
import app.revanced.patches.strava.misc.extension.sharedExtensionPatch
|
||||||
|
import app.revanced.util.findMutableMethodOf
|
||||||
|
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||||
|
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableMethodReference
|
||||||
|
import com.android.tools.smali.dexlib2.immutable.value.ImmutableBooleanEncodedValue
|
||||||
|
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||||
|
import java.util.logging.Logger
|
||||||
|
|
||||||
|
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/strava/HideDistractionsPatch;"
|
||||||
|
private const val MODULAR_FRAMEWORK_CLASS_DESCRIPTOR_PREFIX = "Lcom/strava/modularframework"
|
||||||
|
|
||||||
|
private const val METHOD_SUFFIX = "\$original"
|
||||||
|
|
||||||
|
private data class FilterablePropertyFingerprint(
|
||||||
|
val name: String,
|
||||||
|
val parameterTypes: List<String> = listOf(),
|
||||||
|
)
|
||||||
|
|
||||||
|
private val fingerprints = arrayOf(
|
||||||
|
FilterablePropertyFingerprint("ChildrenEntries"),
|
||||||
|
FilterablePropertyFingerprint("Entries"),
|
||||||
|
FilterablePropertyFingerprint("Field", listOf("Ljava/lang/String;")),
|
||||||
|
FilterablePropertyFingerprint("Fields"),
|
||||||
|
FilterablePropertyFingerprint("MenuItems"),
|
||||||
|
FilterablePropertyFingerprint("Modules"),
|
||||||
|
FilterablePropertyFingerprint("Properties"),
|
||||||
|
FilterablePropertyFingerprint("StateMap"),
|
||||||
|
FilterablePropertyFingerprint("Submodules"),
|
||||||
|
)
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
val hideDistractionsPatch = bytecodePatch(
|
||||||
|
name = "Hide distractions",
|
||||||
|
description = "Hides elements that are not essential.",
|
||||||
|
) {
|
||||||
|
compatibleWith("com.strava")
|
||||||
|
|
||||||
|
dependsOn(sharedExtensionPatch)
|
||||||
|
|
||||||
|
val logger = Logger.getLogger(this::class.java.name)
|
||||||
|
|
||||||
|
val options = arrayOf(
|
||||||
|
booleanOption(
|
||||||
|
key = "upselling",
|
||||||
|
title = "Upselling",
|
||||||
|
description = "Elements that suggest you subscribe.",
|
||||||
|
default = true,
|
||||||
|
required = true,
|
||||||
|
),
|
||||||
|
booleanOption(
|
||||||
|
key = "promo",
|
||||||
|
title = "Promotions",
|
||||||
|
default = true,
|
||||||
|
required = true,
|
||||||
|
),
|
||||||
|
booleanOption(
|
||||||
|
key = "followSuggestions",
|
||||||
|
title = "Who to Follow",
|
||||||
|
description = "Popular athletes, followers, people near you etc.",
|
||||||
|
default = true,
|
||||||
|
required = true,
|
||||||
|
),
|
||||||
|
booleanOption(
|
||||||
|
key = "challengeSuggestions",
|
||||||
|
title = "Suggested Challenges",
|
||||||
|
description = "Random challenges Strava wants you to join.",
|
||||||
|
default = true,
|
||||||
|
required = true,
|
||||||
|
),
|
||||||
|
booleanOption(
|
||||||
|
key = "joinChallenge",
|
||||||
|
title = "Join Challenge",
|
||||||
|
description = "Challenges your follows have joined.",
|
||||||
|
default = false,
|
||||||
|
required = true,
|
||||||
|
),
|
||||||
|
booleanOption(
|
||||||
|
key = "joinClub",
|
||||||
|
title = "Joined a club",
|
||||||
|
description = "Clubs your follows have joined.",
|
||||||
|
default = false,
|
||||||
|
required = true,
|
||||||
|
),
|
||||||
|
booleanOption(
|
||||||
|
key = "activityLookback",
|
||||||
|
title = "Your activity from X years ago",
|
||||||
|
default = false,
|
||||||
|
required = true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
execute {
|
||||||
|
// region Write option values into extension class.
|
||||||
|
|
||||||
|
val extensionClass = classBy { it.type == EXTENSION_CLASS_DESCRIPTOR }!!.mutableClass.apply {
|
||||||
|
options.forEach { option ->
|
||||||
|
staticFields.first { field -> field.name == option.key }.initialValue =
|
||||||
|
ImmutableBooleanEncodedValue.forBoolean(option.value == true).toMutable()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region Intercept all classes' property getter calls.
|
||||||
|
|
||||||
|
fun MutableMethod.cloneAndIntercept(
|
||||||
|
classDef: MutableClass,
|
||||||
|
extensionMethodName: String,
|
||||||
|
extensionMethodParameterTypes: List<String>,
|
||||||
|
) {
|
||||||
|
val extensionMethodReference = ImmutableMethodReference(
|
||||||
|
EXTENSION_CLASS_DESCRIPTOR,
|
||||||
|
extensionMethodName,
|
||||||
|
extensionMethodParameterTypes,
|
||||||
|
returnType,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (extensionClass.directMethods.none { method ->
|
||||||
|
MethodUtil.methodSignaturesMatch(method, extensionMethodReference)
|
||||||
|
}) {
|
||||||
|
logger.info { "Skipped interception of $this due to missing $extensionMethodReference" }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
classDef.virtualMethods -= this
|
||||||
|
|
||||||
|
val clone = ImmutableMethod.of(this).toMutable()
|
||||||
|
|
||||||
|
classDef.virtualMethods += clone
|
||||||
|
|
||||||
|
if (implementation != null) {
|
||||||
|
val registers = List(extensionMethodParameterTypes.size) { index -> "p$index" }.joinToString(
|
||||||
|
separator = ",",
|
||||||
|
prefix = "{",
|
||||||
|
postfix = "}",
|
||||||
|
)
|
||||||
|
|
||||||
|
clone.addInstructions(
|
||||||
|
0,
|
||||||
|
"""
|
||||||
|
invoke-static $registers, $extensionMethodReference
|
||||||
|
move-result-object v0
|
||||||
|
return-object v0
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.fine { "Intercepted $this with $extensionMethodReference" }
|
||||||
|
}
|
||||||
|
|
||||||
|
name += METHOD_SUFFIX
|
||||||
|
|
||||||
|
classDef.virtualMethods += this
|
||||||
|
}
|
||||||
|
|
||||||
|
classes.filter { it.type.startsWith(MODULAR_FRAMEWORK_CLASS_DESCRIPTOR_PREFIX) }.forEach { classDef ->
|
||||||
|
val classDefProxy by lazy { proxy(classDef) }
|
||||||
|
|
||||||
|
classDef.virtualMethods.forEach { method ->
|
||||||
|
fingerprints.find { fingerprint ->
|
||||||
|
method.name == "get${fingerprint.name}" && method.parameterTypes == fingerprint.parameterTypes
|
||||||
|
}?.let { fingerprint ->
|
||||||
|
classDefProxy.mutableClass.let { mutableClass ->
|
||||||
|
// Upcast to the interface if this is an interface implementation.
|
||||||
|
val parameterType = classDef.interfaces.find {
|
||||||
|
classes.find { interfaceDef -> interfaceDef.type == it }?.virtualMethods?.any { interfaceMethod ->
|
||||||
|
MethodUtil.methodSignaturesMatch(interfaceMethod, method)
|
||||||
|
} == true
|
||||||
|
} ?: classDef.type
|
||||||
|
|
||||||
|
mutableClass.findMutableMethodOf(method).cloneAndIntercept(
|
||||||
|
mutableClass,
|
||||||
|
"filter${fingerprint.name}",
|
||||||
|
listOf(parameterType) + fingerprint.parameterTypes
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
-56
@@ -1,67 +1,22 @@
|
|||||||
package app.revanced.patches.strava.upselling
|
package app.revanced.patches.strava.upselling
|
||||||
|
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
|
|
||||||
import app.revanced.patcher.patch.bytecodePatch
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
import app.revanced.patches.strava.distractions.hideDistractionsPatch
|
||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
|
||||||
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
|
||||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
|
@Deprecated("Superseded by \"Hide distractions\" patch", ReplaceWith("hideDistractionsPatch"))
|
||||||
val disableSubscriptionSuggestionsPatch = bytecodePatch(
|
val disableSubscriptionSuggestionsPatch = bytecodePatch(
|
||||||
name = "Disable subscription suggestions",
|
name = "Disable subscription suggestions",
|
||||||
) {
|
) {
|
||||||
compatibleWith("com.strava")
|
compatibleWith("com.strava")
|
||||||
|
|
||||||
execute {
|
dependsOn(hideDistractionsPatch.apply {
|
||||||
val helperMethodName = "getModulesIfNotUpselling"
|
options["upselling"] = true
|
||||||
val pageSuffix = "_upsell"
|
options["promo"] = false
|
||||||
val label = "original"
|
options["followSuggestions"] = false
|
||||||
|
options["challengeSuggestions"] = false
|
||||||
val className = getModulesFingerprint.originalClassDef.type
|
options["joinChallenge"] = false
|
||||||
val originalMethod = getModulesFingerprint.method
|
options["joinClub"] = false
|
||||||
val returnType = originalMethod.returnType
|
options["activityLookback"] = false
|
||||||
|
})
|
||||||
getModulesFingerprint.classDef.methods.add(
|
|
||||||
ImmutableMethod(
|
|
||||||
className,
|
|
||||||
helperMethodName,
|
|
||||||
emptyList(),
|
|
||||||
returnType,
|
|
||||||
AccessFlags.PRIVATE.value,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
MutableMethodImplementation(3),
|
|
||||||
).toMutable().apply {
|
|
||||||
addInstructions(
|
|
||||||
"""
|
|
||||||
iget-object v0, p0, $className->page:Ljava/lang/String;
|
|
||||||
const-string v1, "$pageSuffix"
|
|
||||||
invoke-virtual {v0, v1}, Ljava/lang/String;->endsWith(Ljava/lang/String;)Z
|
|
||||||
move-result v0
|
|
||||||
if-eqz v0, :$label
|
|
||||||
invoke-static {}, Ljava/util/Collections;->emptyList()Ljava/util/List;
|
|
||||||
move-result-object v0
|
|
||||||
return-object v0
|
|
||||||
:$label
|
|
||||||
iget-object v0, p0, $className->modules:Ljava/util/List;
|
|
||||||
return-object v0
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
val getModulesIndex = getModulesFingerprint.patternMatch!!.startIndex
|
|
||||||
with(originalMethod) {
|
|
||||||
removeInstruction(getModulesIndex)
|
|
||||||
addInstructions(
|
|
||||||
getModulesIndex,
|
|
||||||
"""
|
|
||||||
invoke-direct {p0}, $className->$helperMethodName()$returnType
|
|
||||||
move-result-object v0
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ import app.revanced.patcher.patch.resourcePatch
|
|||||||
import app.revanced.patches.all.misc.resources.addResources
|
import app.revanced.patches.all.misc.resources.addResources
|
||||||
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||||
import app.revanced.patches.shared.misc.fix.verticalscroll.verticalScrollPatch
|
import app.revanced.patches.shared.misc.fix.verticalscroll.verticalScrollPatch
|
||||||
|
import app.revanced.patches.shared.misc.litho.filter.addLithoFilter
|
||||||
import app.revanced.patches.shared.misc.mapping.get
|
import app.revanced.patches.shared.misc.mapping.get
|
||||||
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
|
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
|
||||||
import app.revanced.patches.shared.misc.mapping.resourceMappings
|
import app.revanced.patches.shared.misc.mapping.resourceMappings
|
||||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||||
import app.revanced.patches.youtube.ad.getpremium.hideGetPremiumPatch
|
import app.revanced.patches.youtube.ad.getpremium.hideGetPremiumPatch
|
||||||
import app.revanced.patches.youtube.misc.fix.backtoexitgesture.fixBackToExitGesturePatch
|
import app.revanced.patches.youtube.misc.fix.backtoexitgesture.fixBackToExitGesturePatch
|
||||||
import app.revanced.patches.youtube.misc.litho.filter.addLithoFilter
|
|
||||||
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
|
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
|
||||||
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ import app.revanced.patches.all.misc.resources.addResourcesPatch
|
|||||||
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
|
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
|
||||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
|
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
|
||||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||||
import app.revanced.patches.youtube.misc.litho.filter.addLithoFilter
|
import app.revanced.patches.shared.misc.litho.filter.addLithoFilter
|
||||||
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
|
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
|
||||||
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||||
|
|
||||||
|
|||||||
+276
-298
@@ -9,34 +9,27 @@ import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
|||||||
import app.revanced.patcher.extensions.InstructionExtensions.instructions
|
import app.revanced.patcher.extensions.InstructionExtensions.instructions
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
|
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||||
import app.revanced.patcher.patch.bytecodePatch
|
|
||||||
import app.revanced.patcher.patch.resourcePatch
|
import app.revanced.patcher.patch.resourcePatch
|
||||||
import app.revanced.patcher.util.smali.ExternalLabel
|
import app.revanced.patcher.util.smali.ExternalLabel
|
||||||
import app.revanced.patches.all.misc.resources.addResources
|
import app.revanced.patches.all.misc.resources.addResources
|
||||||
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
import app.revanced.patches.shared.layout.hide.general.hideLayoutComponentsPatch
|
||||||
import app.revanced.patches.shared.misc.mapping.get
|
import app.revanced.patches.shared.misc.mapping.get
|
||||||
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
|
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
|
||||||
import app.revanced.patches.shared.misc.mapping.resourceMappings
|
import app.revanced.patches.shared.misc.mapping.resourceMappings
|
||||||
import app.revanced.patches.shared.misc.settings.preference.*
|
import app.revanced.patches.shared.misc.settings.preference.*
|
||||||
import app.revanced.patches.youtube.misc.litho.filter.addLithoFilter
|
|
||||||
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
|
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
|
||||||
import app.revanced.patches.youtube.misc.navigation.navigationBarHookPatch
|
import app.revanced.patches.youtube.misc.navigation.navigationBarHookPatch
|
||||||
import app.revanced.patches.youtube.misc.playservice.is_20_07_or_greater
|
import app.revanced.patches.youtube.misc.playservice.is_20_07_or_greater
|
||||||
import app.revanced.patches.youtube.misc.playservice.is_20_09_or_greater
|
import app.revanced.patches.youtube.misc.playservice.is_20_09_or_greater
|
||||||
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||||
import app.revanced.util.findFreeRegister
|
import app.revanced.util.*
|
||||||
import app.revanced.util.findInstructionIndicesReversedOrThrow
|
|
||||||
import app.revanced.util.getReference
|
|
||||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
|
||||||
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
|
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
import com.android.tools.smali.dexlib2.iface.Method
|
import com.android.tools.smali.dexlib2.iface.Method
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||||
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
|
|
||||||
|
|
||||||
var expandButtonDownId = -1L
|
var expandButtonDownId = -1L
|
||||||
private set
|
private set
|
||||||
@@ -108,184 +101,170 @@ private const val DESCRIPTION_COMPONENTS_FILTER_CLASS_NAME =
|
|||||||
private const val COMMENTS_FILTER_CLASS_NAME =
|
private const val COMMENTS_FILTER_CLASS_NAME =
|
||||||
"Lapp/revanced/extension/youtube/patches/components/CommentsFilter;"
|
"Lapp/revanced/extension/youtube/patches/components/CommentsFilter;"
|
||||||
private const val CUSTOM_FILTER_CLASS_NAME =
|
private const val CUSTOM_FILTER_CLASS_NAME =
|
||||||
"Lapp/revanced/extension/youtube/patches/components/CustomFilter;"
|
"Lapp/revanced/extension/shared/patches/components/CustomFilter;"
|
||||||
private const val KEYWORD_FILTER_CLASS_NAME =
|
private const val KEYWORD_FILTER_CLASS_NAME =
|
||||||
"Lapp/revanced/extension/youtube/patches/components/KeywordContentFilter;"
|
"Lapp/revanced/extension/youtube/patches/components/KeywordContentFilter;"
|
||||||
|
|
||||||
val hideLayoutComponentsPatch = bytecodePatch(
|
|
||||||
name = "Hide layout components",
|
|
||||||
description = "Adds options to hide general layout components.",
|
|
||||||
|
|
||||||
) {
|
val hideLayoutComponentsPatch = hideLayoutComponentsPatch(
|
||||||
dependsOn(
|
lithoFilterPatch = lithoFilterPatch,
|
||||||
lithoFilterPatch,
|
settingsPatch = settingsPatch,
|
||||||
settingsPatch,
|
additionalDependencies = setOf(
|
||||||
addResourcesPatch,
|
|
||||||
hideLayoutComponentsResourcePatch,
|
hideLayoutComponentsResourcePatch,
|
||||||
navigationBarHookPatch,
|
navigationBarHookPatch,
|
||||||
)
|
),
|
||||||
|
filterClasses = setOf(
|
||||||
compatibleWith(
|
LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR,
|
||||||
"com.google.android.youtube"(
|
DESCRIPTION_COMPONENTS_FILTER_CLASS_NAME,
|
||||||
|
COMMENTS_FILTER_CLASS_NAME,
|
||||||
|
KEYWORD_FILTER_CLASS_NAME,
|
||||||
|
CUSTOM_FILTER_CLASS_NAME
|
||||||
|
),
|
||||||
|
compatibleWithPackages = arrayOf(
|
||||||
|
"com.google.android.youtube" to setOf(
|
||||||
"19.34.42",
|
"19.34.42",
|
||||||
"20.07.39",
|
"20.07.39",
|
||||||
"20.13.41",
|
"20.13.41",
|
||||||
"20.14.43",
|
"20.14.43",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
) {
|
||||||
|
addResources("youtube", "layout.hide.general.hideLayoutComponentsPatch")
|
||||||
|
|
||||||
execute {
|
PreferenceScreen.PLAYER.addPreferences(
|
||||||
addResources("youtube", "layout.hide.general.hideLayoutComponentsPatch")
|
PreferenceScreenPreference(
|
||||||
|
key = "revanced_hide_description_components_screen",
|
||||||
|
preferences = setOf(
|
||||||
|
SwitchPreference("revanced_hide_ai_generated_video_summary_section"),
|
||||||
|
SwitchPreference("revanced_hide_ask_section"),
|
||||||
|
SwitchPreference("revanced_hide_attributes_section"),
|
||||||
|
SwitchPreference("revanced_hide_chapters_section"),
|
||||||
|
SwitchPreference("revanced_hide_featured_links_section"),
|
||||||
|
SwitchPreference("revanced_hide_featured_videos_section"),
|
||||||
|
SwitchPreference("revanced_hide_info_cards_section"),
|
||||||
|
SwitchPreference("revanced_hide_how_this_was_made_section"),
|
||||||
|
SwitchPreference("revanced_hide_hype_points"),
|
||||||
|
SwitchPreference("revanced_hide_key_concepts_section"),
|
||||||
|
SwitchPreference("revanced_hide_podcast_section"),
|
||||||
|
SwitchPreference("revanced_hide_subscribe_button"),
|
||||||
|
SwitchPreference("revanced_hide_transcript_section"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
PreferenceScreenPreference(
|
||||||
|
"revanced_comments_screen",
|
||||||
|
preferences = setOf(
|
||||||
|
SwitchPreference("revanced_hide_comments_ai_chat_summary"),
|
||||||
|
SwitchPreference("revanced_hide_comments_ai_summary"),
|
||||||
|
SwitchPreference("revanced_hide_comments_channel_guidelines"),
|
||||||
|
SwitchPreference("revanced_hide_comments_by_members_header"),
|
||||||
|
SwitchPreference("revanced_hide_comments_section"),
|
||||||
|
SwitchPreference("revanced_hide_comments_community_guidelines"),
|
||||||
|
SwitchPreference("revanced_hide_comments_create_a_short_button"),
|
||||||
|
SwitchPreference("revanced_hide_comments_emoji_and_timestamp_buttons"),
|
||||||
|
SwitchPreference("revanced_hide_comments_preview_comment"),
|
||||||
|
SwitchPreference("revanced_hide_comments_thanks_button"),
|
||||||
|
),
|
||||||
|
sorting = PreferenceScreenPreference.Sorting.UNSORTED,
|
||||||
|
),
|
||||||
|
SwitchPreference("revanced_hide_channel_bar"),
|
||||||
|
SwitchPreference("revanced_hide_channel_watermark"),
|
||||||
|
SwitchPreference("revanced_hide_crowdfunding_box"),
|
||||||
|
SwitchPreference("revanced_hide_emergency_box"),
|
||||||
|
SwitchPreference("revanced_hide_info_panels"),
|
||||||
|
SwitchPreference("revanced_hide_join_membership_button"),
|
||||||
|
SwitchPreference("revanced_hide_medical_panels"),
|
||||||
|
SwitchPreference("revanced_hide_quick_actions"),
|
||||||
|
SwitchPreference("revanced_hide_related_videos"),
|
||||||
|
SwitchPreference("revanced_hide_subscribers_community_guidelines"),
|
||||||
|
SwitchPreference("revanced_hide_timed_reactions"),
|
||||||
|
)
|
||||||
|
|
||||||
PreferenceScreen.PLAYER.addPreferences(
|
PreferenceScreen.FEED.addPreferences(
|
||||||
PreferenceScreenPreference(
|
PreferenceScreenPreference(
|
||||||
key = "revanced_hide_description_components_screen",
|
key = "revanced_hide_keyword_content_screen",
|
||||||
preferences = setOf(
|
sorting = PreferenceScreenPreference.Sorting.UNSORTED,
|
||||||
SwitchPreference("revanced_hide_ai_generated_video_summary_section"),
|
preferences = setOf(
|
||||||
SwitchPreference("revanced_hide_ask_section"),
|
SwitchPreference("revanced_hide_keyword_content_home"),
|
||||||
SwitchPreference("revanced_hide_attributes_section"),
|
SwitchPreference("revanced_hide_keyword_content_subscriptions"),
|
||||||
SwitchPreference("revanced_hide_chapters_section"),
|
SwitchPreference("revanced_hide_keyword_content_search"),
|
||||||
SwitchPreference("revanced_hide_featured_links_section"),
|
TextPreference("revanced_hide_keyword_content_phrases", inputType = InputType.TEXT_MULTI_LINE),
|
||||||
SwitchPreference("revanced_hide_featured_videos_section"),
|
NonInteractivePreference(
|
||||||
SwitchPreference("revanced_hide_info_cards_section"),
|
key = "revanced_hide_keyword_content_about",
|
||||||
SwitchPreference("revanced_hide_how_this_was_made_section"),
|
tag = "app.revanced.extension.shared.settings.preference.BulletPointPreference"
|
||||||
SwitchPreference("revanced_hide_hype_points"),
|
),
|
||||||
SwitchPreference("revanced_hide_key_concepts_section"),
|
NonInteractivePreference(
|
||||||
SwitchPreference("revanced_hide_podcast_section"),
|
key = "revanced_hide_keyword_content_about_whole_words",
|
||||||
SwitchPreference("revanced_hide_subscribe_button"),
|
tag = "app.revanced.extension.youtube.settings.preference.HtmlPreference",
|
||||||
SwitchPreference("revanced_hide_transcript_section"),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
PreferenceScreenPreference(
|
),
|
||||||
"revanced_comments_screen",
|
PreferenceScreenPreference(
|
||||||
preferences = setOf(
|
key = "revanced_hide_filter_bar_screen",
|
||||||
SwitchPreference("revanced_hide_comments_ai_chat_summary"),
|
preferences = setOf(
|
||||||
SwitchPreference("revanced_hide_comments_ai_summary"),
|
SwitchPreference("revanced_hide_filter_bar_feed_in_feed"),
|
||||||
SwitchPreference("revanced_hide_comments_channel_guidelines"),
|
SwitchPreference("revanced_hide_filter_bar_feed_in_related_videos"),
|
||||||
SwitchPreference("revanced_hide_comments_by_members_header"),
|
SwitchPreference("revanced_hide_filter_bar_feed_in_search"),
|
||||||
SwitchPreference("revanced_hide_comments_section"),
|
SwitchPreference("revanced_hide_filter_bar_feed_in_history"),
|
||||||
SwitchPreference("revanced_hide_comments_community_guidelines"),
|
|
||||||
SwitchPreference("revanced_hide_comments_create_a_short_button"),
|
|
||||||
SwitchPreference("revanced_hide_comments_emoji_and_timestamp_buttons"),
|
|
||||||
SwitchPreference("revanced_hide_comments_preview_comment"),
|
|
||||||
SwitchPreference("revanced_hide_comments_thanks_button"),
|
|
||||||
),
|
|
||||||
sorting = PreferenceScreenPreference.Sorting.UNSORTED,
|
|
||||||
),
|
),
|
||||||
SwitchPreference("revanced_hide_channel_bar"),
|
),
|
||||||
SwitchPreference("revanced_hide_channel_watermark"),
|
PreferenceScreenPreference(
|
||||||
SwitchPreference("revanced_hide_crowdfunding_box"),
|
key = "revanced_channel_screen",
|
||||||
SwitchPreference("revanced_hide_emergency_box"),
|
preferences = setOf(
|
||||||
SwitchPreference("revanced_hide_info_panels"),
|
SwitchPreference("revanced_hide_community_button"),
|
||||||
SwitchPreference("revanced_hide_join_membership_button"),
|
SwitchPreference("revanced_hide_for_you_shelf"),
|
||||||
SwitchPreference("revanced_hide_medical_panels"),
|
SwitchPreference("revanced_hide_join_button"),
|
||||||
SwitchPreference("revanced_hide_quick_actions"),
|
SwitchPreference("revanced_hide_links_preview"),
|
||||||
SwitchPreference("revanced_hide_related_videos"),
|
SwitchPreference("revanced_hide_members_shelf"),
|
||||||
SwitchPreference("revanced_hide_subscribers_community_guidelines"),
|
SwitchPreference("revanced_hide_store_button"),
|
||||||
SwitchPreference("revanced_hide_timed_reactions"),
|
SwitchPreference("revanced_hide_subscribe_button_in_channel_page"),
|
||||||
)
|
|
||||||
|
|
||||||
PreferenceScreen.FEED.addPreferences(
|
|
||||||
PreferenceScreenPreference(
|
|
||||||
key = "revanced_hide_keyword_content_screen",
|
|
||||||
sorting = PreferenceScreenPreference.Sorting.UNSORTED,
|
|
||||||
preferences = setOf(
|
|
||||||
SwitchPreference("revanced_hide_keyword_content_home"),
|
|
||||||
SwitchPreference("revanced_hide_keyword_content_subscriptions"),
|
|
||||||
SwitchPreference("revanced_hide_keyword_content_search"),
|
|
||||||
TextPreference("revanced_hide_keyword_content_phrases", inputType = InputType.TEXT_MULTI_LINE),
|
|
||||||
NonInteractivePreference(
|
|
||||||
key = "revanced_hide_keyword_content_about",
|
|
||||||
tag = "app.revanced.extension.shared.settings.preference.BulletPointPreference"
|
|
||||||
),
|
|
||||||
NonInteractivePreference(
|
|
||||||
key = "revanced_hide_keyword_content_about_whole_words",
|
|
||||||
tag = "app.revanced.extension.youtube.settings.preference.HtmlPreference",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
PreferenceScreenPreference(
|
),
|
||||||
key = "revanced_hide_filter_bar_screen",
|
SwitchPreference("revanced_hide_album_cards"),
|
||||||
preferences = setOf(
|
SwitchPreference("revanced_hide_artist_cards"),
|
||||||
SwitchPreference("revanced_hide_filter_bar_feed_in_feed"),
|
SwitchPreference("revanced_hide_chips_shelf"),
|
||||||
SwitchPreference("revanced_hide_filter_bar_feed_in_related_videos"),
|
SwitchPreference("revanced_hide_community_posts"),
|
||||||
SwitchPreference("revanced_hide_filter_bar_feed_in_search"),
|
SwitchPreference("revanced_hide_compact_banner"),
|
||||||
SwitchPreference("revanced_hide_filter_bar_feed_in_history"),
|
SwitchPreference("revanced_hide_expandable_card"),
|
||||||
),
|
SwitchPreference("revanced_hide_floating_microphone_button"),
|
||||||
),
|
SwitchPreference(
|
||||||
PreferenceScreenPreference(
|
key = "revanced_hide_horizontal_shelves",
|
||||||
key = "revanced_channel_screen",
|
tag = "app.revanced.extension.shared.settings.preference.BulletPointSwitchPreference"
|
||||||
preferences = setOf(
|
),
|
||||||
SwitchPreference("revanced_hide_community_button"),
|
SwitchPreference("revanced_hide_image_shelf"),
|
||||||
SwitchPreference("revanced_hide_for_you_shelf"),
|
SwitchPreference("revanced_hide_latest_posts"),
|
||||||
SwitchPreference("revanced_hide_join_button"),
|
SwitchPreference("revanced_hide_mix_playlists"),
|
||||||
SwitchPreference("revanced_hide_links_preview"),
|
SwitchPreference("revanced_hide_movies_section"),
|
||||||
SwitchPreference("revanced_hide_members_shelf"),
|
SwitchPreference("revanced_hide_notify_me_button"),
|
||||||
SwitchPreference("revanced_hide_store_button"),
|
SwitchPreference("revanced_hide_playables"),
|
||||||
SwitchPreference("revanced_hide_subscribe_button_in_channel_page"),
|
SwitchPreference("revanced_hide_show_more_button"),
|
||||||
),
|
SwitchPreference("revanced_hide_surveys"),
|
||||||
),
|
SwitchPreference("revanced_hide_ticket_shelf"),
|
||||||
SwitchPreference("revanced_hide_album_cards"),
|
SwitchPreference("revanced_hide_upload_time"),
|
||||||
SwitchPreference("revanced_hide_artist_cards"),
|
SwitchPreference("revanced_hide_video_recommendation_labels"),
|
||||||
SwitchPreference("revanced_hide_chips_shelf"),
|
SwitchPreference("revanced_hide_view_count"),
|
||||||
SwitchPreference("revanced_hide_community_posts"),
|
SwitchPreference("revanced_hide_visual_spacer"),
|
||||||
SwitchPreference("revanced_hide_compact_banner"),
|
SwitchPreference("revanced_hide_doodles"),
|
||||||
SwitchPreference("revanced_hide_expandable_card"),
|
)
|
||||||
SwitchPreference("revanced_hide_floating_microphone_button"),
|
|
||||||
SwitchPreference(
|
|
||||||
key = "revanced_hide_horizontal_shelves",
|
|
||||||
tag = "app.revanced.extension.shared.settings.preference.BulletPointSwitchPreference"
|
|
||||||
),
|
|
||||||
SwitchPreference("revanced_hide_image_shelf"),
|
|
||||||
SwitchPreference("revanced_hide_latest_posts"),
|
|
||||||
SwitchPreference("revanced_hide_mix_playlists"),
|
|
||||||
SwitchPreference("revanced_hide_movies_section"),
|
|
||||||
SwitchPreference("revanced_hide_notify_me_button"),
|
|
||||||
SwitchPreference("revanced_hide_playables"),
|
|
||||||
SwitchPreference("revanced_hide_show_more_button"),
|
|
||||||
SwitchPreference("revanced_hide_surveys"),
|
|
||||||
SwitchPreference("revanced_hide_ticket_shelf"),
|
|
||||||
SwitchPreference("revanced_hide_upload_time"),
|
|
||||||
SwitchPreference("revanced_hide_video_recommendation_labels"),
|
|
||||||
SwitchPreference("revanced_hide_view_count"),
|
|
||||||
SwitchPreference("revanced_hide_visual_spacer"),
|
|
||||||
SwitchPreference("revanced_hide_doodles"),
|
|
||||||
)
|
|
||||||
|
|
||||||
PreferenceScreen.GENERAL_LAYOUT.addPreferences(
|
// region Mix playlists
|
||||||
PreferenceScreenPreference(
|
|
||||||
key = "revanced_custom_filter_screen",
|
|
||||||
sorting = PreferenceScreenPreference.Sorting.UNSORTED,
|
|
||||||
preferences = setOf(
|
|
||||||
SwitchPreference("revanced_custom_filter"),
|
|
||||||
TextPreference("revanced_custom_filter_strings", inputType = InputType.TEXT_MULTI_LINE),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
addLithoFilter(LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR)
|
(if (is_20_09_or_greater) parseElementFromBufferFingerprint
|
||||||
addLithoFilter(DESCRIPTION_COMPONENTS_FILTER_CLASS_NAME)
|
else if (is_20_07_or_greater) parseElementFromBufferLegacy2007Fingerprint
|
||||||
addLithoFilter(COMMENTS_FILTER_CLASS_NAME)
|
else parseElementFromBufferLegacy1901Fingerprint).let {
|
||||||
addLithoFilter(KEYWORD_FILTER_CLASS_NAME)
|
it.method.apply {
|
||||||
addLithoFilter(CUSTOM_FILTER_CLASS_NAME)
|
val byteArrayParameter = "p3"
|
||||||
|
val startIndex = it.patternMatch!!.startIndex
|
||||||
|
val conversionContextRegister = getInstruction<TwoRegisterInstruction>(startIndex).registerA
|
||||||
|
val returnEmptyComponentInstruction = instructions.last { it.opcode == Opcode.INVOKE_STATIC }
|
||||||
|
val returnEmptyComponentRegister =
|
||||||
|
(returnEmptyComponentInstruction as FiveRegisterInstruction).registerC
|
||||||
|
val insertIndex = startIndex + 1
|
||||||
|
val freeRegister =
|
||||||
|
findFreeRegister(insertIndex, conversionContextRegister, returnEmptyComponentRegister)
|
||||||
|
|
||||||
// region Mix playlists
|
addInstructionsWithLabels(
|
||||||
|
insertIndex,
|
||||||
(if (is_20_09_or_greater) parseElementFromBufferFingerprint
|
"""
|
||||||
else if (is_20_07_or_greater) parseElementFromBufferLegacy2007Fingerprint
|
|
||||||
else parseElementFromBufferLegacy1901Fingerprint).let {
|
|
||||||
it.method.apply {
|
|
||||||
val byteArrayParameter = "p3"
|
|
||||||
val startIndex = it.patternMatch!!.startIndex
|
|
||||||
val conversionContextRegister = getInstruction<TwoRegisterInstruction>(startIndex).registerA
|
|
||||||
val returnEmptyComponentInstruction = instructions.last { it.opcode == Opcode.INVOKE_STATIC }
|
|
||||||
val returnEmptyComponentRegister = (returnEmptyComponentInstruction as FiveRegisterInstruction).registerC
|
|
||||||
val insertIndex = startIndex + 1
|
|
||||||
val freeRegister = findFreeRegister(insertIndex, conversionContextRegister, returnEmptyComponentRegister)
|
|
||||||
|
|
||||||
addInstructionsWithLabels(
|
|
||||||
insertIndex,
|
|
||||||
"""
|
|
||||||
invoke-static { v$conversionContextRegister, $byteArrayParameter }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->filterMixPlaylists(Ljava/lang/Object;[B)Z
|
invoke-static { v$conversionContextRegister, $byteArrayParameter }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->filterMixPlaylists(Ljava/lang/Object;[B)Z
|
||||||
move-result v$freeRegister
|
move-result v$freeRegister
|
||||||
if-eqz v$freeRegister, :show
|
if-eqz v$freeRegister, :show
|
||||||
@@ -294,193 +273,192 @@ val hideLayoutComponentsPatch = bytecodePatch(
|
|||||||
:show
|
:show
|
||||||
nop
|
nop
|
||||||
""",
|
""",
|
||||||
ExternalLabel("return_empty_component", returnEmptyComponentInstruction),
|
ExternalLabel("return_empty_component", returnEmptyComponentInstruction),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region Watermark (legacy code for old versions of YouTube)
|
// region Watermark (legacy code for old versions of YouTube)
|
||||||
|
|
||||||
showWatermarkFingerprint.match(
|
showWatermarkFingerprint.match(
|
||||||
playerOverlayFingerprint.originalClassDef,
|
playerOverlayFingerprint.originalClassDef,
|
||||||
).method.apply {
|
).method.apply {
|
||||||
val index = implementation!!.instructions.size - 5
|
val index = implementation!!.instructions.size - 5
|
||||||
|
|
||||||
removeInstruction(index)
|
removeInstruction(index)
|
||||||
addInstructions(
|
addInstructions(
|
||||||
index,
|
index,
|
||||||
"""
|
"""
|
||||||
invoke-static {}, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->showWatermark()Z
|
invoke-static {}, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->showWatermark()Z
|
||||||
move-result p2
|
move-result p2
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region Show more button
|
// region Show more button
|
||||||
|
|
||||||
hideShowMoreButtonFingerprint.method.apply {
|
hideShowMoreButtonFingerprint.method.apply {
|
||||||
val moveRegisterIndex = hideShowMoreButtonFingerprint.patternMatch!!.endIndex
|
val moveRegisterIndex = hideShowMoreButtonFingerprint.patternMatch!!.endIndex
|
||||||
val viewRegister = getInstruction<OneRegisterInstruction>(moveRegisterIndex).registerA
|
val viewRegister = getInstruction<OneRegisterInstruction>(moveRegisterIndex).registerA
|
||||||
|
|
||||||
|
val insertIndex = moveRegisterIndex + 1
|
||||||
|
addInstruction(
|
||||||
|
insertIndex,
|
||||||
|
"invoke-static { v$viewRegister }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR" +
|
||||||
|
"->hideShowMoreButton(Landroid/view/View;)V",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region crowdfunding box
|
||||||
|
crowdfundingBoxFingerprint.let {
|
||||||
|
it.method.apply {
|
||||||
|
val insertIndex = it.patternMatch!!.endIndex
|
||||||
|
val objectRegister = getInstruction<TwoRegisterInstruction>(insertIndex).registerA
|
||||||
|
|
||||||
val insertIndex = moveRegisterIndex + 1
|
|
||||||
addInstruction(
|
addInstruction(
|
||||||
insertIndex,
|
insertIndex,
|
||||||
"invoke-static { v$viewRegister }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR" +
|
"invoke-static {v$objectRegister}, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR" +
|
||||||
"->hideShowMoreButton(Landroid/view/View;)V",
|
"->hideCrowdfundingBox(Landroid/view/View;)V",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region crowdfunding box
|
// region hide album cards
|
||||||
crowdfundingBoxFingerprint.let {
|
|
||||||
it.method.apply {
|
|
||||||
val insertIndex = it.patternMatch!!.endIndex
|
|
||||||
val objectRegister = getInstruction<TwoRegisterInstruction>(insertIndex).registerA
|
|
||||||
|
|
||||||
addInstruction(
|
albumCardsFingerprint.let {
|
||||||
insertIndex,
|
it.method.apply {
|
||||||
"invoke-static {v$objectRegister}, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR" +
|
val checkCastAnchorIndex = it.patternMatch!!.endIndex
|
||||||
"->hideCrowdfundingBox(Landroid/view/View;)V",
|
val insertIndex = checkCastAnchorIndex + 1
|
||||||
)
|
val register = getInstruction<OneRegisterInstruction>(checkCastAnchorIndex).registerA
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
addInstruction(
|
||||||
|
insertIndex,
|
||||||
// region hide album cards
|
"invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR" +
|
||||||
|
|
||||||
albumCardsFingerprint.let {
|
|
||||||
it.method.apply {
|
|
||||||
val checkCastAnchorIndex = it.patternMatch!!.endIndex
|
|
||||||
val insertIndex = checkCastAnchorIndex + 1
|
|
||||||
val register = getInstruction<OneRegisterInstruction>(checkCastAnchorIndex).registerA
|
|
||||||
|
|
||||||
addInstruction(
|
|
||||||
insertIndex,
|
|
||||||
"invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR" +
|
|
||||||
"->hideAlbumCard(Landroid/view/View;)V",
|
"->hideAlbumCard(Landroid/view/View;)V",
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region hide floating microphone
|
// region hide floating microphone
|
||||||
|
|
||||||
showFloatingMicrophoneButtonFingerprint.method.apply {
|
showFloatingMicrophoneButtonFingerprint.method.apply {
|
||||||
val literalIndex = indexOfFirstLiteralInstructionOrThrow(fabButtonId)
|
val literalIndex = indexOfFirstLiteralInstructionOrThrow(fabButtonId)
|
||||||
val booleanIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.IGET_BOOLEAN)
|
val booleanIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.IGET_BOOLEAN)
|
||||||
val register = getInstruction<TwoRegisterInstruction>(booleanIndex).registerA
|
val register = getInstruction<TwoRegisterInstruction>(booleanIndex).registerA
|
||||||
|
|
||||||
addInstructions(
|
addInstructions(
|
||||||
booleanIndex + 1,
|
booleanIndex + 1,
|
||||||
"""
|
"""
|
||||||
invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideFloatingMicrophoneButton(Z)Z
|
invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideFloatingMicrophoneButton(Z)Z
|
||||||
move-result v$register
|
move-result v$register
|
||||||
"""
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region 'Yoodles'
|
||||||
|
|
||||||
|
yoodlesImageViewFingerprint.method.apply {
|
||||||
|
findInstructionIndicesReversedOrThrow {
|
||||||
|
getReference<MethodReference>()?.name == "setImageDrawable"
|
||||||
|
}.forEach { insertIndex ->
|
||||||
|
val drawableRegister = getInstruction<FiveRegisterInstruction>(insertIndex).registerD
|
||||||
|
val imageViewRegister = getInstruction<FiveRegisterInstruction>(insertIndex).registerC
|
||||||
|
|
||||||
|
replaceInstruction(
|
||||||
|
insertIndex,
|
||||||
|
"invoke-static { v$imageViewRegister, v$drawableRegister }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->" +
|
||||||
|
"setDoodleDrawable(Landroid/widget/ImageView;Landroid/graphics/drawable/Drawable;)V"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region 'Yoodles'
|
|
||||||
|
|
||||||
yoodlesImageViewFingerprint.method.apply {
|
// region hide view count
|
||||||
findInstructionIndicesReversedOrThrow {
|
|
||||||
getReference<MethodReference>()?.name == "setImageDrawable"
|
|
||||||
}.forEach { insertIndex ->
|
|
||||||
val drawableRegister = getInstruction<FiveRegisterInstruction>(insertIndex).registerD
|
|
||||||
val imageViewRegister = getInstruction<FiveRegisterInstruction>(insertIndex).registerC
|
|
||||||
|
|
||||||
replaceInstruction(
|
hideViewCountFingerprint.method.apply {
|
||||||
insertIndex,
|
val startIndex = hideViewCountFingerprint.patternMatch!!.startIndex
|
||||||
"invoke-static { v$imageViewRegister, v$drawableRegister }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->" +
|
var returnStringRegister = getInstruction<OneRegisterInstruction>(startIndex).registerA
|
||||||
"setDoodleDrawable(Landroid/widget/ImageView;Landroid/graphics/drawable/Drawable;)V"
|
|
||||||
)
|
// Find the instruction where the text dimension is retrieved.
|
||||||
}
|
val applyDimensionIndex = indexOfFirstInstructionReversedOrThrow {
|
||||||
|
val reference = getReference<MethodReference>()
|
||||||
|
opcode == Opcode.INVOKE_STATIC &&
|
||||||
|
reference?.definingClass == "Landroid/util/TypedValue;" &&
|
||||||
|
reference.returnType == "F" &&
|
||||||
|
reference.name == "applyDimension" &&
|
||||||
|
reference.parameterTypes == listOf("I", "F", "Landroid/util/DisplayMetrics;")
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
// A float value is passed which is used to determine subtitle text size.
|
||||||
|
val floatDimensionRegister = getInstruction<OneRegisterInstruction>(
|
||||||
|
applyDimensionIndex + 1
|
||||||
|
).registerA
|
||||||
|
|
||||||
|
addInstructions(
|
||||||
// region hide view count
|
applyDimensionIndex - 1,
|
||||||
|
"""
|
||||||
hideViewCountFingerprint.method.apply {
|
|
||||||
val startIndex = hideViewCountFingerprint.patternMatch!!.startIndex
|
|
||||||
var returnStringRegister = getInstruction<OneRegisterInstruction>(startIndex).registerA
|
|
||||||
|
|
||||||
// Find the instruction where the text dimension is retrieved.
|
|
||||||
val applyDimensionIndex = indexOfFirstInstructionReversedOrThrow {
|
|
||||||
val reference = getReference<MethodReference>()
|
|
||||||
opcode == Opcode.INVOKE_STATIC &&
|
|
||||||
reference?.definingClass == "Landroid/util/TypedValue;" &&
|
|
||||||
reference.returnType == "F" &&
|
|
||||||
reference.name == "applyDimension" &&
|
|
||||||
reference.parameterTypes == listOf("I", "F", "Landroid/util/DisplayMetrics;")
|
|
||||||
}
|
|
||||||
|
|
||||||
// A float value is passed which is used to determine subtitle text size.
|
|
||||||
val floatDimensionRegister = getInstruction<OneRegisterInstruction>(
|
|
||||||
applyDimensionIndex + 1
|
|
||||||
).registerA
|
|
||||||
|
|
||||||
addInstructions(
|
|
||||||
applyDimensionIndex - 1,
|
|
||||||
"""
|
|
||||||
invoke-static { v$returnStringRegister, v$floatDimensionRegister }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->modifyFeedSubtitleSpan(Landroid/text/SpannableString;F)Landroid/text/SpannableString;
|
invoke-static { v$returnStringRegister, v$floatDimensionRegister }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->modifyFeedSubtitleSpan(Landroid/text/SpannableString;F)Landroid/text/SpannableString;
|
||||||
move-result-object v$returnStringRegister
|
move-result-object v$returnStringRegister
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region hide filter bar
|
// region hide filter bar
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Patch a [Method] with a given [instructions].
|
* Patch a [Method] with a given [instructions].
|
||||||
*
|
*
|
||||||
* @param RegisterInstruction The type of instruction to get the register from.
|
* @param RegisterInstruction The type of instruction to get the register from.
|
||||||
* @param insertIndexOffset The offset to add to the end index of the [Match.patternMatch].
|
* @param insertIndexOffset The offset to add to the end index of the [Match.patternMatch].
|
||||||
* @param hookRegisterOffset The offset to add to the register of the hook.
|
* @param hookRegisterOffset The offset to add to the register of the hook.
|
||||||
* @param instructions The instructions to add with the register as a parameter.
|
* @param instructions The instructions to add with the register as a parameter.
|
||||||
*/
|
*/
|
||||||
fun <RegisterInstruction : OneRegisterInstruction> Fingerprint.patch(
|
fun <RegisterInstruction : OneRegisterInstruction> Fingerprint.patch(
|
||||||
insertIndexOffset: Int = 0,
|
insertIndexOffset: Int = 0,
|
||||||
hookRegisterOffset: Int = 0,
|
hookRegisterOffset: Int = 0,
|
||||||
instructions: (Int) -> String,
|
instructions: (Int) -> String,
|
||||||
) = method.apply {
|
) = method.apply {
|
||||||
val endIndex = patternMatch!!.endIndex
|
val endIndex = patternMatch!!.endIndex
|
||||||
|
|
||||||
val insertIndex = endIndex + insertIndexOffset
|
val insertIndex = endIndex + insertIndexOffset
|
||||||
val register =
|
val register =
|
||||||
getInstruction<RegisterInstruction>(endIndex + hookRegisterOffset).registerA
|
getInstruction<RegisterInstruction>(endIndex + hookRegisterOffset).registerA
|
||||||
|
|
||||||
addInstructions(insertIndex, instructions(register))
|
addInstructions(insertIndex, instructions(register))
|
||||||
}
|
}
|
||||||
|
|
||||||
filterBarHeightFingerprint.patch<TwoRegisterInstruction> { register ->
|
filterBarHeightFingerprint.patch<TwoRegisterInstruction> { register ->
|
||||||
"""
|
"""
|
||||||
invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideInFeed(I)I
|
invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideInFeed(I)I
|
||||||
move-result v$register
|
move-result v$register
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
||||||
searchResultsChipBarFingerprint.patch<OneRegisterInstruction>(-1, -2) { register ->
|
searchResultsChipBarFingerprint.patch<OneRegisterInstruction>(-1, -2) { register ->
|
||||||
"""
|
"""
|
||||||
invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideInSearch(I)I
|
invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideInSearch(I)I
|
||||||
move-result v$register
|
move-result v$register
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
||||||
relatedChipCloudFingerprint.patch<OneRegisterInstruction>(1) { register ->
|
relatedChipCloudFingerprint.patch<OneRegisterInstruction>(1) { register ->
|
||||||
"invoke-static { v$register }, " +
|
"invoke-static { v$register }, " +
|
||||||
"$LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideInRelatedVideos(Landroid/view/View;)V"
|
"$LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideInRelatedVideos(Landroid/view/View;)V"
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@ import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
|
|||||||
import app.revanced.patches.shared.misc.mapping.resourceMappings
|
import app.revanced.patches.shared.misc.mapping.resourceMappings
|
||||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||||
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
||||||
import app.revanced.patches.youtube.misc.litho.filter.addLithoFilter
|
import app.revanced.patches.shared.misc.litho.filter.addLithoFilter
|
||||||
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
|
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
|
||||||
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ import app.revanced.patches.all.misc.resources.addResources
|
|||||||
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
|
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
|
||||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||||
import app.revanced.patches.youtube.misc.litho.filter.addLithoFilter
|
import app.revanced.patches.shared.misc.litho.filter.addLithoFilter
|
||||||
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
|
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
|
||||||
import app.revanced.patches.youtube.misc.playertype.playerTypeHookPatch
|
import app.revanced.patches.youtube.misc.playertype.playerTypeHookPatch
|
||||||
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@ import app.revanced.patches.shared.misc.mapping.resourceMappings
|
|||||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
|
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
|
||||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||||
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
||||||
import app.revanced.patches.youtube.misc.litho.filter.addLithoFilter
|
import app.revanced.patches.shared.misc.litho.filter.addLithoFilter
|
||||||
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
|
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
|
||||||
import app.revanced.patches.youtube.misc.navigation.navigationBarHookPatch
|
import app.revanced.patches.youtube.misc.navigation.navigationBarHookPatch
|
||||||
import app.revanced.patches.youtube.misc.playservice.is_19_41_or_greater
|
import app.revanced.patches.youtube.misc.playservice.is_19_41_or_greater
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@ import app.revanced.patches.shared.misc.settings.preference.PreferenceCategory
|
|||||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
|
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
|
||||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||||
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
||||||
import app.revanced.patches.youtube.misc.litho.filter.addLithoFilter
|
import app.revanced.patches.shared.misc.litho.filter.addLithoFilter
|
||||||
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
|
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
|
||||||
import app.revanced.patches.youtube.misc.playertype.playerTypeHookPatch
|
import app.revanced.patches.youtube.misc.playertype.playerTypeHookPatch
|
||||||
import app.revanced.patches.youtube.misc.playservice.is_19_33_or_greater
|
import app.revanced.patches.youtube.misc.playservice.is_19_33_or_greater
|
||||||
|
|||||||
+10
-21
@@ -1,33 +1,22 @@
|
|||||||
package app.revanced.patches.youtube.misc.debugging
|
package app.revanced.patches.youtube.misc.debugging
|
||||||
|
|
||||||
import app.revanced.patches.all.misc.resources.addResources
|
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch;
|
||||||
import app.revanced.patches.shared.misc.debugging.enableDebuggingPatch
|
import app.revanced.patches.shared.misc.debugging.enableDebuggingPatch
|
||||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
|
||||||
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
|
||||||
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
val enableDebuggingPatch = enableDebuggingPatch(
|
val enableDebuggingPatch = enableDebuggingPatch(
|
||||||
block = {
|
sharedExtensionPatch = sharedExtensionPatch,
|
||||||
dependsOn(
|
settingsPatch = settingsPatch,
|
||||||
sharedExtensionPatch,
|
compatibleWithPackages = arrayOf(
|
||||||
settingsPatch,
|
"com.google.android.youtube" to setOf(
|
||||||
|
"19.34.42",
|
||||||
|
"20.07.39",
|
||||||
|
"20.13.41",
|
||||||
|
"20.14.43",
|
||||||
)
|
)
|
||||||
|
),
|
||||||
compatibleWith(
|
|
||||||
"com.google.android.youtube"(
|
|
||||||
"19.34.42",
|
|
||||||
"20.07.39",
|
|
||||||
"20.13.41",
|
|
||||||
"20.14.43",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
executeBlock = {
|
|
||||||
addResources("youtube", "misc.debugging.enableDebuggingPatch")
|
|
||||||
},
|
|
||||||
hookStringFeatureFlag = true,
|
hookStringFeatureFlag = true,
|
||||||
preferenceScreen = PreferenceScreen.MISC,
|
preferenceScreen = PreferenceScreen.MISC,
|
||||||
additionalDebugPreferences = listOf(SwitchPreference("revanced_debug_protobuffer"))
|
|
||||||
)
|
)
|
||||||
|
|||||||
+17
-9
@@ -6,6 +6,8 @@ import app.revanced.patches.all.misc.resources.addResourcesPatch
|
|||||||
import app.revanced.patches.shared.castContextFetchFingerprint
|
import app.revanced.patches.shared.castContextFetchFingerprint
|
||||||
import app.revanced.patches.shared.misc.gms.gmsCoreSupportPatch
|
import app.revanced.patches.shared.misc.gms.gmsCoreSupportPatch
|
||||||
import app.revanced.patches.shared.misc.settings.preference.IntentPreference
|
import app.revanced.patches.shared.misc.settings.preference.IntentPreference
|
||||||
|
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
|
||||||
|
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||||
import app.revanced.patches.shared.primeMethodFingerprint
|
import app.revanced.patches.shared.primeMethodFingerprint
|
||||||
import app.revanced.patches.youtube.layout.buttons.overlay.hidePlayerOverlayButtonsPatch
|
import app.revanced.patches.youtube.layout.buttons.overlay.hidePlayerOverlayButtonsPatch
|
||||||
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
||||||
@@ -39,7 +41,7 @@ val gmsCoreSupportPatch = gmsCoreSupportPatch(
|
|||||||
"20.07.39",
|
"20.07.39",
|
||||||
"20.13.41",
|
"20.13.41",
|
||||||
"20.14.43",
|
"20.14.43",
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,18 +58,24 @@ private fun gmsCoreSupportResourcePatch(
|
|||||||
val gmsCoreVendorGroupId by gmsCoreVendorGroupIdOption
|
val gmsCoreVendorGroupId by gmsCoreVendorGroupIdOption
|
||||||
|
|
||||||
PreferenceScreen.MISC.addPreferences(
|
PreferenceScreen.MISC.addPreferences(
|
||||||
IntentPreference(
|
PreferenceScreenPreference(
|
||||||
"microg_settings",
|
"revanced_gms_core_screen",
|
||||||
intent = IntentPreference.Intent("", "org.microg.gms.ui.SettingsActivity") {
|
preferences = setOf(
|
||||||
"$gmsCoreVendorGroupId.android.gms"
|
SwitchPreference("revanced_gms_core_check_updates"),
|
||||||
}
|
IntentPreference(
|
||||||
)
|
"revanced_gms_core_settings",
|
||||||
|
intent = IntentPreference.Intent("", "org.microg.gms.ui.SettingsActivity") {
|
||||||
|
"$gmsCoreVendorGroupId.android.gms"
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
) {
|
) {
|
||||||
dependsOn(
|
dependsOn(
|
||||||
addResourcesPatch,
|
addResourcesPatch,
|
||||||
settingsPatch,
|
settingsPatch,
|
||||||
accountCredentialsInvalidTextPatch
|
accountCredentialsInvalidTextPatch,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user