1
mirror of https://github.com/revanced/revanced-patcher synced 2025-09-06 16:38:50 +02:00

Compare commits

...

69 Commits

Author SHA1 Message Date
oSumAtrIX
c6fdf97794 refactor: do initial refactoring attempt 2023-07-30 00:10:49 +02:00
oSumAtrIX
c52f0b80f2 feat: Deprecate Version annotation 2023-07-30 00:10:49 +02:00
oSumAtrIX
4b5e25b29c chore: naming and better exception message 2023-07-30 00:10:48 +02:00
oSumAtrIX
c752a3c596 chore: add/fix comments 2023-07-30 00:10:48 +02:00
oSumAtrIX
740911a2a3 refactor: move stuff around and improve memory profile/performance 2023-07-30 00:10:48 +02:00
oSumAtrIX
242d805c6c chore: do not publish library, instead shade 2023-07-30 00:10:47 +02:00
oSumAtrIX
c543fdc18b chore: remove unnecessary naming 2023-07-30 00:10:47 +02:00
oSumAtrIX
d48a8e697f arsclib 2023-07-30 00:10:47 +02:00
Palm
8749a61d39 feat: remove Path option (#202)
BREAKING CHANGE: This removes the previously available `Path` option
2023-07-06 18:17:51 +02:00
oSumAtrIX
a4a030f2b2 build: update gradle 2023-07-06 18:15:40 +02:00
semantic-release-bot
dcc4ecd237 chore(release): 11.0.4 [skip ci]
## [11.0.4](https://github.com/revanced/revanced-patcher/compare/v11.0.3...v11.0.4) (2023-07-01)

### Bug Fixes

* clear method lookup maps ([#198](https://github.com/revanced/revanced-patcher/issues/198)) ([9d81baf](9d81baf4b4))
2023-07-01 00:42:50 +00:00
oSumAtrIX
57f3036a96 chore: merge branch dev to main (#199) 2023-07-01 02:41:21 +02:00
semantic-release-bot
753e55dfc3 chore(release): 11.0.4-dev.1 [skip ci]
## [11.0.4-dev.1](https://github.com/revanced/revanced-patcher/compare/v11.0.3...v11.0.4-dev.1) (2023-07-01)

### Bug Fixes

* clear method lookup maps ([#198](https://github.com/revanced/revanced-patcher/issues/198)) ([9d81baf](9d81baf4b4))
2023-07-01 00:37:30 +00:00
LisoUseInAIKyrios
9d81baf4b4 fix: clear method lookup maps (#198) 2023-07-01 02:35:49 +02:00
semantic-release-bot
7cb4d4c596 chore(release): 11.0.3 [skip ci]
## [11.0.3](https://github.com/revanced/revanced-patcher/compare/v11.0.2...v11.0.3) (2023-06-30)

### Bug Fixes

* NPE on method lookup ([#195](https://github.com/revanced/revanced-patcher/issues/195)) ([fcef434](fcef4342e8))
2023-06-30 23:56:27 +00:00
oSumAtrIX
2c8565508e chore: merge branch dev to main (#196) 2023-07-01 01:54:46 +02:00
semantic-release-bot
c7f156e4c9 chore(release): 11.0.3-dev.1 [skip ci]
## [11.0.3-dev.1](https://github.com/revanced/revanced-patcher/compare/v11.0.2...v11.0.3-dev.1) (2023-06-28)

### Bug Fixes

* NPE on method lookup ([#195](https://github.com/revanced/revanced-patcher/issues/195)) ([fcef434](fcef4342e8))
2023-06-28 19:57:40 +00:00
LisoUseInAIKyrios
fcef4342e8 fix: NPE on method lookup (#195) 2023-06-28 21:56:03 +02:00
semantic-release-bot
72783a5e74 chore(release): 11.0.2 [skip ci]
## [11.0.2](https://github.com/revanced/revanced-patcher/compare/v11.0.1...v11.0.2) (2023-06-27)

### Bug Fixes

* catch exceptions from closing patches ([d5d6f85](d5d6f85084))
* do not load annotations as patches ([519359a](519359a9eb))
* only close succeeded patches ([b8151eb](b8151ebccb))
* use `versionCode` if `versionName` is unavailable ([6e1b647](6e1b6479b6))

### Performance Improvements

* resolve fingerprints using method maps ([#185](https://github.com/revanced/revanced-patcher/issues/185)) ([d718134](d718134ab2))
2023-06-27 02:32:47 +00:00
oSumAtrIX
a379b69eeb chore: merge branch dev to main (#191) 2023-06-27 04:31:09 +02:00
semantic-release-bot
0a8ccba33e chore(release): 11.0.2-dev.4 [skip ci]
## [11.0.2-dev.4](https://github.com/revanced/revanced-patcher/compare/v11.0.2-dev.3...v11.0.2-dev.4) (2023-06-27)

### Bug Fixes

* do not load annotations as patches ([519359a](519359a9eb))
2023-06-27 02:11:33 +00:00
oSumAtrIX
519359a9eb fix: do not load annotations as patches 2023-06-27 04:09:03 +02:00
semantic-release-bot
b615ed6aab chore(release): 11.0.2-dev.3 [skip ci]
## [11.0.2-dev.3](https://github.com/revanced/revanced-patcher/compare/v11.0.2-dev.2...v11.0.2-dev.3) (2023-06-27)

### Performance Improvements

* resolve fingerprints using method maps ([#185](https://github.com/revanced/revanced-patcher/issues/185)) ([d718134](d718134ab2))
2023-06-27 02:08:29 +00:00
LisoUseInAIKyrios
d718134ab2 perf: resolve fingerprints using method maps (#185)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2023-06-27 04:06:51 +02:00
semantic-release-bot
5e681ed381 chore(release): 11.0.2-dev.2 [skip ci]
## [11.0.2-dev.2](https://github.com/revanced/revanced-patcher/compare/v11.0.2-dev.1...v11.0.2-dev.2) (2023-06-18)

### Bug Fixes

* use `versionCode` if `versionName` is unavailable ([6e1b647](6e1b6479b6))
2023-06-18 14:41:34 +00:00
oSumAtrIX
6e1b6479b6 fix: use versionCode if versionName is unavailable 2023-06-18 16:39:49 +02:00
semantic-release-bot
f3c9e28a62 chore(release): 11.0.2-dev.1 [skip ci]
## [11.0.2-dev.1](https://github.com/revanced/revanced-patcher/compare/v11.0.1...v11.0.2-dev.1) (2023-06-14)

### Bug Fixes

* catch exceptions from closing patches ([d5d6f85](d5d6f85084))
* only close succeeded patches ([b8151eb](b8151ebccb))
2023-06-14 00:16:26 +00:00
oSumAtrIX
d5d6f85084 fix: catch exceptions from closing patches 2023-06-14 02:14:37 +02:00
oSumAtrIX
b8151ebccb fix: only close succeeded patches 2023-06-14 01:43:19 +02:00
semantic-release-bot
5650e34432 chore(release): 11.0.1 [skip ci]
## [11.0.1](https://github.com/revanced/revanced-patcher/compare/v11.0.0...v11.0.1) (2023-06-12)

### Bug Fixes

* revert using `OutputStream.nullOutputStream` ([f02a426](f02a42610b))
2023-06-12 03:36:10 +00:00
oSumAtrIX
c893d16d52 chore: merge branch dev to main (#190) 2023-06-12 05:34:23 +02:00
semantic-release-bot
34f08bf206 chore(release): 11.0.1-dev.1 [skip ci]
## [11.0.1-dev.1](https://github.com/revanced/revanced-patcher/compare/v11.0.0...v11.0.1-dev.1) (2023-06-12)

### Bug Fixes

* revert using `OutputStream.nullOutputStream` ([f02a426](f02a42610b))
2023-06-12 03:33:53 +00:00
oSumAtrIX
f02a42610b fix: revert using OutputStream.nullOutputStream
Older Android versions don't support this API
2023-06-12 05:32:13 +02:00
oSumAtrIX
c95e6fa92f ci: add cache step 2023-06-12 02:55:09 +02:00
oSumAtrIX
fd738e723b ci: build before running semantic-release 2023-06-12 01:52:52 +02:00
oSumAtrIX
b1d1956323 ci: remove unnecessary steps 2023-06-12 01:47:26 +02:00
semantic-release-bot
725a8012ac chore(release): 11.0.0 [skip ci]
# [11.0.0](https://github.com/revanced/revanced-patcher/compare/v10.0.0...v11.0.0) (2023-06-10)

### Bug Fixes

* add imports to fix failing tests ([43d6868](43d6868d1f))

* refactor!: move extension functions to their corresponding classes ([a12fe7d](a12fe7dd9e))
* refactor!: use proper extension function names ([efdd01a](efdd01a988))
* fix!: implement extension functions consistently ([aacf900](aacf900764))

### BREAKING CHANGES

* This changes the import paths for extension functions.
* This changes the names of extension functions
* This changes the name of functions
2023-06-10 23:11:52 +00:00
oSumAtrIX
bb9a73e53b chore: merge branch dev to main (#187) 2023-06-11 01:10:59 +02:00
semantic-release-bot
ef2de35a74 chore(release): 11.0.0-dev.2 [skip ci]
# [11.0.0-dev.2](https://github.com/revanced/revanced-patcher/compare/v11.0.0-dev.1...v11.0.0-dev.2) (2023-06-09)

### Bug Fixes

* add imports to fix failing tests ([43d6868](43d6868d1f))
2023-06-09 23:56:16 +00:00
oSumAtrIX
2a453d51a8 refactor: rename helper methods for tests 2023-06-10 01:55:05 +02:00
oSumAtrIX
43d6868d1f fix: add imports to fix failing tests 2023-06-10 01:43:09 +02:00
semantic-release-bot
cea9379b32 chore(release): 11.0.0-dev.1 [skip ci]
# [11.0.0-dev.1](https://github.com/revanced/revanced-patcher/compare/v10.0.0...v11.0.0-dev.1) (2023-06-07)

* refactor!: move extension functions to their corresponding classes ([a12fe7d](a12fe7dd9e))
* refactor!: use proper extension function names ([efdd01a](efdd01a988))
* fix!: implement extension functions consistently ([aacf900](aacf900764))

### BREAKING CHANGES

* This changes the import paths for extension functions.
* This changes the names of extension functions
* This changes the name of functions
2023-06-07 01:45:31 +00:00
oSumAtrIX
a12fe7dd9e refactor!: move extension functions to their corresponding classes
BREAKING CHANGE: This changes the import paths for extension functions.
2023-06-07 03:43:17 +02:00
oSumAtrIX
efdd01a988 refactor!: use proper extension function names
BREAKING CHANGE: This changes the names of extension functions
2023-06-07 03:43:13 +02:00
oSumAtrIX
eafe1c631f refactor: use existing null output stream 2023-06-07 03:32:19 +02:00
oSumAtrIX
aacf900764 fix!: implement extension functions consistently
BREAKING CHANGE: This changes the name of functions
2023-06-07 03:32:19 +02:00
semantic-release-bot
f82494e9bb chore(release): 10.0.0 [skip ci]
# [10.0.0](https://github.com/revanced/revanced-patcher/compare/v9.0.0...v10.0.0) (2023-06-07)

* fix!: check for two methods parameters orders (#183) ([b6d6a75](b6d6a7591b)), closes [#183](https://github.com/revanced/revanced-patcher/issues/183)

### BREAKING CHANGES

* This requires changes to `MethodFingerprint`
2023-06-07 00:59:13 +00:00
oSumAtrIX
1e0ffa176e chore: merge branch dev to main 2023-06-07 02:58:23 +02:00
semantic-release-bot
b7eb2d2249 chore(release): 10.0.0-dev.1 [skip ci]
# [10.0.0-dev.1](https://github.com/revanced/revanced-patcher/compare/v9.0.0...v10.0.0-dev.1) (2023-06-07)

* fix!: check for two methods parameters orders (#183) ([b6d6a75](b6d6a7591b)), closes [#183](https://github.com/revanced/revanced-patcher/issues/183)

### BREAKING CHANGES

* This requires changes to `MethodFingerprint`
2023-06-07 00:52:08 +00:00
LisoUseInAIKyrios
b6d6a7591b fix!: check for two methods parameters orders (#183)
BREAKING CHANGE: This requires changes to `MethodFingerprint`
2023-06-07 02:50:38 +02:00
semantic-release-bot
8f1c835299 chore(release): 9.0.0 [skip ci]
# [9.0.0](https://github.com/revanced/revanced-patcher/compare/v8.0.0...v9.0.0) (2023-05-23)

* refactor!: rename parameter ([526a3d7](526a3d7c35))

### BREAKING CHANGES

* This changes named parameters.
2023-05-23 23:58:07 +00:00
oSumAtrIX
a188c16a99 chore: merge branch dev to main (#177) 2023-05-24 01:56:40 +02:00
semantic-release-bot
3e6804f06c chore(release): 9.0.0-dev.1 [skip ci]
# [9.0.0-dev.1](https://github.com/revanced/revanced-patcher/compare/v8.0.0...v9.0.0-dev.1) (2023-05-23)

* refactor!: rename parameter ([526a3d7](526a3d7c35))

### BREAKING CHANGES

* This changes named parameters.
2023-05-23 23:50:12 +00:00
oSumAtrIX
526a3d7c35 refactor!: rename parameter
BREAKING CHANGE: This changes named parameters.
2023-05-24 01:49:07 +02:00
oSumAtrIX
28fc6a2ddd refactor: apply Kotlin idioms 2023-05-24 01:45:46 +02:00
semantic-release-bot
d4f08d7bff chore(release): 8.0.0 [skip ci]
# [8.0.0](https://github.com/revanced/revanced-patcher/compare/v7.1.1...v8.0.0) (2023-05-13)

* feat!: add `classDef` parameter to `MethodFingerprint` (#175) ([a205220](a2052202b2)), closes [#175](https://github.com/revanced/revanced-patcher/issues/175)

### BREAKING CHANGES

* This changes the signature of the `customFingerprint` function.
2023-05-13 23:58:31 +00:00
oSumAtrIX
ca9fe322eb chore: merge branch dev to main (#174) 2023-05-14 01:57:34 +02:00
Jim Man
239ea0bcaa refactor: simplify loading patches (#172) 2023-05-14 01:55:26 +02:00
semantic-release-bot
7f02b8df48 chore(release): 8.0.0-dev.1 [skip ci]
# [8.0.0-dev.1](https://github.com/revanced/revanced-patcher/compare/v7.1.1...v8.0.0-dev.1) (2023-05-10)

* feat!: add `classDef` parameter to `MethodFingerprint` (#175) ([a205220](a2052202b2)), closes [#175](https://github.com/revanced/revanced-patcher/issues/175)

### BREAKING CHANGES

* This changes the signature of the `customFingerprint` function.
2023-05-10 23:38:38 +00:00
badawoll
a2052202b2 feat!: add classDef parameter to MethodFingerprint (#175)
BREAKING CHANGE: This changes the signature of the `customFingerprint` function.
2023-05-11 01:37:17 +02:00
oSumAtrIX
223cea7021 build: use Java SDK 17 for building 2023-05-09 08:16:15 +02:00
semantic-release-bot
ac9337f694 chore(release): 7.1.1 [skip ci]
## [7.1.1](https://github.com/revanced/revanced-patcher/compare/v7.1.0...v7.1.1) (2023-05-07)

### Bug Fixes

* remove `count` instead of `count + 1` instructions with `removeInstructions` ([#167](https://github.com/revanced/revanced-patcher/issues/167)) ([98f8eed](98f8eedecd))
2023-05-07 23:06:19 +00:00
oSumAtrIX
549651d04a chore: merge branch dev to main (#171) 2023-05-08 01:05:17 +02:00
semantic-release-bot
966bbd902e chore(release): 7.1.1-dev.1 [skip ci]
## [7.1.1-dev.1](https://github.com/revanced/revanced-patcher/compare/v7.1.0...v7.1.1-dev.1) (2023-05-07)

### Bug Fixes

* remove `count` instead of `count + 1` instructions with `removeInstructions` ([#167](https://github.com/revanced/revanced-patcher/issues/167)) ([98f8eed](98f8eedecd))
2023-05-07 23:04:09 +00:00
oSumAtrIX
81e6f8784e docs: fix heading 2023-05-08 01:03:11 +02:00
oSumAtrIX
9c53877888 build: downgrade to JDK version 11
This is to properly support Android
2023-05-08 01:03:11 +02:00
rstular
98f8eedecd fix: remove count instead of count + 1 instructions with removeInstructions (#167)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>

BREAKING-CHANGE: This alters the behaviour of the function. Instead of removing `count + 1` instructions, this now removes `count` instructions.
2023-05-08 01:03:09 +02:00
semantic-release-bot
4ed429d25c chore(release): 7.1.0 [skip ci]
# [7.1.0](https://github.com/revanced/revanced-patcher/compare/v7.0.0...v7.1.0) (2023-05-05)

### Features

* add appreciation message for new contributors ([d674362](d67436271d))
* add overload to get instruction as type ([49c173d](49c173dc14))
2023-05-05 23:06:29 +00:00
oSumAtrIX
119d05f469 chore: merge branch dev to main (#161) 2023-05-06 01:05:01 +02:00
108 changed files with 3205 additions and 1724 deletions

9
.gitattributes vendored Normal file
View File

@@ -0,0 +1,9 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# Linux start script should use lf
/gradlew text eol=lf
# These are Windows script files and should use crlf
*.bat text eol=crlf

View File

@@ -23,17 +23,20 @@ jobs:
# https://github.com/cycjimmy/semantic-release-action#private-packages
persist-credentials: false
fetch-depth: 0
- name: Setup JDK
uses: actions/setup-java@v3
- name: Cache
uses: actions/cache@v3
with:
java-version: '17'
distribution: 'zulu'
cache: gradle
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: "18"
cache: 'npm'
path: |
${{ runner.home }}/.gradle/caches
${{ runner.home }}/.gradle/wrapper
.gradle
build
node_modules
key: ${{ runner.os }}-gradle-npm-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', 'package-lock.json') }}
- name: Build with Gradle
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew clean --no-daemon
- name: Setup semantic-release
run: npm install
- name: Release

View File

@@ -1,3 +1,211 @@
## [11.0.4](https://github.com/revanced/revanced-patcher/compare/v11.0.3...v11.0.4) (2023-07-01)
### Bug Fixes
* clear method lookup maps ([#198](https://github.com/revanced/revanced-patcher/issues/198)) ([9d81baf](https://github.com/revanced/revanced-patcher/commit/9d81baf4b4ca7514f8a1009e72218638609a7c7f))
## [11.0.4-dev.1](https://github.com/revanced/revanced-patcher/compare/v11.0.3...v11.0.4-dev.1) (2023-07-01)
### Bug Fixes
* clear method lookup maps ([#198](https://github.com/revanced/revanced-patcher/issues/198)) ([9d81baf](https://github.com/revanced/revanced-patcher/commit/9d81baf4b4ca7514f8a1009e72218638609a7c7f))
## [11.0.3](https://github.com/revanced/revanced-patcher/compare/v11.0.2...v11.0.3) (2023-06-30)
### Bug Fixes
* NPE on method lookup ([#195](https://github.com/revanced/revanced-patcher/issues/195)) ([fcef434](https://github.com/revanced/revanced-patcher/commit/fcef4342e8bde73945e8315aef6337cc8a8d8572))
## [11.0.3-dev.1](https://github.com/revanced/revanced-patcher/compare/v11.0.2...v11.0.3-dev.1) (2023-06-28)
### Bug Fixes
* NPE on method lookup ([#195](https://github.com/revanced/revanced-patcher/issues/195)) ([fcef434](https://github.com/revanced/revanced-patcher/commit/fcef4342e8bde73945e8315aef6337cc8a8d8572))
## [11.0.2](https://github.com/revanced/revanced-patcher/compare/v11.0.1...v11.0.2) (2023-06-27)
### Bug Fixes
* catch exceptions from closing patches ([d5d6f85](https://github.com/revanced/revanced-patcher/commit/d5d6f85084c03ed9c776632823ca12394a716167))
* do not load annotations as patches ([519359a](https://github.com/revanced/revanced-patcher/commit/519359a9eb0e9dfa390c5016e9fe4a7490b8ab18))
* only close succeeded patches ([b8151eb](https://github.com/revanced/revanced-patcher/commit/b8151ebccb5b27dd9e06fa63235cf9baeef1c0ee))
* use `versionCode` if `versionName` is unavailable ([6e1b647](https://github.com/revanced/revanced-patcher/commit/6e1b6479b677657c226693e9cc6b63f4ef2ee060))
### Performance Improvements
* resolve fingerprints using method maps ([#185](https://github.com/revanced/revanced-patcher/issues/185)) ([d718134](https://github.com/revanced/revanced-patcher/commit/d718134ab26423e02708e01eba711737f9260ba0))
## [11.0.2-dev.4](https://github.com/revanced/revanced-patcher/compare/v11.0.2-dev.3...v11.0.2-dev.4) (2023-06-27)
### Bug Fixes
* do not load annotations as patches ([519359a](https://github.com/revanced/revanced-patcher/commit/519359a9eb0e9dfa390c5016e9fe4a7490b8ab18))
## [11.0.2-dev.3](https://github.com/revanced/revanced-patcher/compare/v11.0.2-dev.2...v11.0.2-dev.3) (2023-06-27)
### Performance Improvements
* resolve fingerprints using method maps ([#185](https://github.com/revanced/revanced-patcher/issues/185)) ([d718134](https://github.com/revanced/revanced-patcher/commit/d718134ab26423e02708e01eba711737f9260ba0))
## [11.0.2-dev.2](https://github.com/revanced/revanced-patcher/compare/v11.0.2-dev.1...v11.0.2-dev.2) (2023-06-18)
### Bug Fixes
* use `versionCode` if `versionName` is unavailable ([6e1b647](https://github.com/revanced/revanced-patcher/commit/6e1b6479b677657c226693e9cc6b63f4ef2ee060))
## [11.0.2-dev.1](https://github.com/revanced/revanced-patcher/compare/v11.0.1...v11.0.2-dev.1) (2023-06-14)
### Bug Fixes
* catch exceptions from closing patches ([d5d6f85](https://github.com/revanced/revanced-patcher/commit/d5d6f85084c03ed9c776632823ca12394a716167))
* only close succeeded patches ([b8151eb](https://github.com/revanced/revanced-patcher/commit/b8151ebccb5b27dd9e06fa63235cf9baeef1c0ee))
## [11.0.1](https://github.com/revanced/revanced-patcher/compare/v11.0.0...v11.0.1) (2023-06-12)
### Bug Fixes
* revert using `OutputStream.nullOutputStream` ([f02a426](https://github.com/revanced/revanced-patcher/commit/f02a42610b7698fc8cc6bc5183c9ccba2ed96cda))
## [11.0.1-dev.1](https://github.com/revanced/revanced-patcher/compare/v11.0.0...v11.0.1-dev.1) (2023-06-12)
### Bug Fixes
* revert using `OutputStream.nullOutputStream` ([f02a426](https://github.com/revanced/revanced-patcher/commit/f02a42610b7698fc8cc6bc5183c9ccba2ed96cda))
# [11.0.0](https://github.com/revanced/revanced-patcher/compare/v10.0.0...v11.0.0) (2023-06-10)
### Bug Fixes
* add imports to fix failing tests ([43d6868](https://github.com/revanced/revanced-patcher/commit/43d6868d1f59922f9812f3e4a2a890f3b331def6))
* refactor!: move extension functions to their corresponding classes ([a12fe7d](https://github.com/revanced/revanced-patcher/commit/a12fe7dd9e976c38a0a82fe35e6650f58f815de4))
* refactor!: use proper extension function names ([efdd01a](https://github.com/revanced/revanced-patcher/commit/efdd01a9886b6f06af213731824621964367b2a3))
* fix!: implement extension functions consistently ([aacf900](https://github.com/revanced/revanced-patcher/commit/aacf9007647b1cc11bc40053625802573efda6ef))
### BREAKING CHANGES
* This changes the import paths for extension functions.
* This changes the names of extension functions
* This changes the name of functions
# [11.0.0-dev.2](https://github.com/revanced/revanced-patcher/compare/v11.0.0-dev.1...v11.0.0-dev.2) (2023-06-09)
### Bug Fixes
* add imports to fix failing tests ([43d6868](https://github.com/revanced/revanced-patcher/commit/43d6868d1f59922f9812f3e4a2a890f3b331def6))
# [11.0.0-dev.1](https://github.com/revanced/revanced-patcher/compare/v10.0.0...v11.0.0-dev.1) (2023-06-07)
* refactor!: move extension functions to their corresponding classes ([a12fe7d](https://github.com/revanced/revanced-patcher/commit/a12fe7dd9e976c38a0a82fe35e6650f58f815de4))
* refactor!: use proper extension function names ([efdd01a](https://github.com/revanced/revanced-patcher/commit/efdd01a9886b6f06af213731824621964367b2a3))
* fix!: implement extension functions consistently ([aacf900](https://github.com/revanced/revanced-patcher/commit/aacf9007647b1cc11bc40053625802573efda6ef))
### BREAKING CHANGES
* This changes the import paths for extension functions.
* This changes the names of extension functions
* This changes the name of functions
# [10.0.0](https://github.com/revanced/revanced-patcher/compare/v9.0.0...v10.0.0) (2023-06-07)
* fix!: check for two methods parameters orders (#183) ([b6d6a75](https://github.com/revanced/revanced-patcher/commit/b6d6a7591ba1c0b48ffdef52352709564da8d9be)), closes [#183](https://github.com/revanced/revanced-patcher/issues/183)
### BREAKING CHANGES
* This requires changes to `MethodFingerprint`
# [10.0.0-dev.1](https://github.com/revanced/revanced-patcher/compare/v9.0.0...v10.0.0-dev.1) (2023-06-07)
* fix!: check for two methods parameters orders (#183) ([b6d6a75](https://github.com/revanced/revanced-patcher/commit/b6d6a7591ba1c0b48ffdef52352709564da8d9be)), closes [#183](https://github.com/revanced/revanced-patcher/issues/183)
### BREAKING CHANGES
* This requires changes to `MethodFingerprint`
# [9.0.0](https://github.com/revanced/revanced-patcher/compare/v8.0.0...v9.0.0) (2023-05-23)
* refactor!: rename parameter ([526a3d7](https://github.com/revanced/revanced-patcher/commit/526a3d7c359e2d95d26756da0f88d5ce975f5d9b))
### BREAKING CHANGES
* This changes named parameters.
# [9.0.0-dev.1](https://github.com/revanced/revanced-patcher/compare/v8.0.0...v9.0.0-dev.1) (2023-05-23)
* refactor!: rename parameter ([526a3d7](https://github.com/revanced/revanced-patcher/commit/526a3d7c359e2d95d26756da0f88d5ce975f5d9b))
### BREAKING CHANGES
* This changes named parameters.
# [8.0.0](https://github.com/revanced/revanced-patcher/compare/v7.1.1...v8.0.0) (2023-05-13)
* feat!: add `classDef` parameter to `MethodFingerprint` (#175) ([a205220](https://github.com/revanced/revanced-patcher/commit/a2052202b23037150df6aadc47f6e91efcd481cf)), closes [#175](https://github.com/revanced/revanced-patcher/issues/175)
### BREAKING CHANGES
* This changes the signature of the `customFingerprint` function.
# [8.0.0-dev.1](https://github.com/revanced/revanced-patcher/compare/v7.1.1...v8.0.0-dev.1) (2023-05-10)
* feat!: add `classDef` parameter to `MethodFingerprint` (#175) ([a205220](https://github.com/revanced/revanced-patcher/commit/a2052202b23037150df6aadc47f6e91efcd481cf)), closes [#175](https://github.com/revanced/revanced-patcher/issues/175)
### BREAKING CHANGES
* This changes the signature of the `customFingerprint` function.
## [7.1.1](https://github.com/revanced/revanced-patcher/compare/v7.1.0...v7.1.1) (2023-05-07)
### Bug Fixes
* remove `count` instead of `count + 1` instructions with `removeInstructions` ([#167](https://github.com/revanced/revanced-patcher/issues/167)) ([98f8eed](https://github.com/revanced/revanced-patcher/commit/98f8eedecd72b0afe6a0f099a3641a1cc6be2698))
## [7.1.1-dev.1](https://github.com/revanced/revanced-patcher/compare/v7.1.0...v7.1.1-dev.1) (2023-05-07)
### Bug Fixes
* remove `count` instead of `count + 1` instructions with `removeInstructions` ([#167](https://github.com/revanced/revanced-patcher/issues/167)) ([98f8eed](https://github.com/revanced/revanced-patcher/commit/98f8eedecd72b0afe6a0f099a3641a1cc6be2698))
# [7.1.0](https://github.com/revanced/revanced-patcher/compare/v7.0.0...v7.1.0) (2023-05-05)
### Features
* add appreciation message for new contributors ([d674362](https://github.com/revanced/revanced-patcher/commit/d67436271ddca9ccfe008272c1ca82c6123ae7ee))
* add overload to get instruction as type ([49c173d](https://github.com/revanced/revanced-patcher/commit/49c173dc14137ddd198a611e9882dc71300831ee))
# [7.1.0-dev.2](https://github.com/revanced/revanced-patcher/compare/v7.1.0-dev.1...v7.1.0-dev.2) (2023-05-05)

View File

@@ -1,2 +1,3 @@
# Patcher
Patcher framework used in the ReVanced project.
# 💉 ReVanced Patcher
ReVanced Patcher used to patch Android applications.

42
arsclib-utils/.gitignore vendored Normal file
View File

@@ -0,0 +1,42 @@
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

View File

@@ -0,0 +1,18 @@
plugins {
kotlin("jvm")
`maven-publish`
}
group = "app.revanced"
dependencies {
implementation("io.github.reandroid:ARSCLib:1.1.7")
}
java {
withSourcesJar()
}
kotlin {
jvmToolchain(11)
}

View File

@@ -0,0 +1,72 @@
package app.revanced.arsc
/**
* An exception thrown when there is an error with APK resources.
*
* @param message The exception message.
* @param throwable The corresponding [Throwable].
*/
sealed class ApkResourceException(message: String, throwable: Throwable? = null) : Exception(message, throwable) {
/**
* An exception when locking resources.
*
* @param message The exception message.
* @param throwable The corresponding [Throwable].
*/
class Locked(message: String, throwable: Throwable? = null) : ApkResourceException(message, throwable)
/**
* An exception when writing resources.
*
* @param message The exception message.
* @param throwable The corresponding [Throwable].
*/
class Write(message: String, throwable: Throwable? = null) : ApkResourceException(message, throwable)
/**
* An exception when reading resources.
*
* @param message The exception message.
* @param throwable The corresponding [Throwable].
*/
class Read(message: String, throwable: Throwable? = null) : ApkResourceException(message, throwable)
/**
* An exception when decoding resources.
*
* @param message The exception message.
* @param throwable The corresponding [Throwable].
*/
class Decode(message: String, throwable: Throwable? = null) : ApkResourceException(message, throwable)
/**
* An exception when encoding resources.
*
* @param message The exception message.
* @param throwable The corresponding [Throwable].
*/
class Encode(message: String, throwable: Throwable? = null) : ApkResourceException(message, throwable)
/**
* An exception thrown when a reference could not be resolved.
*
* @param reference The invalid reference.
* @param throwable The corresponding [Throwable].
*/
class InvalidReference(reference: String, throwable: Throwable? = null) :
ApkResourceException("Failed to resolve: $reference", throwable) {
/**
* An exception thrown when a reference could not be resolved.
*
* @param type The type of the reference.
* @param name The name of the reference.
* @param throwable The corresponding [Throwable].
*/
constructor(type: String, name: String, throwable: Throwable? = null) : this("@$type/$name", throwable)
}
/**
* An exception thrown when the Apk file not have a resource table, but was expected to have one.
*/
class MissingResourceTable : ApkResourceException("Apk does not have a resource table.")
}

View File

@@ -0,0 +1,28 @@
@file:Suppress("MemberVisibilityCanBePrivate")
package app.revanced.arsc.archive
import app.revanced.arsc.resource.ResourceContainer
import com.reandroid.apk.ApkModule
import com.reandroid.apk.DexFileInputSource
import com.reandroid.archive.InputSource
import java.io.File
import java.io.Flushable
/**
* A class for reading/writing files in an [ApkModule].
*
* @param module The [ApkModule] to operate on.
*/
class Archive(internal val module: ApkModule) : Flushable {
val mainPackageResources = ResourceContainer(this, module.tableBlock)
fun save(output: File) {
flush()
module.writeApk(output)
}
fun readDexFiles(): MutableList<DexFileInputSource> = module.listDexFiles()
fun write(inputSource: InputSource) = module.apkArchive.add(inputSource) // Overwrites existing files.
fun read(name: String): InputSource? = module.apkArchive.getInputSource(name)
override fun flush() = mainPackageResources.flush()
}

View File

@@ -0,0 +1,7 @@
package app.revanced.arsc.logging
interface Logger {
fun error(msg: String)
fun warn(msg: String)
fun info(msg: String)
fun trace(msg: String)
}

View File

@@ -0,0 +1,166 @@
package app.revanced.arsc.resource
import app.revanced.arsc.ApkResourceException
import com.reandroid.arsc.coder.EncodeResult
import com.reandroid.arsc.coder.ValueDecoder
import com.reandroid.arsc.value.Entry
import com.reandroid.arsc.value.ValueType
import com.reandroid.arsc.value.array.ArrayBag
import com.reandroid.arsc.value.array.ArrayBagItem
import com.reandroid.arsc.value.plurals.PluralsBag
import com.reandroid.arsc.value.plurals.PluralsBagItem
import com.reandroid.arsc.value.plurals.PluralsQuantity
import com.reandroid.arsc.value.style.StyleBag
import com.reandroid.arsc.value.style.StyleBagItem
/**
* A resource value.
*/
sealed class Resource {
internal abstract fun write(entry: Entry, resources: ResourceContainer)
}
internal val Resource.isComplex get() = when (this) {
is Scalar -> false
is Complex -> true
}
/**
* A simple resource.
*/
open class Scalar internal constructor(private val valueType: ValueType, private val value: Int) : Resource() {
protected open fun data(resources: ResourceContainer) = value
override fun write(entry: Entry, resources: ResourceContainer) {
entry.setValueAsRaw(valueType, data(resources))
}
internal open fun toArrayItem(resources: ResourceContainer) = ArrayBagItem.create(valueType, data(resources))
internal open fun toStyleItem(resources: ResourceContainer) = StyleBagItem.create(valueType, data(resources))
}
/**
* A marker class for complex resources.
*/
sealed class Complex : Resource()
private fun encoded(encodeResult: EncodeResult?) = encodeResult?.let { Scalar(it.valueType, it.value) }
?: throw ApkResourceException.Encode("Failed to encode value")
/**
* Encode a color.
*
* @param hex The hex value of the color.
* @return The encoded [Resource].
*/
fun color(hex: String) = encoded(ValueDecoder.encodeColor(hex))
/**
* Encode a dimension or fraction.
*
* @param value The dimension value such as 24dp.
* @return The encoded [Resource].
*/
fun dimension(value: String) = encoded(ValueDecoder.encodeDimensionOrFraction(value))
/**
* Encode a boolean resource.
*
* @param value The boolean.
* @return The encoded [Resource].
*/
fun boolean(value: Boolean) = Scalar(ValueType.INT_BOOLEAN, if (value) -Int.MAX_VALUE else 0)
/**
* Encode a float.
*
* @param n The number to encode.
* @return The encoded [Resource].
*/
fun float(n: Float) = Scalar(ValueType.FLOAT, n.toBits())
/**
* Create an integer [Resource].
*
* @param n The number to encode.
* @return The integer [Resource].
*/
fun integer(n: Int) = Scalar(ValueType.INT_DEC, n)
/**
* Create a reference [Resource].
*
* @param resourceId The target resource.
* @return The reference resource.
*/
fun reference(resourceId: Int) = Scalar(ValueType.REFERENCE, resourceId)
/**
* Resolve and create a reference [Resource].
*
* @see reference
* @param ref The reference string to resolve.
* @param resourceTable The resource table to resolve the reference with.
* @return The reference resource.
*/
fun reference(resourceTable: ResourceTable, ref: String) = reference(resourceTable.resolve(ref))
/**
* An array [Resource].
*
* @param elements The elements of the array.
*/
class Array(private val elements: Collection<Scalar>) : Complex() {
override fun write(entry: Entry, resources: ResourceContainer) {
ArrayBag.create(entry).addAll(elements.map { it.toArrayItem(resources) })
}
}
/**
* A style resource.
*
* @param elements The attributes to override.
* @param parent A reference to the parent style.
*/
class Style(private val elements: Map<String, Scalar>, private val parent: String? = null) : Complex() {
override fun write(entry: Entry, resources: ResourceContainer) {
val resTable = resources.resourceTable
val style = StyleBag.create(entry)
parent?.let {
style.parentId = resTable.resolve(parent)
}
style.putAll(
elements.asIterable().associate {
StyleBag.resolve(resTable.encodeMaterials, it.key) to it.value.toStyleItem(resources)
})
}
}
/**
* A quantity string [Resource].
*
* @param elements A map of the quantity to the corresponding string.
*/
class Plurals(private val elements: Map<String, String>) : Complex() {
override fun write(entry: Entry, resources: ResourceContainer) {
val plurals = PluralsBag.create(entry)
plurals.putAll(elements.asIterable().associate { (k, v) ->
PluralsQuantity.value(k) to PluralsBagItem.string(resources.getOrCreateString(v))
})
}
}
/**
* A string [Resource].
*
* @param value The string value.
*/
class StringResource(val value: String) : Scalar(ValueType.STRING, 0) {
private fun tableString(resources: ResourceContainer) = resources.getOrCreateString(value)
override fun data(resources: ResourceContainer) = tableString(resources).index
override fun toArrayItem(resources: ResourceContainer) = ArrayBagItem.string(tableString(resources))
override fun toStyleItem(resources: ResourceContainer) = StyleBagItem.string(tableString(resources))
}

View File

@@ -0,0 +1,167 @@
package app.revanced.arsc.resource
import app.revanced.arsc.ApkResourceException
import app.revanced.arsc.archive.Archive
import com.reandroid.apk.xmlencoder.EncodeUtil
import com.reandroid.arsc.chunk.TableBlock
import com.reandroid.arsc.chunk.xml.ResXmlDocument
import com.reandroid.arsc.value.Entry
import com.reandroid.arsc.value.ResConfig
import java.io.Closeable
import java.io.File
import java.io.Flushable
class ResourceContainer(private val archive: Archive, internal val tableBlock: TableBlock) : Flushable {
private val packageBlock = tableBlock.pickOne() // Pick the main package block.
internal lateinit var resourceTable: ResourceTable // TODO: Set this.
private val lockedResourceFileNames = mutableSetOf<String>()
private fun lock(resourceFile: ResourceFile) {
if (resourceFile.name in lockedResourceFileNames) {
throw ApkResourceException.Locked("Resource file ${resourceFile.name} is already locked.")
}
lockedResourceFileNames.add(resourceFile.name)
}
private fun unlock(resourceFile: ResourceFile) {
lockedResourceFileNames.remove(resourceFile.name)
}
fun <T : ResourceFile> openResource(name: String): ResourceFileEditor<T> {
val inputSource = archive.read(name)
?: throw ApkResourceException.Read("Resource file $name not found.")
val resourceFile = when {
ResXmlDocument.isResXmlBlock(inputSource.openStream()) -> {
val xmlDocument = archive.module
.loadResXmlDocument(inputSource)
.decodeToXml(resourceTable.entryStore, packageBlock.id)
ResourceFile.XmlResourceFile(name, xmlDocument)
}
else -> {
val bytes = inputSource.openStream().use { it.readAllBytes() }
ResourceFile.BinaryResourceFile(name, bytes)
}
}
try {
@Suppress("UNCHECKED_CAST")
return ResourceFileEditor(resourceFile as T).also {
lockedResourceFileNames.add(name)
}
} catch (e: ClassCastException) {
throw ApkResourceException.Decode("Resource file $name is not ${resourceFile::class}.", e)
}
}
inner class ResourceFileEditor<T : ResourceFile> internal constructor(
private val resourceFile: T,
) : Closeable {
fun use(block: (T) -> Unit) = block(resourceFile)
override fun close() {
lockedResourceFileNames.remove(resourceFile.name)
}
}
override fun flush() {
TODO("Not yet implemented")
}
/**
* Open a resource file, creating it if the file does not exist.
*
* @param path The resource file path.
* @return The corresponding [ResourceFiles],
*/
fun openFile(path: String) = ResourceFiles(createHandle(path), archive)
private fun getPackageBlock() = packageBlock ?: throw ApkResourceException.MissingResourceTable
internal fun getOrCreateString(value: String) =
tableBlock?.stringPool?.getOrCreate(value) ?: throw ApkResourceException.MissingResourceTable
private fun Entry.set(resource: Resource) {
val existingEntryNameReference = specReference
// Sets this.specReference if the entry is not yet initialized.
// Sets this.specReference to 0 if the resource type of the existing entry changes.
ensureComplex(resource.isComplex)
if (existingEntryNameReference != 0) {
// Preserve the entry name by restoring the previous spec block reference (if present).
specReference = existingEntryNameReference
}
resource.write(this, this@ResourceContainer)
resourceTable.registerChanged(this)
}
/**
* Retrieve an [Entry] from the resource table.
*
* @param type The resource type.
* @param name The resource name.
* @param qualifiers The variant to use.
*/
private fun getEntry(type: String, name: String, qualifiers: String?): Entry? {
val resourceId = try {
resourceTable.resolve("@$type/$name")
} catch (_: ApkResourceException.InvalidReference) {
return null
}
val config = ResConfig.parse(qualifiers)
return tableBlock?.resolveReference(resourceId)?.singleOrNull { it.resConfig == config }
}
/**
* Create a [ResourceFiles.Handle] that can be used to open a [ResourceFiles].
* This may involve looking it up in the resource table to find the actual location in the archive.
*
* @param path The path of the resource.
*/
private fun createHandle(path: String): ResourceFiles.Handle {
if (path.startsWith("res/values")) throw ApkResourceException.Decode("Decoding the resource table as a file is not supported")
var onClose = {}
var archivePath = path
if (tableBlock != null && path.startsWith("res/") && path.count { it == '/' } == 2) {
val file = File(path)
val qualifiers = EncodeUtil.getQualifiersFromResFile(file)
val type = EncodeUtil.getTypeNameFromResFile(file)
val name = file.nameWithoutExtension
// The resource file names that the app developers used may have been minified, so we have to resolve it with the resource table.
// Example: res/drawable-hdpi/icon.png -> res/4a.png
getEntry(type, name, qualifiers)?.resValue?.valueAsString?.let {
archivePath = it
} ?: run {
// An entry for this specific resource file was not found in the resource table, so we have to register it after we save.
onClose = { setResource(type, name, StringResource(archivePath), qualifiers) }
}
}
return ResourceFiles.Handle(path, archivePath, onClose)
}
fun setResource(type: String, entryName: String, resource: Resource, qualifiers: String? = null) =
getPackageBlock().getOrCreate(qualifiers, type, entryName).also { it.set(resource) }.resourceId
fun setResources(type: String, resources: Map<String, Resource>, configuration: String? = null) {
getPackageBlock().getOrCreateSpecTypePair(type).getOrCreateTypeBlock(configuration).apply {
resources.forEach { (entryName, resource) -> getOrCreateEntry(entryName).set(resource) }
}
}
override fun flush() {
packageBlock?.name = archive
}
}

View File

@@ -0,0 +1,91 @@
package app.revanced.arsc.resource
import app.revanced.arsc.ApkResourceException
import app.revanced.arsc.archive.Archive
import com.reandroid.archive.InputSource
import com.reandroid.xml.XMLDocument
import com.reandroid.xml.XMLException
import java.io.*
abstract class ResourceFile(val name: String) {
internal var realName: String? = null
class XmlResourceFile(name: String, val document: XMLDocument) : ResourceFile(name)
class BinaryResourceFile(name: String, var bytes: ByteArray) : ResourceFile(name)
}
class ResourceFiles private constructor(
) : Closeable {
/**
* Instantiate a [ResourceFiles].
*
* @param handle The [Handle] associated with this file.
* @param archive The [Archive] that the file resides in.
*/
internal constructor(handle: Handle, archive: Archive) : this(
handle,
archive,
try {
archive.read(handle.archivePath)
} catch (e: XMLException) {
throw ApkResourceException.Decode("Failed to decode XML while reading ${handle.virtualPath}", e)
} catch (e: IOException) {
throw ApkResourceException.Decode("Could not read ${handle.virtualPath}", e)
}
)
companion object {
const val DEFAULT_BUFFER_SIZE = 1024
}
var contents = readResult?.data ?: ByteArray(0)
set(value) {
pendingWrite = true
field = value
}
val exists = readResult != null
override fun toString() = handle.virtualPath
override fun close() {
if (pendingWrite) {
val path = handle.archivePath
if (isXmlResource) archive.writeXml(
path,
try {
XMLDocument.load(inputStream())
} catch (e: XMLException) {
throw ApkResourceException.Encode("Failed to parse XML while writing ${handle.virtualPath}", e)
}
) else archive.writeRaw(path, contents)
}
handle.onClose()
archive.unlock(this)
}
fun inputStream(): InputStream = ByteArrayInputStream(contents)
fun outputStream(bufferSize: Int = DEFAULT_BUFFER_SIZE): OutputStream =
object : ByteArrayOutputStream(bufferSize) {
override fun close() {
this@ResourceFiles.contents = if (buf.size > count) buf.copyOf(count) else buf
super.close()
}
}
/**
* @param virtualPath The resource file path. Example: /res/drawable-hdpi/icon.png.
* @param archivePath The actual file path in the archive. Example: res/4a.png.
* @param onClose An action to perform when the file associated with this handle is closed
*/
internal data class Handle(val virtualPath: String, val archivePath: String, val onClose: () -> Unit)
}

View File

@@ -0,0 +1,100 @@
package app.revanced.arsc.resource
import app.revanced.arsc.ApkResourceException
import com.reandroid.apk.xmlencoder.EncodeException
import com.reandroid.apk.xmlencoder.EncodeMaterials
import com.reandroid.arsc.util.FrameworkTable
import com.reandroid.arsc.value.Entry
import com.reandroid.common.TableEntryStore
/**
* A high-level API for resolving resources in the resource table, which spans the entire ApkBundle.
*/
class ResourceTable(base: ResourceContainer, all: Sequence<ResourceContainer>) {
private val packageName = base.tableBlock!!.name
/**
* A [TableEntryStore] used to decode XML.
*/
internal val entryStore = TableEntryStore()
/**
* The [EncodeMaterials] to use for resolving resources and encoding XML.
*/
internal val encodeMaterials: EncodeMaterials = object : EncodeMaterials() {
/*
Our implementation is more efficient because it does not have to loop through every single entry group
when the resource id cannot be found in the TableIdentifier, which does not update when you create a new resource.
It also looks at the entire table instead of just the current package.
*/
override fun resolveLocalResourceId(type: String, name: String) = resolveLocal(type, name)
}
/**
* The resource mappings which are generated when the ApkBundle is created.
*/
private val tableIdentifier = encodeMaterials.tableIdentifier
/**
* A table of all the resources that have been changed or added.
*/
private val modifiedResources = HashMap<String, HashMap<String, Int>>()
/**
* Resolve a resource id for the specified resource.
* Cannot resolve resources from the android framework.
*
* @param type The type of the resource.
* @param name The name of the resource.
* @return The id of the resource.
*/
fun resolveLocal(type: String, name: String) =
modifiedResources[type]?.get(name)
?: tableIdentifier.get(packageName, type, name)?.resourceId
?: throw ApkResourceException.InvalidReference(
type,
name
)
/**
* Resolve a resource id for the specified resource.
*
* @param reference The resource reference string.
* @return The id of the resource.
*/
fun resolve(reference: String) = try {
encodeMaterials.resolveReference(reference)
} catch (e: EncodeException) {
throw ApkResourceException.InvalidReference(reference, e)
}
/**
* Notify the [ResourceTable] that an [Entry] has been created or modified.
*/
internal fun registerChanged(entry: Entry) {
modifiedResources.getOrPut(entry.typeName, ::HashMap)[entry.name] = entry.resourceId
}
init {
all.forEach {
it.tableBlock?.let { table ->
entryStore.add(table)
tableIdentifier.load(table)
}
it.resourceTable = this
}
base.also {
encodeMaterials.currentPackage = it.tableBlock
it.tableBlock!!.frameWorks.forEach { fw ->
if (fw is FrameworkTable) {
entryStore.add(fw)
encodeMaterials.addFramework(fw)
}
}
}
}
}

View File

@@ -0,0 +1,56 @@
package app.revanced.arsc.xml
import app.revanced.arsc.resource.ResourceContainer
import app.revanced.arsc.resource.boolean
import com.reandroid.apk.xmlencoder.EncodeException
import com.reandroid.apk.xmlencoder.XMLEncodeSource
import com.reandroid.arsc.chunk.xml.ResXmlDocument
import com.reandroid.xml.XMLDocument
import com.reandroid.xml.XMLElement
import com.reandroid.xml.source.XMLDocumentSource
/**
* Archive input source to lazily encode an [XMLDocument] after it has been modified.
*
* @param name The file name of this input source.
* @param document The [XMLDocument] to encode.
* @param resources The [ResourceContainer] to use for encoding.
*/
internal class LazyXMLEncodeSource(
name: String,
val document: XMLDocument,
private val resources: ResourceContainer
) : XMLEncodeSource(resources.resourceTable.encodeMaterials, XMLDocumentSource(name, document)) {
private var encoded = false
override fun getResXmlBlock(): ResXmlDocument {
if (encoded) return super.getResXmlBlock()
XMLEncodeSource(resources.resourceTable.encodeMaterials, XMLDocumentSource(name, document))
fun XMLElement.registerIds() {
listAttributes().forEach { attr ->
if (!attr.value.startsWith("@+id/")) return@forEach
val name = attr.value.split('/').last()
resources.setResource("id", name, boolean(false))
attr.value = "@id/$name"
}
listChildElements().forEach { it.registerIds() }
}
// Handle all @+id/id_name references in the document.
document.documentElement.registerIds()
encoded = true
// This will call XMLEncodeSource.getResXmlBlock(),
// which will encode the document if it has not already been encoded.
try {
return super.getResXmlBlock()
} catch (e: EncodeException) {
throw EncodeException("Failed to encode $name", e)
}
}
}

View File

@@ -1,67 +1,3 @@
plugins {
kotlin("jvm") version "1.8.10"
`maven-publish`
}
group = "app.revanced"
val githubUsername: String = project.findProperty("gpr.user") as? String ?: System.getenv("GITHUB_ACTOR")
val githubPassword: String = project.findProperty("gpr.key") as? String ?: System.getenv("GITHUB_TOKEN")
repositories {
mavenCentral()
maven {
url = uri("https://maven.pkg.github.com/revanced/multidexlib2")
credentials {
username = githubUsername
password = githubPassword
}
}
}
dependencies {
implementation("xpp3:xpp3:1.1.4c")
implementation("app.revanced:smali:2.5.3-a3836654")
implementation("app.revanced:multidexlib2:2.5.3-a3836654")
implementation("app.revanced:apktool-lib:2.7.0")
implementation("org.jetbrains.kotlin:kotlin-reflect:1.8.20-RC")
testImplementation("org.jetbrains.kotlin:kotlin-test:1.8.20-RC")
}
tasks {
test {
useJUnitPlatform()
testLogging {
events("PASSED", "SKIPPED", "FAILED")
}
}
processResources {
expand("projectVersion" to project.version)
}
}
java {
withSourcesJar()
}
publishing {
repositories {
if (System.getenv("GITHUB_ACTOR") != null)
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/revanced/revanced-patcher")
credentials {
username = System.getenv("GITHUB_ACTOR")
password = System.getenv("GITHUB_TOKEN")
}
}
else
mavenLocal()
}
publications {
register<MavenPublication>("gpr") {
from(components["java"])
}
}
kotlin("jvm") version "1.8.20" apply false
}

View File

@@ -1,2 +1,4 @@
org.gradle.parallel=true
org.gradle.caching=true
kotlin.code.style = official
version = 7.1.0-dev.2
version = 11.0.4

Binary file not shown.

View File

@@ -1,5 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

40
gradlew vendored
View File

@@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -32,10 +32,10 @@
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
@@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -80,13 +80,10 @@ do
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -133,22 +130,29 @@ location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -193,6 +197,10 @@ if "$cygwin" || "$msys" ; then
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
@@ -205,6 +213,12 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.

15
gradlew.bat vendored
View File

@@ -14,7 +14,7 @@
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -25,7 +25,8 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal

View File

@@ -0,0 +1,61 @@
plugins {
kotlin("jvm")
`maven-publish`
}
group = "app.revanced"
dependencies {
implementation("xpp3:xpp3:1.1.4c")
implementation("app.revanced:smali:2.5.3-a3836654")
implementation("app.revanced:multidexlib2:2.5.3-a3836654")
implementation("io.github.reandroid:ARSCLib:1.1.7")
implementation(project(":arsclib-utils"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")
implementation("org.jetbrains.kotlin:kotlin-reflect:1.8.20-RC")
testImplementation("org.jetbrains.kotlin:kotlin-test:1.8.20-RC")
compileOnly("com.google.android:android:4.1.1.4")
}
tasks {
test {
useJUnitPlatform()
testLogging {
events("PASSED", "SKIPPED", "FAILED")
}
}
processResources {
expand("projectVersion" to project.version)
}
}
java {
withSourcesJar()
}
kotlin {
jvmToolchain(11)
}
publishing {
repositories {
if (System.getenv("GITHUB_ACTOR") != null)
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/revanced/revanced-patcher")
credentials {
username = System.getenv("GITHUB_ACTOR")
password = System.getenv("GITHUB_TOKEN")
}
}
else
mavenLocal()
}
publications {
register<MavenPublication>("gpr") {
from(components["java"])
}
}
}

View File

@@ -0,0 +1 @@
rootProject.name = "revanced-patcher"

View File

@@ -0,0 +1,113 @@
package app.revanced.patcher
import app.revanced.arsc.resource.ResourceContainer
import app.revanced.patcher.apk.Apk
import app.revanced.patcher.apk.ApkBundle
import app.revanced.arsc.resource.ResourceFiles
import app.revanced.patcher.util.method.MethodWalker
import org.jf.dexlib2.iface.Method
import org.w3c.dom.Document
import java.io.Closeable
import java.io.InputStream
import java.io.StringWriter
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult
/**
* A common class to constrain [Context] to [BytecodeContext] and [ResourceContext].
* @param apkBundle The [ApkBundle] for this context.
*/
sealed class Context(val apkBundle: ApkBundle)
/**
* A context for the bytecode of an [Apk.Base] file.
*
* @param apkBundle The [ApkBundle] for this context.
*/
class BytecodeContext internal constructor(apkBundle: ApkBundle) : Context(apkBundle) {
/**
* The list of classes.
*/
val classes = apkBundle.base.bytecodeData.classes
/**
* Create a [MethodWalker] instance for the current [BytecodeContext].
*
* @param startMethod The method to start at.
* @return A [MethodWalker] instance.
*/
fun traceMethodCalls(startMethod: Method) = MethodWalker(this, startMethod)
}
/**
* A context for [Apk] file resources.
*
* @param apkBundle the [ApkBundle] for this context.
*/
class ResourceContext internal constructor(apkBundle: ApkBundle) : Context(apkBundle) {
/**
* Open an [DomFileEditor] for a given DOM file.
*
* @param inputStream The input stream to read the DOM file from.
* @return A [DomFileEditor] instance.
*/
fun openXmlFile(inputStream: InputStream) = DomFileEditor(inputStream)
}
/**
* Open a [DomFileEditor] for a resource file in the archive.
*
* @see [ResourceContainer.openFile]
* @param path The resource file path.
* @return A [DomFileEditor].
*/
fun ResourceContainer.openXmlFile(path: String) = DomFileEditor(openFile(path))
/**
* Wrapper for a file that can be edited as a dom document.
*
* @param inputStream the input stream to read the xml file from.
* @param onSave A callback that will be called when the editor is closed to save the file.
*/
class DomFileEditor internal constructor(
private val inputStream: InputStream,
private val onSave: ((String) -> Unit)? = null
) : Closeable {
private var closed: Boolean = false
/**
* The document of the xml file.
*/
val file: Document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream)
.also(Document::normalize)
internal constructor(file: ResourceFiles) : this(
file.inputStream(),
{
file.contents = it.toByteArray()
file.close()
}
)
/**
* Closes the editor and writes back to the file.
*/
override fun close() {
if (closed) return
inputStream.close()
onSave?.let { callback ->
// Save the updated file.
val writer = StringWriter()
TransformerFactory.newInstance().newTransformer().transform(DOMSource(file), StreamResult(writer))
callback(writer.toString())
}
closed = true
}
}

View File

@@ -0,0 +1,222 @@
package app.revanced.patcher
import app.revanced.patcher.apk.Apk
import app.revanced.patcher.extensions.PatchExtensions.dependencies
import app.revanced.patcher.extensions.PatchExtensions.patchName
import app.revanced.patcher.extensions.PatchExtensions.requiresIntegrations
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolveUsingLookupMap
import app.revanced.patcher.patch.*
import app.revanced.patcher.util.VersionReader
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import lanchon.multidexlib2.BasicDexFileNamer
import java.io.Closeable
import java.io.File
import java.util.function.Function
typealias ExecutedPatchResults = Flow<Pair<String, PatchException?>>
/**
* The ReVanced Patcher.
* @param options The options for the patcher.
* @param patches The patches to use.
* @param integrations The integrations to merge if necessary. Must be dex files or dex file container such as ZIP, APK or DEX files.
*/
class Patcher(private val options: PatcherOptions, patches: Iterable<PatchClass>, integrations: Iterable<File>) :
Function<Boolean, ExecutedPatchResults> {
private val context = PatcherContext(options, patches.toList(), integrations)
private val logger = options.logger
companion object {
/**
* The version of the ReVanced Patcher.
*/
@JvmStatic
val version = VersionReader.read()
@Suppress("SpellCheckingInspection")
internal val dexFileNamer = BasicDexFileNamer()
}
init {
/**
* Returns true if at least one patches or its dependencies matches the given predicate.
*/
fun PatchClass.anyRecursively(predicate: (PatchClass) -> Boolean): Boolean =
predicate(this) || dependencies?.any { it.java.anyRecursively(predicate) } == true
// Determine if merging integrations is required.
for (patch in context.patches) {
if (patch.anyRecursively { it.requiresIntegrations }) {
context.integrations.merge = true
break
}
}
}
/**
* Execute the patcher.
*
* @param stopOnError If true, the patches will stop on the first error.
* @return A pair of the name of the [Patch] and a [PatchException] if it failed.
*/
override fun apply(stopOnError: Boolean) = flow {
/**
* Execute a [Patch] and its dependencies recursively.
*
* @param patchClass The [Patch] to execute.
* @param executedPatches A map of [Patch]es paired to a boolean indicating their success, to prevent infinite recursion.
*/
suspend fun executePatch(
patchClass: PatchClass,
executedPatches: HashMap<String, ExecutedPatch>
) {
val patchName = patchClass.patchName
// If the patch has already executed silently skip it.
if (executedPatches.contains(patchName)) {
if (!executedPatches[patchName]!!.success)
throw PatchException("'$patchName' did not succeed previously")
logger.trace("Skipping '$patchName' because it has already been executed")
return
}
// Recursively execute all dependency patches.
patchClass.dependencies?.forEach { dependencyClass ->
val dependency = dependencyClass.java
try {
executePatch(dependency, executedPatches)
} catch (throwable: Throwable) {
throw PatchException(
"'$patchName' depends on '${dependency.patchName}' " +
"but the following exception was raised: ${throwable.cause?.stackTraceToString() ?: throwable.message}",
throwable
)
}
}
val isResourcePatch = ResourcePatch::class.java.isAssignableFrom(patchClass)
val patchInstance = patchClass.getDeclaredConstructor().newInstance()
// TODO: implement this in a more polymorphic way.
val patchContext = if (isResourcePatch) {
context.resourceContext
} else {
context.bytecodeContext.apply {
val bytecodePatch = patchInstance as BytecodePatch
bytecodePatch.fingerprints?.resolveUsingLookupMap(context.bytecodeContext)
}
}
logger.trace("Executing '$patchName' of type: ${if (isResourcePatch) "resource" else "bytecode"}")
var success = false
try {
patchInstance.execute(patchContext)
success = true
} catch (patchException: PatchException) {
throw patchException
} catch (throwable: Throwable) {
throw PatchException("Unhandled patch exception: ${throwable.message}", throwable)
} finally {
executedPatches[patchName] = ExecutedPatch(patchInstance, success)
}
}
if (context.integrations.merge) context.integrations.merge(logger)
logger.trace("Initialize lookup maps for method MethodFingerprint resolution")
MethodFingerprint.initializeFingerprintResolutionLookupMaps(context.bytecodeContext)
logger.info("Executing patches")
// Key is patch name.
LinkedHashMap<String, ExecutedPatch>().apply {
context.patches.forEach { patch ->
var exception: PatchException? = null
try {
executePatch(patch, this)
} catch (patchException: PatchException) {
exception = patchException
}
// TODO: only emit if the patch is not a closeable.
// If it is a closeable, this should be done when closing the patch.
emit(patch.patchName to exception)
if (stopOnError && exception != null) return@flow
}
}.let {
it.values
.filter(ExecutedPatch::success)
.map(ExecutedPatch::patchInstance)
.filterIsInstance(Closeable::class.java)
.asReversed().forEach { patch ->
try {
patch.close()
} catch (throwable: Throwable) {
val patchException =
if (throwable is PatchException) throwable
else PatchException(throwable)
val patchName = (patch as Patch<Context>).javaClass.patchName
logger.error("Failed to close '$patchName': ${patchException.stackTraceToString()}")
emit(patchName to patchException)
// This is not failsafe. If a patch throws an exception while closing,
// the other patches that depend on it may fail.
if (stopOnError) return@flow
}
}
}
MethodFingerprint.clearFingerprintResolutionLookupMaps()
}
/**
* Finish patching all [Apk]s.
*
* @return The [PatcherResult] of the [Patcher].
*/
fun finish(): PatcherResult {
val patchResults = buildList {
logger.info("Processing patched apks")
options.apkBundle.cleanup(options).forEach { result ->
if (result.exception != null) {
logger.error("Got exception while processing ${result.apk}: ${result.exception.stackTraceToString()}")
return@forEach
}
val patch = result.let {
when (it.apk) {
is Apk.Base -> PatcherResult.Patch.Base(it.apk)
is Apk.Split -> PatcherResult.Patch.Split(it.apk)
}
}
add(patch)
logger.info("Patched ${result.apk}")
}
}
return PatcherResult(patchResults)
}
}
/**
* A result of executing a [Patch].
*
* @param patchInstance The instance of the [Patch] that was executed.
* @param success The result of the [Patch].
*/
internal data class ExecutedPatch(val patchInstance: Patch<Context>, val success: Boolean)

View File

@@ -0,0 +1,55 @@
package app.revanced.patcher
import app.revanced.patcher.logging.Logger
import app.revanced.patcher.patch.PatchClass
import app.revanced.patcher.util.ClassMerger.merge
import lanchon.multidexlib2.MultiDexIO
import java.io.File
class PatcherContext(
options: PatcherOptions,
internal val patches: List<PatchClass>,
integrations: Iterable<File>
) {
internal val integrations = Integrations(this, integrations)
internal val bytecodeContext = BytecodeContext(options.apkBundle)
internal val resourceContext = ResourceContext(options.apkBundle)
internal class Integrations(val context: PatcherContext, private val dexContainers: Iterable<File>) {
var merge = false
/**
* Merge integrations.
* @param logger A logger.
*/
fun merge(logger: Logger) {
context.bytecodeContext.classes.apply {
for (integrations in dexContainers) {
logger.info("Merging $integrations")
for (classDef in MultiDexIO.readDexFile(true, integrations, Patcher.dexFileNamer, null, null).classes) {
val type = classDef.type
val existingClassIndex = this.indexOfFirst { it.type == type }
if (existingClassIndex == -1) {
logger.trace("Merging type $type")
add(classDef)
continue
}
logger.trace("Type $type exists. Adding missing methods and fields.")
get(existingClassIndex).apply {
merge(classDef, context.bytecodeContext, logger).let { mergedClass ->
if (mergedClass !== this) // referential equality check
set(existingClassIndex, mergedClass)
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,14 @@
package app.revanced.patcher
import app.revanced.patcher.apk.ApkBundle
import app.revanced.patcher.logging.Logger
/**
* Options for the [Patcher].
* @param apkBundle The [ApkBundle].
* @param logger Custom logger implementation for the [Patcher].
*/
class PatcherOptions(
internal val apkBundle: ApkBundle,
internal val logger: Logger = Logger.Nop
)

View File

@@ -0,0 +1,33 @@
package app.revanced.patcher
import app.revanced.patcher.apk.Apk
import java.io.File
/**
* The result of a patcher.
* @param apkFiles The patched [Apk] files.
*/
data class PatcherResult(val apkFiles: List<Patch>) {
/**
* The result of a patch.
*
* @param apk The patched [Apk] file.
*/
sealed class Patch(val apk: Apk) {
/**
* The result of a patch of an [Apk.Split] file.
*
* @param apk The patched [Apk.Split] file.
*/
class Split(apk: Apk.Split) : Patch(apk)
/**
* The result of a patch of an [Apk.Split] file.
*
* @param apk The patched [Apk.Base] file.
*/
class Base(apk: Apk.Base) : Patch(apk)
}
}

View File

@@ -26,6 +26,7 @@ annotation class Description(
* @param version The version of a [Patch].
*/
@Target(AnnotationTarget.CLASS)
@Deprecated("This annotation is deprecated and will be removed in the future.")
annotation class Version(
val version: String,
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,105 @@
@file:Suppress("MemberVisibilityCanBePrivate")
package app.revanced.patcher.apk
import app.revanced.arsc.ApkResourceException
import app.revanced.arsc.resource.ResourceTable
import app.revanced.patcher.Patcher
import app.revanced.patcher.PatcherOptions
import app.revanced.patcher.apk.Apk.Companion.identify
import com.reandroid.apk.ApkModule
import java.io.File
/**
* An [Apk] file of type [Apk.Split].
*
* @param files A list of apk files to load.
*/
class ApkBundle(files: List<File>) : Sequence<Apk> {
/**
* The [Apk.Base] of this [ApkBundle].
*/
val base: Apk.Base
/**
* A map containing all the [Apk.Split]s in this bundle associated by their configuration.
*/
val splits: Map<String, Apk.Split>?
init {
var baseApk: Apk.Base? = null
splits = buildMap {
files.forEach {
val apk = ApkModule.loadApkFile(it)
val (module, type) = apk.identify()
if (module is Apk.Module.DynamicFeature) {
return@forEach // Dynamic feature modules are not supported yet.
}
when (type) {
Apk.Type.Base -> {
if (baseApk != null) {
throw IllegalArgumentException("Cannot have more than one base apk")
}
baseApk = Apk.Base(apk)
}
is Apk.Type.SplitConfig -> {
val target = type.target
if (this.contains(target)) {
throw IllegalArgumentException("Duplicate split: $target")
}
val constructor = when (type) {
is Apk.Type.Asset -> Apk.Split::Asset
is Apk.Type.Library -> Apk.Split::Library
is Apk.Type.Language -> Apk.Split::Language
}
this[target] = constructor(target, apk)
}
}
}
}.takeIf { it.isNotEmpty() }
base = baseApk ?: throw IllegalArgumentException("Base apk not found")
}
/**
* The [ResourceTable] of this [ApkBundle].
*/
val resources = ResourceTable(base.resources, map { it.resources })
override fun iterator() = sequence {
yield(base)
splits?.values?.let {
yieldAll(it)
}
}.iterator()
/**
* Refresh all updated resources in an [ApkBundle].
*
* @param options The [PatcherOptions] of the [Patcher].
* @return A sequence of the [Apk] files which are being refreshed.
*/
internal fun cleanup(options: PatcherOptions) = map {
var exception: ApkResourceException? = null
try {
it.cleanup(options)
} catch (e: ApkResourceException) {
exception = e
}
SplitApkResult(it, exception)
}
/**
* The result of writing an [Apk] file.
*
* @param apk The corresponding [Apk] file.
* @param exception The optional [ApkResourceException] when an exception occurred.
*/
data class SplitApkResult(val apk: Apk, val exception: ApkResourceException? = null)
}

View File

@@ -0,0 +1,33 @@
package app.revanced.patcher.extensions
import kotlin.reflect.KClass
internal object AnnotationExtensions {
/**
* Recursively find a given annotation on a class.
*
* @param targetAnnotation The annotation to find.
* @return The annotation.
*/
fun <T : Annotation> Class<*>.findAnnotationRecursively(targetAnnotation: KClass<T>): T? {
fun <T : Annotation> Class<*>.findAnnotationRecursively(
targetAnnotation: Class<T>, traversed: MutableSet<Annotation>
): T? {
val found = this.annotations.firstOrNull { it.annotationClass.java.name == targetAnnotation.name }
@Suppress("UNCHECKED_CAST") if (found != null) return found as T
for (annotation in this.annotations) {
if (traversed.contains(annotation)) continue
traversed.add(annotation)
return (annotation.annotationClass.java.findAnnotationRecursively(targetAnnotation, traversed))
?: continue
}
return null
}
return this.findAnnotationRecursively(targetAnnotation.java, mutableSetOf())
}
}

Some files were not shown because too many files have changed in this diff Show More