Compare commits

..

41 Commits

Author SHA1 Message Date
semantic-release-bot
4937fa5fbd chore(release): 4.3.0-dev.8 [skip ci]
# [4.3.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v4.3.0-dev.7...v4.3.0-dev.8) (2024-02-28)

### Bug Fixes

* Remove extra space from patch description ([#2780](https://github.com/ReVanced/revanced-patches/issues/2780)) ([a6f5dd9](a6f5dd933f))
2024-02-28 20:35:11 +00:00
KobeW50
a6f5dd933f fix: Remove extra space from patch description (#2780) 2024-02-28 21:32:55 +01:00
semantic-release-bot
97c4682ccc chore(release): 4.3.0-dev.7 [skip ci]
# [4.3.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v4.3.0-dev.6...v4.3.0-dev.7) (2024-02-26)

### Features

* **OpeningHours:** Add `Fix crash` patch ([#2697](https://github.com/ReVanced/revanced-patches/issues/2697)) ([6742cd9](6742cd9232))
2024-02-26 03:52:58 +00:00
Linus
6742cd9232 feat(OpeningHours): Add Fix crash patch (#2697)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2024-02-26 04:50:59 +01:00
oSumAtrIX
5b2cc10adb docs: Fix broken links 2024-02-26 04:37:44 +01:00
semantic-release-bot
dbad6252fb chore(release): 4.3.0-dev.6 [skip ci]
# [4.3.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v4.3.0-dev.5...v4.3.0-dev.6) (2024-02-25)

### Features

* **VSCO - Unlock pro:** Constrain to last working version ([0f7ed84](0f7ed841d1))
2024-02-25 18:30:14 +00:00
oSumAtrIX
0f7ed841d1 feat(VSCO - Unlock pro): Constrain to last working version 2024-02-25 19:28:12 +01:00
semantic-release-bot
d33d7d8f35 chore(release): 4.3.0-dev.5 [skip ci]
# [4.3.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v4.3.0-dev.4...v4.3.0-dev.5) (2024-02-25)

### Features

* Remove unnecessary description from patch ([348e42a](348e42a374))
* **Twitter - Unlock downloads:** Unlock GIF downloads ([3200da8](3200da8657))
2024-02-25 07:02:56 +00:00
oSumAtrIX
3200da8657 feat(Twitter - Unlock downloads): Unlock GIF downloads 2024-02-25 08:00:29 +01:00
oSumAtrIX
348e42a374 feat: Remove unnecessary description from patch 2024-02-25 06:51:51 +01:00
oSumAtrIX
cbbac445b6 chore: Rename issue templates 2024-02-25 03:37:53 +01:00
oSumAtrIX
1593d1352a ci: Fix indentation in workflow 2024-02-24 01:14:45 +01:00
oSumAtrIX
c6fedaa7bc docs: Break long lines 2024-02-23 04:12:28 +01:00
oSumAtrIX
d070aebec4 docs: Fix mistakes and wording 2024-02-23 04:12:28 +01:00
oSumAtrIX
d583256f06 docs: Remove documentation section
The documentation for patches is being moved to the ReVanced Patcher repository. The reason for this is because the documentation about how to create patches using ReVanced Patcher, belongs in the ReVanced Patcher repository, whereas documentation specific to the public API of ReVanced Patches belongs in this repository. Because the public API has no documentation yet, the section is removed until then.
2024-02-23 02:55:45 +01:00
oSumAtrIX
fc67d284f7 ci: Fix PR builds by adding missing GitHub token 2024-02-23 02:23:50 +01:00
semantic-release-bot
d3f3f81b75 chore(release): 4.3.0-dev.4 [skip ci]
# [4.3.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v4.3.0-dev.3...v4.3.0-dev.4) (2024-02-22)

### Bug Fixes

* Compile DEX without debugging information ([afe7f06](afe7f0605e))
* Use deprecated members to ensure backwards compatibility ([a6d8e42](a6d8e4210f))

### Features

* **X:** Add `Open links as query` patch ([#2730](https://github.com/ReVanced/revanced-patches/issues/2730)) ([43359b9](43359b95eb))
2024-02-22 00:13:48 +00:00
oSumAtrIX
8e8600c7c3 chore: Update fingerprint
The compiler of ReVanced Integrations now compiles the target method without `AccessFlags.Public`.
2024-02-22 01:11:17 +01:00
oSumAtrIX
ea33863083 ci: Do not start a Gradle daemon for PR builds
Because there are no subsequent builds, a daemon is not necessary
2024-02-22 01:10:09 +01:00
oSumAtrIX
a6d8e4210f fix: Use deprecated members to ensure backwards compatibility
By migrating to early to new APIs of ReVanced Patcher, if you were to use old versions of ReVanced Patcher, you would get compatibility issues. By using deprecated members until most have updated ReVanced Patcher, we can ensure seamless migration.
2024-02-22 01:10:09 +01:00
oSumAtrIX
1e3356808d ci: Split release into a separate PR build workflow
Because the release workflow already runs on dev and main, it is not necessary to also trigger it for PRs.
2024-02-22 01:07:05 +01:00
dic1911
43359b95eb feat(X): Add Open links as query patch (#2730)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2024-02-22 01:07:05 +01:00
oSumAtrIX
afe7f0605e fix: Compile DEX without debugging information 2024-02-21 04:15:38 +01:00
oSumAtrIX
8916789f7a build: Publish to GitHub Packages 2024-02-21 04:15:38 +01:00
oSumAtrIX
0d14aa1191 build: Sign release artifacts 2024-02-21 04:15:38 +01:00
oSumAtrIX
376dda6e81 build: Bump dependencies
This commit also migrates away from deprecated to new APIs
2024-02-21 04:07:32 +01:00
semantic-release-bot
ff8b58b645 chore(release): 4.3.0-dev.3 [skip ci]
# [4.3.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v4.3.0-dev.2...v4.3.0-dev.3) (2024-02-20)

### Features

* **YouTube - Change header:** Improve patch option description ([e775bc2](e775bc2cae))
* **YouTube - Custom branding:** Improve patch option description ([f4b888b](f4b888be56))
2024-02-20 21:43:11 +00:00
oSumAtrIX
f4b888be56 feat(YouTube - Custom branding): Improve patch option description 2024-02-20 22:41:06 +01:00
oSumAtrIX
e775bc2cae feat(YouTube - Change header): Improve patch option description 2024-02-20 22:41:06 +01:00
oSumAtrIX
5c566753a8 build: Bump dependencies 2024-02-14 02:44:25 +01:00
oSumAtrIX
968ebf9f50 build: Bump Gradle 2024-02-14 02:41:21 +01:00
oSumAtrIX
75c510d876 chore: Fix ReplaceWith of Deprecated annotation 2024-02-13 03:27:03 +01:00
oSumAtrIX
72528cb2f1 chore: Remove dummy subproject
It is not necessary anymore
2024-02-13 02:58:24 +01:00
oSumAtrIX
81d79111cf chore: Add .editorconfig 2024-02-13 02:55:07 +01:00
oSumAtrIX
33036092b4 build: Bump dependencies 2024-02-13 02:54:40 +01:00
semantic-release-bot
f5f88092b6 chore(release): 4.3.0-dev.2 [skip ci]
# [4.3.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v4.3.0-dev.1...v4.3.0-dev.2) (2024-02-09)

### Features

* **Sync for Reddit:** Add `Fix /s/ links` patch ([0434d88](0434d8812b))
2024-02-09 02:47:31 +00:00
oSumAtrIX
0434d8812b feat(Sync for Reddit): Add Fix /s/ links patch 2024-02-09 03:45:37 +01:00
semantic-release-bot
ec38b8e51c chore(release): 4.3.0-dev.1 [skip ci]
# [4.3.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v4.2.0...v4.3.0-dev.1) (2024-02-09)

### Features

* **YouTube - Change start page:** Add more start pages ([96f9b73](96f9b73c74))
2024-02-09 00:17:34 +00:00
oSumAtrIX
96f9b73c74 feat(YouTube - Change start page): Add more start pages 2024-02-09 00:50:16 +01:00
semantic-release-bot
1502fe1f7f chore(release): 4.2.0 [skip ci]
# [4.2.0](https://github.com/ReVanced/revanced-patches/compare/v4.1.0...v4.2.0) (2024-02-08)

### Bug Fixes

* **Infinity for Reddit - Unlock subscription:** Do not crash by patching billing client ([53b29ea](53b29ea270))

### Features

* **X:** Add `Hide view count` patch ([1bf9582](1bf9582437))
* **X:** Add `Unlock downloads` patch ([3343b5c](3343b5cb12))
2024-02-08 19:17:40 +00:00
oSumAtrIX
824d094394 chore: Merge branch dev to main (#2693) 2024-02-08 20:15:32 +01:00
77 changed files with 1351 additions and 689 deletions

3
.editorconfig Normal file
View File

@@ -0,0 +1,3 @@
[*.{kt,kts}]
ktlint_code_style = intellij_idea
ktlint_standard_no-wildcard-imports = disabled

View File

@@ -0,0 +1,25 @@
name: Build pull request
on:
workflow_dispatch:
pull_request:
branches:
- dev
jobs:
release:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Cache Gradle
uses: burrunan/gradle-cache-action@v1
- name: Build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew build --no-daemon

View File

@@ -6,10 +6,6 @@ on:
branches: branches:
- main - main
- dev - dev
pull_request:
branches:
- main
- dev
jobs: jobs:
release: release:
@@ -41,6 +37,13 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: npm install run: npm install
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@v6
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.GPG_PASSPHRASE }}
fingerprint: ${{ env.GPG_FINGERPRINT }}
- name: Release - name: Release
env: env:
GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }} GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }}

View File

@@ -33,7 +33,7 @@
{ {
"assets": [ "assets": [
{ {
"path": "build/libs/*.jar" "path": "build/libs/revanced-patches*"
}, },
{ {
"path": "patches.json" "path": "patches.json"

View File

@@ -1,3 +1,80 @@
# [4.3.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v4.3.0-dev.7...v4.3.0-dev.8) (2024-02-28)
### Bug Fixes
* Remove extra space from patch description ([#2780](https://github.com/ReVanced/revanced-patches/issues/2780)) ([96a3f35](https://github.com/ReVanced/revanced-patches/commit/96a3f359266ff8d16ae9ee3c6ce2f16ce67a3b93))
# [4.3.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v4.3.0-dev.6...v4.3.0-dev.7) (2024-02-26)
### Features
* **OpeningHours:** Add `Fix crash` patch ([#2697](https://github.com/ReVanced/revanced-patches/issues/2697)) ([0d011b8](https://github.com/ReVanced/revanced-patches/commit/0d011b876ecf05031a7daa54ab7e6d3506728a47))
# [4.3.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v4.3.0-dev.5...v4.3.0-dev.6) (2024-02-25)
### Features
* **VSCO - Unlock pro:** Constrain to last working version ([6dd4a7c](https://github.com/ReVanced/revanced-patches/commit/6dd4a7c29e48c3bc517bbdd7ed160624c36c2333))
# [4.3.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v4.3.0-dev.4...v4.3.0-dev.5) (2024-02-25)
### Features
* Remove unnecessary description from patch ([1a89dd9](https://github.com/ReVanced/revanced-patches/commit/1a89dd9f8cd0c614055a9da97338839b77a25ed1))
* **Twitter - Unlock downloads:** Unlock GIF downloads ([d0f91c8](https://github.com/ReVanced/revanced-patches/commit/d0f91c8550592723e1252e1af2971b508591dd59))
# [4.3.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v4.3.0-dev.3...v4.3.0-dev.4) (2024-02-22)
### Bug Fixes
* Compile DEX without debugging information ([f5df957](https://github.com/ReVanced/revanced-patches/commit/f5df9578669f71a67411bc93a25a7e8da43610d0))
* Use deprecated members to ensure backwards compatibility ([083bd40](https://github.com/ReVanced/revanced-patches/commit/083bd4009231b9612394b4496ca1d329947d6577))
### Features
* **X:** Add `Open links as query` patch ([#2730](https://github.com/ReVanced/revanced-patches/issues/2730)) ([ba75a51](https://github.com/ReVanced/revanced-patches/commit/ba75a51b71dbb9157db230b3e97a90361019fe30))
# [4.3.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v4.3.0-dev.2...v4.3.0-dev.3) (2024-02-20)
### Features
* **YouTube - Change header:** Improve patch option description ([3b8bc08](https://github.com/ReVanced/revanced-patches/commit/3b8bc08d4ed3a3a0f96d2f476e5059840b9f9d9b))
* **YouTube - Custom branding:** Improve patch option description ([e27f56c](https://github.com/ReVanced/revanced-patches/commit/e27f56c8a34d41167b290f47280276c1c6003876))
# [4.3.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v4.3.0-dev.1...v4.3.0-dev.2) (2024-02-09)
### Features
* **Sync for Reddit:** Add `Fix /s/ links` patch ([f15ef3f](https://github.com/ReVanced/revanced-patches/commit/f15ef3f63460254236185f8e22c9395db4db9465))
# [4.3.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v4.2.0...v4.3.0-dev.1) (2024-02-09)
### Features
* **YouTube - Change start page:** Add more start pages ([cc1d9b7](https://github.com/ReVanced/revanced-patches/commit/cc1d9b743633c619fb6acc428e884c1c9b53e10b))
# [4.2.0](https://github.com/ReVanced/revanced-patches/compare/v4.1.0...v4.2.0) (2024-02-08)
### Bug Fixes
* **Infinity for Reddit - Unlock subscription:** Do not crash by patching billing client ([7d76e2e](https://github.com/ReVanced/revanced-patches/commit/7d76e2e43c69b2b75f40a15a9147d041c77cbcd9))
### Features
* **X:** Add `Hide view count` patch ([bf064ec](https://github.com/ReVanced/revanced-patches/commit/bf064ecc1d5de8b592d14d34acfa1a4314c374ba))
* **X:** Add `Unlock downloads` patch ([2c20844](https://github.com/ReVanced/revanced-patches/commit/2c20844eaae698f185a9d321e2c70bde4b485cee))
# [4.2.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v4.1.1-dev.1...v4.2.0-dev.1) (2024-02-08) # [4.2.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v4.1.1-dev.1...v4.2.0-dev.1) (2024-02-08)

View File

@@ -64,15 +64,15 @@ This document describes how to contribute to ReVanced Patches.
## 📖 Resources to help you get started ## 📖 Resources to help you get started
* The [documentation](https://github.com/ReVanced/revanced-patches/tree/docs/docs) provides the fundamentals of patches * The [documentation](https://github.com/ReVanced/revanced-patcher/tree/docs/docs) contains the fundamentals
and everything necessary to create your own patch from scratch of ReVanced Patcher and how to use ReVanced Patcher to create patches
* [Our backlog](https://github.com/orgs/ReVanced/projects/12) is where we keep track of what we're working on * [Our backlog](https://github.com/orgs/ReVanced/projects/12) is where we keep track of what we're working on
* [Issues](https://github.com/ReVanced/revanced-patches/issues) are where we keep track of bugs and feature requests * [Issues](https://github.com/ReVanced/revanced-patches/issues) are where we keep track of bugs and feature requests
## 🙏 Submitting a feature request ## 🙏 Submitting a feature request
Features can be requested by opening an issue using the Features can be requested by opening an issue using the
[Feature request issue template](https://github.com/ReVanced/revanced-patches/issues/new?assignees=&labels=Feature+request&projects=&template=feature-request.yml&title=feat%3A+). [Feature request issue template](https://github.com/ReVanced/revanced-patches/issues/new?assignees=&labels=Feature+request&projects=&template=feature_request.yml&title=feat%3A+).
> **Note** > **Note**
> Requests can be accepted or rejected at the discretion of maintainers of ReVanced Patches. > Requests can be accepted or rejected at the discretion of maintainers of ReVanced Patches.
@@ -81,7 +81,7 @@ Features can be requested by opening an issue using the
## 🐞 Submitting a bug report ## 🐞 Submitting a bug report
If you encounter a bug while using ReVanced Patches, open an issue using the If you encounter a bug while using ReVanced Patches, open an issue using the
[Bug report issue template](https://github.com/ReVanced/revanced-patches/issues/new?assignees=&labels=Bug+report&projects=&template=bug-report.yml&title=bug%3A+). [Bug report issue template](https://github.com/ReVanced/revanced-patches/issues/new?assignees=&labels=Bug+report&projects=&template=bug_report.yml&title=bug%3A+).
## 🧑‍⚖️ Guidelines for requesting or contributing patches ## 🧑‍⚖️ Guidelines for requesting or contributing patches
@@ -110,7 +110,7 @@ are unaffected by this change.
## 📝 How to contribute ## 📝 How to contribute
1. Before contributing, it is recommended to open an issue to discuss your change 1. Before contributing, it is recommended to open an issue to discuss your change
with the maintainers of ReVanced Patches. This will help you determine whether your change is acceptable with the maintainers of ReVanced Patches. This will help you determine whether your change is acceptable
and whether it is worth your time to implement it and whether it is worth your time to implement it
2. Development happens on the `dev` branch. Fork the repository and create your branch from `dev` 2. Development happens on the `dev` branch. Fork the repository and create your branch from `dev`

View File

@@ -67,7 +67,7 @@ This repository contains a collection of ReVanced Patches.
## ❓ About ## ❓ About
Patches are small modifications to Android apps that allow you to change the behaviour of or add new features, Patches are small modifications to Android apps that allow you to change the behavior of or add new features,
block ads, customize the appearance, and much more. block ads, customize the appearance, and much more.
## 💪 Features ## 💪 Features
@@ -77,11 +77,11 @@ Some of the features the patches provide are:
* 🚫 **Block ads**: Say goodbye to ads * 🚫 **Block ads**: Say goodbye to ads
***Customize your app**: Personalize the appearance of apps with various layouts and themes ***Customize your app**: Personalize the appearance of apps with various layouts and themes
* 🪄 **Add new features**: Extend the functionality of apps with lots of new features * 🪄 **Add new features**: Extend the functionality of apps with lots of new features
* ⚙️ **Miscellaneous and general purpose**: Rename packages, enable debugging, disable screen capture restrictions, * ⚙️ **Miscellaneous and general purpose**: Rename packages, enable debugging, disable screen capture restrictions,
export activities, etc. export activities, etc.
***And much more!** ***And much more!**
For a full list of all available patches, visit [revanced.app/patches](https://revanced.app/patches). For a complete list of all available patches, visit [revanced.app/patches](https://revanced.app/patches).
## 🚀 How to get started ## 🚀 How to get started
@@ -93,17 +93,13 @@ You can use [ReVanced CLI](https://github.com/ReVanced/revanced-cli) or [ReVance
Thank you for considering contributing to ReVanced Patches. You can find the contribution guidelines [here](CONTRIBUTING.md). Thank you for considering contributing to ReVanced Patches. You can find the contribution guidelines [here](CONTRIBUTING.md).
### 📃 Documentation
The documentation provides the fundamentals of patches and everything necessary to create your own patch from scratch.
You can find it [here](https://github.com/ReVanced/revanced-patches/tree/docs/docs).
### 🛠️ Building ### 🛠️ Building
In order to build ReVanced Patches, you can follow the [ReVanced documentation](https://github.com/ReVanced/revanced-documentation). To build ReVanced Patches, you can follow the [ReVanced documentation](https://github.com/ReVanced/revanced-documentation).
## 📜 Licence ## 📜 Licence
ReVanced Patches is licensed under the GPLv3 licence. Please see the [licence file](LICENSE) for more information. ReVanced Patches is licensed under the GPLv3 license. Please see the [license file](LICENSE) for more information.
[tl;dr](https://www.tldrlegal.com/license/gnu-general-public-license-v3-gpl-3) you may copy, distribute and modify ReVanced Patches as long as you track changes/dates in source files. [tl;dr](https://www.tldrlegal.com/license/gnu-general-public-license-v3-gpl-3) you may copy, distribute and modify ReVanced Patches as long as you track changes/dates in source files.
Any modifications to ReVanced Patches must also be made available under the GPL along with build & install instructions. Any modifications to ReVanced Patches must also be made available under the GPL,
along with build & install instructions.

View File

@@ -1,3 +1,7 @@
public final class app/revanced/generator/MainKt {
public static synthetic fun main ([Ljava/lang/String;)V
}
public final class app/revanced/patches/all/activity/exportall/ExportAllActivitiesPatch : app/revanced/patcher/patch/ResourcePatch { public final class app/revanced/patches/all/activity/exportall/ExportAllActivitiesPatch : app/revanced/patcher/patch/ResourcePatch {
public static final field INSTANCE Lapp/revanced/patches/all/activity/exportall/ExportAllActivitiesPatch; public static final field INSTANCE Lapp/revanced/patches/all/activity/exportall/ExportAllActivitiesPatch;
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
@@ -406,6 +410,12 @@ public final class app/revanced/patches/nyx/misc/pro/UnlockProPatch : app/revanc
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
} }
public final class app/revanced/patches/openinghours/misc/fix/crash/FixCrashPatch : app/revanced/patcher/patch/BytecodePatch {
public static final field INSTANCE Lapp/revanced/patches/openinghours/misc/fix/crash/FixCrashPatch;
public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
}
public final class app/revanced/patches/photomath/detection/deviceid/SpoofDeviceIdPatch : app/revanced/patcher/patch/BytecodePatch { public final class app/revanced/patches/photomath/detection/deviceid/SpoofDeviceIdPatch : app/revanced/patcher/patch/BytecodePatch {
public static final field INSTANCE Lapp/revanced/patches/photomath/detection/deviceid/SpoofDeviceIdPatch; public static final field INSTANCE Lapp/revanced/patches/photomath/detection/deviceid/SpoofDeviceIdPatch;
public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
@@ -550,6 +560,12 @@ public final class app/revanced/patches/reddit/customclients/syncforreddit/detec
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
} }
public final class app/revanced/patches/reddit/customclients/syncforreddit/fix/slink/FixSLinksPatch : app/revanced/patcher/patch/BytecodePatch {
public static final field INSTANCE Lapp/revanced/patches/reddit/customclients/syncforreddit/fix/slink/FixSLinksPatch;
public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
}
public final class app/revanced/patches/reddit/layout/disablescreenshotpopup/DisableScreenshotPopupPatch : app/revanced/patcher/patch/BytecodePatch { public final class app/revanced/patches/reddit/layout/disablescreenshotpopup/DisableScreenshotPopupPatch : app/revanced/patcher/patch/BytecodePatch {
public static final field INSTANCE Lapp/revanced/patches/reddit/layout/disablescreenshotpopup/DisableScreenshotPopupPatch; public static final field INSTANCE Lapp/revanced/patches/reddit/layout/disablescreenshotpopup/DisableScreenshotPopupPatch;
public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
@@ -1104,6 +1120,12 @@ public final class app/revanced/patches/twitter/misc/hook/patch/recommendation/H
public static final field INSTANCE Lapp/revanced/patches/twitter/misc/hook/patch/recommendation/HideRecommendedUsersPatch; public static final field INSTANCE Lapp/revanced/patches/twitter/misc/hook/patch/recommendation/HideRecommendedUsersPatch;
} }
public final class app/revanced/patches/twitter/misc/links/OpenLinksWithAppChooserPatch : app/revanced/patcher/patch/BytecodePatch {
public static final field INSTANCE Lapp/revanced/patches/twitter/misc/links/OpenLinksWithAppChooserPatch;
public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
}
public final class app/revanced/patches/vsco/misc/pro/UnlockProPatch : app/revanced/patcher/patch/BytecodePatch { public final class app/revanced/patches/vsco/misc/pro/UnlockProPatch : app/revanced/patcher/patch/BytecodePatch {
public static final field INSTANCE Lapp/revanced/patches/vsco/misc/pro/UnlockProPatch; public static final field INSTANCE Lapp/revanced/patches/vsco/misc/pro/UnlockProPatch;
public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V

View File

@@ -1,9 +1,10 @@
import org.gradle.kotlin.dsl.support.listFilesOrdered import org.gradle.kotlin.dsl.support.listFilesOrdered
plugins { plugins {
kotlin("jvm") version "1.9.22" alias(libs.plugins.kotlin)
alias(libs.plugins.binary.compatibility.validator) alias(libs.plugins.binary.compatibility.validator)
`maven-publish` `maven-publish`
signing
} }
group = "app.revanced" group = "app.revanced"
@@ -12,7 +13,14 @@ repositories {
mavenCentral() mavenCentral()
mavenLocal() mavenLocal()
google() google()
maven { url = uri("https://jitpack.io") } maven {
// A repository must be speficied for some reason. "registry" is a dummy.
url = uri("https://maven.pkg.github.com/revanced/registry")
credentials {
username = project.findProperty("gpr.user") as String? ?: System.getenv("GITHUB_ACTOR")
password = project.findProperty("gpr.key") as String? ?: System.getenv("GITHUB_TOKEN")
}
}
} }
dependencies { dependencies {
@@ -22,34 +30,32 @@ dependencies {
implementation(libs.guava) implementation(libs.guava)
// Used in JsonGenerator. // Used in JsonGenerator.
implementation(libs.gson) implementation(libs.gson)
// A dependency to the Android library unfortunately fails the build, which is why this is required.
compileOnly(project("dummy"))
} }
kotlin { kotlin {
jvmToolchain(11) jvmToolchain(11)
} }
tasks.withType(Jar::class) {
exclude("app/revanced/meta")
manifest {
attributes["Name"] = "ReVanced Patches"
attributes["Description"] = "Patches for ReVanced."
attributes["Version"] = version
attributes["Timestamp"] = System.currentTimeMillis().toString()
attributes["Source"] = "git@github.com:revanced/revanced-patches.git"
attributes["Author"] = "ReVanced"
attributes["Contact"] = "contact@revanced.app"
attributes["Origin"] = "https://revanced.app"
attributes["License"] = "GNU General Public License v3.0"
}
}
tasks { tasks {
register<DefaultTask>("generateBundle") { withType(Jar::class) {
description = "Generate DEX files and add them in the JAR file" exclude("app/revanced/meta")
manifest {
attributes["Name"] = "ReVanced Patches"
attributes["Description"] = "Patches for ReVanced."
attributes["Version"] = version
attributes["Timestamp"] = System.currentTimeMillis().toString()
attributes["Source"] = "git@github.com:revanced/revanced-patches.git"
attributes["Author"] = "ReVanced"
attributes["Contact"] = "contact@revanced.app"
attributes["Origin"] = "https://revanced.app"
attributes["License"] = "GNU General Public License v3.0"
}
}
register("buildDexJar") {
description = "Build and add a DEX to the JAR file"
group = "build"
dependsOn(build) dependsOn(build)
@@ -57,39 +63,50 @@ tasks {
val d8 = File(System.getenv("ANDROID_HOME")).resolve("build-tools") val d8 = File(System.getenv("ANDROID_HOME")).resolve("build-tools")
.listFilesOrdered().last().resolve("d8").absolutePath .listFilesOrdered().last().resolve("d8").absolutePath
val artifacts = configurations.archives.get().allArtifacts.files.files.first().absolutePath val patchesJar = configurations.archives.get().allArtifacts.files.files.first().absolutePath
val workingDirectory = layout.buildDirectory.dir("libs").get().asFile val workingDirectory = layout.buildDirectory.dir("libs").get().asFile
exec { exec {
workingDir = workingDirectory workingDir = workingDirectory
commandLine = listOf(d8, artifacts) commandLine = listOf(d8, "--release", patchesJar)
} }
exec { exec {
workingDir = workingDirectory workingDir = workingDirectory
commandLine = listOf("zip", "-u", artifacts, "classes.dex") commandLine = listOf("zip", "-u", patchesJar, "classes.dex")
} }
} }
} }
register<JavaExec>("generateMeta") { register<JavaExec>("generatePatchesFiles") {
description = "Generate metadata for this bundle" description = "Generate patches files"
dependsOn(build) dependsOn(build)
classpath = sourceSets["main"].runtimeClasspath classpath = sourceSets["main"].runtimeClasspath
mainClass.set("app.revanced.meta.IPatchesFileGenerator") mainClass.set("app.revanced.generator.MainKt")
} }
// Required to run tasks because Gradle semantic-release plugin runs the publish task. // Needed by gradle-semantic-release-plugin.
// Tracking: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435 // Tracking: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435
named("publish") { publish {
dependsOn("generateBundle") dependsOn("buildDexJar")
dependsOn("generateMeta") dependsOn("generatePatchesFiles")
} }
} }
publishing { publishing {
repositories {
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/revanced/revanced-patches")
credentials {
username = System.getenv("GITHUB_ACTOR")
password = System.getenv("GITHUB_TOKEN")
}
}
}
publications { publications {
create<MavenPublication>("revanced-patches-publication") { create<MavenPublication>("revanced-patches-publication") {
from(components["java"]) from(components["java"])
@@ -120,4 +137,10 @@ publishing {
} }
} }
} }
} }
signing {
useGpgCmd()
sign(publishing.publications["revanced-patches-publication"])
}

View File

@@ -1,9 +0,0 @@
plugins {
id("java")
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(11))
}
}

View File

@@ -1,9 +0,0 @@
package android.os;
import java.io.File;
public final class Environment {
public static File getExternalStorageDirectory() {
throw new UnsupportedOperationException("Stub");
}
}

View File

@@ -1,4 +1,4 @@
org.gradle.parallel = true org.gradle.parallel = true
org.gradle.caching = true org.gradle.caching = true
kotlin.code.style = official kotlin.code.style = official
version = 4.2.0-dev.1 version = 4.3.0-dev.8

View File

@@ -1,9 +1,10 @@
[versions] [versions]
revanced-patcher = "19.2.0" revanced-patcher = "19.3.1"
smali = "3.0.3" smali = "3.0.4"
guava = "33.0.0-jre" guava = "33.0.0-jre"
gson = "2.10.1" gson = "2.10.1"
binary-compatibility-validator = "0.13.2" binary-compatibility-validator = "0.14.0"
kotlin = "1.9.22"
[libraries] [libraries]
revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" } revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" }
@@ -13,3 +14,4 @@ gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
[plugins] [plugins]
binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binary-compatibility-validator" } binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binary-compatibility-validator" }
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }

View File

@@ -1,8 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionSha256Sum=9d926787066a081739e8200858338b4a69e837c3a821a33aca9db09dd4a41026 distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip distributionSha256Sum=9631d53cf3e74bfa726893aee1f8994fee4e060c401335946dba2156f440f24c
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dist

280
package-lock.json generated
View File

@@ -9,7 +9,7 @@
"@semantic-release/changelog": "^6.0.3", "@semantic-release/changelog": "^6.0.3",
"@semantic-release/git": "^10.0.1", "@semantic-release/git": "^10.0.1",
"gradle-semantic-release-plugin": "^1.9.1", "gradle-semantic-release-plugin": "^1.9.1",
"semantic-release": "^23.0.0" "semantic-release": "^23.0.2"
} }
}, },
"node_modules/@babel/code-frame": { "node_modules/@babel/code-frame": {
@@ -326,9 +326,9 @@
} }
}, },
"node_modules/@octokit/request": { "node_modules/@octokit/request": {
"version": "8.1.6", "version": "8.2.0",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.1.6.tgz", "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.2.0.tgz",
"integrity": "sha512-YhPaGml3ncZC1NfXpP3WZ7iliL1ap6tLkAp6MvbK2fTTPytzVUyUesBBogcdMm86uRYO5rHaM1xIWxigWZ17MQ==", "integrity": "sha512-exPif6x5uwLqv1N1irkLG1zZNJkOtj8bZxuVHd71U5Ftuxf2wGNvAJyNBcPbPC+EBzwYEbBDdSFb8EPcjpYxPQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@octokit/endpoint": "^9.0.0", "@octokit/endpoint": "^9.0.0",
@@ -564,6 +564,26 @@
"node": ">= 16" "node": ">= 16"
} }
}, },
"node_modules/@saithodev/semantic-release-backmerge/node_modules/marked-terminal": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-6.2.0.tgz",
"integrity": "sha512-ubWhwcBFHnXsjYNsu+Wndpg0zhY4CahSpPlA70PlO0rR9r2sZpkyU+rkCsOWH+KMEkx847UpALON+HWgxowFtw==",
"dev": true,
"dependencies": {
"ansi-escapes": "^6.2.0",
"cardinal": "^2.1.1",
"chalk": "^5.3.0",
"cli-table3": "^0.6.3",
"node-emoji": "^2.1.3",
"supports-hyperlinks": "^3.0.0"
},
"engines": {
"node": ">=16.0.0"
},
"peerDependencies": {
"marked": ">=1 <12"
}
},
"node_modules/@saithodev/semantic-release-backmerge/node_modules/mimic-fn": { "node_modules/@saithodev/semantic-release-backmerge/node_modules/mimic-fn": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
@@ -1195,9 +1215,9 @@
} }
}, },
"node_modules/@sindresorhus/merge-streams": { "node_modules/@sindresorhus/merge-streams": {
"version": "1.0.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-1.0.0.tgz", "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.2.0.tgz",
"integrity": "sha512-rUV5WyJrJLoloD4NDN1V1+LDMDWOa4OTsT4yYJwQNpTU6FWxkxHpL7eu4w+DmiH8x/EAM1otkPE1+LaspIbplw==", "integrity": "sha512-UTce8mUwUW0RikMb/eseJ7ys0BRkZVFB86orHzrfW12ZmFtym5zua8joZ4L7okH2dDFHkcFjqnZ5GocWBXOFtA==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">=18" "node": ">=18"
@@ -1282,6 +1302,12 @@
"integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==", "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==",
"dev": true "dev": true
}, },
"node_modules/any-promise": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
"dev": true
},
"node_modules/argparse": { "node_modules/argparse": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -1376,6 +1402,81 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/cli-highlight": {
"version": "2.1.11",
"resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz",
"integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==",
"dev": true,
"dependencies": {
"chalk": "^4.0.0",
"highlight.js": "^10.7.1",
"mz": "^2.4.0",
"parse5": "^5.1.1",
"parse5-htmlparser2-tree-adapter": "^6.0.0",
"yargs": "^16.0.0"
},
"bin": {
"highlight": "bin/highlight"
},
"engines": {
"node": ">=8.0.0",
"npm": ">=5.0.0"
}
},
"node_modules/cli-highlight/node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/cli-highlight/node_modules/cliui": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
"integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
"dev": true,
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^7.0.0"
}
},
"node_modules/cli-highlight/node_modules/yargs": {
"version": "16.2.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
"integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
"dev": true,
"dependencies": {
"cliui": "^7.0.2",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.0",
"y18n": "^5.0.5",
"yargs-parser": "^20.2.2"
},
"engines": {
"node": ">=10"
}
},
"node_modules/cli-highlight/node_modules/yargs-parser": {
"version": "20.2.9",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
"integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
"dev": true,
"engines": {
"node": ">=10"
}
},
"node_modules/cli-table3": { "node_modules/cli-table3": {
"version": "0.6.3", "version": "0.6.3",
"resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz",
@@ -1818,9 +1919,9 @@
} }
}, },
"node_modules/escalade": { "node_modules/escalade": {
"version": "3.1.1", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">=6" "node": ">=6"
@@ -1888,9 +1989,9 @@
} }
}, },
"node_modules/fastq": { "node_modules/fastq": {
"version": "1.16.0", "version": "1.17.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
"integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==", "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"reusify": "^1.0.4" "reusify": "^1.0.4"
@@ -2052,12 +2153,12 @@
} }
}, },
"node_modules/globby": { "node_modules/globby": {
"version": "14.0.0", "version": "14.0.1",
"resolved": "https://registry.npmjs.org/globby/-/globby-14.0.0.tgz", "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.1.tgz",
"integrity": "sha512-/1WM/LNHRAOH9lZta77uGbq0dAEQM+XjNesWwhlERDVenqothRbnzTrL3/LrIoEPPjeUHC3vrS6TwoyxeHs7MQ==", "integrity": "sha512-jOMLD2Z7MAhyG8aJpNOpmziMOP4rPLcc95oQPKXBazW82z+CEgPFBQvEpRUa1KeIMUJo4Wsm+q6uzO/Q/4BksQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@sindresorhus/merge-streams": "^1.0.0", "@sindresorhus/merge-streams": "^2.1.0",
"fast-glob": "^3.3.2", "fast-glob": "^3.3.2",
"ignore": "^5.2.4", "ignore": "^5.2.4",
"path-type": "^5.0.0", "path-type": "^5.0.0",
@@ -2142,9 +2243,9 @@
} }
}, },
"node_modules/hasown": { "node_modules/hasown": {
"version": "2.0.0", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz",
"integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"function-bind": "^1.1.2" "function-bind": "^1.1.2"
@@ -2153,6 +2254,15 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/highlight.js": {
"version": "10.7.3",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
"integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==",
"dev": true,
"engines": {
"node": "*"
}
},
"node_modules/hook-std": { "node_modules/hook-std": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/hook-std/-/hook-std-3.0.0.tgz", "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-3.0.0.tgz",
@@ -2178,9 +2288,9 @@
} }
}, },
"node_modules/http-proxy-agent": { "node_modules/http-proxy-agent": {
"version": "7.0.0", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.1.tgz",
"integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", "integrity": "sha512-My1KCEPs6A0hb4qCVzYp8iEvA8j8YqcvXLZZH8C9OFuTYpYjHE7N2dtG3mRl1HMD4+VGXpF3XcDVcxGBT7yDZQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"agent-base": "^7.1.0", "agent-base": "^7.1.0",
@@ -2191,9 +2301,9 @@
} }
}, },
"node_modules/https-proxy-agent": { "node_modules/https-proxy-agent": {
"version": "7.0.2", "version": "7.0.3",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.3.tgz",
"integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", "integrity": "sha512-kCnwztfX0KZJSLOBrcL0emLeFako55NWMovvyPP2AjsghNk9RB1yjSI+jVumPHYZsNXegNoqupSW9IY3afSH8w==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"agent-base": "^7.0.2", "agent-base": "^7.0.2",
@@ -2213,9 +2323,9 @@
} }
}, },
"node_modules/ignore": { "node_modules/ignore": {
"version": "5.3.0", "version": "5.3.1",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
"integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">= 4" "node": ">= 4"
@@ -2629,9 +2739,9 @@
} }
}, },
"node_modules/marked": { "node_modules/marked": {
"version": "11.1.1", "version": "12.0.0",
"resolved": "https://registry.npmjs.org/marked/-/marked-11.1.1.tgz", "resolved": "https://registry.npmjs.org/marked/-/marked-12.0.0.tgz",
"integrity": "sha512-EgxRjgK9axsQuUa/oKMx5DEY8oXpKJfk61rT5iY3aRlgU6QJtUcxU5OAymdhCvWvhYcd9FKmO5eQoX8m9VGJXg==", "integrity": "sha512-Vkwtq9rLqXryZnWaQc86+FHLC6tr/fycMfYAhiOIXkrNmeGAyhSxjqu0Rs1i0bBqw5u0S7+lV9fdH2ZSVaoa0w==",
"dev": true, "dev": true,
"bin": { "bin": {
"marked": "bin/marked.js" "marked": "bin/marked.js"
@@ -2641,14 +2751,14 @@
} }
}, },
"node_modules/marked-terminal": { "node_modules/marked-terminal": {
"version": "6.2.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-6.2.0.tgz", "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-7.0.0.tgz",
"integrity": "sha512-ubWhwcBFHnXsjYNsu+Wndpg0zhY4CahSpPlA70PlO0rR9r2sZpkyU+rkCsOWH+KMEkx847UpALON+HWgxowFtw==", "integrity": "sha512-sNEx8nn9Ktcm6pL0TnRz8tnXq/mSS0Q1FRSwJOAqw4lAB4l49UeDf85Gm1n9RPFm5qurCPjwi1StAQT2XExhZw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"ansi-escapes": "^6.2.0", "ansi-escapes": "^6.2.0",
"cardinal": "^2.1.1",
"chalk": "^5.3.0", "chalk": "^5.3.0",
"cli-highlight": "^2.1.11",
"cli-table3": "^0.6.3", "cli-table3": "^0.6.3",
"node-emoji": "^2.1.3", "node-emoji": "^2.1.3",
"supports-hyperlinks": "^3.0.0" "supports-hyperlinks": "^3.0.0"
@@ -2657,7 +2767,7 @@
"node": ">=16.0.0" "node": ">=16.0.0"
}, },
"peerDependencies": { "peerDependencies": {
"marked": ">=1 <12" "marked": ">=1 <13"
} }
}, },
"node_modules/meow": { "node_modules/meow": {
@@ -2739,6 +2849,17 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true "dev": true
}, },
"node_modules/mz": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
"dev": true,
"dependencies": {
"any-promise": "^1.0.0",
"object-assign": "^4.0.1",
"thenify-all": "^1.0.0"
}
},
"node_modules/neo-async": { "node_modules/neo-async": {
"version": "2.6.2", "version": "2.6.2",
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
@@ -5551,6 +5672,15 @@
"inBundle": true, "inBundle": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/once": { "node_modules/once": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -5695,6 +5825,27 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/parse5": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz",
"integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==",
"dev": true
},
"node_modules/parse5-htmlparser2-tree-adapter": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz",
"integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==",
"dev": true,
"dependencies": {
"parse5": "^6.0.1"
}
},
"node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
"integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==",
"dev": true
},
"node_modules/parsimmon": { "node_modules/parsimmon": {
"version": "1.18.1", "version": "1.18.1",
"resolved": "https://registry.npmjs.org/parsimmon/-/parsimmon-1.18.1.tgz", "resolved": "https://registry.npmjs.org/parsimmon/-/parsimmon-1.18.1.tgz",
@@ -5860,9 +6011,9 @@
} }
}, },
"node_modules/read-pkg-up/node_modules/type-fest": { "node_modules/read-pkg-up/node_modules/type-fest": {
"version": "4.10.1", "version": "4.10.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.10.1.tgz", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.10.2.tgz",
"integrity": "sha512-7ZnJYTp6uc04uYRISWtiX3DSKB/fxNQT0B5o1OUeCqiQiwF+JC9+rJiZIDrPrNCLLuTqyQmh4VdQqh/ZOkv9MQ==", "integrity": "sha512-anpAG63wSpdEbLwOqH8L84urkL6PiVIov3EMmgIhhThevh9aiMQov+6Btx0wldNcvm4wV+e2/Rt1QdDwKHFbHw==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">=16" "node": ">=16"
@@ -5889,9 +6040,9 @@
} }
}, },
"node_modules/read-pkg/node_modules/type-fest": { "node_modules/read-pkg/node_modules/type-fest": {
"version": "4.10.1", "version": "4.10.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.10.1.tgz", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.10.2.tgz",
"integrity": "sha512-7ZnJYTp6uc04uYRISWtiX3DSKB/fxNQT0B5o1OUeCqiQiwF+JC9+rJiZIDrPrNCLLuTqyQmh4VdQqh/ZOkv9MQ==", "integrity": "sha512-anpAG63wSpdEbLwOqH8L84urkL6PiVIov3EMmgIhhThevh9aiMQov+6Btx0wldNcvm4wV+e2/Rt1QdDwKHFbHw==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">=16" "node": ">=16"
@@ -5994,9 +6145,9 @@
"dev": true "dev": true
}, },
"node_modules/semantic-release": { "node_modules/semantic-release": {
"version": "23.0.0", "version": "23.0.2",
"resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-23.0.0.tgz", "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-23.0.2.tgz",
"integrity": "sha512-Jz7jEWO2igTtske112gC4PPE2whCMVrsgxUPG3/SZI7VE357suIUZFlJd1Yu0g2I6RPc2HxNEfUg7KhmDTjwqg==", "integrity": "sha512-OnVYJ6Xgzwe1x8MKswba7RU9+5djS1MWRTrTn5qsq3xZYpslroZkV9Pt0dA2YcIuieeuSZWJhn+yUWoBUHO5Fw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@semantic-release/commit-analyzer": "^11.0.0", "@semantic-release/commit-analyzer": "^11.0.0",
@@ -6017,8 +6168,8 @@
"hosted-git-info": "^7.0.0", "hosted-git-info": "^7.0.0",
"import-from-esm": "^1.3.1", "import-from-esm": "^1.3.1",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"marked": "^11.0.0", "marked": "^12.0.0",
"marked-terminal": "^6.0.0", "marked-terminal": "^7.0.0",
"micromatch": "^4.0.2", "micromatch": "^4.0.2",
"p-each-series": "^3.0.0", "p-each-series": "^3.0.0",
"p-reduce": "^3.0.0", "p-reduce": "^3.0.0",
@@ -6247,9 +6398,9 @@
} }
}, },
"node_modules/semver": { "node_modules/semver": {
"version": "7.5.4", "version": "7.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"lru-cache": "^6.0.0" "lru-cache": "^6.0.0"
@@ -6481,9 +6632,9 @@
} }
}, },
"node_modules/spdx-license-ids": { "node_modules/spdx-license-ids": {
"version": "3.0.16", "version": "3.0.17",
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz",
"integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==", "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==",
"dev": true "dev": true
}, },
"node_modules/split2": { "node_modules/split2": {
@@ -6655,6 +6806,27 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/thenify": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
"dev": true,
"dependencies": {
"any-promise": "^1.0.0"
}
},
"node_modules/thenify-all": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
"dev": true,
"dependencies": {
"thenify": ">= 3.1.0 < 4"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/through": { "node_modules/through": {
"version": "2.3.8", "version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",

View File

@@ -4,6 +4,6 @@
"@semantic-release/changelog": "^6.0.3", "@semantic-release/changelog": "^6.0.3",
"@semantic-release/git": "^10.0.1", "@semantic-release/git": "^10.0.1",
"gradle-semantic-release-plugin": "^1.9.1", "gradle-semantic-release-plugin": "^1.9.1",
"semantic-release": "^23.0.0" "semantic-release": "^23.0.2"
} }
} }

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,3 @@
include("dummy")
rootProject.name = "revanced-patches" rootProject.name = "revanced-patches"
buildCache { buildCache {

View File

@@ -1,11 +1,11 @@
package app.revanced.meta package app.revanced.generator
import app.revanced.patcher.PatchSet import app.revanced.patcher.PatchSet
import app.revanced.patcher.patch.Patch import app.revanced.patcher.patch.Patch
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import java.io.File import java.io.File
internal class JsonPatchesFileGenerator : IPatchesFileGenerator { internal class JsonPatchesFileGenerator : PatchesFileGenerator {
override fun generate(patches: PatchSet) = patches.map { override fun generate(patches: PatchSet) = patches.map {
JsonPatch( JsonPatch(
it.name!!, it.name!!,
@@ -20,9 +20,9 @@ internal class JsonPatchesFileGenerator : IPatchesFileGenerator {
option.values, option.values,
option.title, option.title,
option.description, option.description,
option.required option.required,
) )
} },
) )
}.let { }.let {
File("patches.json").writeText(GsonBuilder().serializeNulls().create().toJson(it)) File("patches.json").writeText(GsonBuilder().serializeNulls().create().toJson(it))
@@ -35,7 +35,7 @@ internal class JsonPatchesFileGenerator : IPatchesFileGenerator {
val compatiblePackages: Set<Patch.CompatiblePackage>? = null, val compatiblePackages: Set<Patch.CompatiblePackage>? = null,
val use: Boolean = true, val use: Boolean = true,
val requiresIntegrations: Boolean = false, val requiresIntegrations: Boolean = false,
val options: List<Option> val options: List<Option>,
) { ) {
class Option( class Option(
val key: String, val key: String,
@@ -46,4 +46,4 @@ internal class JsonPatchesFileGenerator : IPatchesFileGenerator {
val required: Boolean, val required: Boolean,
) )
} }
} }

View File

@@ -0,0 +1,12 @@
package app.revanced.generator
import app.revanced.patcher.PatchBundleLoader
import java.io.File
internal fun main() = PatchBundleLoader.Jar(
File("build/libs/").listFiles { it -> it.name.endsWith(".jar") }!!.first(),
).also { loader ->
if (loader.isEmpty()) throw IllegalStateException("No patches found")
}.let { bundle ->
arrayOf(JsonPatchesFileGenerator()).forEach { generator -> generator.generate(bundle) }
}

View File

@@ -0,0 +1,7 @@
package app.revanced.generator
import app.revanced.patcher.PatchSet
internal interface PatchesFileGenerator {
fun generate(patches: PatchSet)
}

View File

@@ -1,20 +0,0 @@
package app.revanced.meta
import app.revanced.patcher.PatchBundleLoader
import app.revanced.patcher.PatchSet
import java.io.File
internal interface IPatchesFileGenerator {
fun generate(patches: PatchSet)
private companion object {
@JvmStatic
fun main(args: Array<String>) = PatchBundleLoader.Jar(
File("build/libs/").listFiles { it -> it.name.endsWith(".jar") }!!.first()
).also { loader ->
if (loader.isEmpty()) throw IllegalStateException("No patches found")
}.let { bundle ->
arrayOf(JsonPatchesFileGenerator()).forEach { generator -> generator.generate(bundle) }
}
}
}

View File

@@ -7,29 +7,34 @@ import app.revanced.patcher.patch.annotation.Patch
@Patch( @Patch(
name = "Export all activities", name = "Export all activities",
description = "Makes all app activities exportable.", description = "Makes all app activities exportable.",
use = false use = false,
) )
@Suppress("unused") @Suppress("unused")
object ExportAllActivitiesPatch : ResourcePatch() { object ExportAllActivitiesPatch : ResourcePatch() {
private const val EXPORTED_FLAG = "android:exported" private const val EXPORTED_FLAG = "android:exported"
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
context.xmlEditor["AndroidManifest.xml"].use { editor -> context.xmlEditor["AndroidManifest.xml"].use { editor ->
val document = editor.file val document = editor.file
val activities = document.getElementsByTagName("activity") val activities = document.getElementsByTagName("activity")
for(i in 0..activities.length) { for (i in 0..activities.length) {
activities.item(i)?.apply { activities.item(i)?.apply {
val exportedAttribute = attributes.getNamedItem(EXPORTED_FLAG) val exportedAttribute = attributes.getNamedItem(EXPORTED_FLAG)
if (exportedAttribute != null) { if (exportedAttribute != null) {
if (exportedAttribute.nodeValue != "true") if (exportedAttribute.nodeValue != "true") {
exportedAttribute.nodeValue = "true" exportedAttribute.nodeValue = "true"
}
} }
// Reason why the attribute is added in the case it does not exist: // Reason why the attribute is added in the case it does not exist:
// https://github.com/revanced/revanced-patches/pull/1751/files#r1141481604 // https://github.com/revanced/revanced-patches/pull/1751/files#r1141481604
else document.createAttribute(EXPORTED_FLAG) else {
.apply { value = "true" } document.createAttribute(EXPORTED_FLAG)
.let(attributes::setNamedItem) .apply { value = "true" }
.let(attributes::setNamedItem)
}
} }
} }
} }

View File

@@ -7,7 +7,7 @@ import app.revanced.patcher.patch.annotation.Patch
@Patch( @Patch(
name = "Predictive back gesture", name = "Predictive back gesture",
description = "Enables the predictive back gesture introduced on Android 13.", description = "Enables the predictive back gesture introduced on Android 13.",
use = false use = false,
) )
@Suppress("unused") @Suppress("unused")
object PredictiveBackGesturePatch : ResourcePatch() { object PredictiveBackGesturePatch : ResourcePatch() {

View File

@@ -8,16 +8,18 @@ import org.w3c.dom.Element
@Patch( @Patch(
name = "Enable Android debugging", name = "Enable Android debugging",
description = "Enables Android debugging capabilities. This can slow down the app.", description = "Enables Android debugging capabilities. This can slow down the app.",
use = false use = false,
) )
@Suppress("unused") @Suppress("unused")
object EnableAndroidDebuggingPatch : ResourcePatch() { object EnableAndroidDebuggingPatch : ResourcePatch() {
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
context.xmlEditor["AndroidManifest.xml"].use { dom -> context.xmlEditor["AndroidManifest.xml"].use { editor ->
val applicationNode = dom val document = editor.file
.file
.getElementsByTagName("application") val applicationNode =
.item(0) as Element document
.getElementsByTagName("application")
.item(0) as Element
// set application as debuggable // set application as debuggable
applicationNode.setAttribute("android:debuggable", "true") applicationNode.setAttribute("android:debuggable", "true")

View File

@@ -11,16 +11,17 @@ import java.io.File
name = "Override certificate pinning", name = "Override certificate pinning",
description = "Overrides certificate pinning, allowing to inspect traffic via a proxy.", description = "Overrides certificate pinning, allowing to inspect traffic via a proxy.",
dependencies = [EnableAndroidDebuggingPatch::class], dependencies = [EnableAndroidDebuggingPatch::class],
use = false use = false,
) )
@Suppress("unused") @Suppress("unused")
object OverrideCertificatePinningPatch : ResourcePatch() { object OverrideCertificatePinningPatch : ResourcePatch() {
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
val resXmlDirectory = context["res/xml"] val resXmlDirectory = context.get("res/xml")
// Add android:networkSecurityConfig="@xml/network_security_config" and the "networkSecurityConfig" attribute if it does not exist. // Add android:networkSecurityConfig="@xml/network_security_config" and the "networkSecurityConfig" attribute if it does not exist.
context.xmlEditor["AndroidManifest.xml"].use { editor -> context.xmlEditor["AndroidManifest.xml"].use { editor ->
val document = editor.file val document = editor.file
val applicationNode = document.getElementsByTagName("application").item(0) as Element val applicationNode = document.getElementsByTagName("application").item(0) as Element
if (!applicationNode.hasAttribute("networkSecurityConfig")) { if (!applicationNode.hasAttribute("networkSecurityConfig")) {
@@ -54,7 +55,7 @@ object OverrideCertificatePinningPatch : ResourcePatch() {
</trust-anchors> </trust-anchors>
</debug-overrides> </debug-overrides>
</network-security-config> </network-security-config>
""" """,
) )
} else { } else {
// If the file already exists. // If the file already exists.
@@ -63,12 +64,11 @@ object OverrideCertificatePinningPatch : ResourcePatch() {
writeText( writeText(
text.replace( text.replace(
"<trust-anchors>", "<trust-anchors>",
"<trust-anchors>\n<certificates src=\"user\" overridePins=\"true\" />\n<certificates src=\"system\" />" "<trust-anchors>\n<certificates src=\"user\" overridePins=\"true\" />\n<certificates src=\"system\" />",
) ),
) )
} }
} }
} }
} }
} }

View File

@@ -11,20 +11,21 @@ import java.io.Closeable
@Patch( @Patch(
name = "Change package name", name = "Change package name",
description = "Appends \".revanced\" to the package name by default. Changing the package name of the app can lead to unexpected issues.", description = "Appends \".revanced\" to the package name by default. Changing the package name of the app can lead to unexpected issues.",
use = false use = false,
) )
@Suppress("unused") @Suppress("unused")
object ChangePackageNamePatch : ResourcePatch(), Closeable { object ChangePackageNamePatch : ResourcePatch(), Closeable {
private val packageNameOption = stringPatchOption( private val packageNameOption =
key = "packageName", stringPatchOption(
default = "Default", key = "packageName",
values = mapOf("Default" to "Default"), default = "Default",
title = "Package name", values = mapOf("Default" to "Default"),
description = "The name of the package to rename the app to.", title = "Package name",
required = true description = "The name of the package to rename the app to.",
) { required = true,
it == "Default" || it!!.matches(Regex("^[a-z]\\w*(\\.[a-z]\\w*)+\$")) ) {
} it == "Default" || it!!.matches(Regex("^[a-z]\\w*(\\.[a-z]\\w*)+\$"))
}
private lateinit var context: ResourceContext private lateinit var context: ResourceContext
@@ -43,20 +44,27 @@ object ChangePackageNamePatch : ResourcePatch(), Closeable {
fun setOrGetFallbackPackageName(fallbackPackageName: String): String { fun setOrGetFallbackPackageName(fallbackPackageName: String): String {
val packageName = packageNameOption.value!! val packageName = packageNameOption.value!!
return if (packageName == packageNameOption.default) return if (packageName == packageNameOption.default) {
fallbackPackageName.also { packageNameOption.value = it } fallbackPackageName.also { packageNameOption.value = it }
else } else {
packageName packageName
}
} }
override fun close() = context.xmlEditor["AndroidManifest.xml"].use { editor -> override fun close() =
val replacementPackageName = packageNameOption.value context.xmlEditor["AndroidManifest.xml"].use { editor ->
val document = editor.file
val manifest = editor.file.getElementsByTagName("manifest").item(0) as Element val replacementPackageName = packageNameOption.value
manifest.setAttribute(
"package", val manifest = document.getElementsByTagName("manifest").item(0) as Element
if (replacementPackageName != packageNameOption.default) replacementPackageName manifest.setAttribute(
else "${manifest.getAttribute("package")}.revanced" "package",
) if (replacementPackageName != packageNameOption.default) {
} replacementPackageName
} else {
"${manifest.getAttribute("package")}.revanced"
},
)
}
} }

View File

@@ -19,6 +19,7 @@ import java.util.*
* An identifier of an app. For example, `youtube`. * An identifier of an app. For example, `youtube`.
*/ */
private typealias AppId = String private typealias AppId = String
/** /**
* An identifier of a patch. For example, `ad.general.HideAdsPatch`. * An identifier of a patch. For example, `ad.general.HideAdsPatch`.
*/ */
@@ -28,10 +29,12 @@ private typealias PatchId = String
* A set of resources of a patch. * A set of resources of a patch.
*/ */
private typealias PatchResources = MutableSet<BaseResource> private typealias PatchResources = MutableSet<BaseResource>
/** /**
* A map of resources belonging to a patch. * A map of resources belonging to a patch.
*/ */
private typealias AppResources = MutableMap<PatchId, PatchResources> private typealias AppResources = MutableMap<PatchId, PatchResources>
/** /**
* A map of resources belonging to an app. * A map of resources belonging to an app.
*/ */
@@ -67,40 +70,44 @@ object AddResourcesPatch : ResourcePatch(), MutableMap<Value, MutableSet<BaseRes
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
this.context = context this.context = context
resources = buildMap { resources =
/** buildMap {
* Puts resources under `/resources/addresources/<value>/<resourceKind>.xml` into the map. /**
* * Puts resources under `/resources/addresources/<value>/<resourceKind>.xml` into the map.
* @param value The value of the resource. For example, `values` or `values-de`. *
* @param resourceKind The kind of the resource. For example, `strings` or `arrays`. * @param value The value of the resource. For example, `values` or `values-de`.
* @param transform A function that transforms the [Node]s from the XML files to a [BaseResource]. * @param resourceKind The kind of the resource. For example, `strings` or `arrays`.
*/ * @param transform A function that transforms the [Node]s from the XML files to a [BaseResource].
fun addResources( */
value: Value, fun addResources(
resourceKind: String, value: Value,
transform: (Node) -> BaseResource, resourceKind: String,
) { transform: (Node) -> BaseResource,
inputStreamFromBundledResource( ) {
"addresources", inputStreamFromBundledResource(
"$value/$resourceKind.xml" "addresources",
)?.let { stream -> "$value/$resourceKind.xml",
// Add the resources associated with the given value to the map, )?.let { stream ->
// instead of overwriting it. // Add the resources associated with the given value to the map,
// This covers the example case such as adding strings and arrays of the same value. // instead of overwriting it.
getOrPut(value, ::mutableMapOf).apply { // This covers the example case such as adding strings and arrays of the same value.
context.xmlEditor[stream].use { getOrPut(value, ::mutableMapOf).apply {
it.file.getElementsByTagName("app").asSequence().forEach { app -> context.xmlEditor[stream].use { editor ->
val appId = app.attributes.getNamedItem("id").textContent val document = editor.file
getOrPut(appId, ::mutableMapOf).apply { document.getElementsByTagName("app").asSequence().forEach { app ->
app.forEachChildElement { patch -> val appId = app.attributes.getNamedItem("id").textContent
val patchId = patch.attributes.getNamedItem("id").textContent
getOrPut(patchId, ::mutableSetOf).apply { getOrPut(appId, ::mutableMapOf).apply {
patch.forEachChildElement { resourceNode -> app.forEachChildElement { patch ->
val resource = transform(resourceNode) val patchId = patch.attributes.getNamedItem("id").textContent
add(resource) getOrPut(patchId, ::mutableSetOf).apply {
patch.forEachChildElement { resourceNode ->
val resource = transform(resourceNode)
add(resource)
}
} }
} }
} }
@@ -109,23 +116,22 @@ object AddResourcesPatch : ResourcePatch(), MutableMap<Value, MutableSet<BaseRes
} }
} }
} }
}
// Stage all resources to a temporary map. // Stage all resources to a temporary map.
// Staged resources consumed by AddResourcesPatch#invoke(PatchClass) // Staged resources consumed by AddResourcesPatch#invoke(PatchClass)
// are later used in AddResourcesPatch#close. // are later used in AddResourcesPatch#close.
try { try {
val addStringResources = { value: Value -> val addStringResources = { value: Value ->
addResources(value, "strings", StringResource::fromNode) addResources(value, "strings", StringResource::fromNode)
}
Locale.getISOLanguages().asSequence().map { "values-$it" }.forEach { addStringResources(it) }
addStringResources("values")
addResources("values", "arrays", ArrayResource::fromNode)
} catch (e: Exception) {
throw PatchException("Failed to read resources", e)
} }
Locale.getISOLanguages().asSequence().map { "values-$it" }.forEach { addStringResources(it) }
addStringResources("values")
addResources("values", "arrays", ArrayResource::fromNode)
} catch (e: Exception) {
throw PatchException("Failed to read resources", e)
} }
}
} }
/** /**
@@ -136,8 +142,10 @@ object AddResourcesPatch : ResourcePatch(), MutableMap<Value, MutableSet<BaseRes
* *
* @return True if the resource was added, false if it already existed. * @return True if the resource was added, false if it already existed.
*/ */
operator fun invoke(value: Value, resource: BaseResource) = operator fun invoke(
getOrPut(value, ::mutableSetOf).add(resource) value: Value,
resource: BaseResource,
) = getOrPut(value, ::mutableSetOf).add(resource)
/** /**
* Adds a list of [BaseResource]s to the map using [MutableMap.getOrPut]. * Adds a list of [BaseResource]s to the map using [MutableMap.getOrPut].
@@ -147,8 +155,10 @@ object AddResourcesPatch : ResourcePatch(), MutableMap<Value, MutableSet<BaseRes
* *
* @return True if the resources were added, false if they already existed. * @return True if the resources were added, false if they already existed.
*/ */
operator fun invoke(value: Value, resources: Iterable<BaseResource>) = operator fun invoke(
getOrPut(value, ::mutableSetOf).addAll(resources) value: Value,
resources: Iterable<BaseResource>,
) = getOrPut(value, ::mutableSetOf).addAll(resources)
/** /**
* Adds a [StringResource]. * Adds a [StringResource].
@@ -177,10 +187,9 @@ object AddResourcesPatch : ResourcePatch(), MutableMap<Value, MutableSet<BaseRes
*/ */
operator fun invoke( operator fun invoke(
name: String, name: String,
items: List<String> items: List<String>,
) = invoke("values", ArrayResource(name, items)) ) = invoke("values", ArrayResource(name, items))
/** /**
* Puts all resources of any [Value] staged in [resources] for the given [PatchClass] to [AddResourcesPatch]. * Puts all resources of any [Value] staged in [resources] for the given [PatchClass] to [AddResourcesPatch].
* *
@@ -209,7 +218,7 @@ object AddResourcesPatch : ResourcePatch(), MutableMap<Value, MutableSet<BaseRes
appId to patchId appId to patchId
} }
} },
): Boolean { ): Boolean {
val (appId, patchId) = patch.parseIds() val (appId, patchId) = patch.parseIds()
@@ -218,7 +227,7 @@ object AddResourcesPatch : ResourcePatch(), MutableMap<Value, MutableSet<BaseRes
// Stage resources for the given patch to AddResourcesPatch associated with their value. // Stage resources for the given patch to AddResourcesPatch associated with their value.
resources.forEach { (value, resources) -> resources.forEach { (value, resources) ->
resources[appId]?.get(patchId)?.let { patchResources -> resources[appId]?.get(patchId)?.let { patchResources ->
if (invoke(value, patchResources)) result = true if (invoke(value, patchResources)) result = true
} }
} }
@@ -232,28 +241,32 @@ object AddResourcesPatch : ResourcePatch(), MutableMap<Value, MutableSet<BaseRes
override fun close() { override fun close() {
operator fun MutableMap<String, Pair<DomFileEditor, Node>>.invoke( operator fun MutableMap<String, Pair<DomFileEditor, Node>>.invoke(
value: Value, value: Value,
resource: BaseResource resource: BaseResource,
) { ) {
// TODO: Fix open-closed principle violation by modifying BaseResource#serialize so that it accepts // TODO: Fix open-closed principle violation by modifying BaseResource#serialize so that it accepts
// a Value and the map of editors. It will then get or put the editor suitable for its resource type // a Value and the map of documents. It will then get or put the document suitable for its resource type
// to serialize itself to it. // to serialize itself to it.
val resourceFileName = when (resource) { val resourceFileName =
is StringResource -> "strings" when (resource) {
is ArrayResource -> "arrays" is StringResource -> "strings"
else -> throw NotImplementedError("Unsupported resource type") is ArrayResource -> "arrays"
} else -> throw NotImplementedError("Unsupported resource type")
getOrPut(resourceFileName) {
val targetFile = context["res/$value/$resourceFileName.xml"].also {
it.parentFile?.mkdirs()
it.createNewFile()
} }
getOrPut(resourceFileName) {
val targetFile =
context.get("res/$value/$resourceFileName.xml").also {
it.parentFile?.mkdirs()
it.createNewFile()
}
context.xmlEditor[targetFile.path].let { editor -> context.xmlEditor[targetFile.path].let { editor ->
val document = editor.file
// Save the target node here as well // Save the target node here as well
// in order to avoid having to call editor.getNode("resources") // in order to avoid having to call document.getNode("resources")
// every time addUsingEditors is called but also save the editor so that it can be closed later. // but also save the document so that it can be closed later.
editor to editor.getNode("resources") editor to document.getNode("resources")
} }
}.let { (_, targetNode) -> }.let { (_, targetNode) ->
targetNode.addResource(resource) { invoke(value, it) } targetNode.addResource(resource) { invoke(value, it) }
@@ -261,17 +274,17 @@ object AddResourcesPatch : ResourcePatch(), MutableMap<Value, MutableSet<BaseRes
} }
forEach { (value, resources) -> forEach { (value, resources) ->
// A map of editors associated by their kind (e.g. strings, arrays). // A map of document associated by their kind (e.g. strings, arrays).
// Each editor is accompanied by the target node to which resources are added. // Each document is accompanied by the target node to which resources are added.
// A map is used because Map#getOrPut allows opening a new editor for the duration of a resource value. // A map is used because Map#getOrPut allows opening a new document for the duration of a resource value.
// This is done to prevent having to open the files for every resource that is added. // This is done to prevent having to open the files for every resource that is added.
// Instead, it is cached once and reused for resources of the same value. // Instead, it is cached once and reused for resources of the same value.
// This map is later accessed to close all editors for the current resource value. // This map is later accessed to close all documents for the current resource value.
val resourceFileEditors = mutableMapOf<String, Pair<DomFileEditor, Node>>() val documents = mutableMapOf<String, Pair<DomFileEditor, Node>>()
resources.forEach { resource -> resourceFileEditors(value, resource) } resources.forEach { resource -> documents(value, resource) }
resourceFileEditors.values.forEach { (editor, _) -> editor.close() } documents.values.forEach { (document, _) -> document.close() }
} }
} }
} }

View File

@@ -9,12 +9,12 @@ import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.Instruction import com.android.tools.smali.dexlib2.iface.instruction.Instruction
@Suppress("MemberVisibilityCanBePrivate") @Suppress("MemberVisibilityCanBePrivate")
abstract class BaseTransformInstructionsPatch<T> : BytecodePatch() { abstract class BaseTransformInstructionsPatch<T> : BytecodePatch(emptySet()) {
abstract fun filterMap( abstract fun filterMap(
classDef: ClassDef, classDef: ClassDef,
method: Method, method: Method,
instruction: Instruction, instruction: Instruction,
instructionIndex: Int instructionIndex: Int,
): T? ): T?
abstract fun transform(mutableMethod: MutableMethod, entry: T) abstract fun transform(mutableMethod: MutableMethod, entry: T)

View File

@@ -8,16 +8,17 @@ import org.w3c.dom.Element
@Patch(description = "Sets allowAudioPlaybackCapture in manifest to true.") @Patch(description = "Sets allowAudioPlaybackCapture in manifest to true.")
internal object RemoveCaptureRestrictionResourcePatch : ResourcePatch() { internal object RemoveCaptureRestrictionResourcePatch : ResourcePatch() {
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
// create an xml editor instance context.xmlEditor["AndroidManifest.xml"].use { editor ->
context.xmlEditor["AndroidManifest.xml"].use { dom -> val document = editor.file
// get the application node // get the application node
val applicationNode = dom val applicationNode =
.file document
.getElementsByTagName("application") .getElementsByTagName("application")
.item(0) as Element .item(0) as Element
// set allowAudioPlaybackCapture attribute to true // set allowAudioPlaybackCapture attribute to true
applicationNode.setAttribute("android:allowAudioPlaybackCapture", "true") applicationNode.setAttribute("android:allowAudioPlaybackCapture", "true")
} }
} }
} }

View File

@@ -1,51 +0,0 @@
package app.revanced.patches.music.audio.exclusiveaudio.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.annotation.FuzzyPatternScanMethod
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
@FuzzyPatternScanMethod(2) // FIXME: Test this threshold and find the best value.
internal object ExclusiveAudioFingerprint : MethodFingerprint(
"V",
AccessFlags.PUBLIC or AccessFlags.FINAL,
listOf("L", "Z"),
listOf(
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.CHECK_CAST,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT,
Opcode.IF_EQ,
Opcode.CONST_4,
Opcode.GOTO,
Opcode.NOP,
Opcode.IGET_OBJECT,
Opcode.INVOKE_INTERFACE,
Opcode.MOVE_RESULT_OBJECT,
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.IGET_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.INVOKE_INTERFACE,
Opcode.MOVE_RESULT,
Opcode.IF_EQZ,
Opcode.INVOKE_INTERFACE,
Opcode.MOVE_RESULT_OBJECT,
Opcode.CHECK_CAST,
Opcode.IF_EQZ,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.CHECK_CAST,
Opcode.IF_EQZ,
Opcode.IF_EQZ,
Opcode.INVOKE_INTERFACE,
Opcode.INVOKE_INTERFACE,
Opcode.GOTO,
Opcode.RETURN_VOID
)
)

View File

@@ -1,14 +1,12 @@
package app.revanced.patches.music.misc.gms.fingerprints package app.revanced.patches.music.misc.gms.fingerprints
import app.revanced.patcher.extensions.or import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.annotation.FuzzyPatternScanMethod
import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
internal object ServiceCheckFingerprint : MethodFingerprint( internal object ServiceCheckFingerprint : MethodFingerprint(
"V", "V",
AccessFlags.PUBLIC or AccessFlags.STATIC, AccessFlags.PUBLIC or AccessFlags.STATIC,
listOf("L", "I"), listOf("L", "I"),
strings = listOf("Google Play Services not available") strings = listOf("Google Play Services not available"),
) )

View File

@@ -6,8 +6,5 @@ import app.revanced.patches.shared.misc.integrations.BaseIntegrationsPatch
@Patch(requiresIntegrations = true) @Patch(requiresIntegrations = true)
object IntegrationsPatch : BaseIntegrationsPatch( object IntegrationsPatch : BaseIntegrationsPatch(
"Lapp/revanced/integrations/utils/ReVancedUtils;", setOf(ApplicationInitFingerprint),
setOf(
ApplicationInitFingerprint,
),
) )

View File

@@ -6,23 +6,24 @@ import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch import app.revanced.patcher.patch.annotation.Patch
import org.w3c.dom.Element import org.w3c.dom.Element
@Patch( @Patch(
name = "Remove broadcasts restriction", name = "Remove broadcasts restriction",
description = "Enables starting/stopping NetGuard via broadcasts.", description = "Enables starting/stopping NetGuard via broadcasts.",
compatiblePackages = [CompatiblePackage("eu.faircode.netguard")], compatiblePackages = [CompatiblePackage("eu.faircode.netguard")],
use = false use = false,
) )
@Suppress("unused") @Suppress("unused")
object RemoveBroadcastsRestrictionPatch : ResourcePatch() { object RemoveBroadcastsRestrictionPatch : ResourcePatch() {
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
context.xmlEditor["AndroidManifest.xml"].use { dom -> context.xmlEditor["AndroidManifest.xml"].use { editor ->
val applicationNode = dom val document = editor.file
.file
.getElementsByTagName("application")
.item(0) as Element
applicationNode.getElementsByTagName("receiver").also { list -> val applicationNode =
document
.getElementsByTagName("application")
.item(0) as Element
applicationNode.getElementsByTagName("receiver").also { list ->
for (i in 0 until list.length) { for (i in 0 until list.length) {
val element = list.item(i) as? Element ?: continue val element = list.item(i) as? Element ?: continue
if (element.getAttribute("android:name") == "eu.faircode.netguard.WidgetAdmin") { if (element.getAttribute("android:name") == "eu.faircode.netguard.WidgetAdmin") {

View File

@@ -0,0 +1,115 @@
package app.revanced.patches.openinghours.misc.fix.crash
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.getInstructions
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.extensions.newLabel
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.openinghours.misc.fix.crash.fingerprints.SetPlaceFingerprint
import app.revanced.util.exception
import app.revanced.util.getReference
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21t
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
@Patch(
name = "Fix crash",
compatiblePackages = [CompatiblePackage("de.simon.openinghours", ["1.0"])],
)
@Suppress("unused")
object FixCrashPatch : BytecodePatch(
setOf(SetPlaceFingerprint),
) {
override fun execute(context: BytecodeContext) {
SetPlaceFingerprint.result?.let {
val indexedInstructions = it.mutableMethod.getInstructions().withIndex().toList()
/**
* This function replaces all `checkNotNull` instructions in the integer interval
* from [startIndex] to [endIndex], both inclusive. In place of the `checkNotNull`
* instruction an if-null check is inserted. If the if-null check yields that
* the value is indeed null, we jump to a newly created label at `endIndex + 1`.
*/
fun avoidNullPointerException(startIndex: Int, endIndex: Int) {
val continueLabel = it.mutableMethod.newLabel(endIndex + 1)
for (index in startIndex..endIndex) {
val instruction = indexedInstructions[index].value
if (!instruction.isCheckNotNullInstruction) {
continue
}
val checkNotNullInstruction = instruction as FiveRegisterInstruction
val originalRegister = checkNotNullInstruction.registerC
it.mutableMethod.replaceInstruction(
index,
BuilderInstruction21t(
Opcode.IF_EQZ,
originalRegister,
continueLabel,
),
)
}
}
val getOpeningHoursIndex = getIndicesOfInvoke(
indexedInstructions,
"Lde/simon/openinghours/models/Place;",
"getOpeningHours",
)
val setWeekDayTextIndex = getIndexOfInvoke(
indexedInstructions,
"Lde/simon/openinghours/views/custom/PlaceCard;",
"setWeekDayText",
)
val startCalculateStatusIndex = getIndexOfInvoke(
indexedInstructions,
"Lde/simon/openinghours/views/custom/PlaceCard;",
"startCalculateStatus",
)
// Replace the Intrinsics;->checkNotNull instructions with a null check
// and jump to our newly created label if it returns true.
// This avoids the NullPointerExceptions.
avoidNullPointerException(getOpeningHoursIndex[1], startCalculateStatusIndex)
avoidNullPointerException(getOpeningHoursIndex[0], setWeekDayTextIndex)
} ?: throw SetPlaceFingerprint.exception
}
private fun isInvokeInstruction(instruction: Instruction, className: String, methodName: String): Boolean {
val methodRef = instruction.getReference<MethodReference>() ?: return false
return methodRef.definingClass == className && methodRef.name == methodName
}
private fun getIndicesOfInvoke(
instructions: List<IndexedValue<Instruction>>,
className: String,
methodName: String,
): List<Int> = instructions.mapNotNull { (index, instruction) ->
if (isInvokeInstruction(instruction, className, methodName)) {
index
} else {
null
}
}
private fun getIndexOfInvoke(
instructions: List<IndexedValue<Instruction>>,
className: String,
methodName: String,
): Int = instructions.first { (_, instruction) ->
isInvokeInstruction(instruction, className, methodName)
}.index
private val Instruction.isCheckNotNullInstruction
get() = isInvokeInstruction(this, "Lkotlin/jvm/internal/Intrinsics;", "checkNotNull")
}

View File

@@ -0,0 +1,12 @@
package app.revanced.patches.openinghours.misc.fix.crash.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint
internal object SetPlaceFingerprint : MethodFingerprint(
"V",
parameters = listOf("Lde/simon/openinghours/models/Place;"),
customFingerprint = { methodDef, _ ->
methodDef.definingClass == "Lde/simon/openinghours/views/custom/PlaceCard;" &&
methodDef.name == "setPlace"
},
)

View File

@@ -9,8 +9,10 @@ object HideBannerPatch : ResourcePatch() {
private const val RESOURCE_FILE_PATH = "res/layout/merge_listheader_link_detail.xml" private const val RESOURCE_FILE_PATH = "res/layout/merge_listheader_link_detail.xml"
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
context.xmlEditor[RESOURCE_FILE_PATH].use { context.xmlEditor[RESOURCE_FILE_PATH].use { editor ->
it.file.getElementsByTagName("merge").item(0).childNodes.apply { val document = editor.file
document.getElementsByTagName("merge").item(0).childNodes.apply {
val attributes = arrayOf("height", "width") val attributes = arrayOf("height", "width")
for (i in 1 until length) { for (i in 1 until length) {
@@ -30,4 +32,3 @@ object HideBannerPatch : ResourcePatch() {
} }
} }
} }

View File

@@ -0,0 +1,32 @@
package app.revanced.patches.reddit.customclients.syncforreddit.fix.slink
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.reddit.customclients.syncforreddit.fix.slink.fingerprints.LinkHelperOpenLinkFingerprint
import app.revanced.util.exception
@Patch(
name = "Fix /s/ links",
description = "Fixes the issue where /s/ links do not work.",
compatiblePackages = [
CompatiblePackage("com.laurencedawson.reddit_sync"),
CompatiblePackage("com.laurencedawson.reddit_sync.pro"),
CompatiblePackage("com.laurencedawson.reddit_sync.dev")
],
requiresIntegrations = true
)
object FixSLinksPatch : BytecodePatch(
setOf(LinkHelperOpenLinkFingerprint)
) {
override fun execute(context: BytecodeContext) =
LinkHelperOpenLinkFingerprint.result?.mutableMethod?.addInstructions(
1,
"""
invoke-static { p3 }, Lapp/revanced/integrations/syncforreddit/FixSLinksPatch;->resolveSLink(Ljava/lang/String;)Ljava/lang/String;
move-result-object p3
"""
) ?: throw LinkHelperOpenLinkFingerprint.exception
}

View File

@@ -0,0 +1,7 @@
package app.revanced.patches.reddit.customclients.syncforreddit.fix.slink.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint
internal object LinkHelperOpenLinkFingerprint: MethodFingerprint(
strings = listOf("Link title: ")
)

View File

@@ -22,19 +22,21 @@ abstract class BaseGmsCoreSupportResourcePatch(
private val fromPackageName: String, private val fromPackageName: String,
private val toPackageName: String, private val toPackageName: String,
private val spoofedPackageSignature: String, private val spoofedPackageSignature: String,
dependencies: Set<PatchClass> = setOf() dependencies: Set<PatchClass> = setOf(),
) : ResourcePatch(dependencies = setOf(ChangePackageNamePatch::class, AddResourcesPatch::class) + dependencies) { ) : ResourcePatch(dependencies = setOf(ChangePackageNamePatch::class, AddResourcesPatch::class) + dependencies) {
internal val gmsCoreVendorOption = stringPatchOption( internal val gmsCoreVendorOption =
key = "gmsCoreVendor", stringPatchOption(
default = "com.mgoogle", key = "gmsCoreVendor",
values = mapOf( default = "com.mgoogle",
"Vanced" to "com.mgoogle", values =
"ReVanced" to "app.revanced" mapOf(
), "Vanced" to "com.mgoogle",
title = "GmsCore Vendor", "ReVanced" to "app.revanced",
description = "The group id of the GmsCore vendor.", ),
required = true title = "GmsCore Vendor",
) { it!!.matches(Regex("^[a-z]\\w*(\\.[a-z]\\w*)+\$")) } description = "The group id of the GmsCore vendor.",
required = true,
) { it!!.matches(Regex("^[a-z]\\w*(\\.[a-z]\\w*)+\$")) }
protected val gmsCoreVendor by gmsCoreVendorOption protected val gmsCoreVendor by gmsCoreVendorOption
@@ -49,17 +51,22 @@ abstract class BaseGmsCoreSupportResourcePatch(
* Add metadata to manifest to support spoofing the package name and signature of GmsCore. * Add metadata to manifest to support spoofing the package name and signature of GmsCore.
*/ */
private fun ResourceContext.addSpoofingMetadata() { private fun ResourceContext.addSpoofingMetadata() {
fun Node.adoptChild(tagName: String, block: Element.() -> Unit) { fun Node.adoptChild(
tagName: String,
block: Element.() -> Unit,
) {
val child = ownerDocument.createElement(tagName) val child = ownerDocument.createElement(tagName)
child.block() child.block()
appendChild(child) appendChild(child)
} }
xmlEditor["AndroidManifest.xml"].use { xmlEditor["AndroidManifest.xml"].use { editor ->
val applicationNode = it val document = editor.file
.file
.getElementsByTagName("application") val applicationNode =
.item(0) document
.getElementsByTagName("application")
.item(0)
// Spoof package name and signature. // Spoof package name and signature.
applicationNode.adoptChild("meta-data") { applicationNode.adoptChild("meta-data") {
@@ -87,27 +94,27 @@ abstract class BaseGmsCoreSupportResourcePatch(
private fun ResourceContext.patchManifest() { private fun ResourceContext.patchManifest() {
val packageName = ChangePackageNamePatch.setOrGetFallbackPackageName(toPackageName) val packageName = ChangePackageNamePatch.setOrGetFallbackPackageName(toPackageName)
val manifest = this["AndroidManifest.xml"].readText() val manifest = this.get("AndroidManifest.xml").readText()
this["AndroidManifest.xml"].writeText( this.get("AndroidManifest.xml").writeText(
manifest.replace( manifest.replace(
"package=\"$fromPackageName", "package=\"$fromPackageName",
"package=\"$packageName" "package=\"$packageName",
).replace( ).replace(
"android:authorities=\"$fromPackageName", "android:authorities=\"$fromPackageName",
"android:authorities=\"$packageName" "android:authorities=\"$packageName",
).replace( ).replace(
"$fromPackageName.permission.C2D_MESSAGE", "$fromPackageName.permission.C2D_MESSAGE",
"$packageName.permission.C2D_MESSAGE" "$packageName.permission.C2D_MESSAGE",
).replace( ).replace(
"$fromPackageName.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION", "$fromPackageName.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION",
"$packageName.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION" "$packageName.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION",
).replace( ).replace(
"com.google.android.c2dm", "com.google.android.c2dm",
"$gmsCoreVendor.android.c2dm" "$gmsCoreVendor.android.c2dm",
).replace( ).replace(
"</queries>", "</queries>",
"<package android:name=\"$gmsCoreVendor.android.gms\"/></queries>" "<package android:name=\"$gmsCoreVendor.android.gms\"/></queries>",
) ),
) )
} }
} }

View File

@@ -11,23 +11,25 @@ import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.iface.Method import com.android.tools.smali.dexlib2.iface.Method
abstract class BaseIntegrationsPatch( abstract class BaseIntegrationsPatch(
private val hooks: Set<IntegrationsFingerprint> private val hooks: Set<IntegrationsFingerprint>,
) : BytecodePatch(hooks) { ) : BytecodePatch(hooks) {
@Deprecated( @Deprecated(
"Use the constructor without the integrationsDescriptor parameter", "Use the constructor without the integrationsDescriptor parameter",
ReplaceWith("AbstractIntegrationsPatch(hooks)") ReplaceWith("BaseIntegrationsPatch(hooks)"),
) )
@Suppress("UNUSED_PARAMETER") @Suppress("UNUSED_PARAMETER")
constructor( constructor(
integrationsDescriptor: String, integrationsDescriptor: String,
hooks: Set<IntegrationsFingerprint> hooks: Set<IntegrationsFingerprint>,
) : this(hooks) ) : this(hooks)
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
if (context.findClass(INTEGRATIONS_CLASS_DESCRIPTOR) == null) throw PatchException( if (context.findClass(INTEGRATIONS_CLASS_DESCRIPTOR) == null) {
"Integrations have not been merged yet. This patch can not succeed without merging the integrations." throw PatchException(
) "Integrations have not been merged yet. This patch can not succeed without merging the integrations.",
)
}
hooks.forEach { hook -> hooks.forEach { hook ->
hook.invoke(INTEGRATIONS_CLASS_DESCRIPTOR) hook.invoke(INTEGRATIONS_CLASS_DESCRIPTOR)
@@ -47,14 +49,14 @@ abstract class BaseIntegrationsPatch(
opcodes: Iterable<Opcode?>? = null, opcodes: Iterable<Opcode?>? = null,
strings: Iterable<String>? = null, strings: Iterable<String>? = null,
customFingerprint: ((methodDef: Method, classDef: ClassDef) -> Boolean)? = null, customFingerprint: ((methodDef: Method, classDef: ClassDef) -> Boolean)? = null,
private val contextRegisterResolver: (Method) -> Int = object : IRegisterResolver {} private val contextRegisterResolver: (Method) -> Int = object : IRegisterResolver {},
) : MethodFingerprint( ) : MethodFingerprint(
returnType, returnType,
accessFlags, accessFlags,
parameters, parameters,
opcodes, opcodes,
strings, strings,
customFingerprint customFingerprint,
) { ) {
fun invoke(integrationsDescriptor: String) { fun invoke(integrationsDescriptor: String) {
result?.mutableMethod?.let { method -> result?.mutableMethod?.let { method ->
@@ -63,7 +65,7 @@ abstract class BaseIntegrationsPatch(
method.addInstruction( method.addInstruction(
0, 0,
"sput-object v$contextRegister, " + "sput-object v$contextRegister, " +
"$integrationsDescriptor->context:Landroid/content/Context;" "$integrationsDescriptor->context:Landroid/content/Context;",
) )
} ?: throw PatchException("Could not find hook target fingerprint.") } ?: throw PatchException("Could not find hook target fingerprint.")
} }
@@ -76,4 +78,4 @@ abstract class BaseIntegrationsPatch(
private companion object { private companion object {
private const val INTEGRATIONS_CLASS_DESCRIPTOR = "Lapp/revanced/integrations/shared/Utils;" private const val INTEGRATIONS_CLASS_DESCRIPTOR = "Lapp/revanced/integrations/shared/Utils;"
} }
} }

View File

@@ -7,7 +7,6 @@ import java.util.*
import java.util.concurrent.Executors import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
object ResourceMappingPatch : ResourcePatch() { object ResourceMappingPatch : ResourcePatch() {
internal lateinit var resourceMappings: List<ResourceElement> internal lateinit var resourceMappings: List<ResourceElement>
private set private set
@@ -17,7 +16,7 @@ object ResourceMappingPatch : ResourcePatch() {
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
// save the file in memory to concurrently read from // save the file in memory to concurrently read from
val resourceXmlFile = context["res/values/public.xml"].readBytes() val resourceXmlFile = context.get("res/values/public.xml").readBytes()
// create a synchronized list to store the resource mappings // create a synchronized list to store the resource mappings
val mappings = Collections.synchronizedList(mutableListOf<ResourceElement>()) val mappings = Collections.synchronizedList(mutableListOf<ResourceElement>())
@@ -25,7 +24,9 @@ object ResourceMappingPatch : ResourcePatch() {
for (threadIndex in 0 until THREAD_COUNT) { for (threadIndex in 0 until THREAD_COUNT) {
threadPoolExecutor.execute thread@{ threadPoolExecutor.execute thread@{
context.xmlEditor[resourceXmlFile.inputStream()].use { editor -> context.xmlEditor[resourceXmlFile.inputStream()].use { editor ->
val resources = editor.file.documentElement.childNodes val document = editor.file
val resources = document.documentElement.childNodes
val resourcesLength = resources.length val resourcesLength = resources.length
val jobSize = resourcesLength / THREAD_COUNT val jobSize = resourcesLength / THREAD_COUNT
@@ -59,4 +60,4 @@ object ResourceMappingPatch : ResourcePatch() {
} }
data class ResourceElement(val type: String, val name: String, val id: Long) data class ResourceElement(val type: String, val name: String, val id: Long)
} }

View File

@@ -21,16 +21,18 @@ import java.io.Closeable
*/ */
abstract class BaseSettingsResourcePatch( abstract class BaseSettingsResourcePatch(
private val rootPreference: Pair<IntentPreference, String>? = null, private val rootPreference: Pair<IntentPreference, String>? = null,
dependencies: Set<PatchClass> = emptySet() dependencies: Set<PatchClass> = emptySet(),
) : ResourcePatch( ) : ResourcePatch(
dependencies = setOf(AddResourcesPatch::class) + dependencies dependencies = setOf(AddResourcesPatch::class) + dependencies,
), MutableSet<BasePreference> by mutableSetOf(), Closeable { ),
MutableSet<BasePreference> by mutableSetOf(),
Closeable {
private lateinit var context: ResourceContext private lateinit var context: ResourceContext
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
context.copyResources( context.copyResources(
"settings", "settings",
ResourceGroup("xml", "revanced_prefs.xml") ResourceGroup("xml", "revanced_prefs.xml"),
) )
this.context = context this.context = context
@@ -49,14 +51,18 @@ abstract class BaseSettingsResourcePatch(
// Add the root preference to an existing fragment if needed. // Add the root preference to an existing fragment if needed.
rootPreference?.let { (intentPreference, fragment) -> rootPreference?.let { (intentPreference, fragment) ->
context.xmlEditor["res/xml/$fragment.xml"].use { context.xmlEditor["res/xml/$fragment.xml"].use { editor ->
it.getNode("PreferenceScreen").addPreference(intentPreference) val document = editor.file
document.getNode("PreferenceScreen").addPreference(intentPreference)
} }
} }
// Add all preferences to the ReVanced fragment. // Add all preferences to the ReVanced fragment.
context.xmlEditor["res/xml/revanced_prefs.xml"].use { editor -> context.xmlEditor["res/xml/revanced_prefs.xml"].use { editor ->
val revancedPreferenceScreenNode = editor.getNode("PreferenceScreen") val document = editor.file
val revancedPreferenceScreenNode = document.getNode("PreferenceScreen")
forEach { revancedPreferenceScreenNode.addPreference(it) } forEach { revancedPreferenceScreenNode.addPreference(it) }
} }
} }

View File

@@ -10,7 +10,7 @@ import org.w3c.dom.Element
@Patch( @Patch(
name = "Custom theme", name = "Custom theme",
description = "Applies a custom theme.", description = "Applies a custom theme.",
compatiblePackages = [CompatiblePackage("com.spotify.music")] compatiblePackages = [CompatiblePackage("com.spotify.music")],
) )
@Suppress("unused") @Suppress("unused")
object CustomThemePatch : ResourcePatch() { object CustomThemePatch : ResourcePatch() {
@@ -19,7 +19,7 @@ object CustomThemePatch : ResourcePatch() {
default = "@android:color/black", default = "@android:color/black",
title = "Primary background color", title = "Primary background color",
description = "The background color. Can be a hex color or a resource reference.", description = "The background color. Can be a hex color or a resource reference.",
required = true required = true,
) )
private var backgroundColorSecondary by stringPatchOption( private var backgroundColorSecondary by stringPatchOption(
@@ -27,7 +27,7 @@ object CustomThemePatch : ResourcePatch() {
default = "#ff282828", default = "#ff282828",
title = "Secondary background color", title = "Secondary background color",
description = "The secondary background color. (e.g. search box, artist & podcast). Can be a hex color or a resource reference.", description = "The secondary background color. (e.g. search box, artist & podcast). Can be a hex color or a resource reference.",
required = true required = true,
) )
private var accentColor by stringPatchOption( private var accentColor by stringPatchOption(
@@ -35,16 +35,17 @@ object CustomThemePatch : ResourcePatch() {
default = "#ff1ed760", default = "#ff1ed760",
title = "Accent color", title = "Accent color",
description = "The accent color ('Spotify green' by default). Can be a hex color or a resource reference.", description = "The accent color ('Spotify green' by default). Can be a hex color or a resource reference.",
required = true required = true,
) )
private var accentColorPressed by stringPatchOption( private var accentColorPressed by stringPatchOption(
key = "accentColorPressed", key = "accentColorPressed",
default = "#ff169c46", default = "#ff169c46",
title = "Pressed dark theme accent color", title = "Pressed dark theme accent color",
description = "The color when accented buttons are pressed, by default slightly darker than accent. " description =
+ "Can be a hex color or a resource reference.", "The color when accented buttons are pressed, by default slightly darker than accent. " +
required = true "Can be a hex color or a resource reference.",
required = true,
) )
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
@@ -54,23 +55,27 @@ object CustomThemePatch : ResourcePatch() {
val accentColorPressed = accentColorPressed!! val accentColorPressed = accentColorPressed!!
context.xmlEditor["res/values/colors.xml"].use { editor -> context.xmlEditor["res/values/colors.xml"].use { editor ->
val resourcesNode = editor.file.getElementsByTagName("resources").item(0) as Element val document = editor.file
val resourcesNode = document.getElementsByTagName("resources").item(0) as Element
for (i in 0 until resourcesNode.childNodes.length) { for (i in 0 until resourcesNode.childNodes.length) {
val node = resourcesNode.childNodes.item(i) as? Element ?: continue val node = resourcesNode.childNodes.item(i) as? Element ?: continue
node.textContent = when (node.getAttribute("name")) { node.textContent =
"dark_base_background_elevated_base", "design_dark_default_color_background", when (node.getAttribute("name")) {
"design_dark_default_color_surface", "gray_7", "gray_background", "gray_layer", "dark_base_background_elevated_base", "design_dark_default_color_background",
"sthlm_blk" -> backgroundColor "design_dark_default_color_surface", "gray_7", "gray_background", "gray_layer",
"sthlm_blk",
-> backgroundColor
"gray_15" -> backgroundColorSecondary "gray_15" -> backgroundColorSecondary
"dark_brightaccent_background_base", "dark_base_text_brightaccent", "green_light" -> accentColor "dark_brightaccent_background_base", "dark_base_text_brightaccent", "green_light" -> accentColor
"dark_brightaccent_background_press" -> accentColorPressed "dark_brightaccent_background_press" -> accentColorPressed
else -> continue else -> continue
} }
} }
} }
} }

View File

@@ -23,19 +23,19 @@ import com.android.tools.smali.dexlib2.iface.reference.MethodReference
dependencies = [IntegrationsPatch::class, SettingsPatch::class], dependencies = [IntegrationsPatch::class, SettingsPatch::class],
compatiblePackages = [ compatiblePackages = [
CompatiblePackage("com.ss.android.ugc.trill"), CompatiblePackage("com.ss.android.ugc.trill"),
CompatiblePackage("com.zhiliaoapp.musically") CompatiblePackage("com.zhiliaoapp.musically"),
], ],
use = false use = false,
) )
@Suppress("unused") @Suppress("unused")
object SpoofSimPatch : BytecodePatch() { object SpoofSimPatch : BytecodePatch(emptySet()) {
private val replacements = hashMapOf( private val replacements = hashMapOf(
"getSimCountryIso" to "getCountryIso", "getSimCountryIso" to "getCountryIso",
"getNetworkCountryIso" to "getCountryIso", "getNetworkCountryIso" to "getCountryIso",
"getSimOperator" to "getOperator", "getSimOperator" to "getOperator",
"getNetworkOperator" to "getOperator", "getNetworkOperator" to "getOperator",
"getSimOperatorName" to "getOperatorName", "getSimOperatorName" to "getOperatorName",
"getNetworkOperatorName" to "getOperatorName" "getNetworkOperatorName" to "getOperatorName",
) )
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
@@ -85,7 +85,7 @@ object SpoofSimPatch : BytecodePatch() {
with(SettingsStatusLoadFingerprint.result!!.mutableMethod) { with(SettingsStatusLoadFingerprint.result!!.mutableMethod) {
addInstruction( addInstruction(
0, 0,
"invoke-static {}, Lapp/revanced/integrations/tiktok/settings/SettingsStatus;->enableSimSpoof()V" "invoke-static {}, Lapp/revanced/integrations/tiktok/settings/SettingsStatus;->enableSimSpoof()V",
) )
} }
} }
@@ -99,7 +99,7 @@ object SpoofSimPatch : BytecodePatch() {
""" """
invoke-static {v$resultReg}, Lapp/revanced/integrations/tiktok/spoof/sim/SpoofSimPatch;->$replacement(Ljava/lang/String;)Ljava/lang/String; invoke-static {v$resultReg}, Lapp/revanced/integrations/tiktok/spoof/sim/SpoofSimPatch;->$replacement(Ljava/lang/String;)Ljava/lang/String;
move-result-object v$resultReg move-result-object v$resultReg
""" """,
) )
} }
} }

View File

@@ -10,11 +10,11 @@ import app.revanced.patches.tumblr.timelinefilter.TimelineFilterPatch
name = "Disable dashboard ads", name = "Disable dashboard ads",
description = "Disables ads in the dashboard.", description = "Disables ads in the dashboard.",
compatiblePackages = [CompatiblePackage("com.tumblr")], compatiblePackages = [CompatiblePackage("com.tumblr")],
dependencies = [TimelineFilterPatch::class] dependencies = [TimelineFilterPatch::class],
) )
@Suppress("unused") @Suppress("unused")
object DisableDashboardAds : BytecodePatch() { object DisableDashboardAds : BytecodePatch(emptySet()) {
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
// The timeline object types are filtered by their name in the TimelineObjectType enum. // The timeline object types are filtered by their name in the TimelineObjectType enum.
// This is often different from the "object_type" returned in the api (noted in comments here) // This is often different from the "object_type" returned in the api (noted in comments here)
arrayOf( arrayOf(
@@ -29,9 +29,9 @@ object DisableDashboardAds : BytecodePatch() {
"DISPLAY_IO_INTERSCROLLER_AD", // "display_io_interscroller" "DISPLAY_IO_INTERSCROLLER_AD", // "display_io_interscroller"
"DISPLAY_IO_HEADLINE_VIDEO_AD", // "display_io_headline_video" "DISPLAY_IO_HEADLINE_VIDEO_AD", // "display_io_headline_video"
"FACEBOOK_BIDDAABLE", // "facebook_biddable_sdk_ad" "FACEBOOK_BIDDAABLE", // "facebook_biddable_sdk_ad"
"GOOGLE_NATIVE" // "google_native_ad" "GOOGLE_NATIVE", // "google_native_ad"
).forEach { ).forEach {
TimelineFilterPatch.addObjectTypeFilter(it) TimelineFilterPatch.addObjectTypeFilter(it)
} }
} }
} }

View File

@@ -10,13 +10,13 @@ import app.revanced.patches.tumblr.featureflags.OverrideFeatureFlagsPatch
name = "Disable in-app update", name = "Disable in-app update",
description = "Disables the in-app update check and update prompt.", description = "Disables the in-app update check and update prompt.",
dependencies = [OverrideFeatureFlagsPatch::class], dependencies = [OverrideFeatureFlagsPatch::class],
compatiblePackages = [CompatiblePackage("com.tumblr")] compatiblePackages = [CompatiblePackage("com.tumblr")],
) )
@Suppress("unused") @Suppress("unused")
object DisableInAppUpdatePatch : BytecodePatch() { object DisableInAppUpdatePatch : BytecodePatch(emptySet()) {
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
// Before checking for updates using Google Play core AppUpdateManager, the value of this feature flag is checked. // Before checking for updates using Google Play core AppUpdateManager, the value of this feature flag is checked.
// If this flag is false or the last update check was today and no update check is performed. // If this flag is false or the last update check was today and no update check is performed.
OverrideFeatureFlagsPatch.addOverride("inAppUpdate", "false") OverrideFeatureFlagsPatch.addOverride("inAppUpdate", "false")
} }
} }

View File

@@ -11,10 +11,10 @@ import app.revanced.patches.tumblr.timelinefilter.TimelineFilterPatch
name = "Disable Tumblr Live", name = "Disable Tumblr Live",
description = "Disable the Tumblr Live tab button and dashboard carousel.", description = "Disable the Tumblr Live tab button and dashboard carousel.",
dependencies = [OverrideFeatureFlagsPatch::class, TimelineFilterPatch::class], dependencies = [OverrideFeatureFlagsPatch::class, TimelineFilterPatch::class],
compatiblePackages = [CompatiblePackage("com.tumblr")] compatiblePackages = [CompatiblePackage("com.tumblr")],
) )
@Suppress("unused") @Suppress("unused")
object DisableTumblrLivePatch : BytecodePatch() { object DisableTumblrLivePatch : BytecodePatch(emptySet()) {
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
// Hide the LIVE_MARQUEE timeline element that appears in the feed // Hide the LIVE_MARQUEE timeline element that appears in the feed
// Called "live_marquee" in api response // Called "live_marquee" in api response
@@ -23,4 +23,4 @@ object DisableTumblrLivePatch : BytecodePatch() {
// Hide the Tab button for Tumblr Live by forcing the feature flag to false // Hide the Tab button for Tumblr Live by forcing the feature flag to false
OverrideFeatureFlagsPatch.addOverride("liveStreaming", "false") OverrideFeatureFlagsPatch.addOverride("liveStreaming", "false")
} }
} }

View File

@@ -2,27 +2,36 @@ package app.revanced.patches.twitter.interaction.downloads
import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.getInstructions import app.revanced.patcher.extensions.InstructionExtensions.getInstructions
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patcher.fingerprint.MethodFingerprint
import app.revanced.patcher.fingerprint.MethodFingerprintResult import app.revanced.patcher.fingerprint.MethodFingerprintResult
import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.CompatiblePackage import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.twitter.interaction.downloads.fingerprints.BuildMediaOptionsSheetFingerprint
import app.revanced.patches.twitter.interaction.downloads.fingerprints.ConstructMediaOptionsSheetFingerprint import app.revanced.patches.twitter.interaction.downloads.fingerprints.ConstructMediaOptionsSheetFingerprint
import app.revanced.patches.twitter.interaction.downloads.fingerprints.ShowDownloadVideoUpsellBottomSheetFingerprint import app.revanced.patches.twitter.interaction.downloads.fingerprints.ShowDownloadVideoUpsellBottomSheetFingerprint
import app.revanced.util.exception import app.revanced.util.exception
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
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
@Patch( @Patch(
name = "Unlock downloads", name = "Unlock downloads",
description = "Unlocks the ability to download any video.", description = "Unlocks the ability to download any video. GIFs can be downloaded via the menu on long press.",
compatiblePackages = [CompatiblePackage("com.twitter.android")] compatiblePackages = [CompatiblePackage("com.twitter.android")],
) )
@Suppress("unused") @Suppress("unused")
object UnlockDownloadsPatch : BytecodePatch( object UnlockDownloadsPatch : BytecodePatch(
setOf(ConstructMediaOptionsSheetFingerprint, ShowDownloadVideoUpsellBottomSheetFingerprint) setOf(
ConstructMediaOptionsSheetFingerprint,
ShowDownloadVideoUpsellBottomSheetFingerprint,
BuildMediaOptionsSheetFingerprint,
),
) { ) {
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
fun MethodFingerprint.patch(getRegisterAndIndex: MethodFingerprintResult.() -> Pair<Int, Int>) = result?.let { fun MethodFingerprint.patch(getRegisterAndIndex: MethodFingerprintResult.() -> Pair<Int, Int>) = result?.let {
@@ -46,5 +55,29 @@ object UnlockDownloadsPatch : BytecodePatch(
showDownloadButtonIndex to register showDownloadButtonIndex to register
} }
// Make GIFs downloadable.
BuildMediaOptionsSheetFingerprint.result?.let {
val scanResult = it.scanResult.patternScanResult!!
it.mutableMethod.apply {
val checkMediaTypeIndex = scanResult.startIndex
val checkMediaTypeInstruction = getInstruction<TwoRegisterInstruction>(checkMediaTypeIndex)
// Treat GIFs as videos.
addInstructionsWithLabels(
checkMediaTypeIndex + 1,
"""
const/4 v${checkMediaTypeInstruction.registerB}, 0x2 # GIF
if-eq v${checkMediaTypeInstruction.registerA}, v${checkMediaTypeInstruction.registerB}, :video
""",
ExternalLabel("video", getInstruction(scanResult.endIndex)),
)
// Remove media.isDownloadable check.
removeInstruction(
getInstructions().first { insn -> insn.opcode == Opcode.IGET_BOOLEAN }.location.index + 1,
)
}
} ?: throw BuildMediaOptionsSheetFingerprint.exception
} }
} }

View File

@@ -0,0 +1,14 @@
package app.revanced.patches.twitter.interaction.downloads.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.Opcode
internal object BuildMediaOptionsSheetFingerprint : MethodFingerprint(
opcodes = listOf(
Opcode.IF_EQ,
Opcode.SGET_OBJECT,
Opcode.GOTO_16,
Opcode.NEW_INSTANCE,
),
strings = listOf("resources.getString(R.string.post_video)"),
)

View File

@@ -11,12 +11,12 @@ import java.nio.file.Files
@Patch( @Patch(
name = "Dynamic color", name = "Dynamic color",
description = "Replaces the default X (Formerly Twitter) Blue with the user's Material You palette.", description = "Replaces the default X (Formerly Twitter) Blue with the user's Material You palette.",
compatiblePackages = [CompatiblePackage("com.twitter.android")] compatiblePackages = [CompatiblePackage("com.twitter.android")],
) )
@Suppress("unused") @Suppress("unused")
object DynamicColorPatch : ResourcePatch() { object DynamicColorPatch : ResourcePatch() {
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
val resDirectory = context["res"] val resDirectory = context.get("res")
if (!resDirectory.isDirectory) throw PatchException("The res folder can not be found.") if (!resDirectory.isDirectory) throw PatchException("The res folder can not be found.")
val valuesV31Directory = resDirectory.resolve("values-v31") val valuesV31Directory = resDirectory.resolve("values-v31")
@@ -28,7 +28,7 @@ object DynamicColorPatch : ResourcePatch() {
listOf(valuesV31Directory, valuesNightV31Directory).forEach { it -> listOf(valuesV31Directory, valuesNightV31Directory).forEach { it ->
val colorsXml = it.resolve("colors.xml") val colorsXml = it.resolve("colors.xml")
if(!colorsXml.exists()) { if (!colorsXml.exists()) {
FileWriter(colorsXml).use { FileWriter(colorsXml).use {
it.write("<?xml version=\"1.0\" encoding=\"utf-8\"?><resources></resources>") it.write("<?xml version=\"1.0\" encoding=\"utf-8\"?><resources></resources>")
} }
@@ -46,7 +46,7 @@ object DynamicColorPatch : ResourcePatch() {
"twitter_blue_opacity_30" to "@android:color/system_accent1_100", "twitter_blue_opacity_30" to "@android:color/system_accent1_100",
"twitter_blue_opacity_50" to "@android:color/system_accent1_200", "twitter_blue_opacity_50" to "@android:color/system_accent1_200",
"twitter_blue_opacity_58" to "@android:color/system_accent1_300", "twitter_blue_opacity_58" to "@android:color/system_accent1_300",
"deep_transparent_twitter_blue" to "@android:color/system_accent1_200" "deep_transparent_twitter_blue" to "@android:color/system_accent1_200",
).forEach { (k, v) -> ).forEach { (k, v) ->
val colorElement = document.createElement("color") val colorElement = document.createElement("color")
@@ -66,7 +66,7 @@ object DynamicColorPatch : ResourcePatch() {
"twitter_blue_opacity_30" to "@android:color/system_accent1_50", "twitter_blue_opacity_30" to "@android:color/system_accent1_50",
"twitter_blue_opacity_50" to "@android:color/system_accent1_100", "twitter_blue_opacity_50" to "@android:color/system_accent1_100",
"twitter_blue_opacity_58" to "@android:color/system_accent1_200", "twitter_blue_opacity_58" to "@android:color/system_accent1_200",
"deep_transparent_twitter_blue" to "@android:color/system_accent1_200" "deep_transparent_twitter_blue" to "@android:color/system_accent1_200",
).forEach { (k, v) -> ).forEach { (k, v) ->
val colorElement = document.createElement("color") val colorElement = document.createElement("color")

View File

@@ -4,7 +4,7 @@ import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patches.twitter.misc.hook.json.JsonHookPatch import app.revanced.patches.twitter.misc.hook.json.JsonHookPatch
abstract class BaseHookPatch(private val hookClassDescriptor: String) : BytecodePatch() { abstract class BaseHookPatch(private val hookClassDescriptor: String) : BytecodePatch(emptySet()) {
override fun execute(context: BytecodeContext) = override fun execute(context: BytecodeContext) =
JsonHookPatch.hooks.addHook(JsonHookPatch.Hook(context, hookClassDescriptor)) JsonHookPatch.hooks.addHook(JsonHookPatch.Hook(context, hookClassDescriptor))
} }

View File

@@ -7,9 +7,8 @@ import app.revanced.patches.twitter.misc.hook.patch.BaseHookPatch
@Patch( @Patch(
name = "Hide ads", name = "Hide ads",
description = "Hides ads.",
dependencies = [JsonHookPatch::class], dependencies = [JsonHookPatch::class],
compatiblePackages = [CompatiblePackage("com.twitter.android")] compatiblePackages = [CompatiblePackage("com.twitter.android")],
) )
@Suppress("unused") @Suppress("unused")
object HideAdsHookPatch : BaseHookPatch("Lapp/revanced/integrations/twitter/patches/hook/patch/ads/AdsHook;") object HideAdsHookPatch : BaseHookPatch("Lapp/revanced/integrations/twitter/patches/hook/patch/ads/AdsHook;")

View File

@@ -0,0 +1,35 @@
package app.revanced.patches.twitter.misc.links
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.twitter.misc.links.fingerprints.OpenLinkFingerprint
import app.revanced.util.exception
@Patch(
name = "Open links with app chooser",
description = "Instead of opening links directly, open them with an app chooser. " +
"As a result you can select a browser to open the link with.",
compatiblePackages = [CompatiblePackage("com.twitter.android")],
use = false,
)
@Suppress("unused")
object OpenLinksWithAppChooserPatch : BytecodePatch(
setOf(OpenLinkFingerprint),
) {
private const val METHOD_REFERENCE =
"Lapp/revanced/integrations/twitter/patches/links/OpenLinksWithAppChooserPatch;->" +
"openWithChooser(Landroid/content/Context;Landroid/content/Intent;)V"
override fun execute(context: BytecodeContext) {
OpenLinkFingerprint.result?.mutableMethod?.addInstructions(
0,
"""
invoke-static { p0, p1 }, $METHOD_REFERENCE
return-void
""",
) ?: throw OpenLinkFingerprint.exception
}
}

View File

@@ -0,0 +1,8 @@
package app.revanced.patches.twitter.misc.links.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint
internal object OpenLinkFingerprint : MethodFingerprint(
returnType = "V",
parameters = listOf("Landroid/content/Context;", "Landroid/content/Intent;", "Landroid/os/Bundle;"),
)

View File

@@ -1,20 +1,20 @@
package app.revanced.patches.vsco.misc.pro package app.revanced.patches.vsco.misc.pro
import app.revanced.util.exception
import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.CompatiblePackage import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.vsco.misc.pro.fingerprints.RevCatSubscriptionFingerprint import app.revanced.patches.vsco.misc.pro.fingerprints.RevCatSubscriptionFingerprint
import app.revanced.util.exception
@Patch( @Patch(
name = "Unlock pro", name = "Unlock pro",
description = "Unlocks pro features.", description = "Unlocks pro features.",
compatiblePackages = [CompatiblePackage("com.vsco.cam")] compatiblePackages = [CompatiblePackage("com.vsco.cam", ["345"])],
) )
object UnlockProPatch : BytecodePatch( object UnlockProPatch : BytecodePatch(
setOf(RevCatSubscriptionFingerprint) setOf(RevCatSubscriptionFingerprint),
) { ) {
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
RevCatSubscriptionFingerprint.result?.mutableMethod?.apply { RevCatSubscriptionFingerprint.result?.mutableMethod?.apply {
@@ -23,7 +23,7 @@ object UnlockProPatch : BytecodePatch(
0, 0,
""" """
const p1, 0x1 const p1, 0x1
""" """,
) )
} ?: throw RevCatSubscriptionFingerprint.exception } ?: throw RevCatSubscriptionFingerprint.exception
} }

View File

@@ -1,7 +1,5 @@
package app.revanced.patches.youtube.ad.general package app.revanced.patches.youtube.ad.general
import app.revanced.util.findMutableMethodOf
import app.revanced.util.injectHideViewCall
import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.CompatiblePackage import app.revanced.patcher.patch.annotation.CompatiblePackage
@@ -9,6 +7,8 @@ import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.shared.misc.fix.verticalscroll.VerticalScrollPatch import app.revanced.patches.shared.misc.fix.verticalscroll.VerticalScrollPatch
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.util.findMutableMethodOf
import app.revanced.util.injectHideViewCall
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction31i import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction31i
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c
@@ -20,11 +20,12 @@ import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c
HideGetPremiumPatch::class, HideGetPremiumPatch::class,
HideAdsResourcePatch::class, HideAdsResourcePatch::class,
VerticalScrollPatch::class, VerticalScrollPatch::class,
FixBackToExitGesturePatch::class FixBackToExitGesturePatch::class,
], ],
compatiblePackages = [ compatiblePackages = [
CompatiblePackage( CompatiblePackage(
"com.google.android.youtube", [ "com.google.android.youtube",
[
"18.32.39", "18.32.39",
"18.37.36", "18.37.36",
"18.38.44", "18.38.44",
@@ -37,30 +38,33 @@ import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c
"19.02.39", "19.02.39",
"19.03.35", "19.03.35",
"19.03.36", "19.03.36",
"19.04.37" "19.04.37",
] ],
) ),
] ],
) )
@Suppress("unused") @Suppress("unused")
object HideAdsPatch : BytecodePatch() { object HideAdsPatch : BytecodePatch(emptySet()) {
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
context.classes.forEach { classDef -> context.classes.forEach { classDef ->
classDef.methods.forEach { method -> classDef.methods.forEach { method ->
with(method.implementation) { with(method.implementation) {
this?.instructions?.forEachIndexed { index, instruction -> this?.instructions?.forEachIndexed { index, instruction ->
if (instruction.opcode != Opcode.CONST) if (instruction.opcode != Opcode.CONST) {
return@forEachIndexed return@forEachIndexed
}
// Instruction to store the id adAttribution into a register // Instruction to store the id adAttribution into a register
if ((instruction as Instruction31i).wideLiteral != HideAdsResourcePatch.adAttributionId) if ((instruction as Instruction31i).wideLiteral != HideAdsResourcePatch.adAttributionId) {
return@forEachIndexed return@forEachIndexed
}
val insertIndex = index + 1 val insertIndex = index + 1
// Call to get the view with the id adAttribution // Call to get the view with the id adAttribution
with(instructions.elementAt(insertIndex)) { with(instructions.elementAt(insertIndex)) {
if (opcode != Opcode.INVOKE_VIRTUAL) if (opcode != Opcode.INVOKE_VIRTUAL) {
return@forEachIndexed return@forEachIndexed
}
// Hide the view // Hide the view
val viewRegister = (this as Instruction35c).registerC val viewRegister = (this as Instruction35c).registerC
@@ -71,7 +75,7 @@ object HideAdsPatch : BytecodePatch() {
insertIndex, insertIndex,
viewRegister, viewRegister,
"Lapp/revanced/integrations/youtube/patches/components/AdsFilter;", "Lapp/revanced/integrations/youtube/patches/components/AdsFilter;",
"hideAdAttributionView" "hideAdAttributionView",
) )
} }
} }

View File

@@ -13,28 +13,29 @@ import app.revanced.patches.youtube.video.information.VideoInformationPatch
dependencies = [ dependencies = [
CopyVideoUrlResourcePatch::class, CopyVideoUrlResourcePatch::class,
PlayerControlsBytecodePatch::class, PlayerControlsBytecodePatch::class,
VideoInformationPatch::class VideoInformationPatch::class,
], ],
compatiblePackages = [ compatiblePackages = [
CompatiblePackage( CompatiblePackage(
"com.google.android.youtube", [ "com.google.android.youtube",
[
"18.48.39", "18.48.39",
"18.49.37", "18.49.37",
"19.01.34", "19.01.34",
"19.02.39", "19.02.39",
"19.03.35", "19.03.35",
"19.03.36", "19.03.36",
"19.04.37" "19.04.37",
] ],
) ),
] ],
) )
@Suppress("unused") @Suppress("unused")
object CopyVideoUrlBytecodePatch : BytecodePatch() { object CopyVideoUrlBytecodePatch : BytecodePatch(emptySet()) {
private const val INTEGRATIONS_PLAYER_PACKAGE = "Lapp/revanced/integrations/youtube/videoplayer" private const val INTEGRATIONS_PLAYER_PACKAGE = "Lapp/revanced/integrations/youtube/videoplayer"
private val BUTTONS_DESCRIPTORS = listOf( private val BUTTONS_DESCRIPTORS = listOf(
"$INTEGRATIONS_PLAYER_PACKAGE/CopyVideoUrlButton;", "$INTEGRATIONS_PLAYER_PACKAGE/CopyVideoUrlButton;",
"$INTEGRATIONS_PLAYER_PACKAGE/CopyVideoUrlTimestampButton;" "$INTEGRATIONS_PLAYER_PACKAGE/CopyVideoUrlTimestampButton;",
) )
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
@@ -44,4 +45,4 @@ object CopyVideoUrlBytecodePatch : BytecodePatch() {
PlayerControlsBytecodePatch.injectVisibilityCheckCall("$descriptor->changeVisibility(Z)V") PlayerControlsBytecodePatch.injectVisibilityCheckCall("$descriptor->changeVisibility(Z)V")
} }
} }
} }

View File

@@ -13,24 +13,25 @@ import app.revanced.patches.youtube.video.information.VideoInformationPatch
dependencies = [ dependencies = [
ExternalDownloadsResourcePatch::class, ExternalDownloadsResourcePatch::class,
PlayerControlsBytecodePatch::class, PlayerControlsBytecodePatch::class,
VideoInformationPatch::class VideoInformationPatch::class,
], ],
compatiblePackages = [ compatiblePackages = [
CompatiblePackage( CompatiblePackage(
"com.google.android.youtube", [ "com.google.android.youtube",
[
"18.48.39", "18.48.39",
"18.49.37", "18.49.37",
"19.01.34", "19.01.34",
"19.02.39", "19.02.39",
"19.03.35", "19.03.35",
"19.03.36", "19.03.36",
"19.04.37" "19.04.37",
] ],
), ),
] ],
) )
@Suppress("unused") @Suppress("unused")
object ExternalDownloadsBytecodePatch : BytecodePatch() { object ExternalDownloadsBytecodePatch : BytecodePatch(emptySet()) {
private const val BUTTON_DESCRIPTOR = "Lapp/revanced/integrations/youtube/videoplayer/ExternalDownloadButton;" private const val BUTTON_DESCRIPTOR = "Lapp/revanced/integrations/youtube/videoplayer/ExternalDownloadButton;"
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
@@ -39,13 +40,15 @@ object ExternalDownloadsBytecodePatch : BytecodePatch() {
*/ */
PlayerControlsBytecodePatch.initializeControl( PlayerControlsBytecodePatch.initializeControl(
"$BUTTON_DESCRIPTOR->initializeButton(Landroid/view/View;)V") "$BUTTON_DESCRIPTOR->initializeButton(Landroid/view/View;)V",
)
/* /*
add code to change the visibility of the control add code to change the visibility of the control
*/ */
PlayerControlsBytecodePatch.injectVisibilityCheckCall( PlayerControlsBytecodePatch.injectVisibilityCheckCall(
"$BUTTON_DESCRIPTOR->changeVisibility(Z)V") "$BUTTON_DESCRIPTOR->changeVisibility(Z)V",
)
} }
} }

View File

@@ -17,7 +17,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
@Patch( @Patch(
name = "Enable slide to seek", name = "Enable slide to seek",
description = "Adds an option to enable slide to seek instead of playing at 2x speed when pressing and holding in the video player. Including this patch may cause issues with tapping or double tapping the video player overlay.", description = "Adds an option to enable slide to seek instead of playing at 2x speed when pressing and holding in the video player. Including this patch may cause issues with tapping or double tapping the video player overlay.",
dependencies = [IntegrationsPatch::class, SettingsPatch::class, AddResourcesPatch::class], dependencies = [IntegrationsPatch::class, SettingsPatch::class, AddResourcesPatch::class],
compatiblePackages = [ compatiblePackages = [
CompatiblePackage( CompatiblePackage(

View File

@@ -15,9 +15,9 @@ import java.nio.file.Files
name = "Custom branding", name = "Custom branding",
description = "Applies a custom app name and icon. Defaults to \"YouTube ReVanced\" and the ReVanced logo.", description = "Applies a custom app name and icon. Defaults to \"YouTube ReVanced\" and the ReVanced logo.",
compatiblePackages = [ compatiblePackages = [
CompatiblePackage("com.google.android.youtube") CompatiblePackage("com.google.android.youtube"),
], ],
use = false use = false,
) )
@Suppress("unused") @Suppress("unused")
object CustomBrandingPatch : ResourcePatch() { object CustomBrandingPatch : ResourcePatch() {
@@ -28,7 +28,7 @@ object CustomBrandingPatch : ResourcePatch() {
"adaptiveproduct_youtube_background_color_108", "adaptiveproduct_youtube_background_color_108",
"adaptiveproduct_youtube_foreground_color_108", "adaptiveproduct_youtube_foreground_color_108",
"ic_launcher", "ic_launcher",
"ic_launcher_round" "ic_launcher_round",
).map { "$it.png" }.toTypedArray() ).map { "$it.png" }.toTypedArray()
private val mipmapDirectories = arrayOf( private val mipmapDirectories = arrayOf(
@@ -36,7 +36,7 @@ object CustomBrandingPatch : ResourcePatch() {
"xxhdpi", "xxhdpi",
"xhdpi", "xhdpi",
"hdpi", "hdpi",
"mdpi" "mdpi",
).map { "mipmap-$it" } ).map { "mipmap-$it" }
private var appName by stringPatchOption( private var appName by stringPatchOption(
@@ -49,7 +49,7 @@ object CustomBrandingPatch : ResourcePatch() {
"YouTube" to "YouTube", "YouTube" to "YouTube",
), ),
title = "App name", title = "App name",
description = "The name of the app." description = "The name of the app.",
) )
private var icon by stringPatchOption( private var icon by stringPatchOption(
@@ -58,14 +58,16 @@ object CustomBrandingPatch : ResourcePatch() {
values = mapOf("ReVanced Logo" to REVANCED_ICON), values = mapOf("ReVanced Logo" to REVANCED_ICON),
title = "App icon", title = "App icon",
description = """ description = """
The path to a folder containing the following folders: The icon to apply to the app.
If a path to a folder is provided, the folder must contain the following folders:
${mipmapDirectories.joinToString("\n") { "- $it" }} ${mipmapDirectories.joinToString("\n") { "- $it" }}
Each of these folders has to have the following files: Each of these folders must contain the following files:
${iconResourceFileNames.joinToString("\n") { "- $it" }} ${iconResourceFileNames.joinToString("\n") { "- $it" }}
""".trimIndentMultiline() """.trimIndentMultiline(),
) )
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
@@ -73,12 +75,13 @@ object CustomBrandingPatch : ResourcePatch() {
// Change the app icon. // Change the app icon.
mipmapDirectories.map { directory -> mipmapDirectories.map { directory ->
ResourceGroup( ResourceGroup(
directory, *iconResourceFileNames directory,
*iconResourceFileNames,
) )
}.let { resourceGroups -> }.let { resourceGroups ->
if (icon != REVANCED_ICON) { if (icon != REVANCED_ICON) {
val path = File(icon) val path = File(icon)
val resourceDirectory = context["res"] val resourceDirectory = context.get("res")
resourceGroups.forEach { group -> resourceGroups.forEach { group ->
val fromDirectory = path.resolve(group.resourceDirectoryName) val fromDirectory = path.resolve(group.resourceDirectoryName)
@@ -87,23 +90,25 @@ object CustomBrandingPatch : ResourcePatch() {
group.resources.forEach { iconFileName -> group.resources.forEach { iconFileName ->
Files.write( Files.write(
toDirectory.resolve(iconFileName).toPath(), toDirectory.resolve(iconFileName).toPath(),
fromDirectory.resolve(iconFileName).readBytes() fromDirectory.resolve(iconFileName).readBytes(),
) )
} }
} }
} else resourceGroups.forEach { context.copyResources("custom-branding", it) } } else {
resourceGroups.forEach { context.copyResources("custom-branding", it) }
}
} }
} }
appName?.let { name -> appName?.let { name ->
// Change the app name. // Change the app name.
val manifest = context["AndroidManifest.xml"] val manifest = context.get("AndroidManifest.xml")
manifest.writeText( manifest.writeText(
manifest.readText() manifest.readText()
.replace( .replace(
"android:label=\"@string/application_name", "android:label=\"@string/application_name",
"android:label=\"$name" "android:label=\"$name",
) ),
) )
} }
} }

View File

@@ -15,62 +15,69 @@ import java.io.File
name = "Change header", name = "Change header",
description = "Applies a custom header in the top left corner within the app. Defaults to the ReVanced header.", description = "Applies a custom header in the top left corner within the app. Defaults to the ReVanced header.",
compatiblePackages = [ compatiblePackages = [
CompatiblePackage("com.google.android.youtube") CompatiblePackage("com.google.android.youtube"),
], ],
use = false use = false,
) )
@Suppress("unused") @Suppress("unused")
object ChangeHeaderPatch : ResourcePatch() { object ChangeHeaderPatch : ResourcePatch() {
private const val HEADER_NAME = "yt_wordmark_header" private const val HEADER_FILE_NAME = "yt_wordmark_header"
private const val PREMIUM_HEADER_NAME = "yt_premium_wordmark_header" private const val PREMIUM_HEADER_FILE_NAME = "yt_premium_wordmark_header"
private const val REVANCED_HEADER_NAME = "ReVanced"
private const val REVANCED_BORDERLESS_HEADER_NAME = "ReVanced (borderless logo)"
private val targetResourceDirectoryNames = arrayOf( private const val HEADER_OPTION = "header*"
"xxxhdpi", private const val PREMIUM_HEADER_OPTION = "premium*header"
"xxhdpi", private const val REVANCED_HEADER_OPTION = "revanced*"
"xhdpi", private const val REVANCED_BORDERLESS_HEADER_OPTION = "revanced*borderless"
"mdpi",
"hdpi", private val targetResourceDirectoryNames = mapOf(
).map { dpi -> "xxxhdpi" to "512px x 192px",
"drawable-$dpi" "xxhdpi" to "387px x 144px",
} "xhdpi" to "258px x 96px",
"hdpi" to "194px x 72px",
"mdpi" to "129px x 48px",
).map { (dpi, dim) ->
"drawable-$dpi" to dim
}.toMap()
private val variants = arrayOf("light", "dark") private val variants = arrayOf("light", "dark")
private val header by stringPatchOption( private val header by stringPatchOption(
key = "header", key = "header",
default = REVANCED_BORDERLESS_HEADER_NAME, default = REVANCED_BORDERLESS_HEADER_OPTION,
values = mapOf( values = mapOf(
"YouTube" to HEADER_NAME, "YouTube" to HEADER_OPTION,
"YouTube Premium" to PREMIUM_HEADER_NAME, "YouTube Premium" to PREMIUM_HEADER_OPTION,
"ReVanced" to REVANCED_HEADER_NAME, "ReVanced" to REVANCED_HEADER_OPTION,
"ReVanced (borderless logo)" to REVANCED_BORDERLESS_HEADER_NAME, "ReVanced (borderless logo)" to REVANCED_BORDERLESS_HEADER_OPTION,
), ),
title = "Header", title = "Header",
description = """ description = """
Either a header name or a path to a custom header folder to use in the top bar. The header to apply to the app.
The path to a folder must contain one or more of the following folders matching the DPI of your device:
If a path to a folder is provided, the folder must contain one or more of the following folders, depending on the DPI of the device:
${targetResourceDirectoryNames.keys.joinToString("\n") { "- $it" }}
Each of the folders must contain all of the following files:
${variants.joinToString("\n") { variant -> "- ${HEADER_FILE_NAME}_$variant.png" }}
${targetResourceDirectoryNames.joinToString("\n") { "- $it" }} The image dimensions must be as follows:
${targetResourceDirectoryNames.map { (dpi, dim) -> "- $dpi: $dim" }.joinToString("\n")}
These folders must contain the following files:
${variants.joinToString("\n") { variant -> "- ${HEADER_NAME}_$variant.png" }}
""".trimIndentMultiline(), """.trimIndentMultiline(),
required = true, required = true,
) )
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
// The directories to copy the header to. // The directories to copy the header to.
val targetResourceDirectories = targetResourceDirectoryNames.mapNotNull { val targetResourceDirectories = targetResourceDirectoryNames.keys.mapNotNull {
context["res"].resolve(it).takeIf(File::exists) context.get("res").resolve(it).takeIf(File::exists)
} }
// The files to replace in the target directories. // The files to replace in the target directories.
val targetResourceFiles = targetResourceDirectoryNames.map { directoryName -> val targetResourceFiles = targetResourceDirectoryNames.keys.map { directoryName ->
ResourceGroup( ResourceGroup(
directoryName, directoryName,
*variants.map { variant -> "${HEADER_NAME}_$variant.png" }.toTypedArray() *variants.map { variant -> "${HEADER_FILE_NAME}_$variant.png" }.toTypedArray(),
) )
} }
@@ -89,8 +96,8 @@ object ChangeHeaderPatch : ResourcePatch() {
} }
// Functions to overwrite the header to the different variants. // Functions to overwrite the header to the different variants.
val toPremium = { overwriteFromTo(PREMIUM_HEADER_NAME, HEADER_NAME) } val toPremium = { overwriteFromTo(PREMIUM_HEADER_FILE_NAME, HEADER_FILE_NAME) }
val toHeader = { overwriteFromTo(HEADER_NAME, PREMIUM_HEADER_NAME) } val toHeader = { overwriteFromTo(HEADER_FILE_NAME, PREMIUM_HEADER_FILE_NAME) }
val toReVanced = { val toReVanced = {
// Copy the ReVanced header to the resource directories. // Copy the ReVanced header to the resource directories.
targetResourceFiles.forEach { context.copyResources("change-header/revanced", it) } targetResourceFiles.forEach { context.copyResources("change-header/revanced", it) }
@@ -106,32 +113,38 @@ object ChangeHeaderPatch : ResourcePatch() {
toHeader() toHeader()
} }
val toCustom = { val toCustom = {
var copiedReplacementImages = false val sourceFolders = File(header!!).listFiles { file -> file.isDirectory }
// For all the resource groups in the custom header folder, copy them to the resource directories. ?: throw PatchException("The provided path is not a directory: $header")
File(header!!).listFiles { file -> file.isDirectory }?.forEach { folder ->
val targetDirectory = context["res"].resolve(folder.name)
// Skip if the target directory (DPI) doesn't exist.
if (!targetDirectory.exists()) return@forEach
folder.listFiles { file -> file.isFile }?.forEach { var copiedFiles = false
val targetResourceFile = targetDirectory.resolve(it.name)
it.copyTo(targetResourceFile, true) // For each source folder, copy the files to the target resource directories.
copiedReplacementImages = true sourceFolders.forEach { dpiSourceFolder ->
val targetDpiFolder = context.get("res").resolve(dpiSourceFolder.name)
if (!targetDpiFolder.exists()) return@forEach
val imgSourceFiles = dpiSourceFolder.listFiles { file -> file.isFile }!!
imgSourceFiles.forEach { imgSourceFile ->
val imgTargetFile = targetDpiFolder.resolve(imgSourceFile.name)
imgSourceFile.copyTo(imgTargetFile, true)
copiedFiles = true
} }
} }
if (!copiedReplacementImages) throw PatchException("Could not find any custom images resources in directory: $header") if (!copiedFiles) {
throw PatchException("No header files were copied from the provided path: $header.")
}
// Overwrite the premium with the custom header as well. // Overwrite the premium with the custom header as well.
toHeader() toHeader()
} }
when (header) { when (header) {
HEADER_NAME -> toHeader HEADER_OPTION -> toHeader
PREMIUM_HEADER_NAME -> toPremium PREMIUM_HEADER_OPTION -> toPremium
REVANCED_HEADER_NAME -> toReVanced REVANCED_HEADER_OPTION -> toReVanced
REVANCED_BORDERLESS_HEADER_NAME -> toReVancedBorderless REVANCED_BORDERLESS_HEADER_OPTION -> toReVancedBorderless
else -> toCustom else -> toCustom
}() }()
} }

View File

@@ -17,13 +17,13 @@ import app.revanced.patches.youtube.misc.settings.SettingsPatch
dependencies = [ dependencies = [
IntegrationsPatch::class, IntegrationsPatch::class,
SettingsPatch::class, SettingsPatch::class,
AddResourcesPatch::class AddResourcesPatch::class,
], ],
compatiblePackages = [ compatiblePackages = [
CompatiblePackage("com.google.android.youtube") CompatiblePackage("com.google.android.youtube"),
] ],
) )
object HideCastButtonPatch : BytecodePatch() { object HideCastButtonPatch : BytecodePatch(emptySet()) {
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
AddResourcesPatch(this::class) AddResourcesPatch(this::class)
@@ -38,7 +38,7 @@ object HideCastButtonPatch : BytecodePatch() {
""" """
invoke-static {p1}, Lapp/revanced/integrations/youtube/patches/HideCastButtonPatch;->getCastButtonOverrideV2(I)I invoke-static {p1}, Lapp/revanced/integrations/youtube/patches/HideCastButtonPatch;->getCastButtonOverrideV2(I)I
move-result p1 move-result p1
""" """,
) )
} ?: throw PatchException("setVisibility method not found.") } ?: throw PatchException("setVisibility method not found.")
} }

View File

@@ -12,7 +12,8 @@ import org.w3c.dom.Element
description = "Removes the dark background surrounding the video player controls.", description = "Removes the dark background surrounding the video player controls.",
compatiblePackages = [ compatiblePackages = [
CompatiblePackage( CompatiblePackage(
"com.google.android.youtube", [ "com.google.android.youtube",
[
"18.32.39", "18.32.39",
"18.37.36", "18.37.36",
"18.38.44", "18.38.44",
@@ -25,11 +26,11 @@ import org.w3c.dom.Element
"19.02.39", "19.02.39",
"19.03.35", "19.03.35",
"19.03.36", "19.03.36",
"19.04.37" "19.04.37",
] ],
) ),
], ],
use = false use = false,
) )
@Suppress("unused") @Suppress("unused")
object PlayerControlsBackgroundPatch : ResourcePatch() { object PlayerControlsBackgroundPatch : ResourcePatch() {
@@ -37,7 +38,9 @@ object PlayerControlsBackgroundPatch : ResourcePatch() {
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
context.xmlEditor[RESOURCE_FILE_PATH].use { editor -> context.xmlEditor[RESOURCE_FILE_PATH].use { editor ->
editor.file.doRecursively node@{ node -> val document = editor.file
document.doRecursively node@{ node ->
if (node !is Element) return@node if (node !is Element) return@node
node.getAttributeNode("android:color")?.let { attribute -> node.getAttributeNode("android:color")?.let { attribute ->

View File

@@ -9,7 +9,7 @@ import app.revanced.patches.youtube.misc.settings.SettingsPatch
import org.w3c.dom.Element import org.w3c.dom.Element
@Patch(dependencies = [SettingsPatch::class, ResourceMappingPatch::class]) @Patch(dependencies = [SettingsPatch::class, ResourceMappingPatch::class])
internal object SeekbarColorResourcePatch : ResourcePatch(){ internal object SeekbarColorResourcePatch : ResourcePatch() {
internal var reelTimeBarPlayedColorId = -1L internal var reelTimeBarPlayedColorId = -1L
internal var inlineTimeBarColorizedBarPlayedColorDarkId = -1L internal var inlineTimeBarColorizedBarPlayedColorDarkId = -1L
internal var inlineTimeBarPlayedNotHighlightedColorId = -1L internal var inlineTimeBarPlayedNotHighlightedColorId = -1L
@@ -18,7 +18,7 @@ internal object SeekbarColorResourcePatch : ResourcePatch(){
fun findColorResource(resourceName: String): Long { fun findColorResource(resourceName: String): Long {
return ResourceMappingPatch.resourceMappings return ResourceMappingPatch.resourceMappings
.find { it.type == "color" && it.name == resourceName }?.id .find { it.type == "color" && it.name == resourceName }?.id
?: throw PatchException("Could not find color resource: $resourceName") ?: throw PatchException("Could not find color resource: $resourceName")
} }
reelTimeBarPlayedColorId = reelTimeBarPlayedColorId =
@@ -30,15 +30,19 @@ internal object SeekbarColorResourcePatch : ResourcePatch(){
// Edit the resume playback drawable and replace the progress bar with a custom drawable // Edit the resume playback drawable and replace the progress bar with a custom drawable
context.xmlEditor["res/drawable/resume_playback_progressbar_drawable.xml"].use { editor -> context.xmlEditor["res/drawable/resume_playback_progressbar_drawable.xml"].use { editor ->
val layerList = editor.file.getElementsByTagName("layer-list").item(0) as Element val document = editor.file
val layerList = document.getElementsByTagName("layer-list").item(0) as Element
val progressNode = layerList.getElementsByTagName("item").item(1) as Element val progressNode = layerList.getElementsByTagName("item").item(1) as Element
if (!progressNode.getAttributeNode("android:id").value.endsWith("progress")) { if (!progressNode.getAttributeNode("android:id").value.endsWith("progress")) {
throw PatchException("Could not find progress bar") throw PatchException("Could not find progress bar")
} }
val scaleNode = progressNode.getElementsByTagName("scale").item(0) as Element val scaleNode = progressNode.getElementsByTagName("scale").item(0) as Element
val shapeNode = scaleNode.getElementsByTagName("shape").item(0) as Element val shapeNode = scaleNode.getElementsByTagName("shape").item(0) as Element
val replacementNode = editor.file.createElement( val replacementNode =
"app.revanced.integrations.youtube.patches.theme.ProgressBarDrawable") document.createElement(
"app.revanced.integrations.youtube.patches.theme.ProgressBarDrawable",
)
scaleNode.replaceChild(replacementNode, shapeNode) scaleNode.replaceChild(replacementNode, shapeNode)
} }
} }

View File

@@ -17,26 +17,25 @@ import app.revanced.util.inputStreamFromBundledResource
dependencies = [ dependencies = [
SettingsPatch::class, SettingsPatch::class,
ResourceMappingPatch::class, ResourceMappingPatch::class,
AddResourcesPatch::class AddResourcesPatch::class,
] ],
) )
internal object SponsorBlockResourcePatch : ResourcePatch() { internal object SponsorBlockResourcePatch : ResourcePatch() {
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
AddResourcesPatch(this::class) AddResourcesPatch(this::class)
SettingsPatch.PreferenceScreen.LAYOUT.addPreferences( SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
IntentPreference( IntentPreference(
"revanced_sb_settings", "revanced_sb_settings",
intent = SettingsPatch.newIntent("revanced_sb_settings_intent") intent = SettingsPatch.newIntent("revanced_sb_settings_intent"),
) ),
) )
arrayOf( arrayOf(
ResourceGroup( ResourceGroup(
"layout", "layout",
"revanced_sb_inline_sponsor_overlay.xml", "revanced_sb_inline_sponsor_overlay.xml",
"revanced_sb_new_segment.xml", "revanced_sb_new_segment.xml",
"revanced_sb_skip_sponsor_button.xml" "revanced_sb_skip_sponsor_button.xml",
), ),
ResourceGroup( ResourceGroup(
// required resource for back button, because when the base APK is used, this resource will not exist // required resource for back button, because when the base APK is used, this resource will not exist
@@ -46,37 +45,49 @@ internal object SponsorBlockResourcePatch : ResourcePatch() {
"revanced_sb_edit.xml", "revanced_sb_edit.xml",
"revanced_sb_logo.xml", "revanced_sb_logo.xml",
"revanced_sb_publish.xml", "revanced_sb_publish.xml",
"revanced_sb_voting.xml" "revanced_sb_voting.xml",
), ),
ResourceGroup( ResourceGroup(
// required resource for back button, because when the base APK is used, this resource will not exist // required resource for back button, because when the base APK is used, this resource will not exist
"drawable-xxxhdpi", "quantum_ic_skip_next_white_24.png" "drawable-xxxhdpi",
) "quantum_ic_skip_next_white_24.png",
),
).forEach { resourceGroup -> ).forEach { resourceGroup ->
context.copyResources("sponsorblock", resourceGroup) context.copyResources("sponsorblock", resourceGroup)
} }
// copy nodes from host resources to their real xml files // copy nodes from host resources to their real xml files
val hostingResourceStream = inputStreamFromBundledResource( val hostingResourceStream =
"sponsorblock", inputStreamFromBundledResource(
"host/layout/youtube_controls_layout.xml" "sponsorblock",
)!! "host/layout/youtube_controls_layout.xml",
)!!
var modifiedControlsLayout = false var modifiedControlsLayout = false
val targetXmlEditor = context.xmlEditor["res/layout/youtube_controls_layout.xml"] val editor = context.xmlEditor["res/layout/youtube_controls_layout.xml"]
"RelativeLayout".copyXmlNode( "RelativeLayout".copyXmlNode(
context.xmlEditor[hostingResourceStream], context.xmlEditor[hostingResourceStream],
targetXmlEditor editor,
).also { ).also {
val children = targetXmlEditor.file.getElementsByTagName("RelativeLayout").item(0).childNodes val document = editor.file
val children = document.getElementsByTagName("RelativeLayout").item(0).childNodes
// Replace the startOf with the voting button view so that the button does not overlap // Replace the startOf with the voting button view so that the button does not overlap
for (i in 1 until children.length) { for (i in 1 until children.length) {
val view = children.item(i) val view = children.item(i)
// Replace the attribute for a specific node only // Replace the attribute for a specific node only
if (!(view.hasAttributes() && view.attributes.getNamedItem("android:id").nodeValue.endsWith("live_chat_overlay_button"))) continue if (!(
view.hasAttributes() &&
view.attributes.getNamedItem(
"android:id",
).nodeValue.endsWith("live_chat_overlay_button")
)
) {
continue
}
// voting button id from the voting button view from the youtube_controls_layout.xml host file // voting button id from the voting button view from the youtube_controls_layout.xml host file
val votingButtonId = "@+id/revanced_sb_voting_button" val votingButtonId = "@+id/revanced_sb_voting_button"
@@ -90,4 +101,4 @@ internal object SponsorBlockResourcePatch : ResourcePatch() {
if (!modifiedControlsLayout) throw PatchException("Could not modify controls layout") if (!modifiedControlsLayout) throw PatchException("Could not modify controls layout")
} }
} }

View File

@@ -20,8 +20,8 @@ import org.w3c.dom.Element
SettingsPatch::class, SettingsPatch::class,
ResourceMappingPatch::class, ResourceMappingPatch::class,
SeekbarPreferencesPatch::class, SeekbarPreferencesPatch::class,
AddResourcesPatch::class AddResourcesPatch::class,
] ],
) )
internal object ThemeResourcePatch : ResourcePatch() { internal object ThemeResourcePatch : ResourcePatch() {
private const val SPLASH_BACKGROUND_COLOR = "revanced_splash_background_color" private const val SPLASH_BACKGROUND_COLOR = "revanced_splash_background_color"
@@ -31,28 +31,31 @@ internal object ThemeResourcePatch : ResourcePatch() {
SeekbarPreferencesPatch.addPreferences( SeekbarPreferencesPatch.addPreferences(
SwitchPreference("revanced_seekbar_custom_color"), SwitchPreference("revanced_seekbar_custom_color"),
TextPreference("revanced_seekbar_custom_color_value", inputType = InputType.TEXT_CAP_CHARACTERS) TextPreference("revanced_seekbar_custom_color_value", inputType = InputType.TEXT_CAP_CHARACTERS),
) )
// Edit theme colors via resources. // Edit theme colors via resources.
context.xmlEditor["res/values/colors.xml"].use { editor -> context.xmlEditor["res/values/colors.xml"].use { editor ->
val resourcesNode = editor.file.getElementsByTagName("resources").item(0) as Element val document = editor.file
val resourcesNode = document.getElementsByTagName("resources").item(0) as Element
val children = resourcesNode.childNodes val children = resourcesNode.childNodes
for (i in 0 until children.length) { for (i in 0 until children.length) {
val node = children.item(i) as? Element ?: continue val node = children.item(i) as? Element ?: continue
node.textContent = when (node.getAttribute("name")) { node.textContent =
"yt_black0", "yt_black1", "yt_black1_opacity95", "yt_black1_opacity98", "yt_black2", "yt_black3", when (node.getAttribute("name")) {
"yt_black4", "yt_status_bar_background_dark", "material_grey_850" "yt_black0", "yt_black1", "yt_black1_opacity95", "yt_black1_opacity98", "yt_black2", "yt_black3",
-> darkThemeBackgroundColor ?: continue "yt_black4", "yt_status_bar_background_dark", "material_grey_850",
-> darkThemeBackgroundColor ?: continue
"yt_white1", "yt_white1_opacity95", "yt_white1_opacity98", "yt_white1", "yt_white1_opacity95", "yt_white1_opacity98",
"yt_white2", "yt_white3", "yt_white4", "yt_white2", "yt_white3", "yt_white4",
-> lightThemeBackgroundColor ?: continue -> lightThemeBackgroundColor ?: continue
else -> continue else -> continue
} }
} }
} }
@@ -68,14 +71,17 @@ internal object ThemeResourcePatch : ResourcePatch() {
// Edit splash screen files and change the background color, // Edit splash screen files and change the background color,
// if the background colors are set. // if the background colors are set.
if (darkThemeBackgroundColor != null && lightThemeBackgroundColor != null) { if (darkThemeBackgroundColor != null && lightThemeBackgroundColor != null) {
val splashScreenResourceFiles = listOf( val splashScreenResourceFiles =
"res/drawable/quantum_launchscreen_youtube.xml", listOf(
"res/drawable-sw600dp/quantum_launchscreen_youtube.xml" "res/drawable/quantum_launchscreen_youtube.xml",
) "res/drawable-sw600dp/quantum_launchscreen_youtube.xml",
)
splashScreenResourceFiles.forEach editSplashScreen@{ resourceFile -> splashScreenResourceFiles.forEach editSplashScreen@{ resourceFile ->
context.xmlEditor[resourceFile].use { context.xmlEditor[resourceFile].use { editor ->
val layerList = it.file.getElementsByTagName("layer-list").item(0) as Element val document = editor.file
val layerList = document.getElementsByTagName("layer-list").item(0) as Element
val childNodes = layerList.childNodes val childNodes = layerList.childNodes
for (i in 0 until childNodes.length) { for (i in 0 until childNodes.length) {
@@ -89,24 +95,26 @@ internal object ThemeResourcePatch : ResourcePatch() {
} }
} }
} }
} }
private fun addColorResource( private fun addColorResource(
context: ResourceContext, context: ResourceContext,
resourceFile: String, resourceFile: String,
colorName: String, colorName: String,
colorValue: String colorValue: String,
) { ) {
context.xmlEditor[resourceFile].use { context.xmlEditor[resourceFile].use { editor ->
val resourcesNode = it.file.getElementsByTagName("resources").item(0) as Element val document = editor.file
val resourcesNode = document.getElementsByTagName("resources").item(0) as Element
resourcesNode.appendChild( resourcesNode.appendChild(
it.file.createElement("color").apply { document.createElement("color").apply {
setAttribute("name", colorName) setAttribute("name", colorName)
setAttribute("category", "color") setAttribute("category", "color")
textContent = colorValue textContent = colorValue
}) },
)
} }
} }
} }

View File

@@ -5,9 +5,9 @@ import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
internal object LithoFilterFingerprint : MethodFingerprint( internal object LithoFilterFingerprint : MethodFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC or AccessFlags.CONSTRUCTOR, accessFlags = AccessFlags.STATIC or AccessFlags.CONSTRUCTOR,
returnType = "V", returnType = "V",
customFingerprint = { _, classDef -> customFingerprint = { _, classDef ->
classDef.type.endsWith("LithoFilterPatch;") classDef.type.endsWith("LithoFilterPatch;")
} },
) )

View File

@@ -18,11 +18,11 @@ object BottomControlsResourcePatch : ResourcePatch(), Closeable {
private var lastLeftOf = "fullscreen_button" private var lastLeftOf = "fullscreen_button"
private lateinit var resourceContext: ResourceContext private lateinit var resourceContext: ResourceContext
private lateinit var targetXmlEditor: DomFileEditor private lateinit var targetDocumentEditor: DomFileEditor
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
resourceContext = context resourceContext = context
targetXmlEditor = context.xmlEditor[TARGET_RESOURCE] targetDocumentEditor = context.xmlEditor[TARGET_RESOURCE]
bottomUiContainerResourceId = ResourceMappingPatch.resourceMappings bottomUiContainerResourceId = ResourceMappingPatch.resourceMappings
.single { it.type == "id" && it.name == "bottom_ui_container_stub" }.id .single { it.type == "id" && it.name == "bottom_ui_container_stub" }.id
@@ -34,21 +34,21 @@ object BottomControlsResourcePatch : ResourcePatch(), Closeable {
* @param resourceDirectoryName The name of the directory containing the hosting resource. * @param resourceDirectoryName The name of the directory containing the hosting resource.
*/ */
fun addControls(resourceDirectoryName: String) { fun addControls(resourceDirectoryName: String) {
val sourceXmlEditor = resourceContext.xmlEditor[ val sourceDocumentEditor = resourceContext.xmlEditor[
this::class.java.classLoader.getResourceAsStream( this::class.java.classLoader.getResourceAsStream(
"$resourceDirectoryName/host/layout/$TARGET_RESOURCE_NAME" "$resourceDirectoryName/host/layout/$TARGET_RESOURCE_NAME",
)!! )!!,
] ]
val sourceDocument = sourceDocumentEditor.file
val targetDocument = targetDocumentEditor.file
val targetElement = "android.support.constraint.ConstraintLayout" val targetElementTag = "android.support.constraint.ConstraintLayout"
val hostElements = sourceXmlEditor.file.getElementsByTagName(targetElement).item(0).childNodes val sourceElements = sourceDocument.getElementsByTagName(targetElementTag).item(0).childNodes
val targetElement = targetDocument.getElementsByTagName(targetElementTag).item(0)
val destinationResourceFile = targetXmlEditor.file for (index in 1 until sourceElements.length) {
val destinationElement = destinationResourceFile.getElementsByTagName(targetElement).item(0) val element = sourceElements.item(index).cloneNode(true)
for (index in 1 until hostElements.length) {
val element = hostElements.item(index).cloneNode(true)
// If the element has no attributes there's no point to adding it to the destination. // If the element has no attributes there's no point to adding it to the destination.
if (!element.hasAttributes()) continue if (!element.hasAttributes()) continue
@@ -63,11 +63,11 @@ object BottomControlsResourcePatch : ResourcePatch(), Closeable {
lastLeftOf = element.attributes.getNamedItem("android:id").nodeValue.substring(nameSpaceLength) lastLeftOf = element.attributes.getNamedItem("android:id").nodeValue.substring(nameSpaceLength)
// Add the element. // Add the element.
destinationResourceFile.adoptNode(element) targetDocument.adoptNode(element)
destinationElement.appendChild(element) targetElement.appendChild(element)
} }
sourceXmlEditor.close() sourceDocumentEditor.close()
} }
override fun close() = targetXmlEditor.close() override fun close() = targetDocumentEditor.close()
} }

View File

@@ -12,12 +12,13 @@ import org.w3c.dom.Element
object SettingsResourcePatch : BaseSettingsResourcePatch( object SettingsResourcePatch : BaseSettingsResourcePatch(
IntentPreference( IntentPreference(
"revanced_settings", "revanced_settings",
intent = SettingsPatch.newIntent("revanced_settings_intent") intent = SettingsPatch.newIntent("revanced_settings_intent"),
) to "settings_fragment", ) to "settings_fragment",
dependencies = setOf( dependencies =
setOf(
ResourceMappingPatch::class, ResourceMappingPatch::class,
AddResourcesPatch::class, AddResourcesPatch::class,
) ),
) { ) {
// Used for a fingerprint from SettingsPatch. // Used for a fingerprint from SettingsPatch.
internal var appearanceStringId = -1L internal var appearanceStringId = -1L
@@ -28,12 +29,13 @@ object SettingsResourcePatch : BaseSettingsResourcePatch(
AddResourcesPatch(this::class) AddResourcesPatch(this::class)
// Used for a fingerprint from SettingsPatch. // Used for a fingerprint from SettingsPatch.
appearanceStringId = ResourceMappingPatch.resourceMappings.find { appearanceStringId =
it.type == "string" && it.name == "app_theme_appearance_dark" ResourceMappingPatch.resourceMappings.find {
}!!.id it.type == "string" && it.name == "app_theme_appearance_dark"
}!!.id
arrayOf( arrayOf(
ResourceGroup("layout", "revanced_settings_with_toolbar.xml") ResourceGroup("layout", "revanced_settings_with_toolbar.xml"),
).forEach { resourceGroup -> ).forEach { resourceGroup ->
context.copyResources("settings", resourceGroup) context.copyResources("settings", resourceGroup)
} }
@@ -42,19 +44,21 @@ object SettingsResourcePatch : BaseSettingsResourcePatch(
// Some devices freak out if undeclared data is passed to an intent, // Some devices freak out if undeclared data is passed to an intent,
// and this change appears to fix the issue. // and this change appears to fix the issue.
context.xmlEditor["AndroidManifest.xml"].use { editor -> context.xmlEditor["AndroidManifest.xml"].use { editor ->
val document = editor.file
// A xml regular-expression would probably work better than this manual searching. // A xml regular-expression would probably work better than this manual searching.
val manifestNodes = editor.file.getElementsByTagName("manifest").item(0).childNodes val manifestNodes = document.getElementsByTagName("manifest").item(0).childNodes
for (i in 0..manifestNodes.length) { for (i in 0..manifestNodes.length) {
val node = manifestNodes.item(i) val node = manifestNodes.item(i)
if (node != null && node.nodeName == "application") { if (node != null && node.nodeName == "application") {
val applicationNodes = node.childNodes val applicationNodes = node.childNodes
for (j in 0..applicationNodes.length) { for (j in 0..applicationNodes.length) {
val applicationChild = applicationNodes.item(j) val applicationChild = applicationNodes.item(j)
if (applicationChild is Element && applicationChild.nodeName == "activity" if (applicationChild is Element && applicationChild.nodeName == "activity" &&
&& applicationChild.getAttribute("android:name") == "com.google.android.libraries.social.licenses.LicenseActivity" applicationChild.getAttribute("android:name") == "com.google.android.libraries.social.licenses.LicenseActivity"
) { ) {
val intentFilter = editor.file.createElement("intent-filter") val intentFilter = document.createElement("intent-filter")
val mimeType = editor.file.createElement("data") val mimeType = document.createElement("data")
mimeType.setAttribute("android:mimeType", "text/plain") mimeType.setAttribute("android:mimeType", "text/plain")
intentFilter.appendChild(mimeType) intentFilter.appendChild(mimeType)
applicationChild.appendChild(intentFilter) applicationChild.appendChild(intentFilter)
@@ -65,4 +69,4 @@ object SettingsResourcePatch : BaseSettingsResourcePatch(
} }
} }
} }
} }

View File

@@ -13,20 +13,21 @@ import app.revanced.patches.youtube.video.speed.remember.RememberPlaybackSpeedPa
dependencies = [CustomPlaybackSpeedPatch::class, RememberPlaybackSpeedPatch::class], dependencies = [CustomPlaybackSpeedPatch::class, RememberPlaybackSpeedPatch::class],
compatiblePackages = [ compatiblePackages = [
CompatiblePackage( CompatiblePackage(
"com.google.android.youtube", [ "com.google.android.youtube",
[
"18.48.39", "18.48.39",
"18.49.37", "18.49.37",
"19.01.34", "19.01.34",
"19.02.39", "19.02.39",
"19.03.35", "19.03.35",
"19.03.36", "19.03.36",
"19.04.37" "19.04.37",
] ],
) ),
] ],
) )
@Suppress("unused") @Suppress("unused")
object PlaybackSpeedPatch : BytecodePatch() { object PlaybackSpeedPatch : BytecodePatch(emptySet()) {
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
// All patches this patch depends on succeed. // All patches this patch depends on succeed.
} }

View File

@@ -2,7 +2,6 @@ package app.revanced.util
import app.revanced.patcher.data.ResourceContext import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.util.DomFileEditor import app.revanced.patcher.util.DomFileEditor
import app.revanced.patches.all.misc.resources.AddResourcesPatch
import app.revanced.util.resource.BaseResource import app.revanced.util.resource.BaseResource
import org.w3c.dom.Node import org.w3c.dom.Node
import org.w3c.dom.NodeList import org.w3c.dom.NodeList
@@ -25,9 +24,10 @@ fun Node.childElementsSequence() = this.childNodes.asSequence().filter { it.node
/** /**
* Performs the given [action] on each child element. * Performs the given [action] on each child element.
*/ */
fun Node.forEachChildElement(action: (Node) -> Unit) = childElementsSequence().forEach { fun Node.forEachChildElement(action: (Node) -> Unit) =
action(it) childElementsSequence().forEach {
} action(it)
}
/** /**
* Recursively traverse the DOM tree starting from the given root node. * Recursively traverse the DOM tree starting from the given root node.
@@ -45,15 +45,19 @@ fun Node.doRecursively(action: (Node) -> Unit) {
* @param sourceResourceDirectory The source resource directory name. * @param sourceResourceDirectory The source resource directory name.
* @param resources The resources to copy. * @param resources The resources to copy.
*/ */
fun ResourceContext.copyResources(sourceResourceDirectory: String, vararg resources: ResourceGroup) { fun ResourceContext.copyResources(
val targetResourceDirectory = this["res"] sourceResourceDirectory: String,
vararg resources: ResourceGroup,
) {
val targetResourceDirectory = this.get("res")
for (resourceGroup in resources) { for (resourceGroup in resources) {
resourceGroup.resources.forEach { resource -> resourceGroup.resources.forEach { resource ->
val resourceFile = "${resourceGroup.resourceDirectoryName}/$resource" val resourceFile = "${resourceGroup.resourceDirectoryName}/$resource"
Files.copy( Files.copy(
inputStreamFromBundledResource(sourceResourceDirectory, resourceFile)!!, inputStreamFromBundledResource(sourceResourceDirectory, resourceFile)!!,
targetResourceDirectory.resolve(resourceFile).toPath(), StandardCopyOption.REPLACE_EXISTING targetResourceDirectory.resolve(resourceFile).toPath(),
StandardCopyOption.REPLACE_EXISTING,
) )
} }
} }
@@ -61,7 +65,7 @@ fun ResourceContext.copyResources(sourceResourceDirectory: String, vararg resour
internal fun inputStreamFromBundledResource( internal fun inputStreamFromBundledResource(
sourceResourceDirectory: String, sourceResourceDirectory: String,
resourceFile: String resourceFile: String,
): InputStream? = classLoader.getResourceAsStream("$sourceResourceDirectory/$resourceFile") ): InputStream? = classLoader.getResourceAsStream("$sourceResourceDirectory/$resourceFile")
/** /**
@@ -80,20 +84,15 @@ class ResourceGroup(val resourceDirectoryName: String, vararg val resources: Str
fun ResourceContext.iterateXmlNodeChildren( fun ResourceContext.iterateXmlNodeChildren(
resource: String, resource: String,
targetTag: String, targetTag: String,
callback: (node: Node) -> Unit callback: (node: Node) -> Unit,
) = ) = xmlEditor[classLoader.getResourceAsStream(resource)!!].use { editor ->
xmlEditor[classLoader.getResourceAsStream(resource)!!].use { val document = editor.file
val stringsNode = it.file.getElementsByTagName(targetTag).item(0).childNodes
for (i in 1 until stringsNode.length - 1) callback(stringsNode.item(i))
}
val stringsNode = document.getElementsByTagName(targetTag).item(0).childNodes
for (i in 1 until stringsNode.length - 1) callback(stringsNode.item(i))
}
/** // TODO: After the migration to the new patcher, remove the following code and replace it with the commented code below.
* Copies the specified node of the source [DomFileEditor] to the target [DomFileEditor].
* @param source the source [DomFileEditor].
* @param target the target [DomFileEditor]-
* @return AutoCloseable that closes the target [DomFileEditor]s.
*/
fun String.copyXmlNode(source: DomFileEditor, target: DomFileEditor): AutoCloseable { fun String.copyXmlNode(source: DomFileEditor, target: DomFileEditor): AutoCloseable {
val hostNodes = source.file.getElementsByTagName(this).item(0).childNodes val hostNodes = source.file.getElementsByTagName(this).item(0).childNodes
@@ -112,14 +111,56 @@ fun String.copyXmlNode(source: DomFileEditor, target: DomFileEditor): AutoClosea
} }
} }
// /**
// * Copies the specified node of the source [Document] to the target [Document].
// * @param source the source [Document].
// * @param target the target [Document]-
// * @return AutoCloseable that closes the [Document]s.
// */
// fun String.copyXmlNode(
// source: Document,
// target: Document,
// ): AutoCloseable {
// val hostNodes = source.getElementsByTagName(this).item(0).childNodes
//
// val destinationNode = target.getElementsByTagName(this).item(0)
//
// for (index in 0 until hostNodes.length) {
// val node = hostNodes.item(index).cloneNode(true)
// target.adoptNode(node)
// destinationNode.appendChild(node)
// }
//
// return AutoCloseable {
// source.close()
// target.close()
// }
// }
// @Deprecated(
// "Use copyXmlNode(Document, Document) instead.",
// ReplaceWith(
// "this.copyXmlNode(source.file as Document, target.file as Document)",
// "app.revanced.patcher.util.Document",
// "app.revanced.patcher.util.Document",
// ),
// )
// fun String.copyXmlNode(
// source: DomFileEditor,
// target: DomFileEditor,
// ) = this.copyXmlNode(source.file as Document, target.file as Document)
/** /**
* Add a resource node child. * Add a resource node child.
* *
* @param resource The resource to add. * @param resource The resource to add.
* @param resourceCallback Called when a resource has been processed. * @param resourceCallback Called when a resource has been processed.
*/ */
internal fun Node.addResource(resource: BaseResource, resourceCallback: (BaseResource) -> Unit = { }) { internal fun Node.addResource(
resource: BaseResource,
resourceCallback: (BaseResource) -> Unit = { },
) {
appendChild(resource.serialize(ownerDocument, resourceCallback)) appendChild(resource.serialize(ownerDocument, resourceCallback))
} }
internal fun DomFileEditor?.getNode(tagName: String) = this!!.file.getElementsByTagName(tagName).item(0) internal fun org.w3c.dom.Document.getNode(tagName: String) = this.getElementsByTagName(tagName).item(0)

View File

@@ -20,12 +20,16 @@
</patch> </patch>
<patch id="layout.startpage.ChangeStartPagePatch"> <patch id="layout.startpage.ChangeStartPagePatch">
<string-array name="revanced_start_page_entries"> <string-array name="revanced_start_page_entries">
<item>@string/revanced_start_page_home_entry_0</item> <item>@string/revanced_start_page_entry_0</item>
<item>@string/revanced_start_page_home_entry_1</item> <item>@string/revanced_start_page_entry_1</item>
<item>@string/revanced_start_page_search_entry_2</item> <item>@string/revanced_start_page_entry_2</item>
<item>@string/revanced_start_page_subscriptions_entry_3</item> <item>@string/revanced_start_page_entry_3</item>
<item>@string/revanced_start_page_explore_entry_4</item> <item>@string/revanced_start_page_entry_4</item>
<item>@string/revanced_start_page_shorts_entry_5</item> <item>@string/revanced_start_page_entry_5</item>
<item>@string/revanced_start_page_entry_6</item>
<item>@string/revanced_start_page_entry_7</item>
<item>@string/revanced_start_page_entry_8</item>
<item>@string/revanced_start_page_entry_9</item>
</string-array> </string-array>
<string-array name="revanced_start_page_entry_values"> <string-array name="revanced_start_page_entry_values">
<item/> <item/>
@@ -34,6 +38,11 @@
<item>open.subscriptions</item> <item>open.subscriptions</item>
<item>open.explore</item> <item>open.explore</item>
<item>open.shorts</item> <item>open.shorts</item>
<item>www.youtube.com/feed/library</item>
<!-- Liked videos -->
<item>www.youtube.com/playlist?list=LL</item>
<item>www.youtube.com/feed/history</item>
<item>www.youtube.com/feed/trending</item>
</string-array> </string-array>
</patch> </patch>
<patch id="layout.thumbnails.AlternativeThumbnailsPatch"> <patch id="layout.thumbnails.AlternativeThumbnailsPatch">

View File

@@ -792,12 +792,16 @@
</patch> </patch>
<patch id="layout.startpage.ChangeStartPagePatch"> <patch id="layout.startpage.ChangeStartPagePatch">
<string name="revanced_start_page_title">Set start page</string> <string name="revanced_start_page_title">Set start page</string>
<string name="revanced_start_page_home_entry_0">Default</string> <string name="revanced_start_page_entry_0">Default</string>
<string name="revanced_start_page_home_entry_1">Home</string> <string name="revanced_start_page_entry_1">Home</string>
<string name="revanced_start_page_search_entry_2">Search</string> <string name="revanced_start_page_entry_2">Search</string>
<string name="revanced_start_page_subscriptions_entry_3">Subscriptions</string> <string name="revanced_start_page_entry_3">Subscriptions</string>
<string name="revanced_start_page_explore_entry_4">Explore</string> <string name="revanced_start_page_entry_4">Explore</string>
<string name="revanced_start_page_shorts_entry_5">Shorts</string> <string name="revanced_start_page_entry_5">Shorts</string>
<string name="revanced_start_page_entry_6">Library</string>
<string name="revanced_start_page_entry_7">Liked videos</string>
<string name="revanced_start_page_entry_8">History</string>
<string name="revanced_start_page_entry_9">Trending</string>
</patch> </patch>
<patch id="layout.startupshortsreset.DisableResumingShortsOnStartupPatch"> <patch id="layout.startupshortsreset.DisableResumingShortsOnStartupPatch">
<string name="revanced_disable_resuming_shorts_player_title">Disable resuming Shorts player</string> <string name="revanced_disable_resuming_shorts_player_title">Disable resuming Shorts player</string>