mirror of
https://github.com/ReVanced/revanced-patcher.git
synced 2026-01-11 22:06:18 +00:00
Compare commits
141 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5650e34432 | ||
|
|
c893d16d52 | ||
|
|
34f08bf206 | ||
|
|
f02a42610b | ||
|
|
c95e6fa92f | ||
|
|
fd738e723b | ||
|
|
b1d1956323 | ||
|
|
725a8012ac | ||
|
|
bb9a73e53b | ||
|
|
ef2de35a74 | ||
|
|
2a453d51a8 | ||
|
|
43d6868d1f | ||
|
|
cea9379b32 | ||
|
|
a12fe7dd9e | ||
|
|
efdd01a988 | ||
|
|
eafe1c631f | ||
|
|
aacf900764 | ||
|
|
f82494e9bb | ||
|
|
1e0ffa176e | ||
|
|
b7eb2d2249 | ||
|
|
b6d6a7591b | ||
|
|
8f1c835299 | ||
|
|
a188c16a99 | ||
|
|
3e6804f06c | ||
|
|
526a3d7c35 | ||
|
|
28fc6a2ddd | ||
|
|
d4f08d7bff | ||
|
|
ca9fe322eb | ||
|
|
239ea0bcaa | ||
|
|
7f02b8df48 | ||
|
|
a2052202b2 | ||
|
|
223cea7021 | ||
|
|
ac9337f694 | ||
|
|
549651d04a | ||
|
|
966bbd902e | ||
|
|
81e6f8784e | ||
|
|
9c53877888 | ||
|
|
98f8eedecd | ||
|
|
4ed429d25c | ||
|
|
119d05f469 | ||
|
|
2432fde6bf | ||
|
|
49c173dc14 | ||
|
|
d83e9372bb | ||
|
|
7e8cd3bede | ||
|
|
d67436271d | ||
|
|
aa07f35f06 | ||
|
|
77e0536838 | ||
|
|
a49e78234b | ||
|
|
a3ae825e48 | ||
|
|
146c8504ed | ||
|
|
2eb125ad69 | ||
|
|
6e24a85eab | ||
|
|
e4c3e9ffc5 | ||
|
|
4c1778a62f | ||
|
|
d99261cdbb | ||
|
|
ac1c0f2773 | ||
|
|
eddd4ec7ac | ||
|
|
07a2829c65 | ||
|
|
3d77e299d9 | ||
|
|
f1336f89e4 | ||
|
|
0502f84c20 | ||
|
|
058d292ad5 | ||
|
|
1029d56a52 | ||
|
|
709b5a0fec | ||
|
|
e1accc5041 | ||
|
|
6dbbf2e03e | ||
|
|
16557eeab0 | ||
|
|
6bca3e2bb5 | ||
|
|
a263fdfd41 | ||
|
|
e4b4bacae8 | ||
|
|
cbc97af155 | ||
|
|
d5533788e2 | ||
|
|
5a4ea5cd7d | ||
|
|
70f3c8b38c | ||
|
|
6b410a0eea | ||
|
|
73a013d75b | ||
|
|
7159f3db4c | ||
|
|
7d5ecf095c | ||
|
|
fa015a424d | ||
|
|
dd7dd38357 | ||
|
|
22356f2d26 | ||
|
|
66701f6076 | ||
|
|
6a6ded084e | ||
|
|
5887c69bde | ||
|
|
4102f43b8a | ||
|
|
5c09ef7837 | ||
|
|
3e0bf8c863 | ||
|
|
8f3ecc318c | ||
|
|
365da96e2b | ||
|
|
cd68ec4803 | ||
|
|
35265e029c | ||
|
|
9f0a09a756 | ||
|
|
e802141df5 | ||
|
|
abebc0862c | ||
|
|
96ef150e89 | ||
|
|
c5de9e2988 | ||
|
|
c391ca648b | ||
|
|
7cf79e68e0 | ||
|
|
f07db3c214 | ||
|
|
88bb3a8845 | ||
|
|
b9e6bd6775 | ||
|
|
cd1b72e078 | ||
|
|
6b889557ab | ||
|
|
4b1be8c647 | ||
|
|
73c893c6e7 | ||
|
|
75b36823b8 | ||
|
|
d2d93cd075 | ||
|
|
26b8621ac8 | ||
|
|
f365a41741 | ||
|
|
9ec720e983 | ||
|
|
0f432b3fdd | ||
|
|
96cd5618dd | ||
|
|
c2a5a55e67 | ||
|
|
6c5de8b414 | ||
|
|
ea773cfa56 | ||
|
|
a306561b55 | ||
|
|
b6dcd88495 | ||
|
|
a925650044 | ||
|
|
77bbf6be1f | ||
|
|
bd053b7e99 | ||
|
|
fd742eba63 | ||
|
|
ba9d998681 | ||
|
|
75df245ec3 | ||
|
|
4164cb0dea | ||
|
|
18fe35ae73 | ||
|
|
f9bc95f220 | ||
|
|
d2f91a8545 | ||
|
|
4016bdc37f | ||
|
|
538b2a8599 | ||
|
|
4aa14bbb85 | ||
|
|
d37452997b | ||
|
|
db21d5e953 | ||
|
|
4d581811db | ||
|
|
8c502448be | ||
|
|
fec16d9442 | ||
|
|
5583904994 | ||
|
|
797286b758 | ||
|
|
4ae9ad09d6 | ||
|
|
447e1ad30e | ||
|
|
843e62ad29 | ||
|
|
9c07ffcc7a |
2
.github/config.yml
vendored
Normal file
2
.github/config.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
firstPRMergeComment: >
|
||||
Thank you for contributing to ReVanced. Join us on [Discord](https://revanced.app/discord) if you want to receive a contributor role.
|
||||
25
.github/workflows/pull_request.yml
vendored
Normal file
25
.github/workflows/pull_request.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: PR to main
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
MESSAGE: merge branch `${{ github.head_ref || github.ref_name }}` to `main`
|
||||
|
||||
jobs:
|
||||
pull-request:
|
||||
name: Open pull request
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Open pull request
|
||||
uses: repo-sync/pull-request@v2
|
||||
with:
|
||||
destination_branch: 'main'
|
||||
pr_title: 'chore: ${{ env.MESSAGE }}'
|
||||
pr_body: 'This pull request will ${{ env.MESSAGE }}.'
|
||||
pr_draft: true
|
||||
33
.github/workflows/release.yml
vendored
33
.github/workflows/release.yml
vendored
@@ -1,4 +1,5 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
@@ -9,32 +10,36 @@ on:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Make sure the release step uses its own credentials:
|
||||
# https://github.com/cycjimmy/semantic-release-action#private-packages
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
- name: Setup JDK
|
||||
uses: actions/setup-java@v2
|
||||
- name: Cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'adopt'
|
||||
cache: gradle
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
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 build
|
||||
run: ./gradlew clean --no-daemon
|
||||
- name: Setup semantic-release
|
||||
run: npm install -g semantic-release @semantic-release/git @semantic-release/changelog gradle-semantic-release-plugin -D
|
||||
run: npm install
|
||||
- name: Release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: npx semantic-release
|
||||
GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }}
|
||||
run: npm exec semantic-release
|
||||
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -74,6 +74,7 @@ cmake-build-*/
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
.idea/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
@@ -115,3 +116,9 @@ gradle-app.setting
|
||||
|
||||
# Avoid ignoring test resources
|
||||
!src/test/resources/*
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
|
||||
# Gradle props, to avoid sharing the gpr key
|
||||
gradle.properties
|
||||
|
||||
9
.idea/.gitignore
generated
vendored
9
.idea/.gitignore
generated
vendored
@@ -1,9 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
/kotlinc.xml
|
||||
10
.idea/codeStyles/Project.xml
generated
10
.idea/codeStyles/Project.xml
generated
@@ -1,10 +0,0 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<JetCodeStyleSettings>
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
||||
5
.idea/codeStyles/codeStyleConfig.xml
generated
5
.idea/codeStyles/codeStyleConfig.xml
generated
@@ -1,5 +0,0 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
||||
7
.idea/discord.xml
generated
7
.idea/discord.xml
generated
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DiscordProjectSettings">
|
||||
<option name="show" value="PROJECT_FILES" />
|
||||
<option name="description" value="" />
|
||||
</component>
|
||||
</project>
|
||||
15
.idea/git_toolbox_prj.xml
generated
15
.idea/git_toolbox_prj.xml
generated
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GitToolBoxProjectSettings">
|
||||
<option name="commitMessageIssueKeyValidationOverride">
|
||||
<BoolValueOverride>
|
||||
<option name="enabled" value="true" />
|
||||
</BoolValueOverride>
|
||||
</option>
|
||||
<option name="commitMessageValidationEnabledOverride">
|
||||
<BoolValueOverride>
|
||||
<option name="enabled" value="true" />
|
||||
</BoolValueOverride>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/inspectionProfiles/Project_Default.xml
generated
6
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -1,6 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="UnusedSymbol" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
</profile>
|
||||
</component>
|
||||
10
.idea/misc.xml
generated
10
.idea/misc.xml
generated
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
<file type="web" url="file://$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="azul-17" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
12
.idea/vcs.xml
generated
12
.idea/vcs.xml
generated
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CommitMessageInspectionProfile">
|
||||
<profile version="1.0">
|
||||
<inspection_tool class="CommitFormat" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="CommitNamingConvention" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
20
.releaserc
20
.releaserc
@@ -7,11 +7,7 @@
|
||||
}
|
||||
],
|
||||
"plugins": [
|
||||
["@semantic-release/commit-analyzer", {
|
||||
"releaseRules": [
|
||||
{"type": "build", "release": "patch"}
|
||||
]
|
||||
}],
|
||||
"@semantic-release/commit-analyzer",
|
||||
"@semantic-release/release-notes-generator",
|
||||
"@semantic-release/changelog",
|
||||
"gradle-semantic-release-plugin",
|
||||
@@ -24,6 +20,18 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"@semantic-release/github"
|
||||
[
|
||||
"@saithodev/semantic-release-backmerge",
|
||||
{
|
||||
backmergeBranches: [{"from": "main", "to": "dev"}],
|
||||
clearWorkspace: true
|
||||
}
|
||||
],
|
||||
[
|
||||
"@semantic-release/github",
|
||||
{
|
||||
successComment: false
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
359
CHANGELOG.md
359
CHANGELOG.md
@@ -1,3 +1,362 @@
|
||||
## [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)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add overload to get instruction as type ([49c173d](https://github.com/revanced/revanced-patcher/commit/49c173dc14137ddd198a611e9882dc71300831ee))
|
||||
|
||||
# [7.1.0-dev.1](https://github.com/revanced/revanced-patcher/compare/v7.0.0...v7.1.0-dev.1) (2023-04-30)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add appreciation message for new contributors ([d674362](https://github.com/revanced/revanced-patcher/commit/d67436271ddca9ccfe008272c1ca82c6123ae7ee))
|
||||
|
||||
# [7.0.0](https://github.com/revanced/revanced-patcher/compare/v6.4.3...v7.0.0) (2023-02-26)
|
||||
|
||||
|
||||
* feat!: merge integrations only when necessary ([6e24a85](https://github.com/revanced/revanced-patcher/commit/6e24a85eabd1e7a1484fad229d5ba55c3ba1f1b4))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* `Patcher.addFiles` is now renamed to `Patcher.addIntegrations`
|
||||
|
||||
Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
|
||||
|
||||
# [7.0.0-dev.1](https://github.com/revanced/revanced-patcher/compare/v6.4.3...v7.0.0-dev.1) (2023-02-26)
|
||||
|
||||
|
||||
* feat!: merge integrations only when necessary ([6e24a85](https://github.com/revanced/revanced-patcher/commit/6e24a85eabd1e7a1484fad229d5ba55c3ba1f1b4))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* `Patcher.addFiles` is now renamed to `Patcher.addIntegrations`
|
||||
|
||||
Signed-off-by: oSumAtrIX <johan.melkonyan1@web.de>
|
||||
|
||||
## [6.4.3](https://github.com/revanced/revanced-patcher/compare/v6.4.2...v6.4.3) (2023-02-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* check `CONST_STRING_JUMP` instructions for matching string ([058d292](https://github.com/revanced/revanced-patcher/commit/058d292ad5e297f4c652ff543c13e77a39f7fb1b))
|
||||
|
||||
## [6.4.3-dev.1](https://github.com/revanced/revanced-patcher/compare/v6.4.2...v6.4.3-dev.1) (2023-02-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* check `CONST_STRING_JUMP` instructions for matching string ([058d292](https://github.com/revanced/revanced-patcher/commit/058d292ad5e297f4c652ff543c13e77a39f7fb1b))
|
||||
|
||||
## [6.4.2](https://github.com/revanced/revanced-patcher/compare/v6.4.1...v6.4.2) (2023-01-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* resolve failing builds ([a263fdf](https://github.com/revanced/revanced-patcher/commit/a263fdfd413fc05098e28d4800e36ce7d313085b))
|
||||
|
||||
## [6.4.2-dev.1](https://github.com/revanced/revanced-patcher/compare/v6.4.1...v6.4.2-dev.1) (2023-01-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* resolve failing builds ([a263fdf](https://github.com/revanced/revanced-patcher/commit/a263fdfd413fc05098e28d4800e36ce7d313085b))
|
||||
|
||||
## [6.4.1](https://github.com/revanced/revanced-patcher/compare/v6.4.0...v6.4.1) (2023-01-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update dependency `app.revanced:multidexlib2` ([#150](https://github.com/revanced/revanced-patcher/issues/150)) ([dd7dd38](https://github.com/revanced/revanced-patcher/commit/dd7dd383577dcfc95e97f77b446a89b41b589dc0))
|
||||
|
||||
## [6.4.1](https://github.com/revanced/revanced-patcher/compare/v6.4.0...v6.4.1) (2023-01-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update dependency `app.revanced:multidexlib2` ([#150](https://github.com/revanced/revanced-patcher/issues/150)) ([dd7dd38](https://github.com/revanced/revanced-patcher/commit/dd7dd383577dcfc95e97f77b446a89b41b589dc0))
|
||||
|
||||
## [6.4.1-dev.1](https://github.com/revanced/revanced-patcher/compare/v6.4.0...v6.4.1-dev.1) (2023-01-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update dependency `app.revanced:multidexlib2` ([#150](https://github.com/revanced/revanced-patcher/issues/150)) ([dd7dd38](https://github.com/revanced/revanced-patcher/commit/dd7dd383577dcfc95e97f77b446a89b41b589dc0))
|
||||
|
||||
# [6.4.0](https://github.com/revanced/revanced-patcher/compare/v6.3.2...v6.4.0) (2023-01-02)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add missing setter to `MutableMethod` ([8f3ecc3](https://github.com/revanced/revanced-patcher/commit/8f3ecc318c39f0270aff53efdee7a1c8d82af421))
|
||||
* do not fix methods or methods in class merger ([4102f43](https://github.com/revanced/revanced-patcher/commit/4102f43b8a9473fd0ee96c5d4fb8f6e9b4e30e70))
|
||||
* fix method and field access when merging classes ([5c09ef7](https://github.com/revanced/revanced-patcher/commit/5c09ef7837f9b731e137b66c19da77f63c007595))
|
||||
* make `aaptPath` nullable ([#146](https://github.com/revanced/revanced-patcher/issues/146)) ([9f0a09a](https://github.com/revanced/revanced-patcher/commit/9f0a09a7569fd5dd78afa27cb66a73d1662edc69))
|
||||
|
||||
# [6.4.0-dev.2](https://github.com/revanced/revanced-patcher/compare/v6.4.0-dev.1...v6.4.0-dev.2) (2023-01-02)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add missing setter to `MutableMethod` ([8f3ecc3](https://github.com/revanced/revanced-patcher/commit/8f3ecc318c39f0270aff53efdee7a1c8d82af421))
|
||||
* do not fix methods or methods in class merger ([4102f43](https://github.com/revanced/revanced-patcher/commit/4102f43b8a9473fd0ee96c5d4fb8f6e9b4e30e70))
|
||||
* fix method and field access when merging classes ([5c09ef7](https://github.com/revanced/revanced-patcher/commit/5c09ef7837f9b731e137b66c19da77f63c007595))
|
||||
|
||||
# [6.4.0-dev.1](https://github.com/revanced/revanced-patcher/compare/v6.3.2...v6.4.0-dev.1) (2022-12-20)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* make `aaptPath` nullable ([#146](https://github.com/revanced/revanced-patcher/issues/146)) ([9f0a09a](https://github.com/revanced/revanced-patcher/commit/9f0a09a7569fd5dd78afa27cb66a73d1662edc69))
|
||||
|
||||
## [6.3.2](https://github.com/revanced/revanced-patcher/compare/v6.3.1...v6.3.2) (2022-12-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* check if fingerprint string is substring of any string references ([c5de9e2](https://github.com/revanced/revanced-patcher/commit/c5de9e29889dffd18b31e62a892881cc48e8b607))
|
||||
* print full exception when patch fails ([7cf79e6](https://github.com/revanced/revanced-patcher/commit/7cf79e68e0e9dfd9faddee33139b127b71882d3e))
|
||||
|
||||
## [6.3.2-dev.1](https://github.com/revanced/revanced-patcher/compare/v6.3.1...v6.3.2-dev.1) (2022-12-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* check if fingerprint string is substring of any string references ([c5de9e2](https://github.com/revanced/revanced-patcher/commit/c5de9e29889dffd18b31e62a892881cc48e8b607))
|
||||
* print full exception when patch fails ([7cf79e6](https://github.com/revanced/revanced-patcher/commit/7cf79e68e0e9dfd9faddee33139b127b71882d3e))
|
||||
|
||||
## [6.3.2-dev.1](https://github.com/revanced/revanced-patcher/compare/v6.3.1...v6.3.2-dev.1) (2022-12-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* print full exception when patch fails ([27a8401](https://github.com/revanced/revanced-patcher/commit/27a8401d81e078e0303f7ddcb0ac6f342f8e4def))
|
||||
|
||||
## [6.3.1](https://github.com/revanced/revanced-patcher/compare/v6.3.0...v6.3.1) (2022-12-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* publicize types when merging files if necessary ([#137](https://github.com/revanced/revanced-patcher/issues/137)) ([9ec720e](https://github.com/revanced/revanced-patcher/commit/9ec720e983785d8b1dde330cc0e0e0f914c1803c))
|
||||
|
||||
## [6.3.1-dev.1](https://github.com/revanced/revanced-patcher/compare/v6.3.0...v6.3.1-dev.1) (2022-12-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* publicize types when merging files if necessary ([#137](https://github.com/revanced/revanced-patcher/issues/137)) ([9ec720e](https://github.com/revanced/revanced-patcher/commit/9ec720e983785d8b1dde330cc0e0e0f914c1803c))
|
||||
|
||||
# [6.3.0](https://github.com/revanced/revanced-patcher/compare/v6.2.0...v6.3.0) (2022-12-02)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* sort patches in lexicographical order ([a306561](https://github.com/revanced/revanced-patcher/commit/a306561b55ac848792046378f582a036f7ffab03)), closes [#125](https://github.com/revanced/revanced-patcher/issues/125)
|
||||
|
||||
# [6.2.0](https://github.com/revanced/revanced-patcher/compare/v6.1.1...v6.2.0) (2022-12-02)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* merge classes on addition ([#127](https://github.com/revanced/revanced-patcher/issues/127)) ([a925650](https://github.com/revanced/revanced-patcher/commit/a9256500440f9b4117f1b8813ba0097dafee4ebb))
|
||||
|
||||
## [6.1.1](https://github.com/revanced/revanced-patcher/compare/v6.1.0...v6.1.1) (2022-11-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* use `MethodUtil.methodSignaturesMatch` instead of `Method.softCompareTo` ([bd053b7](https://github.com/revanced/revanced-patcher/commit/bd053b7e9974c0282d56e6762459db7070452e4a))
|
||||
|
||||
# [6.1.0](https://github.com/revanced/revanced-patcher/compare/v6.0.2...v6.1.0) (2022-11-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* apply changes from ReVanced Patcher ([ba9d998](https://github.com/revanced/revanced-patcher/commit/ba9d99868103406fe36b9aa0cfaa0ed5023edfab))
|
||||
|
||||
## [6.0.2](https://github.com/revanced/revanced-patcher/compare/v6.0.1...v6.0.2) (2022-11-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fallback to patch class name instead of `java.lang.Class` class name ([4164cb0](https://github.com/revanced/revanced-patcher/commit/4164cb0deacc7e1eed9fce63dab030180f28b762))
|
||||
|
||||
## [6.0.1](https://github.com/revanced/revanced-patcher/compare/v6.0.0...v6.0.1) (2022-11-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove unnecessary dummy nop instructions ([#111](https://github.com/revanced/revanced-patcher/issues/111)) ([f9bc95f](https://github.com/revanced/revanced-patcher/commit/f9bc95f220aa434308ce6950ba6ad2e7efac9c8a))
|
||||
|
||||
# [6.0.0](https://github.com/revanced/revanced-patcher/compare/v5.1.2...v6.0.0) (2022-10-05)
|
||||
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* improve structuring of classes and their implementations ([4aa14bb](https://github.com/revanced/revanced-patcher/commit/4aa14bbb858af9253eae9328b759f3298b65a215))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* remove unused annotation `DirectPatternScanMethod` ([538b2a8](https://github.com/revanced/revanced-patcher/commit/538b2a859962570c700362afc88704ed3611aa87))
|
||||
* remove unused annotation `SincePatcher` ([4ae9ad0](https://github.com/revanced/revanced-patcher/commit/4ae9ad09d64a3f69512ccb037f816cb847d7350f))
|
||||
* remove unused extension `dependsOn` ([797286b](https://github.com/revanced/revanced-patcher/commit/797286b7588646272dea2fd35e8e78b0ffb18a0f))
|
||||
* remove unused patch extensions ([5583904](https://github.com/revanced/revanced-patcher/commit/55839049948033ad02414517fd3ba03619216aec))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* various changes in which packages classes previously where and their implementation
|
||||
* These extensions do not exist anymore and any use should be removed
|
||||
* The extension does not exist anymore and any use should be removed
|
||||
* The annotation does not exist anymore and any use should be removed
|
||||
|
||||
## [5.1.2](https://github.com/revanced/revanced-patcher/compare/v5.1.1...v5.1.2) (2022-09-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* check dependencies for resource patches ([9c07ffc](https://github.com/revanced/revanced-patcher/commit/9c07ffcc7af9f088426528561f4321c5cc6b5b15))
|
||||
* use instruction index instead of strings list index for `StringMatch` ([843e62a](https://github.com/revanced/revanced-patcher/commit/843e62ad290ee0a707be9322ee943921da3ea420))
|
||||
|
||||
## [5.1.1](https://github.com/revanced/revanced-patcher/compare/v5.1.0...v5.1.1) (2022-09-26)
|
||||
|
||||
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
# Patcher
|
||||
Patcher framework used in the ReVanced project.
|
||||
# 💉 ReVanced Patcher
|
||||
|
||||
ReVanced Patcher used to patch Android applications.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
plugins {
|
||||
kotlin("jvm") version "1.7.0"
|
||||
java
|
||||
kotlin("jvm") version "1.8.10"
|
||||
`maven-publish`
|
||||
}
|
||||
|
||||
@@ -22,12 +21,12 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
implementation("xpp3:xpp3:1.1.4c")
|
||||
implementation("org.smali:smali:2.5.2")
|
||||
implementation("app.revanced:multidexlib2:2.5.2.r2")
|
||||
implementation("org.apktool:apktool-lib:2.8.1-SNAPSHOT")
|
||||
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(kotlin("reflect"))
|
||||
testImplementation(kotlin("test"))
|
||||
implementation("org.jetbrains.kotlin:kotlin-reflect:1.8.20-RC")
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test:1.8.20-RC")
|
||||
}
|
||||
|
||||
tasks {
|
||||
@@ -46,6 +45,10 @@ java {
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(11)
|
||||
}
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
if (System.getenv("GITHUB_ACTOR") != null)
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
kotlin.code.style = official
|
||||
version = 5.1.1
|
||||
version = 11.0.1
|
||||
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
6580
package-lock.json
generated
Normal file
6580
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
9
package.json
Normal file
9
package.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"@saithodev/semantic-release-backmerge": "^3.1.0",
|
||||
"@semantic-release/changelog": "^6.0.2",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"gradle-semantic-release-plugin": "^1.7.6",
|
||||
"semantic-release": "^20.1.0"
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,11 @@
|
||||
package app.revanced.patcher
|
||||
|
||||
import app.revanced.patcher.data.Data
|
||||
import app.revanced.patcher.data.impl.findIndexed
|
||||
import app.revanced.patcher.data.Context
|
||||
import app.revanced.patcher.extensions.PatchExtensions.dependencies
|
||||
import app.revanced.patcher.extensions.PatchExtensions.deprecated
|
||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||
import app.revanced.patcher.extensions.PatchExtensions.sincePatcherVersion
|
||||
import app.revanced.patcher.extensions.nullOutputStream
|
||||
import app.revanced.patcher.extensions.PatchExtensions.requiresIntegrations
|
||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.patch.PatchResult
|
||||
import app.revanced.patcher.patch.PatchResultError
|
||||
import app.revanced.patcher.patch.PatchResultSuccess
|
||||
import app.revanced.patcher.patch.impl.BytecodePatch
|
||||
import app.revanced.patcher.patch.impl.ResourcePatch
|
||||
import app.revanced.patcher.util.ListBackedSet
|
||||
import app.revanced.patcher.patch.*
|
||||
import app.revanced.patcher.util.VersionReader
|
||||
import brut.androlib.Androlib
|
||||
import brut.androlib.meta.UsesFramework
|
||||
@@ -30,14 +21,13 @@ import lanchon.multidexlib2.BasicDexFileNamer
|
||||
import lanchon.multidexlib2.DexIO
|
||||
import lanchon.multidexlib2.MultiDexIO
|
||||
import org.jf.dexlib2.Opcodes
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
import org.jf.dexlib2.iface.DexFile
|
||||
import org.jf.dexlib2.writer.io.MemoryDataStore
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
import java.io.OutputStream
|
||||
import java.nio.file.Files
|
||||
|
||||
private val NAMER = BasicDexFileNamer()
|
||||
internal val NAMER = BasicDexFileNamer()
|
||||
|
||||
/**
|
||||
* The ReVanced Patcher.
|
||||
@@ -47,7 +37,8 @@ class Patcher(private val options: PatcherOptions) {
|
||||
private val logger = options.logger
|
||||
private val opcodes: Opcodes
|
||||
private var resourceDecodingMode = ResourceDecodingMode.MANIFEST_ONLY
|
||||
val data: PatcherData
|
||||
private var mergeIntegrations = false
|
||||
val context: PatcherContext
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@@ -65,50 +56,27 @@ class Patcher(private val options: PatcherOptions) {
|
||||
val dexFile = MultiDexIO.readDexFile(true, options.inputFile, NAMER, null, null)
|
||||
// get the opcodes
|
||||
opcodes = dexFile.opcodes
|
||||
// finally create patcher data
|
||||
data = PatcherData(dexFile.classes.toMutableList(), options.resourceCacheDirectory)
|
||||
// finally create patcher context
|
||||
context = PatcherContext(dexFile.classes.toMutableList(), File(options.resourceCacheDirectory))
|
||||
|
||||
// decode manifest file
|
||||
decodeResources(ResourceDecodingMode.MANIFEST_ONLY)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add additional dex file container to the patcher.
|
||||
* @param files The dex file containers to add to the patcher.
|
||||
* @param allowedOverwrites A list of class types that are allowed to be overwritten.
|
||||
* @param throwOnDuplicates If this is set to true, the patcher will throw an exception if a duplicate class has been found.
|
||||
* Add integrations to be merged by the patcher.
|
||||
* The integrations will only be merged, if necessary.
|
||||
*
|
||||
* @param integrations The integrations, must be dex files or dex file container such as ZIP, APK or DEX files.
|
||||
* @param callback The callback for [integrations] which are being added.
|
||||
*/
|
||||
fun addFiles(
|
||||
files: List<File>,
|
||||
allowedOverwrites: Iterable<String> = emptyList(),
|
||||
throwOnDuplicates: Boolean = false,
|
||||
fun addIntegrations(
|
||||
integrations: List<File>,
|
||||
callback: (File) -> Unit
|
||||
) {
|
||||
for (file in files) {
|
||||
var modified = false
|
||||
for (classDef in MultiDexIO.readDexFile(true, file, NAMER, null, null).classes) {
|
||||
val type = classDef.type
|
||||
|
||||
val existingClass = data.bytecodeData.classes.internalClasses.findIndexed { it.type == type }
|
||||
if (existingClass == null) {
|
||||
if (throwOnDuplicates) throw Exception("Class $type has already been added to the patcher")
|
||||
|
||||
logger.trace("Merging $type")
|
||||
data.bytecodeData.classes.internalClasses.add(classDef)
|
||||
modified = true
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if (!allowedOverwrites.contains(type)) continue
|
||||
|
||||
logger.trace("Overwriting $type")
|
||||
|
||||
val index = existingClass.second
|
||||
data.bytecodeData.classes.internalClasses[index] = classDef
|
||||
modified = true
|
||||
}
|
||||
if (modified) callback(file)
|
||||
context.integrations.apply integrations@{
|
||||
add(integrations)
|
||||
this@integrations.callback = callback
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +84,7 @@ class Patcher(private val options: PatcherOptions) {
|
||||
* Save the patched dex file.
|
||||
*/
|
||||
fun save(): PatcherResult {
|
||||
val packageMetadata = data.packageMetadata
|
||||
val packageMetadata = context.packageMetadata
|
||||
val metaInfo = packageMetadata.metaInfo
|
||||
var resourceFile: File? = null
|
||||
|
||||
@@ -164,19 +132,14 @@ class Patcher(private val options: PatcherOptions) {
|
||||
cacheDirectory.close()
|
||||
}
|
||||
}
|
||||
|
||||
else -> logger.info("Not compiling resources because resource patching is not required")
|
||||
}
|
||||
|
||||
logger.trace("Creating new dex file")
|
||||
val newDexFile = object : DexFile {
|
||||
override fun getClasses(): Set<ClassDef> {
|
||||
data.bytecodeData.classes.applyProxies()
|
||||
return ListBackedSet(data.bytecodeData.classes.internalClasses)
|
||||
}
|
||||
|
||||
override fun getOpcodes(): Opcodes {
|
||||
return this@Patcher.opcodes
|
||||
}
|
||||
override fun getClasses() = context.bytecodeContext.classes.also { it.replaceClasses() }
|
||||
override fun getOpcodes() = this@Patcher.opcodes
|
||||
}
|
||||
|
||||
// write modified dex files
|
||||
@@ -200,96 +163,31 @@ class Patcher(private val options: PatcherOptions) {
|
||||
* Add [Patch]es to the patcher.
|
||||
* @param patches [Patch]es The patches to add.
|
||||
*/
|
||||
fun addPatches(patches: Iterable<Class<out Patch<Data>>>) {
|
||||
fun addPatches(patches: Iterable<Class<out Patch<Context>>>) {
|
||||
/**
|
||||
* Fill the cache with the instances of the [Patch]es for later use.
|
||||
* Note: Dependencies of the [Patch] will be cached as well.
|
||||
* Returns true if at least one patches or its dependencies matches the given predicate.
|
||||
*/
|
||||
fun Class<out Patch<Data>>.isResource() {
|
||||
this.also {
|
||||
if (!ResourcePatch::class.java.isAssignableFrom(it)) return
|
||||
// set the mode to decode all resources before running the patches
|
||||
fun Class<out Patch<Context>>.anyRecursively(predicate: (Class<out Patch<Context>>) -> Boolean): Boolean =
|
||||
predicate(this) || dependencies?.any { it.java.anyRecursively(predicate) } == true
|
||||
|
||||
|
||||
// Determine if resource patching is required.
|
||||
for (patch in patches) {
|
||||
if (patch.anyRecursively { ResourcePatch::class.java.isAssignableFrom(it) }) {
|
||||
resourceDecodingMode = ResourceDecodingMode.FULL
|
||||
}.dependencies?.forEach { it.java.isResource() }
|
||||
}
|
||||
|
||||
data.patches.addAll(
|
||||
patches.onEach(Class<out Patch<Data>>::isResource).onEach { patch ->
|
||||
val needsVersion = patch.sincePatcherVersion
|
||||
if (needsVersion != null && needsVersion > version) {
|
||||
logger.error("Patch '${patch.patchName}' requires Patcher version $needsVersion or higher")
|
||||
logger.error("Current Patcher version is $version")
|
||||
logger.warn("Skipping '${patch.patchName}'!")
|
||||
return@onEach // TODO: continue or halt/throw?
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a [patch] and its dependencies recursively.
|
||||
* @param patch The [patch] to apply.
|
||||
* @param appliedPatches A map of [patch]es paired to a boolean indicating their success, to prevent infinite recursion.
|
||||
* @return The result of executing the [patch].
|
||||
*/
|
||||
private fun applyPatch(
|
||||
patch: Class<out Patch<Data>>,
|
||||
appliedPatches: LinkedHashMap<String, AppliedPatch>
|
||||
): PatchResult {
|
||||
val patchName = patch.patchName
|
||||
|
||||
// if the patch has already applied silently skip it
|
||||
if (appliedPatches.contains(patchName)) {
|
||||
if (!appliedPatches[patchName]!!.success)
|
||||
return PatchResultError("'$patchName' did not succeed previously")
|
||||
|
||||
logger.trace("Skipping '$patchName' because it has already been applied")
|
||||
|
||||
return PatchResultSuccess()
|
||||
}
|
||||
|
||||
// recursively apply all dependency patches
|
||||
patch.dependencies?.forEach { dependency ->
|
||||
val result = applyPatch(dependency.java, appliedPatches)
|
||||
|
||||
if (result.isSuccess()) return@forEach
|
||||
|
||||
val error = result.error()!!
|
||||
val errorMessage = error.cause ?: error.message
|
||||
return PatchResultError("'$patchName' depends on '${patch.patchName}' but the following error was raised: $errorMessage")
|
||||
}
|
||||
|
||||
patch.deprecated?.let { (reason, replacement) ->
|
||||
logger.warn("'$patchName' is deprecated, reason: $reason")
|
||||
if (replacement != null) logger.warn("Use '${replacement.java.patchName}' instead")
|
||||
}
|
||||
|
||||
val patchInstance = patch.getDeclaredConstructor().newInstance()
|
||||
|
||||
val isResourcePatch = ResourcePatch::class.java.isAssignableFrom(patch)
|
||||
// TODO: implement this in a more polymorphic way
|
||||
val data = if (isResourcePatch) {
|
||||
data.resourceData
|
||||
} else {
|
||||
data.bytecodeData.also { data ->
|
||||
(patchInstance as BytecodePatch).fingerprints?.resolve(
|
||||
data,
|
||||
data.classes.internalClasses
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
logger.trace("Executing '$patchName' of type: ${if (isResourcePatch) "resource" else "bytecode"}")
|
||||
|
||||
return try {
|
||||
patchInstance.execute(data).also {
|
||||
appliedPatches[patchName] = AppliedPatch(patchInstance, it.isSuccess())
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
PatchResultError(e).also {
|
||||
appliedPatches[patchName] = AppliedPatch(patchInstance, false)
|
||||
// Determine if merging integrations is required.
|
||||
for (patch in patches) {
|
||||
if (patch.anyRecursively { it.requiresIntegrations }) {
|
||||
mergeIntegrations = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
context.patches.addAll(patches)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -320,7 +218,7 @@ class Patcher(private val options: PatcherOptions) {
|
||||
androlib.decodeResourcesFull(extInputFile, outDir, resourceTable)
|
||||
|
||||
// read additional metadata from the resource table
|
||||
data.packageMetadata.let { metadata ->
|
||||
context.packageMetadata.let { metadata ->
|
||||
metadata.metaInfo.usesFramework = UsesFramework().also { framework ->
|
||||
framework.ids = resourceTable.listFramePackages().map { it.id }.sorted()
|
||||
}
|
||||
@@ -332,6 +230,7 @@ class Patcher(private val options: PatcherOptions) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ResourceDecodingMode.MANIFEST_ONLY -> {
|
||||
logger.info("Decoding AndroidManifest.xml only, because resources are not needed")
|
||||
|
||||
@@ -348,13 +247,18 @@ class Patcher(private val options: PatcherOptions) {
|
||||
XmlPullStreamDecoder(
|
||||
axmlParser, AndrolibResources().resXmlSerializer
|
||||
).decodeManifest(
|
||||
extInputFile.directory.getFileInput("AndroidManifest.xml"), nullOutputStream
|
||||
extInputFile.directory.getFileInput("AndroidManifest.xml"),
|
||||
object : OutputStream() {
|
||||
override fun write(b: Int) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// read of the resourceTable which is created by reading the manifest file
|
||||
data.packageMetadata.let { metadata ->
|
||||
context.packageMetadata.let { metadata ->
|
||||
metadata.packageName = resourceTable.currentResPackage.name
|
||||
metadata.packageVersion = resourceTable.versionInfo.versionName
|
||||
metadata.metaInfo.versionInfo = resourceTable.versionInfo
|
||||
@@ -366,37 +270,103 @@ class Patcher(private val options: PatcherOptions) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply patches loaded into the patcher.
|
||||
* Execute patches added the patcher.
|
||||
*
|
||||
* @param stopOnError If true, the patches will stop on the first error.
|
||||
* @return A pair of the name of the [Patch] and its [PatchResult].
|
||||
*/
|
||||
fun applyPatches(stopOnError: Boolean = false) = sequence {
|
||||
// prevent from decoding the manifest twice if it is not needed
|
||||
if (resourceDecodingMode == ResourceDecodingMode.FULL) decodeResources(ResourceDecodingMode.FULL)
|
||||
fun executePatches(stopOnError: Boolean = false): Sequence<Pair<String, Result<PatchResultSuccess>>> {
|
||||
/**
|
||||
* 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.
|
||||
* @return The result of executing the [Patch].
|
||||
*/
|
||||
fun executePatch(
|
||||
patchClass: Class<out Patch<Context>>,
|
||||
executedPatches: LinkedHashMap<String, ExecutedPatch>
|
||||
): PatchResult {
|
||||
val patchName = patchClass.patchName
|
||||
|
||||
logger.trace("Applying all patches")
|
||||
// if the patch has already applied silently skip it
|
||||
if (executedPatches.contains(patchName)) {
|
||||
if (!executedPatches[patchName]!!.success)
|
||||
return PatchResultError("'$patchName' did not succeed previously")
|
||||
|
||||
val appliedPatches = LinkedHashMap<String, AppliedPatch>() // first is name
|
||||
logger.trace("Skipping '$patchName' because it has already been applied")
|
||||
|
||||
try {
|
||||
for (patch in data.patches) {
|
||||
val patchResult = applyPatch(patch, appliedPatches)
|
||||
|
||||
val result = if (patchResult.isSuccess()) {
|
||||
Result.success(patchResult.success()!!)
|
||||
} else {
|
||||
Result.failure(patchResult.error()!!)
|
||||
}
|
||||
|
||||
yield(patch.patchName to result)
|
||||
if (stopOnError && patchResult.isError()) break
|
||||
return PatchResultSuccess()
|
||||
}
|
||||
} finally {
|
||||
// close all closeable patches in order
|
||||
for ((patch, _) in appliedPatches.values.reversed()) {
|
||||
if (patch !is Closeable) continue
|
||||
|
||||
patch.close()
|
||||
// recursively execute all dependency patches
|
||||
patchClass.dependencies?.forEach { dependencyClass ->
|
||||
val dependency = dependencyClass.java
|
||||
|
||||
val result = executePatch(dependency, executedPatches)
|
||||
if (result.isSuccess()) return@forEach
|
||||
|
||||
return PatchResultError(
|
||||
"'$patchName' depends on '${dependency.patchName}' but the following error was raised: " +
|
||||
result.error()!!.let { it.cause?.stackTraceToString() ?: it.message }
|
||||
)
|
||||
}
|
||||
|
||||
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.also { context ->
|
||||
(patchInstance as BytecodePatch).fingerprints?.resolve(
|
||||
context,
|
||||
context.classes.classes
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
logger.trace("Executing '$patchName' of type: ${if (isResourcePatch) "resource" else "bytecode"}")
|
||||
|
||||
return try {
|
||||
patchInstance.execute(patchContext).also {
|
||||
executedPatches[patchName] = ExecutedPatch(patchInstance, it.isSuccess())
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
PatchResultError(e).also {
|
||||
executedPatches[patchName] = ExecutedPatch(patchInstance, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sequence {
|
||||
if (mergeIntegrations) context.integrations.merge(logger)
|
||||
|
||||
// prevent from decoding the manifest twice if it is not needed
|
||||
if (resourceDecodingMode == ResourceDecodingMode.FULL) decodeResources(ResourceDecodingMode.FULL)
|
||||
|
||||
logger.trace("Executing all patches")
|
||||
|
||||
val executedPatches = LinkedHashMap<String, ExecutedPatch>() // first is name
|
||||
|
||||
try {
|
||||
context.patches.forEach { patch ->
|
||||
val patchResult = executePatch(patch, executedPatches)
|
||||
|
||||
val result = if (patchResult.isSuccess()) {
|
||||
Result.success(patchResult.success()!!)
|
||||
} else {
|
||||
Result.failure(patchResult.error()!!)
|
||||
}
|
||||
|
||||
yield(patch.patchName to result)
|
||||
if (stopOnError && patchResult.isError()) return@sequence
|
||||
}
|
||||
} finally {
|
||||
executedPatches.values.reversed().forEach { (patch, _) ->
|
||||
patch.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -418,9 +388,9 @@ class Patcher(private val options: PatcherOptions) {
|
||||
}
|
||||
|
||||
/**
|
||||
* A result of applying a [Patch].
|
||||
* A result of executing a [Patch].
|
||||
*
|
||||
* @param patchInstance The instance of the [Patch] that was applied.
|
||||
* @param success The result of the [Patch].
|
||||
*/
|
||||
internal data class AppliedPatch(val patchInstance: Patch<Data>, val success: Boolean)
|
||||
internal data class ExecutedPatch(val patchInstance: Patch<Context>, val success: Boolean)
|
||||
64
src/main/kotlin/app/revanced/patcher/PatcherContext.kt
Normal file
64
src/main/kotlin/app/revanced/patcher/PatcherContext.kt
Normal file
@@ -0,0 +1,64 @@
|
||||
package app.revanced.patcher
|
||||
|
||||
import app.revanced.patcher.data.*
|
||||
import app.revanced.patcher.logging.Logger
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.util.ClassMerger.merge
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
import java.io.File
|
||||
|
||||
data class PatcherContext(
|
||||
val classes: MutableList<ClassDef>,
|
||||
val resourceCacheDirectory: File,
|
||||
) {
|
||||
val packageMetadata = PackageMetadata()
|
||||
internal val patches = mutableListOf<Class<out Patch<Context>>>()
|
||||
internal val integrations = Integrations(this)
|
||||
internal val bytecodeContext = BytecodeContext(classes)
|
||||
internal val resourceContext = ResourceContext(resourceCacheDirectory)
|
||||
|
||||
internal class Integrations(val context: PatcherContext) {
|
||||
var callback: ((File) -> Unit)? = null
|
||||
private val integrations: MutableList<File> = mutableListOf()
|
||||
|
||||
fun add(integrations: List<File>) = this@Integrations.integrations.addAll(integrations)
|
||||
|
||||
/**
|
||||
* Merge integrations.
|
||||
* @param logger A logger.
|
||||
*/
|
||||
fun merge(logger: Logger) {
|
||||
with(context.bytecodeContext.classes) {
|
||||
for (integrations in integrations) {
|
||||
callback?.let { it(integrations) }
|
||||
|
||||
for (classDef in lanchon.multidexlib2.MultiDexIO.readDexFile(
|
||||
true,
|
||||
integrations,
|
||||
NAMER,
|
||||
null,
|
||||
null
|
||||
).classes) {
|
||||
val type = classDef.type
|
||||
|
||||
val result = classes.findIndexed { it.type == type }
|
||||
if (result == null) {
|
||||
logger.trace("Merging type $type")
|
||||
classes.add(classDef)
|
||||
continue
|
||||
}
|
||||
|
||||
val (existingClass, existingClassIndex) = result
|
||||
|
||||
logger.trace("Type $type exists. Adding missing methods and fields.")
|
||||
|
||||
existingClass.merge(classDef, context, logger).let { mergedClass ->
|
||||
if (mergedClass !== existingClass) // referential equality check
|
||||
classes[existingClassIndex] = mergedClass
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package app.revanced.patcher
|
||||
|
||||
import app.revanced.patcher.data.Data
|
||||
import app.revanced.patcher.data.PackageMetadata
|
||||
import app.revanced.patcher.data.impl.BytecodeData
|
||||
import app.revanced.patcher.data.impl.ResourceData
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
import java.io.File
|
||||
|
||||
data class PatcherData(
|
||||
val internalClasses: MutableList<ClassDef>,
|
||||
val resourceCacheDirectory: String,
|
||||
) {
|
||||
val packageMetadata = PackageMetadata()
|
||||
internal val patches = mutableListOf<Class<out Patch<Data>>>()
|
||||
internal val bytecodeData = BytecodeData(internalClasses)
|
||||
internal val resourceData = ResourceData(File(resourceCacheDirectory))
|
||||
}
|
||||
@@ -15,7 +15,7 @@ import java.io.File
|
||||
data class PatcherOptions(
|
||||
internal val inputFile: File,
|
||||
internal val resourceCacheDirectory: String,
|
||||
internal val aaptPath: String = "",
|
||||
internal val aaptPath: String? = null,
|
||||
internal val frameworkFolderLocation: String? = null,
|
||||
internal val logger: Logger = NopLogger
|
||||
)
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
package app.revanced.patcher.annotation
|
||||
|
||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||
import app.revanced.patcher.patch.Patch
|
||||
|
||||
/**
|
||||
* Annotation to constrain a [Patch] or [MethodFingerprint] to compatible packages.
|
||||
* @param compatiblePackages A list of packages a [Patch] or [MethodFingerprint] is compatible with.
|
||||
* Annotation to constrain a [Patch] to compatible packages.
|
||||
* @param compatiblePackages A list of packages a [Patch] is compatible with.
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@MustBeDocumented
|
||||
annotation class Compatibility(
|
||||
val compatiblePackages: Array<Package>,
|
||||
)
|
||||
@@ -17,11 +14,9 @@ annotation class Compatibility(
|
||||
/**
|
||||
* Annotation to represent packages a patch can be compatible with.
|
||||
* @param name The package identifier name.
|
||||
* @param versions The versions of the package the [Patch] or [MethodFingerprint]is compatible with.
|
||||
* @param versions The versions of the package the [Patch] is compatible with.
|
||||
*/
|
||||
@Target()
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@MustBeDocumented
|
||||
annotation class Package(
|
||||
val name: String,
|
||||
val versions: Array<String> = [],
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
package app.revanced.patcher.annotation
|
||||
|
||||
import app.revanced.patcher.data.Data
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* Declares a [Patch] deprecated for removal.
|
||||
* @param reason The reason why the patch is deprecated.
|
||||
* @param replacement The replacement for the deprecated patch, if any.
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@MustBeDocumented
|
||||
annotation class PatchDeprecated(
|
||||
val reason: String,
|
||||
val replacement: KClass<out Patch<Data>> = Patch::class
|
||||
// Values cannot be nullable in annotations, so this will have to do.
|
||||
)
|
||||
@@ -1,38 +1,31 @@
|
||||
package app.revanced.patcher.annotation
|
||||
|
||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||
import app.revanced.patcher.patch.Patch
|
||||
|
||||
/**
|
||||
* Annotation to name a [Patch] or [MethodFingerprint].
|
||||
* @param name A suggestive name for the [Patch] or [MethodFingerprint].
|
||||
* Annotation to name a [Patch].
|
||||
* @param name A suggestive name for the [Patch].
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@MustBeDocumented
|
||||
annotation class Name(
|
||||
val name: String,
|
||||
)
|
||||
|
||||
/**
|
||||
* Annotation to describe a [Patch] or [MethodFingerprint].
|
||||
* @param description A description for the [Patch] or [MethodFingerprint].
|
||||
* Annotation to describe a [Patch].
|
||||
* @param description A description for the [Patch].
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@MustBeDocumented
|
||||
annotation class Description(
|
||||
val description: String,
|
||||
)
|
||||
|
||||
|
||||
/**
|
||||
* Annotation to version a [Patch] or [MethodFingerprint].
|
||||
* @param version The version of a [Patch] or [MethodFingerprint].
|
||||
* Annotation to version a [Patch].
|
||||
* @param version The version of a [Patch].
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@MustBeDocumented
|
||||
annotation class Version(
|
||||
val version: String,
|
||||
)
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package app.revanced.patcher.annotation
|
||||
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.Patcher
|
||||
|
||||
/**
|
||||
* Declares a [Patch] deprecated for removal.
|
||||
* @param version The minimum version of the [Patcher] this [Patch] supports.
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@MustBeDocumented
|
||||
annotation class SincePatcher(val version: String)
|
||||
@@ -1,6 +1,9 @@
|
||||
package app.revanced.patcher.data.impl
|
||||
package app.revanced.patcher.data
|
||||
|
||||
import app.revanced.patcher.data.Data
|
||||
import app.revanced.patcher.util.ProxyBackedClassList
|
||||
import app.revanced.patcher.util.method.MethodWalker
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
import org.jf.dexlib2.iface.Method
|
||||
import org.w3c.dom.Document
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
@@ -11,7 +14,68 @@ import javax.xml.transform.TransformerFactory
|
||||
import javax.xml.transform.dom.DOMSource
|
||||
import javax.xml.transform.stream.StreamResult
|
||||
|
||||
class ResourceData(private val resourceCacheDirectory: File) : Data, Iterable<File> {
|
||||
/**
|
||||
* A common interface to constrain [Context] to [BytecodeContext] and [ResourceContext].
|
||||
*/
|
||||
|
||||
sealed interface Context
|
||||
|
||||
class BytecodeContext internal constructor(classes: MutableList<ClassDef>) : Context {
|
||||
/**
|
||||
* The list of classes.
|
||||
*/
|
||||
val classes = ProxyBackedClassList(classes)
|
||||
|
||||
/**
|
||||
* Find a class by a given class name.
|
||||
*
|
||||
* @param className The name of the class.
|
||||
* @return A proxy for the first class that matches the class name.
|
||||
*/
|
||||
fun findClass(className: String) = findClass { it.type.contains(className) }
|
||||
|
||||
/**
|
||||
* Find a class by a given predicate.
|
||||
*
|
||||
* @param predicate A predicate to match the class.
|
||||
* @return A proxy for the first class that matches the predicate.
|
||||
*/
|
||||
fun findClass(predicate: (ClassDef) -> Boolean) =
|
||||
// if we already proxied the class matching the predicate...
|
||||
classes.proxies.firstOrNull { predicate(it.immutableClass) } ?:
|
||||
// else resolve the class to a proxy and return it, if the predicate is matching a class
|
||||
classes.find(predicate)?.let { proxy(it) }
|
||||
|
||||
fun proxy(classDef: ClassDef): app.revanced.patcher.util.proxy.ClassProxy {
|
||||
var proxy = this.classes.proxies.find { it.immutableClass.type == classDef.type }
|
||||
if (proxy == null) {
|
||||
proxy = app.revanced.patcher.util.proxy.ClassProxy(classDef)
|
||||
this.classes.add(proxy)
|
||||
}
|
||||
return proxy
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a [MethodWalker] instance for the current [BytecodeContext].
|
||||
*
|
||||
* @param startMethod The method to start at.
|
||||
* @return A [MethodWalker] instance.
|
||||
*/
|
||||
fun BytecodeContext.toMethodWalker(startMethod: Method): MethodWalker {
|
||||
return MethodWalker(this, startMethod)
|
||||
}
|
||||
|
||||
internal inline fun <T> Iterable<T>.findIndexed(predicate: (T) -> Boolean): Pair<T, Int>? {
|
||||
for ((index, element) in this.withIndex()) {
|
||||
if (predicate(element)) {
|
||||
return element to index
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
class ResourceContext internal constructor(private val resourceCacheDirectory: File) : Context, Iterable<File> {
|
||||
val xmlEditor = XmlFileHolder()
|
||||
|
||||
operator fun get(path: String) = resourceCacheDirectory.resolve(path)
|
||||
@@ -23,7 +87,7 @@ class ResourceData(private val resourceCacheDirectory: File) : Data, Iterable<Fi
|
||||
DomFileEditor(inputStream)
|
||||
|
||||
operator fun get(path: String): DomFileEditor {
|
||||
return DomFileEditor(this@ResourceData[path])
|
||||
return DomFileEditor(this@ResourceContext[path])
|
||||
}
|
||||
|
||||
}
|
||||
@@ -31,7 +95,9 @@ class ResourceData(private val resourceCacheDirectory: File) : Data, Iterable<Fi
|
||||
|
||||
/**
|
||||
* Wrapper for a file that can be edited as a dom document.
|
||||
* Note: This constructor does not check for locks to the file when writing. Use the secondary constructor.
|
||||
*
|
||||
* This constructor does not check for locks to the file when writing.
|
||||
* Use the secondary constructor.
|
||||
*
|
||||
* @param inputStream the input stream to read the xml file from.
|
||||
* @param outputStream the output stream to write the xml file to. If null, the file will be read only.
|
||||
@@ -63,7 +129,8 @@ class DomFileEditor internal constructor(
|
||||
|
||||
/**
|
||||
* Closes the editor. Write backs and decreases the lock count.
|
||||
* Note: Will not write back to the file if the file is still locked.
|
||||
*
|
||||
* Will not write back to the file if the file is still locked.
|
||||
*/
|
||||
override fun close() {
|
||||
if (closed) return
|
||||
@@ -100,4 +167,4 @@ class DomFileEditor internal constructor(
|
||||
// map of concurrent open files
|
||||
val locks = mutableMapOf<String, Int>()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package app.revanced.patcher.data
|
||||
|
||||
import app.revanced.patcher.data.impl.BytecodeData
|
||||
import app.revanced.patcher.data.impl.ResourceData
|
||||
|
||||
/**
|
||||
* Constraint interface for [BytecodeData] and [ResourceData]
|
||||
*/
|
||||
interface Data
|
||||
@@ -1,69 +0,0 @@
|
||||
package app.revanced.patcher.data.impl
|
||||
|
||||
import app.revanced.patcher.data.Data
|
||||
import app.revanced.patcher.util.ProxyBackedClassList
|
||||
import app.revanced.patcher.util.method.MethodWalker
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
import org.jf.dexlib2.iface.Method
|
||||
|
||||
class BytecodeData(
|
||||
internalClasses: MutableList<ClassDef>
|
||||
) : Data {
|
||||
val classes = ProxyBackedClassList(internalClasses)
|
||||
|
||||
/**
|
||||
* Find a class by a given class name.
|
||||
* @param className The name of the class.
|
||||
* @return A proxy for the first class that matches the class name.
|
||||
*/
|
||||
fun findClass(className: String) = findClass { it.type.contains(className) }
|
||||
|
||||
/**
|
||||
* Find a class by a given predicate.
|
||||
* @param predicate A predicate to match the class.
|
||||
* @return A proxy for the first class that matches the predicate.
|
||||
*/
|
||||
fun findClass(predicate: (ClassDef) -> Boolean) =
|
||||
// if we already proxied the class matching the predicate...
|
||||
classes.proxies.firstOrNull { predicate(it.immutableClass) } ?:
|
||||
// else resolve the class to a proxy and return it, if the predicate is matching a class
|
||||
classes.find(predicate)?.let { proxy(it) }
|
||||
|
||||
fun proxy(classDef: ClassDef): app.revanced.patcher.util.proxy.ClassProxy {
|
||||
var proxy = this.classes.proxies.find { it.immutableClass.type == classDef.type }
|
||||
if (proxy == null) {
|
||||
proxy = app.revanced.patcher.util.proxy.ClassProxy(classDef)
|
||||
this.classes.add(proxy)
|
||||
}
|
||||
return proxy
|
||||
}
|
||||
}
|
||||
|
||||
internal class MethodNotFoundException(s: String) : Exception(s)
|
||||
|
||||
internal inline fun <reified T> Iterable<T>.find(predicate: (T) -> Boolean): T? {
|
||||
for (element in this) {
|
||||
if (predicate(element)) {
|
||||
return element
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a [MethodWalker] instance for the current [BytecodeData].
|
||||
* @param startMethod The method to start at.
|
||||
* @return A [MethodWalker] instance.
|
||||
*/
|
||||
fun BytecodeData.toMethodWalker(startMethod: Method): MethodWalker {
|
||||
return MethodWalker(this, startMethod)
|
||||
}
|
||||
|
||||
internal inline fun <T> Iterable<T>.findIndexed(predicate: (T) -> Boolean): Pair<T, Int>? {
|
||||
for ((index, element) in this.withIndex()) {
|
||||
if (predicate(element)) {
|
||||
return element to index
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -1,79 +1,33 @@
|
||||
package app.revanced.patcher.extensions
|
||||
|
||||
import app.revanced.patcher.annotation.*
|
||||
import app.revanced.patcher.data.Data
|
||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||
import app.revanced.patcher.patch.OptionsContainer
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.patch.PatchOptions
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KVisibility
|
||||
import kotlin.reflect.full.companionObject
|
||||
import kotlin.reflect.full.companionObjectInstance
|
||||
|
||||
/**
|
||||
* Recursively find a given annotation on a class.
|
||||
* @param targetAnnotation The annotation to find.
|
||||
* @return The annotation.
|
||||
*/
|
||||
private fun <T : Annotation> Class<*>.recursiveAnnotation(targetAnnotation: KClass<T>) =
|
||||
this.findAnnotationRecursively(targetAnnotation.java, mutableSetOf())
|
||||
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
|
||||
|
||||
private fun <T : Annotation> Class<*>.findAnnotationRecursively(
|
||||
targetAnnotation: Class<T>, traversed: MutableSet<Annotation>
|
||||
): T? {
|
||||
val found = this.annotations.firstOrNull { it.annotationClass.java.name == targetAnnotation.name }
|
||||
for (annotation in this.annotations) {
|
||||
if (traversed.contains(annotation)) continue
|
||||
traversed.add(annotation)
|
||||
|
||||
@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
|
||||
}
|
||||
|
||||
object PatchExtensions {
|
||||
val Class<*>.patchName: String
|
||||
get() = recursiveAnnotation(Name::class)?.name ?: this.javaClass.simpleName
|
||||
val Class<out Patch<Data>>.version get() = recursiveAnnotation(Version::class)?.version
|
||||
val Class<out Patch<Data>>.include get() = recursiveAnnotation(app.revanced.patcher.patch.annotations.Patch::class)!!.include
|
||||
val Class<out Patch<Data>>.description get() = recursiveAnnotation(Description::class)?.description
|
||||
val Class<out Patch<Data>>.dependencies get() = recursiveAnnotation(app.revanced.patcher.patch.annotations.DependsOn::class)?.dependencies
|
||||
val Class<out Patch<Data>>.compatiblePackages get() = recursiveAnnotation(Compatibility::class)?.compatiblePackages
|
||||
val Class<out Patch<Data>>.options: PatchOptions?
|
||||
get() = kotlin.companionObject?.let { cl ->
|
||||
if (cl.visibility != KVisibility.PUBLIC) return null
|
||||
kotlin.companionObjectInstance?.let {
|
||||
(it as? OptionsContainer)?.options
|
||||
return (annotation.annotationClass.java.findAnnotationRecursively(targetAnnotation, traversed))
|
||||
?: continue
|
||||
}
|
||||
}
|
||||
val Class<out Patch<Data>>.deprecated: Pair<String, KClass<out Patch<Data>>?>?
|
||||
get() = recursiveAnnotation(PatchDeprecated::class)?.let {
|
||||
it.reason to it.replacement.let { cl ->
|
||||
if (cl == Patch::class) null else cl
|
||||
}
|
||||
}
|
||||
val Class<out Patch<Data>>.sincePatcherVersion get() = recursiveAnnotation(SincePatcher::class)?.version
|
||||
|
||||
@JvmStatic
|
||||
fun Class<out Patch<Data>>.dependsOn(patch: Class<out Patch<Data>>): Boolean {
|
||||
if (this.patchName == patch.patchName) throw IllegalArgumentException("thisval and patch may not be the same")
|
||||
return this.dependencies?.any { it.java.patchName == this@dependsOn.patchName } == true
|
||||
return null
|
||||
}
|
||||
|
||||
return this.findAnnotationRecursively(targetAnnotation.java, mutableSetOf())
|
||||
}
|
||||
}
|
||||
|
||||
object MethodFingerprintExtensions {
|
||||
val MethodFingerprint.name: String
|
||||
get() = javaClass.recursiveAnnotation(Name::class)?.name ?: this.javaClass.simpleName
|
||||
val MethodFingerprint.version get() = javaClass.recursiveAnnotation(Version::class)?.version ?: "0.0.1"
|
||||
val MethodFingerprint.description get() = javaClass.recursiveAnnotation(Description::class)?.description
|
||||
val MethodFingerprint.compatiblePackages get() = javaClass.recursiveAnnotation(Compatibility::class)?.compatiblePackages
|
||||
val MethodFingerprint.matchingMethod get() = javaClass.recursiveAnnotation(app.revanced.patcher.fingerprint.method.annotation.MatchingMethod::class)
|
||||
val MethodFingerprint.fuzzyPatternScanMethod get() = javaClass.recursiveAnnotation(app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod::class)
|
||||
val MethodFingerprint.fuzzyScanThreshold get() = fuzzyPatternScanMethod?.threshold ?: 0
|
||||
}
|
||||
@@ -1,259 +1,33 @@
|
||||
package app.revanced.patcher.extensions
|
||||
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||
import app.revanced.patcher.util.smali.ExternalLabel
|
||||
import app.revanced.patcher.util.smali.toInstruction
|
||||
import app.revanced.patcher.util.smali.toInstructions
|
||||
import org.jf.dexlib2.AccessFlags
|
||||
import org.jf.dexlib2.builder.BuilderInstruction
|
||||
import org.jf.dexlib2.builder.BuilderOffsetInstruction
|
||||
import org.jf.dexlib2.builder.Label
|
||||
import org.jf.dexlib2.builder.MutableMethodImplementation
|
||||
import org.jf.dexlib2.builder.instruction.*
|
||||
import org.jf.dexlib2.iface.Method
|
||||
import org.jf.dexlib2.iface.instruction.Instruction
|
||||
import org.jf.dexlib2.iface.reference.MethodReference
|
||||
import org.jf.dexlib2.immutable.ImmutableMethod
|
||||
import org.jf.dexlib2.immutable.ImmutableMethodImplementation
|
||||
import java.io.OutputStream
|
||||
|
||||
infix fun AccessFlags.or(other: AccessFlags) = this.value or other.value
|
||||
infix fun Int.or(other: AccessFlags) = this or other.value
|
||||
|
||||
fun MutableMethodImplementation.addInstructions(index: Int, instructions: List<BuilderInstruction>) {
|
||||
for (i in instructions.lastIndex downTo 0) {
|
||||
this.addInstruction(index, instructions[i])
|
||||
}
|
||||
}
|
||||
|
||||
fun MutableMethodImplementation.addInstructions(instructions: List<BuilderInstruction>) {
|
||||
for (instruction in instructions) {
|
||||
this.addInstruction(instruction)
|
||||
}
|
||||
}
|
||||
|
||||
fun MutableMethodImplementation.replaceInstructions(index: Int, instructions: List<BuilderInstruction>) {
|
||||
for (i in instructions.lastIndex downTo 0) {
|
||||
this.replaceInstruction(index + i, instructions[i])
|
||||
}
|
||||
}
|
||||
|
||||
fun MutableMethodImplementation.removeInstructions(index: Int, count: Int) {
|
||||
for (i in count downTo 0) {
|
||||
this.removeInstruction(index + i)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare a method to another, considering name and parameters.
|
||||
* @param otherMethod The method to compare against.
|
||||
* @return True if the methods match given the conditions.
|
||||
*/
|
||||
fun Method.softCompareTo(otherMethod: MethodReference): Boolean {
|
||||
return this.name == otherMethod.name && parametersEqual(
|
||||
this.parameterTypes, otherMethod.parameterTypes
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones the method.
|
||||
* @param registerCount This parameter allows you to change the register count of the method.
|
||||
* This may be a positive or negative number.
|
||||
* @return The **immutable** cloned method. Call [toMutable] or [cloneMutable] to get a **mutable** copy.
|
||||
*/
|
||||
internal fun Method.clone(registerCount: Int = 0): ImmutableMethod {
|
||||
val clonedImplementation = implementation?.let {
|
||||
ImmutableMethodImplementation(
|
||||
it.registerCount + registerCount,
|
||||
it.instructions,
|
||||
it.tryBlocks,
|
||||
it.debugItems,
|
||||
)
|
||||
}
|
||||
return ImmutableMethod(
|
||||
returnType, name, parameters, returnType, accessFlags, annotations, hiddenApiRestrictions, clonedImplementation
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a smali instruction to the method.
|
||||
* @param instruction The smali instruction to add.
|
||||
*/
|
||||
fun MutableMethod.addInstruction(instruction: String) =
|
||||
this.implementation!!.addInstruction(instruction.toInstruction(this))
|
||||
|
||||
/**
|
||||
* Add a smali instruction to the method.
|
||||
* @param index The index to insert the instruction at.
|
||||
* @param instruction The smali instruction to add.
|
||||
*/
|
||||
fun MutableMethod.addInstruction(index: Int, instruction: String) =
|
||||
this.implementation!!.addInstruction(index, instruction.toInstruction(this))
|
||||
|
||||
/**
|
||||
* Replace a smali instruction within the method.
|
||||
* @param index The index to replace the instruction at.
|
||||
* @param instruction The smali instruction to place.
|
||||
*/
|
||||
fun MutableMethod.replaceInstruction(index: Int, instruction: String) =
|
||||
this.implementation!!.replaceInstruction(index, instruction.toInstruction(this))
|
||||
|
||||
/**
|
||||
* Remove a smali instruction within the method.
|
||||
* @param index The index to delete the instruction at.
|
||||
*/
|
||||
fun MutableMethod.removeInstruction(index: Int) = this.implementation!!.removeInstruction(index)
|
||||
|
||||
/**
|
||||
* Create a label for the instruction at given index in the method's implementation.
|
||||
* Create a label for the instruction at given index.
|
||||
*
|
||||
* @param index The index to create the label for the instruction at.
|
||||
* @return The label.
|
||||
*/
|
||||
fun MutableMethod.label(index: Int) = this.implementation!!.newLabelForIndex(index)
|
||||
fun MutableMethod.newLabel(index: Int) = implementation!!.newLabelForIndex(index)
|
||||
|
||||
/**
|
||||
* Get the instruction at given index in the method's implementation.
|
||||
* @param index The index to get the instruction at.
|
||||
* @return The instruction.
|
||||
* Perform a bitwise OR operation between two [AccessFlags].
|
||||
*
|
||||
* @param other The other [AccessFlags] to perform the operation with.
|
||||
*/
|
||||
fun MutableMethod.instruction(index: Int): BuilderInstruction = this.implementation!!.instructions[index]
|
||||
infix fun AccessFlags.or(other: AccessFlags) = value or other.value
|
||||
|
||||
/**
|
||||
* Add smali instructions to the method.
|
||||
* @param index The index to insert the instructions at.
|
||||
* @param smali The smali instructions to add.
|
||||
* @param externalLabels A list of [ExternalLabel] representing a list of labels for instructions which are not in the method to compile.
|
||||
* Perform a bitwise OR operation between an [AccessFlags] and an [Int].
|
||||
*
|
||||
* @param other The [Int] to perform the operation with.
|
||||
*/
|
||||
|
||||
fun MutableMethod.addInstructions(index: Int, smali: String, externalLabels: List<ExternalLabel> = emptyList()) {
|
||||
// Create reference dummy instructions for the instructions.
|
||||
val nopedSmali = StringBuilder(smali).also { builder ->
|
||||
externalLabels.forEach { (name, _) ->
|
||||
builder.append("\n:$name\nnop")
|
||||
}
|
||||
}.toString()
|
||||
|
||||
// Compile the instructions with the dummy labels
|
||||
val compiledInstructions = nopedSmali.toInstructions(this)
|
||||
|
||||
// Add the compiled list of instructions to the method.
|
||||
val methodImplementation = this.implementation!!
|
||||
methodImplementation.addInstructions(index, compiledInstructions)
|
||||
|
||||
val methodInstructions = methodImplementation.instructions
|
||||
methodInstructions.subList(index, index + compiledInstructions.size)
|
||||
.forEachIndexed { compiledInstructionIndex, compiledInstruction ->
|
||||
// If the compiled instruction is not an offset instruction, skip it.
|
||||
if (compiledInstruction !is BuilderOffsetInstruction) return@forEachIndexed
|
||||
|
||||
/**
|
||||
* Creates a new label for the instruction and replaces it with the label of the [compiledInstruction] at [compiledInstructionIndex].
|
||||
*/
|
||||
fun Instruction.makeNewLabel() {
|
||||
// Create the final label.
|
||||
val label = methodImplementation.newLabelForIndex(methodInstructions.indexOf(this))
|
||||
// Create the final instruction with the new label.
|
||||
val newInstruction = replaceOffset(
|
||||
compiledInstruction, label
|
||||
)
|
||||
// Replace the instruction pointing to the dummy label with the new instruction pointing to the real instruction.
|
||||
methodImplementation.replaceInstruction(index + compiledInstructionIndex, newInstruction)
|
||||
}
|
||||
|
||||
// If the compiled instruction targets its own instruction,
|
||||
// which means it points to some of its own, simply an offset has to be applied.
|
||||
val labelIndex = compiledInstruction.target.location.index
|
||||
if (labelIndex < compiledInstructions.size - externalLabels.size) {
|
||||
// Get the targets index (insertion index + the index of the dummy instruction).
|
||||
methodInstructions[index + labelIndex].makeNewLabel()
|
||||
return@forEachIndexed
|
||||
}
|
||||
|
||||
// Since the compiled instruction points to a dummy instruction,
|
||||
// we can find the real instruction which it was created for by calculation.
|
||||
|
||||
// Get the index of the instruction in the externalLabels list which the dummy instruction was created for.
|
||||
// this line works because we created the dummy instructions in the same order as the externalLabels list.
|
||||
val (_, instruction) = externalLabels[(compiledInstructions.size - 1) - labelIndex]
|
||||
instruction.makeNewLabel()
|
||||
}
|
||||
}
|
||||
infix fun Int.or(other: AccessFlags) = this or other.value
|
||||
|
||||
/**
|
||||
* Add smali instructions to the end of the method.
|
||||
* @param instructions The smali instructions to add.
|
||||
* Perform a bitwise OR operation between an [Int] and an [AccessFlags].
|
||||
*
|
||||
* @param other The [AccessFlags] to perform the operation with.
|
||||
*/
|
||||
fun MutableMethod.addInstructions(instructions: String, labels: List<ExternalLabel> = emptyList()) =
|
||||
this.addInstructions(this.implementation!!.instructions.size, instructions, labels)
|
||||
|
||||
/**
|
||||
* Replace smali instructions within the method.
|
||||
* @param index The index to replace the instructions at.
|
||||
* @param instructions The smali instructions to place.
|
||||
*/
|
||||
fun MutableMethod.replaceInstructions(index: Int, instructions: String) =
|
||||
this.implementation!!.replaceInstructions(index, instructions.toInstructions(this))
|
||||
|
||||
/**
|
||||
* Remove smali instructions from the method.
|
||||
* @param index The index to remove the instructions at.
|
||||
* @param count The amount of instructions to remove.
|
||||
*/
|
||||
fun MutableMethod.removeInstructions(index: Int, count: Int) = this.implementation!!.removeInstructions(index, count)
|
||||
|
||||
private fun replaceOffset(
|
||||
i: BuilderOffsetInstruction, label: Label
|
||||
): BuilderOffsetInstruction {
|
||||
return when (i) {
|
||||
is BuilderInstruction10t -> BuilderInstruction10t(i.opcode, label)
|
||||
is BuilderInstruction20t -> BuilderInstruction20t(i.opcode, label)
|
||||
is BuilderInstruction21t -> BuilderInstruction21t(i.opcode, i.registerA, label)
|
||||
is BuilderInstruction22t -> BuilderInstruction22t(i.opcode, i.registerA, i.registerB, label)
|
||||
is BuilderInstruction30t -> BuilderInstruction30t(i.opcode, label)
|
||||
is BuilderInstruction31t -> BuilderInstruction31t(i.opcode, i.registerA, label)
|
||||
else -> throw IllegalStateException("A non-offset instruction was given, this should never happen!")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones the method.
|
||||
* @param registerCount This parameter allows you to change the register count of the method.
|
||||
* This may be a positive or negative number.
|
||||
* @return The **mutable** cloned method. Call [clone] to get an **immutable** copy.
|
||||
*/
|
||||
internal fun Method.cloneMutable(registerCount: Int = 0) = clone(registerCount).toMutable()
|
||||
|
||||
// FIXME: also check the order of parameters as different order equals different method overload
|
||||
internal fun parametersEqual(
|
||||
parameters1: Iterable<CharSequence>, parameters2: Iterable<CharSequence>
|
||||
): Boolean {
|
||||
return parameters1.count() == parameters2.count() && parameters1.all { parameter ->
|
||||
parameters2.any {
|
||||
it.startsWith(
|
||||
parameter
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal val nullOutputStream = object : OutputStream() {
|
||||
override fun write(b: Int) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be used to parse a list of parameters represented by their first letter,
|
||||
* or in the case of arrays prefixed with an unspecified amount of '[' character.
|
||||
*/
|
||||
internal fun String.parseParameters(): List<String> {
|
||||
val parameters = mutableListOf<String>()
|
||||
var parameter = ""
|
||||
for (char in this.toCharArray()) {
|
||||
parameter += char
|
||||
if (char == '[') continue
|
||||
|
||||
parameters.add(parameter)
|
||||
parameter = ""
|
||||
}
|
||||
return parameters
|
||||
}
|
||||
infix fun AccessFlags.or(other: Int) = value or other
|
||||
@@ -0,0 +1,326 @@
|
||||
package app.revanced.patcher.extensions
|
||||
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patcher.util.smali.ExternalLabel
|
||||
import app.revanced.patcher.util.smali.toInstruction
|
||||
import app.revanced.patcher.util.smali.toInstructions
|
||||
import org.jf.dexlib2.builder.BuilderInstruction
|
||||
import org.jf.dexlib2.builder.BuilderOffsetInstruction
|
||||
import org.jf.dexlib2.builder.Label
|
||||
import org.jf.dexlib2.builder.MutableMethodImplementation
|
||||
import org.jf.dexlib2.builder.instruction.*
|
||||
import org.jf.dexlib2.iface.instruction.Instruction
|
||||
|
||||
object InstructionExtensions {
|
||||
|
||||
/**
|
||||
* Add instructions to a method at the given index.
|
||||
*
|
||||
* @param index The index to add the instructions at.
|
||||
* @param instructions The instructions to add.
|
||||
*/
|
||||
fun MutableMethodImplementation.addInstructions(
|
||||
index: Int,
|
||||
instructions: List<BuilderInstruction>
|
||||
) =
|
||||
instructions.asReversed().forEach { addInstruction(index, it) }
|
||||
|
||||
/**
|
||||
* Add instructions to a method.
|
||||
* The instructions will be added at the end of the method.
|
||||
*
|
||||
* @param instructions The instructions to add.
|
||||
*/
|
||||
fun MutableMethodImplementation.addInstructions(instructions: List<BuilderInstruction>) =
|
||||
instructions.forEach { this.addInstruction(it) }
|
||||
|
||||
/**
|
||||
* Remove instructions from a method at the given index.
|
||||
*
|
||||
* @param index The index to remove the instructions at.
|
||||
* @param count The amount of instructions to remove.
|
||||
*/
|
||||
fun MutableMethodImplementation.removeInstructions(index: Int, count: Int) = repeat(count) {
|
||||
removeInstruction(index)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the first instructions from a method.
|
||||
*
|
||||
* @param count The amount of instructions to remove.
|
||||
*/
|
||||
fun MutableMethodImplementation.removeInstructions(count: Int) = removeInstructions(0, count)
|
||||
|
||||
/**
|
||||
* Replace instructions at the given index with the given instructions.
|
||||
* The amount of instructions to replace is the amount of instructions in the given list.
|
||||
*
|
||||
* @param index The index to replace the instructions at.
|
||||
* @param instructions The instructions to replace the instructions with.
|
||||
*/
|
||||
fun MutableMethodImplementation.replaceInstructions(index: Int, instructions: List<BuilderInstruction>) {
|
||||
// Remove the instructions at the given index.
|
||||
removeInstructions(index, instructions.size)
|
||||
|
||||
// Add the instructions at the given index.
|
||||
addInstructions(index, instructions)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an instruction to a method at the given index.
|
||||
*
|
||||
* @param index The index to add the instruction at.
|
||||
* @param instruction The instruction to add.
|
||||
*/
|
||||
fun MutableMethod.addInstruction(index: Int, instruction: BuilderInstruction) =
|
||||
implementation!!.addInstruction(index, instruction)
|
||||
|
||||
/**
|
||||
* Add an instruction to a method.
|
||||
*
|
||||
* @param instruction The instructions to add.
|
||||
*/
|
||||
fun MutableMethod.addInstruction(instruction: BuilderInstruction) =
|
||||
implementation!!.addInstruction(instruction)
|
||||
|
||||
/**
|
||||
* Add an instruction to a method at the given index.
|
||||
*
|
||||
* @param index The index to add the instruction at.
|
||||
* @param smaliInstructions The instruction to add.
|
||||
*/
|
||||
fun MutableMethod.addInstruction(index: Int, smaliInstructions: String) =
|
||||
implementation!!.addInstruction(index, smaliInstructions.toInstruction(this))
|
||||
|
||||
/**
|
||||
* Add an instruction to a method.
|
||||
*
|
||||
* @param smaliInstructions The instruction to add.
|
||||
*/
|
||||
fun MutableMethod.addInstruction(smaliInstructions: String) =
|
||||
implementation!!.addInstruction(smaliInstructions.toInstruction(this))
|
||||
|
||||
|
||||
/**
|
||||
* Add instructions to a method at the given index.
|
||||
*
|
||||
* @param index The index to add the instructions at.
|
||||
* @param instructions The instructions to add.
|
||||
*/
|
||||
fun MutableMethod.addInstructions(index: Int, instructions: List<BuilderInstruction>) =
|
||||
implementation!!.addInstructions(index, instructions)
|
||||
|
||||
/**
|
||||
* Add instructions to a method.
|
||||
*
|
||||
* @param instructions The instructions to add.
|
||||
*/
|
||||
fun MutableMethod.addInstructions(instructions: List<BuilderInstruction>) =
|
||||
implementation!!.addInstructions(instructions)
|
||||
|
||||
/**
|
||||
* Add instructions to a method.
|
||||
*
|
||||
* @param smaliInstructions The instructions to add.
|
||||
*/
|
||||
fun MutableMethod.addInstructions(index: Int, smaliInstructions: String) =
|
||||
implementation!!.addInstructions(index, smaliInstructions.toInstructions(this))
|
||||
|
||||
/**
|
||||
* Add instructions to a method.
|
||||
*
|
||||
* @param smaliInstructions The instructions to add.
|
||||
*/
|
||||
fun MutableMethod.addInstructions(smaliInstructions: String) =
|
||||
implementation!!.addInstructions(smaliInstructions.toInstructions(this))
|
||||
|
||||
/**
|
||||
* Add instructions to a method at the given index.
|
||||
*
|
||||
* @param index The index to add the instructions at.
|
||||
* @param smaliInstructions The instructions to add.
|
||||
* @param externalLabels A list of [ExternalLabel] for instructions outside of [smaliInstructions].
|
||||
*/
|
||||
// Special function for adding instructions with external labels.
|
||||
fun MutableMethod.addInstructionsWithLabels(
|
||||
index: Int,
|
||||
smaliInstructions: String,
|
||||
vararg externalLabels: ExternalLabel
|
||||
) {
|
||||
// Create reference dummy instructions for the instructions.
|
||||
val nopSmali = StringBuilder(smaliInstructions).also { builder ->
|
||||
externalLabels.forEach { (name, _) ->
|
||||
builder.append("\n:$name\nnop")
|
||||
}
|
||||
}.toString()
|
||||
|
||||
// Compile the instructions with the dummy labels
|
||||
val compiledInstructions = nopSmali.toInstructions(this)
|
||||
|
||||
// Add the compiled list of instructions to the method.
|
||||
addInstructions(
|
||||
index,
|
||||
compiledInstructions.subList(0, compiledInstructions.size - externalLabels.size)
|
||||
)
|
||||
|
||||
implementation!!.apply {
|
||||
this@apply.instructions.subList(index, index + compiledInstructions.size - externalLabels.size)
|
||||
.forEachIndexed { compiledInstructionIndex, compiledInstruction ->
|
||||
// If the compiled instruction is not an offset instruction, skip it.
|
||||
if (compiledInstruction !is BuilderOffsetInstruction) return@forEachIndexed
|
||||
|
||||
/**
|
||||
* Creates a new label for the instruction
|
||||
* and replaces it with the label of the [compiledInstruction] at [compiledInstructionIndex].
|
||||
*/
|
||||
fun Instruction.makeNewLabel() {
|
||||
fun replaceOffset(
|
||||
i: BuilderOffsetInstruction, label: Label
|
||||
): BuilderOffsetInstruction {
|
||||
return when (i) {
|
||||
is BuilderInstruction10t -> BuilderInstruction10t(i.opcode, label)
|
||||
is BuilderInstruction20t -> BuilderInstruction20t(i.opcode, label)
|
||||
is BuilderInstruction21t -> BuilderInstruction21t(i.opcode, i.registerA, label)
|
||||
is BuilderInstruction22t -> BuilderInstruction22t(
|
||||
i.opcode,
|
||||
i.registerA,
|
||||
i.registerB,
|
||||
label
|
||||
)
|
||||
is BuilderInstruction30t -> BuilderInstruction30t(i.opcode, label)
|
||||
is BuilderInstruction31t -> BuilderInstruction31t(i.opcode, i.registerA, label)
|
||||
else -> throw IllegalStateException(
|
||||
"A non-offset instruction was given, this should never happen!"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Create the final label.
|
||||
val label = newLabelForIndex(this@apply.instructions.indexOf(this))
|
||||
|
||||
// Create the final instruction with the new label.
|
||||
val newInstruction = replaceOffset(
|
||||
compiledInstruction, label
|
||||
)
|
||||
|
||||
// Replace the instruction pointing to the dummy label
|
||||
// with the new instruction pointing to the real instruction.
|
||||
replaceInstruction(index + compiledInstructionIndex, newInstruction)
|
||||
}
|
||||
|
||||
// If the compiled instruction targets its own instruction,
|
||||
// which means it points to some of its own, simply an offset has to be applied.
|
||||
val labelIndex = compiledInstruction.target.location.index
|
||||
if (labelIndex < compiledInstructions.size - externalLabels.size) {
|
||||
// Get the targets index (insertion index + the index of the dummy instruction).
|
||||
this.instructions[index + labelIndex].makeNewLabel()
|
||||
return@forEachIndexed
|
||||
}
|
||||
|
||||
// Since the compiled instruction points to a dummy instruction,
|
||||
// we can find the real instruction which it was created for by calculation.
|
||||
|
||||
// Get the index of the instruction in the externalLabels list
|
||||
// which the dummy instruction was created for.
|
||||
// This works because we created the dummy instructions in the same order as the externalLabels list.
|
||||
val (_, instruction) = externalLabels[(compiledInstructions.size - 1) - labelIndex]
|
||||
instruction.makeNewLabel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an instruction at the given index.
|
||||
*
|
||||
* @param index The index to remove the instruction at.
|
||||
*/
|
||||
fun MutableMethod.removeInstruction(index: Int) =
|
||||
implementation!!.removeInstruction(index)
|
||||
|
||||
/**
|
||||
* Remove instructions at the given index.
|
||||
*
|
||||
* @param index The index to remove the instructions at.
|
||||
* @param count The amount of instructions to remove.
|
||||
*/
|
||||
fun MutableMethod.removeInstructions(index: Int, count: Int) =
|
||||
implementation!!.removeInstructions(index, count)
|
||||
|
||||
/**
|
||||
* Remove instructions at the given index.
|
||||
*
|
||||
* @param count The amount of instructions to remove.
|
||||
*/
|
||||
fun MutableMethod.removeInstructions(count: Int) =
|
||||
implementation!!.removeInstructions(count)
|
||||
|
||||
/**
|
||||
* Replace an instruction at the given index.
|
||||
*
|
||||
* @param index The index to replace the instruction at.
|
||||
* @param instruction The instruction to replace the instruction with.
|
||||
*/
|
||||
fun MutableMethod.replaceInstruction(index: Int, instruction: BuilderInstruction) =
|
||||
implementation!!.replaceInstruction(index, instruction)
|
||||
|
||||
/**
|
||||
* Replace an instruction at the given index.
|
||||
*
|
||||
* @param index The index to replace the instruction at.
|
||||
* @param smaliInstruction The smali instruction to replace the instruction with.
|
||||
*/
|
||||
fun MutableMethod.replaceInstruction(index: Int, smaliInstruction: String) =
|
||||
implementation!!.replaceInstruction(index, smaliInstruction.toInstruction(this))
|
||||
|
||||
/**
|
||||
* Replace instructions at the given index.
|
||||
*
|
||||
* @param index The index to replace the instructions at.
|
||||
* @param instructions The instructions to replace the instructions with.
|
||||
*/
|
||||
fun MutableMethod.replaceInstructions(index: Int, instructions: List<BuilderInstruction>) =
|
||||
implementation!!.replaceInstructions(index, instructions)
|
||||
|
||||
/**
|
||||
* Replace instructions at the given index.
|
||||
*
|
||||
* @param index The index to replace the instructions at.
|
||||
* @param smaliInstructions The smali instructions to replace the instructions with.
|
||||
*/
|
||||
fun MutableMethod.replaceInstructions(index: Int, smaliInstructions: String) =
|
||||
implementation!!.replaceInstructions(index, smaliInstructions.toInstructions(this))
|
||||
|
||||
/**
|
||||
* Get an instruction at the given index.
|
||||
*
|
||||
* @param index The index to get the instruction at.
|
||||
* @return The instruction.
|
||||
*/
|
||||
fun MutableMethodImplementation.getInstruction(index: Int): BuilderInstruction = instructions[index]
|
||||
|
||||
/**
|
||||
* Get an instruction at the given index.
|
||||
*
|
||||
* @param index The index to get the instruction at.
|
||||
* @param T The type of instruction to return.
|
||||
* @return The instruction.
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T> MutableMethodImplementation.getInstruction(index: Int): T = getInstruction(index) as T
|
||||
|
||||
/**
|
||||
* Get an instruction at the given index.
|
||||
* @param index The index to get the instruction at.
|
||||
* @return The instruction.
|
||||
*/
|
||||
fun MutableMethod.getInstruction(index: Int): BuilderInstruction = implementation!!.getInstruction(index)
|
||||
|
||||
/**
|
||||
* Get an instruction at the given index.
|
||||
* @param index The index to get the instruction at.
|
||||
* @param T The type of instruction to return.
|
||||
* @return The instruction.
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T> MutableMethod.getInstruction(index: Int): T = implementation!!.getInstruction<T>(index)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package app.revanced.patcher.extensions
|
||||
|
||||
import app.revanced.patcher.extensions.AnnotationExtensions.findAnnotationRecursively
|
||||
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
|
||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||
|
||||
object MethodFingerprintExtensions {
|
||||
|
||||
/**
|
||||
* The name of a [MethodFingerprint].
|
||||
*/
|
||||
val MethodFingerprint.name: String
|
||||
get() = this.javaClass.simpleName
|
||||
|
||||
/**
|
||||
* The [FuzzyPatternScanMethod] annotation of a [MethodFingerprint].
|
||||
*/
|
||||
val MethodFingerprint.fuzzyPatternScanMethod
|
||||
get() = javaClass.findAnnotationRecursively(FuzzyPatternScanMethod::class)
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package app.revanced.patcher.extensions
|
||||
|
||||
import app.revanced.patcher.annotation.Compatibility
|
||||
import app.revanced.patcher.annotation.Description
|
||||
import app.revanced.patcher.annotation.Name
|
||||
import app.revanced.patcher.annotation.Version
|
||||
import app.revanced.patcher.data.Context
|
||||
import app.revanced.patcher.extensions.AnnotationExtensions.findAnnotationRecursively
|
||||
import app.revanced.patcher.patch.OptionsContainer
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.patch.PatchOptions
|
||||
import app.revanced.patcher.patch.annotations.DependsOn
|
||||
import app.revanced.patcher.patch.annotations.RequiresIntegrations
|
||||
import kotlin.reflect.KVisibility
|
||||
import kotlin.reflect.full.companionObject
|
||||
import kotlin.reflect.full.companionObjectInstance
|
||||
|
||||
object PatchExtensions {
|
||||
/**
|
||||
* The name of a [Patch].
|
||||
*/
|
||||
val Class<out Patch<Context>>.patchName: String
|
||||
get() = findAnnotationRecursively(Name::class)?.name ?: this.simpleName
|
||||
|
||||
/**
|
||||
* The version of a [Patch].
|
||||
*/
|
||||
val Class<out Patch<Context>>.version
|
||||
get() = findAnnotationRecursively(Version::class)?.version
|
||||
|
||||
/**
|
||||
* Weather or not a [Patch] should be included.
|
||||
*/
|
||||
val Class<out Patch<Context>>.include
|
||||
get() = findAnnotationRecursively(app.revanced.patcher.patch.annotations.Patch::class)!!.include
|
||||
|
||||
/**
|
||||
* The description of a [Patch].
|
||||
*/
|
||||
val Class<out Patch<Context>>.description
|
||||
get() = findAnnotationRecursively(Description::class)?.description
|
||||
|
||||
/**
|
||||
* The dependencies of a [Patch].
|
||||
*/
|
||||
val Class<out Patch<Context>>.dependencies
|
||||
get() = findAnnotationRecursively(DependsOn::class)?.dependencies
|
||||
|
||||
/**
|
||||
* The packages a [Patch] is compatible with.
|
||||
*/
|
||||
val Class<out Patch<Context>>.compatiblePackages
|
||||
get() = findAnnotationRecursively(Compatibility::class)?.compatiblePackages
|
||||
|
||||
/**
|
||||
* Weather or not a [Patch] requires integrations.
|
||||
*/
|
||||
internal val Class<out Patch<Context>>.requiresIntegrations
|
||||
get() = findAnnotationRecursively(RequiresIntegrations::class) != null
|
||||
|
||||
/**
|
||||
* The options of a [Patch].
|
||||
*/
|
||||
val Class<out Patch<Context>>.options: PatchOptions?
|
||||
get() = kotlin.companionObject?.let { cl ->
|
||||
if (cl.visibility != KVisibility.PUBLIC) return null
|
||||
kotlin.companionObjectInstance?.let {
|
||||
(it as? OptionsContainer)?.options
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,31 +2,11 @@ package app.revanced.patcher.fingerprint.method.annotation
|
||||
|
||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||
|
||||
/**
|
||||
* Annotations for a method which matches to a [MethodFingerprint].
|
||||
* @param definingClass The defining class name of the method.
|
||||
* @param name A suggestive name for the method which the [MethodFingerprint] was created for.
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class MatchingMethod(
|
||||
val definingClass: String = "L<unspecified-class>;",
|
||||
val name: String = "<unspecified-method>"
|
||||
)
|
||||
|
||||
/**
|
||||
* Annotations to scan a pattern [MethodFingerprint] with fuzzy algorithm.
|
||||
* @param threshold if [threshold] or more of the opcodes do not match, skip.
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class FuzzyPatternScanMethod(
|
||||
val threshold: Int = 1
|
||||
)
|
||||
|
||||
/**
|
||||
* Annotations to scan a pattern [MethodFingerprint] directly.
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class DirectPatternScanMethod
|
||||
)
|
||||
@@ -1,10 +1,7 @@
|
||||
package app.revanced.patcher.fingerprint.method.impl
|
||||
|
||||
import app.revanced.patcher.data.impl.BytecodeData
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyPatternScanMethod
|
||||
import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyScanThreshold
|
||||
import app.revanced.patcher.extensions.parametersEqual
|
||||
import app.revanced.patcher.extensions.softCompareTo
|
||||
import app.revanced.patcher.fingerprint.Fingerprint
|
||||
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
|
||||
import app.revanced.patcher.util.proxy.ClassProxy
|
||||
@@ -14,11 +11,15 @@ import org.jf.dexlib2.iface.Method
|
||||
import org.jf.dexlib2.iface.instruction.Instruction
|
||||
import org.jf.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import org.jf.dexlib2.iface.reference.StringReference
|
||||
import org.jf.dexlib2.util.MethodUtil
|
||||
|
||||
private typealias StringMatch = MethodFingerprintResult.MethodFingerprintScanResult.StringsScanResult.StringMatch
|
||||
private typealias StringsScanResult = MethodFingerprintResult.MethodFingerprintScanResult.StringsScanResult
|
||||
|
||||
/**
|
||||
* Represents the [MethodFingerprint] for a method.
|
||||
* @param returnType The return type of the method.
|
||||
* @param access The access flags of the method.
|
||||
* @param accessFlags The access flags of the method.
|
||||
* @param parameters The parameters of the method.
|
||||
* @param opcodes The list of opcodes of the method.
|
||||
* @param strings A list of strings which a method contains.
|
||||
@@ -27,92 +28,109 @@ import org.jf.dexlib2.iface.reference.StringReference
|
||||
*/
|
||||
abstract class MethodFingerprint(
|
||||
internal val returnType: String? = null,
|
||||
internal val access: Int? = null,
|
||||
internal val accessFlags: Int? = null,
|
||||
internal val parameters: Iterable<String>? = null,
|
||||
internal val opcodes: Iterable<Opcode?>? = null,
|
||||
internal val strings: Iterable<String>? = null,
|
||||
internal val customFingerprint: ((methodDef: Method) -> Boolean)? = null
|
||||
internal val customFingerprint: ((methodDef: Method, classDef: ClassDef) -> Boolean)? = null
|
||||
) : Fingerprint {
|
||||
/**
|
||||
* The result of the [MethodFingerprint] the [Method].
|
||||
* The result of the [MethodFingerprint].
|
||||
*/
|
||||
var result: MethodFingerprintResult? = null
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Resolve a list of [MethodFingerprint] against a list of [ClassDef].
|
||||
* @param context The classes on which to resolve the [MethodFingerprint].
|
||||
* @param forData The [BytecodeData] to host proxies.
|
||||
*
|
||||
* @param classes The classes on which to resolve the [MethodFingerprint] in.
|
||||
* @param context The [BytecodeContext] to host proxies.
|
||||
* @return True if the resolution was successful, false otherwise.
|
||||
*/
|
||||
fun Iterable<MethodFingerprint>.resolve(forData: BytecodeData, context: Iterable<ClassDef>) {
|
||||
fun Iterable<MethodFingerprint>.resolve(context: BytecodeContext, classes: Iterable<ClassDef>) {
|
||||
for (fingerprint in this) // For each fingerprint
|
||||
classes@ for (classDef in context) // search through all classes for the fingerprint
|
||||
if (fingerprint.resolve(forData, classDef))
|
||||
classes@ for (classDef in classes) // search through all classes for the fingerprint
|
||||
if (fingerprint.resolve(context, classDef))
|
||||
break@classes // if the resolution succeeded, continue with the next fingerprint
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a [MethodFingerprint] against a [ClassDef].
|
||||
* @param context The class on which to resolve the [MethodFingerprint].
|
||||
* @param forData The [BytecodeData] to host proxies.
|
||||
*
|
||||
* @param forClass The class on which to resolve the [MethodFingerprint] in.
|
||||
* @param context The [BytecodeContext] to host proxies.
|
||||
* @return True if the resolution was successful, false otherwise.
|
||||
*/
|
||||
fun MethodFingerprint.resolve(forData: BytecodeData, context: ClassDef): Boolean {
|
||||
for (method in context.methods)
|
||||
if (this.resolve(forData, method, context))
|
||||
fun MethodFingerprint.resolve(context: BytecodeContext, forClass: ClassDef): Boolean {
|
||||
for (method in forClass.methods)
|
||||
if (this.resolve(context, method, forClass))
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a [MethodFingerprint] against a [Method].
|
||||
* @param context The context on which to resolve the [MethodFingerprint].
|
||||
* @param classDef The class of the matching [Method].
|
||||
* @param forData The [BytecodeData] to host proxies.
|
||||
*
|
||||
* @param method The class on which to resolve the [MethodFingerprint] in.
|
||||
* @param forClass The class on which to resolve the [MethodFingerprint].
|
||||
* @param context The [BytecodeContext] to host proxies.
|
||||
* @return True if the resolution was successful or if the fingerprint is already resolved, false otherwise.
|
||||
*/
|
||||
fun MethodFingerprint.resolve(forData: BytecodeData, context: Method, classDef: ClassDef): Boolean {
|
||||
fun MethodFingerprint.resolve(context: BytecodeContext, method: Method, forClass: ClassDef): Boolean {
|
||||
val methodFingerprint = this
|
||||
|
||||
if (methodFingerprint.result != null) return true
|
||||
|
||||
if (methodFingerprint.returnType != null && !context.returnType.startsWith(methodFingerprint.returnType))
|
||||
if (methodFingerprint.returnType != null && !method.returnType.startsWith(methodFingerprint.returnType))
|
||||
return false
|
||||
|
||||
if (methodFingerprint.access != null && methodFingerprint.access != context.accessFlags)
|
||||
if (methodFingerprint.accessFlags != null && methodFingerprint.accessFlags != method.accessFlags)
|
||||
return false
|
||||
|
||||
|
||||
fun parametersEqual(
|
||||
parameters1: Iterable<CharSequence>, parameters2: Iterable<CharSequence>
|
||||
): Boolean {
|
||||
if (parameters1.count() != parameters2.count()) return false
|
||||
val iterator1 = parameters1.iterator()
|
||||
parameters2.forEach {
|
||||
if (!it.startsWith(iterator1.next())) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if (methodFingerprint.parameters != null && !parametersEqual(
|
||||
methodFingerprint.parameters, // TODO: parseParameters()
|
||||
context.parameterTypes
|
||||
method.parameterTypes
|
||||
)
|
||||
) return false
|
||||
|
||||
@Suppress("UNNECESSARY_NOT_NULL_ASSERTION")
|
||||
if (methodFingerprint.customFingerprint != null && !methodFingerprint.customFingerprint!!(context))
|
||||
if (methodFingerprint.customFingerprint != null && !methodFingerprint.customFingerprint!!(method, forClass))
|
||||
return false
|
||||
|
||||
val stringsScanResult: StringsScanResult? =
|
||||
if (methodFingerprint.strings != null) {
|
||||
StringsScanResult(
|
||||
buildList {
|
||||
val implementation = context.implementation ?: return false
|
||||
val implementation = method.implementation ?: return false
|
||||
|
||||
val stringsList = methodFingerprint.strings.toMutableList()
|
||||
|
||||
implementation.instructions.forEach { instruction ->
|
||||
if (instruction.opcode.ordinal != Opcode.CONST_STRING.ordinal) return@forEach
|
||||
implementation.instructions.forEachIndexed { instructionIndex, instruction ->
|
||||
if (
|
||||
instruction.opcode != Opcode.CONST_STRING &&
|
||||
instruction.opcode != Opcode.CONST_STRING_JUMBO
|
||||
) return@forEachIndexed
|
||||
|
||||
val string = ((instruction as ReferenceInstruction).reference as StringReference).string
|
||||
val index = stringsList.indexOfFirst { it == string }
|
||||
if (index == -1) return@forEach
|
||||
val index = stringsList.indexOfFirst(string::contains)
|
||||
if (index == -1) return@forEachIndexed
|
||||
|
||||
add(
|
||||
StringMatch(
|
||||
string,
|
||||
index
|
||||
instructionIndex
|
||||
)
|
||||
)
|
||||
stringsList.removeAt(index)
|
||||
@@ -124,19 +142,19 @@ abstract class MethodFingerprint(
|
||||
} else null
|
||||
|
||||
val patternScanResult = if (methodFingerprint.opcodes != null) {
|
||||
context.implementation?.instructions ?: return false
|
||||
method.implementation?.instructions ?: return false
|
||||
|
||||
context.patternScan(methodFingerprint) ?: return false
|
||||
method.patternScan(methodFingerprint) ?: return false
|
||||
} else null
|
||||
|
||||
methodFingerprint.result = MethodFingerprintResult(
|
||||
context,
|
||||
classDef,
|
||||
method,
|
||||
forClass,
|
||||
MethodFingerprintResult.MethodFingerprintScanResult(
|
||||
patternScanResult,
|
||||
stringsScanResult
|
||||
),
|
||||
forData
|
||||
context
|
||||
)
|
||||
|
||||
return true
|
||||
@@ -146,7 +164,7 @@ abstract class MethodFingerprint(
|
||||
fingerprint: MethodFingerprint
|
||||
): MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult? {
|
||||
val instructions = this.implementation!!.instructions
|
||||
val fingerprintFuzzyPatternScanThreshold = fingerprint.fuzzyScanThreshold
|
||||
val fingerprintFuzzyPatternScanThreshold = fingerprint.fuzzyPatternScanMethod?.threshold ?: 0
|
||||
|
||||
val pattern = fingerprint.opcodes!!
|
||||
val instructionLength = instructions.count()
|
||||
@@ -210,22 +228,40 @@ abstract class MethodFingerprint(
|
||||
}
|
||||
}
|
||||
|
||||
private typealias StringMatch = MethodFingerprintResult.MethodFingerprintScanResult.StringsScanResult.StringMatch
|
||||
private typealias StringsScanResult = MethodFingerprintResult.MethodFingerprintScanResult.StringsScanResult
|
||||
|
||||
/**
|
||||
* Represents the result of a [MethodFingerprintResult].
|
||||
*
|
||||
* @param method The matching method.
|
||||
* @param classDef The [ClassDef] that contains the matching [method].
|
||||
* @param scanResult The result of scanning for the [MethodFingerprint].
|
||||
* @param data The [BytecodeData] this [MethodFingerprintResult] is attached to, to create proxies.
|
||||
* @param context The [BytecodeContext] this [MethodFingerprintResult] is attached to, to create proxies.
|
||||
*/
|
||||
data class MethodFingerprintResult(
|
||||
val method: Method,
|
||||
val classDef: ClassDef,
|
||||
val scanResult: MethodFingerprintScanResult,
|
||||
internal val data: BytecodeData
|
||||
internal val context: BytecodeContext
|
||||
) {
|
||||
/**
|
||||
* Returns a mutable clone of [classDef]
|
||||
*
|
||||
* Please note, this method allocates a [ClassProxy].
|
||||
* Use [classDef] where possible.
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
val mutableClass by lazy { context.proxy(classDef).mutableClass }
|
||||
|
||||
/**
|
||||
* Returns a mutable clone of [method]
|
||||
*
|
||||
* Please note, this method allocates a [ClassProxy].
|
||||
* Use [method] where possible.
|
||||
*/
|
||||
val mutableMethod by lazy {
|
||||
mutableClass.methods.first {
|
||||
MethodUtil.methodSignaturesMatch(it, this.method)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The result of scanning on the [MethodFingerprint].
|
||||
@@ -240,7 +276,7 @@ data class MethodFingerprintResult(
|
||||
* The result of scanning strings on the [MethodFingerprint].
|
||||
* @param matches The list of strings that were matched.
|
||||
*/
|
||||
data class StringsScanResult(val matches: List<StringMatch>){
|
||||
data class StringsScanResult(val matches: List<StringMatch>) {
|
||||
/**
|
||||
* Represents a match for a string at an index.
|
||||
* @param string The string that was matched.
|
||||
@@ -275,25 +311,4 @@ data class MethodFingerprintResult(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a mutable clone of [classDef]
|
||||
*
|
||||
* Please note, this method allocates a [ClassProxy].
|
||||
* Use [classDef] where possible.
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
val mutableClass by lazy { data.proxy(classDef).resolve() }
|
||||
|
||||
/**
|
||||
* Returns a mutable clone of [method]
|
||||
*
|
||||
* Please note, this method allocates a [ClassProxy].
|
||||
* Use [method] where possible.
|
||||
*/
|
||||
val mutableMethod by lazy {
|
||||
mutableClass.methods.first {
|
||||
it.softCompareTo(this.method)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package app.revanced.patcher.patch
|
||||
|
||||
/**
|
||||
* A container for patch options.
|
||||
*/
|
||||
abstract class OptionsContainer {
|
||||
/**
|
||||
* A list of [PatchOption]s.
|
||||
* @see PatchOptions
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
val options = PatchOptions()
|
||||
|
||||
protected fun <T> option(opt: PatchOption<T>): PatchOption<T> {
|
||||
options.register(opt)
|
||||
return opt
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,44 @@
|
||||
package app.revanced.patcher.patch
|
||||
|
||||
import app.revanced.patcher.data.Data
|
||||
import app.revanced.patcher.patch.impl.BytecodePatch
|
||||
import app.revanced.patcher.patch.impl.ResourcePatch
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.data.Context
|
||||
import app.revanced.patcher.data.ResourceContext
|
||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||
import java.io.Closeable
|
||||
|
||||
/**
|
||||
* A ReVanced patch.
|
||||
*
|
||||
* Can either be a [ResourcePatch] or a [BytecodePatch].
|
||||
* If it implements [Closeable], it will be closed after all patches have been executed.
|
||||
* Closing will be done in reverse execution order.
|
||||
*/
|
||||
abstract class Patch<out T : Data> {
|
||||
sealed interface Patch<out T : Context> : Closeable {
|
||||
/**
|
||||
* The main function of the [Patch] which the patcher will call.
|
||||
*
|
||||
* @param context The [Context] the patch will work on.
|
||||
* @return The result of executing the patch.
|
||||
*/
|
||||
abstract fun execute(data: @UnsafeVariance T): PatchResult
|
||||
fun execute(context: @UnsafeVariance T): PatchResult
|
||||
|
||||
/**
|
||||
* The closing function for this patch.
|
||||
*
|
||||
* This can be treated like popping the patch from the current patch stack.
|
||||
*/
|
||||
override fun close() {}
|
||||
}
|
||||
|
||||
abstract class OptionsContainer {
|
||||
/**
|
||||
* A list of [PatchOption]s.
|
||||
* @see PatchOptions
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
val options = PatchOptions()
|
||||
/**
|
||||
* Resource patch for the Patcher.
|
||||
*/
|
||||
interface ResourcePatch : Patch<ResourceContext>
|
||||
|
||||
protected fun <T> option(opt: PatchOption<T>): PatchOption<T> {
|
||||
options.register(opt)
|
||||
return opt
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Bytecode patch for the Patcher.
|
||||
*
|
||||
* @param fingerprints A list of [MethodFingerprint] this patch relies on.
|
||||
*/
|
||||
abstract class BytecodePatch(
|
||||
internal val fingerprints: Iterable<MethodFingerprint>? = null
|
||||
) : Patch<BytecodeContext>
|
||||
@@ -1,24 +1,27 @@
|
||||
package app.revanced.patcher.patch.annotations
|
||||
|
||||
import app.revanced.patcher.data.Data
|
||||
import app.revanced.patcher.data.Context
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* Annotation to mark a Class as a patch.
|
||||
* Annotation to mark a class as a patch.
|
||||
* @param include If false, the patch should be treated as optional by default.
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@MustBeDocumented
|
||||
annotation class Patch(val include: Boolean = true)
|
||||
|
||||
/**
|
||||
* Annotation for dependencies of [Patch]es .
|
||||
* Annotation for dependencies of [Patch]es.
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@MustBeDocumented
|
||||
annotation class DependsOn(
|
||||
val dependencies: Array<KClass<out Patch<Data>>> = []
|
||||
)
|
||||
val dependencies: Array<KClass<out Patch<Context>>> = []
|
||||
)
|
||||
|
||||
|
||||
/**
|
||||
* Annotation to mark [Patch]es which depend on integrations.
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
annotation class RequiresIntegrations // required because integrations are decoupled from patches
|
||||
@@ -1,13 +0,0 @@
|
||||
package app.revanced.patcher.patch.impl
|
||||
|
||||
import app.revanced.patcher.data.impl.BytecodeData
|
||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||
import app.revanced.patcher.patch.Patch
|
||||
|
||||
/**
|
||||
* Bytecode patch for the Patcher.
|
||||
* @param fingerprints A list of [MethodFingerprint] this patch relies on.
|
||||
*/
|
||||
abstract class BytecodePatch(
|
||||
internal val fingerprints: Iterable<MethodFingerprint>? = null
|
||||
) : Patch<BytecodeData>()
|
||||
@@ -1,9 +0,0 @@
|
||||
package app.revanced.patcher.patch.impl
|
||||
|
||||
import app.revanced.patcher.data.impl.ResourceData
|
||||
import app.revanced.patcher.patch.Patch
|
||||
|
||||
/**
|
||||
* Resource patch for the Patcher.
|
||||
*/
|
||||
abstract class ResourcePatch : Patch<ResourceData>()
|
||||
214
src/main/kotlin/app/revanced/patcher/util/ClassMerger.kt
Normal file
214
src/main/kotlin/app/revanced/patcher/util/ClassMerger.kt
Normal file
@@ -0,0 +1,214 @@
|
||||
package app.revanced.patcher.util
|
||||
|
||||
import app.revanced.patcher.PatcherContext
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.logging.Logger
|
||||
import app.revanced.patcher.util.ClassMerger.Utils.asMutableClass
|
||||
import app.revanced.patcher.util.ClassMerger.Utils.filterAny
|
||||
import app.revanced.patcher.util.ClassMerger.Utils.filterNotAny
|
||||
import app.revanced.patcher.util.ClassMerger.Utils.isPublic
|
||||
import app.revanced.patcher.util.ClassMerger.Utils.toPublic
|
||||
import app.revanced.patcher.util.TypeUtil.traverseClassHierarchy
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass.Companion.toMutable
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableField
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||
import org.jf.dexlib2.AccessFlags
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
import org.jf.dexlib2.util.MethodUtil
|
||||
import kotlin.reflect.KFunction2
|
||||
|
||||
/**
|
||||
* Experimental class to merge a [ClassDef] with another.
|
||||
* Note: This will not consider method implementations or if the class is missing a superclass or interfaces.
|
||||
*/
|
||||
internal object ClassMerger {
|
||||
/**
|
||||
* Merge a class with [otherClass].
|
||||
*
|
||||
* @param otherClass The class to merge with
|
||||
* @param context The context to traverse the class hierarchy in.
|
||||
* @param logger A logger.
|
||||
*/
|
||||
fun ClassDef.merge(otherClass: ClassDef, context: PatcherContext, logger: Logger? = null) = this
|
||||
//.fixFieldAccess(otherClass, logger)
|
||||
//.fixMethodAccess(otherClass, logger)
|
||||
.addMissingFields(otherClass, logger)
|
||||
.addMissingMethods(otherClass, logger)
|
||||
.publicize(otherClass, context, logger)
|
||||
|
||||
/**
|
||||
* Add methods which are missing but existing in [fromClass].
|
||||
*
|
||||
* @param fromClass The class to add missing methods from.
|
||||
* @param logger A logger.
|
||||
*/
|
||||
private fun ClassDef.addMissingMethods(fromClass: ClassDef, logger: Logger? = null): ClassDef {
|
||||
val missingMethods = fromClass.methods.let { fromMethods ->
|
||||
methods.filterNot { method ->
|
||||
fromMethods.any { fromMethod ->
|
||||
MethodUtil.methodSignaturesMatch(fromMethod, method)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (missingMethods.isEmpty()) return this
|
||||
|
||||
logger?.trace("Found ${missingMethods.size} missing methods")
|
||||
|
||||
return asMutableClass().apply {
|
||||
methods.addAll(missingMethods.map { it.toMutable() })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add fields which are missing but existing in [fromClass].
|
||||
*
|
||||
* @param fromClass The class to add missing fields from.
|
||||
* @param logger A logger.
|
||||
*/
|
||||
private fun ClassDef.addMissingFields(fromClass: ClassDef, logger: Logger? = null): ClassDef {
|
||||
val missingFields = fields.filterNotAny(fromClass.fields) { field, fromField ->
|
||||
fromField.name == field.name
|
||||
}
|
||||
|
||||
if (missingFields.isEmpty()) return this
|
||||
|
||||
logger?.trace("Found ${missingFields.size} missing fields")
|
||||
|
||||
return asMutableClass().apply {
|
||||
fields.addAll(missingFields.map { it.toMutable() })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a class and its super class public recursively.
|
||||
* @param reference The class to check the [AccessFlags] of.
|
||||
* @param context The context to traverse the class hierarchy in.
|
||||
* @param logger A logger.
|
||||
*/
|
||||
private fun ClassDef.publicize(reference: ClassDef, context: PatcherContext, logger: Logger? = null) =
|
||||
if (reference.accessFlags.isPublic() && !accessFlags.isPublic())
|
||||
this.asMutableClass().apply {
|
||||
context.bytecodeContext.traverseClassHierarchy(this) {
|
||||
if (accessFlags.isPublic()) return@traverseClassHierarchy
|
||||
|
||||
logger?.trace("Publicizing ${this.type}")
|
||||
|
||||
accessFlags = accessFlags.toPublic()
|
||||
}
|
||||
}
|
||||
else this
|
||||
|
||||
/**
|
||||
* Publicize fields if they are public in [reference].
|
||||
*
|
||||
* @param reference The class to check the [AccessFlags] of the fields in.
|
||||
* @param logger A logger.
|
||||
*/
|
||||
private fun ClassDef.fixFieldAccess(reference: ClassDef, logger: Logger? = null): ClassDef {
|
||||
val brokenFields = fields.filterAny(reference.fields) { field, referenceField ->
|
||||
if (field.name != referenceField.name) return@filterAny false
|
||||
|
||||
referenceField.accessFlags.isPublic() && !field.accessFlags.isPublic()
|
||||
}
|
||||
|
||||
if (brokenFields.isEmpty()) return this
|
||||
|
||||
logger?.trace("Found ${brokenFields.size} broken fields")
|
||||
|
||||
/**
|
||||
* Make a field public.
|
||||
*/
|
||||
fun MutableField.publicize() {
|
||||
accessFlags = accessFlags.toPublic()
|
||||
}
|
||||
|
||||
return asMutableClass().apply {
|
||||
fields.filter { brokenFields.contains(it) }.forEach(MutableField::publicize)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Publicize methods if they are public in [reference].
|
||||
*
|
||||
* @param reference The class to check the [AccessFlags] of the methods in.
|
||||
* @param logger A logger.
|
||||
*/
|
||||
private fun ClassDef.fixMethodAccess(reference: ClassDef, logger: Logger? = null): ClassDef {
|
||||
val brokenMethods = methods.filterAny(reference.methods) { method, referenceMethod ->
|
||||
if (!MethodUtil.methodSignaturesMatch(method, referenceMethod)) return@filterAny false
|
||||
|
||||
referenceMethod.accessFlags.isPublic() && !method.accessFlags.isPublic()
|
||||
}
|
||||
|
||||
if (brokenMethods.isEmpty()) return this
|
||||
|
||||
logger?.trace("Found ${brokenMethods.size} methods")
|
||||
|
||||
/**
|
||||
* Make a method public.
|
||||
*/
|
||||
fun MutableMethod.publicize() {
|
||||
accessFlags = accessFlags.toPublic()
|
||||
}
|
||||
|
||||
return asMutableClass().apply {
|
||||
methods.filter { brokenMethods.contains(it) }.forEach(MutableMethod::publicize)
|
||||
}
|
||||
}
|
||||
|
||||
private object Utils {
|
||||
fun ClassDef.asMutableClass() = if (this is MutableClass) this else this.toMutable()
|
||||
|
||||
/**
|
||||
* Check if the [AccessFlags.PUBLIC] flag is set.
|
||||
*
|
||||
* @return True, if the flag is set.
|
||||
*/
|
||||
fun Int.isPublic() = AccessFlags.PUBLIC.isSet(this)
|
||||
|
||||
/**
|
||||
* Make [AccessFlags] public.
|
||||
*
|
||||
* @return The new [AccessFlags].
|
||||
*/
|
||||
fun Int.toPublic() = this.or(AccessFlags.PUBLIC).and(AccessFlags.PRIVATE.value.inv())
|
||||
|
||||
/**
|
||||
* Filter [this] on [needles] matching the given [predicate].
|
||||
*
|
||||
* @param this The hay to filter for [needles].
|
||||
* @param needles The needles to filter [this] with.
|
||||
* @param predicate The filter.
|
||||
* @return The [this] filtered on [needles] matching the given [predicate].
|
||||
*/
|
||||
fun <HayType, NeedleType> Iterable<HayType>.filterAny(
|
||||
needles: Iterable<NeedleType>, predicate: (HayType, NeedleType) -> Boolean
|
||||
) = Iterable<HayType>::filter.any(this, needles, predicate)
|
||||
|
||||
/**
|
||||
* Filter [this] on [needles] not matching the given [predicate].
|
||||
*
|
||||
* @param this The hay to filter for [needles].
|
||||
* @param needles The needles to filter [this] with.
|
||||
* @param predicate The filter.
|
||||
* @return The [this] filtered on [needles] not matching the given [predicate].
|
||||
*/
|
||||
fun <HayType, NeedleType> Iterable<HayType>.filterNotAny(
|
||||
needles: Iterable<NeedleType>, predicate: (HayType, NeedleType) -> Boolean
|
||||
) = Iterable<HayType>::filterNot.any(this, needles, predicate)
|
||||
|
||||
fun <HayType, NeedleType> KFunction2<Iterable<HayType>, (HayType) -> Boolean, List<HayType>>.any(
|
||||
haystack: Iterable<HayType>,
|
||||
needles: Iterable<NeedleType>,
|
||||
predicate: (HayType, NeedleType) -> Boolean
|
||||
) = this(haystack) { hay ->
|
||||
needles.any { needle ->
|
||||
predicate(hay, needle)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,40 +3,44 @@ package app.revanced.patcher.util
|
||||
import app.revanced.patcher.util.proxy.ClassProxy
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
|
||||
class ProxyBackedClassList(internal val internalClasses: MutableList<ClassDef>) : List<ClassDef> {
|
||||
private val internalProxies = mutableListOf<ClassProxy>()
|
||||
internal val proxies: List<ClassProxy> = internalProxies
|
||||
|
||||
fun add(classDef: ClassDef) = internalClasses.add(classDef)
|
||||
fun add(classProxy: ClassProxy) = internalProxies.add(classProxy)
|
||||
/**
|
||||
* A class that represents a set of classes and proxies.
|
||||
*
|
||||
* @param classes The classes to be backed by proxies.
|
||||
*/
|
||||
class ProxyBackedClassList(internal val classes: MutableList<ClassDef>) : Set<ClassDef> {
|
||||
internal val proxies = mutableListOf<ClassProxy>()
|
||||
|
||||
/**
|
||||
* Apply all resolved classes into [internalClasses] and clean the [proxies] list.
|
||||
* Add a [ClassDef].
|
||||
*/
|
||||
internal fun applyProxies() {
|
||||
// FIXME: check if this could cause issues when multiple patches use the same proxy
|
||||
internalProxies.removeIf { proxy ->
|
||||
// if the proxy is unused, keep it in the list
|
||||
if (!proxy.proxyUsed) return@removeIf false
|
||||
fun add(classDef: ClassDef) = classes.add(classDef)
|
||||
|
||||
// if it has been used, replace the internal class which it proxied
|
||||
val index = internalClasses.indexOfFirst { it.type == proxy.immutableClass.type }
|
||||
internalClasses[index] = proxy.mutatedClass
|
||||
/**
|
||||
* Add a [ClassProxy].
|
||||
*/
|
||||
fun add(classProxy: ClassProxy) = proxies.add(classProxy)
|
||||
|
||||
/**
|
||||
* Replace all classes with their mutated versions.
|
||||
*/
|
||||
internal fun replaceClasses() =
|
||||
proxies.removeIf { proxy ->
|
||||
// if the proxy is unused, keep it in the list
|
||||
if (!proxy.resolved) return@removeIf false
|
||||
|
||||
// if it has been used, replace the original class with the new class
|
||||
val index = classes.indexOfFirst { it.type == proxy.immutableClass.type }
|
||||
classes[index] = proxy.mutableClass
|
||||
|
||||
// return true to remove it from the proxies list
|
||||
return@removeIf true
|
||||
}
|
||||
}
|
||||
|
||||
override val size get() = internalClasses.size
|
||||
override fun contains(element: ClassDef) = internalClasses.contains(element)
|
||||
override fun containsAll(elements: Collection<ClassDef>) = internalClasses.containsAll(elements)
|
||||
override fun get(index: Int) = internalClasses[index]
|
||||
override fun indexOf(element: ClassDef) = internalClasses.indexOf(element)
|
||||
override fun isEmpty() = internalClasses.isEmpty()
|
||||
override fun iterator() = internalClasses.iterator()
|
||||
override fun lastIndexOf(element: ClassDef) = internalClasses.lastIndexOf(element)
|
||||
override fun listIterator() = internalClasses.listIterator()
|
||||
override fun listIterator(index: Int) = internalClasses.listIterator(index)
|
||||
override fun subList(fromIndex: Int, toIndex: Int) = internalClasses.subList(fromIndex, toIndex)
|
||||
|
||||
override val size get() = classes.size
|
||||
override fun contains(element: ClassDef) = classes.contains(element)
|
||||
override fun containsAll(elements: Collection<ClassDef>) = classes.containsAll(elements)
|
||||
override fun isEmpty() = classes.isEmpty()
|
||||
override fun iterator() = classes.iterator()
|
||||
}
|
||||
19
src/main/kotlin/app/revanced/patcher/util/TypeUtil.kt
Normal file
19
src/main/kotlin/app/revanced/patcher/util/TypeUtil.kt
Normal file
@@ -0,0 +1,19 @@
|
||||
package app.revanced.patcher.util
|
||||
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
|
||||
|
||||
object TypeUtil {
|
||||
/**
|
||||
* traverse the class hierarchy starting from the given root class
|
||||
*
|
||||
* @param targetClass the class to start traversing the class hierarchy from
|
||||
* @param callback function that is called for every class in the hierarchy
|
||||
*/
|
||||
fun BytecodeContext.traverseClassHierarchy(targetClass: MutableClass, callback: MutableClass.() -> Unit) {
|
||||
callback(targetClass)
|
||||
this.findClass(targetClass.superclass ?: return)?.mutableClass?.let {
|
||||
traverseClassHierarchy(it, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,27 @@
|
||||
package app.revanced.patcher.util.method
|
||||
|
||||
import app.revanced.patcher.data.impl.BytecodeData
|
||||
import app.revanced.patcher.data.impl.MethodNotFoundException
|
||||
import app.revanced.patcher.extensions.softCompareTo
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import org.jf.dexlib2.iface.Method
|
||||
import org.jf.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import org.jf.dexlib2.iface.reference.MethodReference
|
||||
import org.jf.dexlib2.util.MethodUtil
|
||||
|
||||
/**
|
||||
* Find a method from another method via instruction offsets.
|
||||
* @param bytecodeData The bytecodeData to use when resolving the next method reference.
|
||||
* @param bytecodeContext The context to use when resolving the next method reference.
|
||||
* @param currentMethod The method to start from.
|
||||
*/
|
||||
class MethodWalker internal constructor(
|
||||
private val bytecodeData: BytecodeData,
|
||||
private val bytecodeContext: BytecodeContext,
|
||||
private var currentMethod: Method
|
||||
) {
|
||||
/**
|
||||
* Get the method which was walked last.
|
||||
*
|
||||
* It is possible to cast this method to a [MutableMethod], if the method has been walked mutably.
|
||||
*
|
||||
* @return The method which was walked last.
|
||||
*/
|
||||
fun getMethod(): Method {
|
||||
return currentMethod
|
||||
@@ -27,25 +29,28 @@ class MethodWalker internal constructor(
|
||||
|
||||
/**
|
||||
* Walk to a method defined at the offset in the instruction list of the current method.
|
||||
*
|
||||
* The current method will be mutable.
|
||||
*
|
||||
* @param offset The offset of the instruction. This instruction must be of format 35c.
|
||||
* @param walkMutable If this is true, the class of the method will be resolved mutably.
|
||||
* The current method will be mutable.
|
||||
* @return The same [MethodWalker] instance with the method at [offset].
|
||||
*/
|
||||
fun nextMethod(offset: Int, walkMutable: Boolean = false): MethodWalker {
|
||||
currentMethod.implementation?.instructions?.let { instructions ->
|
||||
val instruction = instructions.elementAt(offset)
|
||||
|
||||
val newMethod = (instruction as ReferenceInstruction).reference as MethodReference
|
||||
val proxy = bytecodeData.findClass(newMethod.definingClass)!!
|
||||
val proxy = bytecodeContext.findClass(newMethod.definingClass)!!
|
||||
|
||||
val methods = if (walkMutable) proxy.resolve().methods else proxy.immutableClass.methods
|
||||
currentMethod = methods.first { it ->
|
||||
return@first it.softCompareTo(newMethod)
|
||||
val methods = if (walkMutable) proxy.mutableClass.methods else proxy.immutableClass.methods
|
||||
currentMethod = methods.first {
|
||||
return@first MethodUtil.methodSignaturesMatch(it, newMethod)
|
||||
}
|
||||
return this
|
||||
}
|
||||
throw MethodNotFoundException("This method can not be walked at offset $offset inside the method ${currentMethod.name}")
|
||||
}
|
||||
|
||||
|
||||
internal class MethodNotFoundException(exception: String) : Exception(exception)
|
||||
}
|
||||
@@ -1,18 +1,70 @@
|
||||
@file:Suppress("unused")
|
||||
|
||||
package app.revanced.patcher.util.patch
|
||||
|
||||
import app.revanced.patcher.data.Data
|
||||
import app.revanced.patcher.data.Context
|
||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import org.jf.dexlib2.DexFileFactory
|
||||
import java.io.File
|
||||
import java.net.URLClassLoader
|
||||
import java.util.jar.JarFile
|
||||
|
||||
/**
|
||||
* A patch bundle.
|
||||
|
||||
* @param path The path to the patch bundle.
|
||||
*/
|
||||
abstract class PatchBundle(path: String) : File(path) {
|
||||
sealed class PatchBundle(path: String) : File(path) {
|
||||
internal fun loadPatches(classLoader: ClassLoader, classNames: Iterator<String>) = buildList {
|
||||
for (className in classNames) {
|
||||
classNames.forEach { className ->
|
||||
val clazz = classLoader.loadClass(className)
|
||||
if (!clazz.isAnnotationPresent(app.revanced.patcher.patch.annotations.Patch::class.java)) continue
|
||||
@Suppress("UNCHECKED_CAST") this.add(clazz as Class<out Patch<Data>>)
|
||||
if (!clazz.isAnnotationPresent(app.revanced.patcher.patch.annotations.Patch::class.java)) return@forEach
|
||||
@Suppress("UNCHECKED_CAST") this.add(clazz as Class<out Patch<Context>>)
|
||||
}
|
||||
}.sortedBy { it.patchName }
|
||||
|
||||
/**
|
||||
* A patch bundle of type [Jar].
|
||||
*
|
||||
* @param patchBundlePath The path to the patch bundle.
|
||||
*/
|
||||
class Jar(patchBundlePath: String) : PatchBundle(patchBundlePath) {
|
||||
|
||||
/**
|
||||
* Load patches from the patch bundle.
|
||||
*
|
||||
* Patches will be loaded with a new [URLClassLoader].
|
||||
*/
|
||||
fun loadPatches() = loadPatches(
|
||||
URLClassLoader(
|
||||
arrayOf(this.toURI().toURL()),
|
||||
Thread.currentThread().contextClassLoader // TODO: find out why this is required
|
||||
),
|
||||
JarFile(this)
|
||||
.stream()
|
||||
.filter {it.name.endsWith(".class") && !it.name.contains("$")}
|
||||
.map({it -> it.realName.replace('/', '.').replace(".class", "")}).iterator()
|
||||
)
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A patch bundle of type [Dex] format.
|
||||
*
|
||||
* @param patchBundlePath The path to a patch bundle of dex format.
|
||||
* @param dexClassLoader The dex class loader.
|
||||
*/
|
||||
class Dex(patchBundlePath: String, private val dexClassLoader: ClassLoader) : PatchBundle(patchBundlePath) {
|
||||
/**
|
||||
* Load patches from the patch bundle.
|
||||
*
|
||||
* Patches will be loaded to the provided [dexClassLoader].
|
||||
*/
|
||||
fun loadPatches() = loadPatches(dexClassLoader,
|
||||
DexFileFactory.loadDexFile(path, null).classes.asSequence().map({ classDef ->
|
||||
classDef.type.substring(1, classDef.length - 1).replace('/', '.')
|
||||
}).iterator())
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package app.revanced.patcher.util.patch
|
||||
|
||||
internal class StringIterator<T, I : Iterator<T>>(
|
||||
private val iterator: I,
|
||||
private val _next: (T) -> String
|
||||
) : Iterator<String> {
|
||||
override fun hasNext() = iterator.hasNext()
|
||||
|
||||
override fun next() = _next(iterator.next())
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package app.revanced.patcher.util.patch.impl
|
||||
|
||||
import app.revanced.patcher.util.patch.PatchBundle
|
||||
import app.revanced.patcher.util.patch.StringIterator
|
||||
import org.jf.dexlib2.DexFileFactory
|
||||
|
||||
/**
|
||||
* A patch bundle of the ReVanced [DexPatchBundle] format.
|
||||
* @param patchBundlePath The path to a patch bundle of dex format.
|
||||
* @param dexClassLoader The dex class loader.
|
||||
*/
|
||||
class DexPatchBundle(patchBundlePath: String, private val dexClassLoader: ClassLoader) : PatchBundle(patchBundlePath) {
|
||||
fun loadPatches() = loadPatches(dexClassLoader,
|
||||
StringIterator(DexFileFactory.loadDexFile(path, null).classes.iterator()) { classDef ->
|
||||
classDef.type.substring(1, classDef.length - 1).replace('/', '.')
|
||||
})
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package app.revanced.patcher.util.patch.impl
|
||||
|
||||
import app.revanced.patcher.util.patch.PatchBundle
|
||||
import app.revanced.patcher.util.patch.StringIterator
|
||||
import java.net.URLClassLoader
|
||||
import java.util.jar.JarFile
|
||||
|
||||
/**
|
||||
* A patch bundle of the ReVanced [JarPatchBundle] format.
|
||||
* @param patchBundlePath The path to the patch bundle.
|
||||
*/
|
||||
class JarPatchBundle(patchBundlePath: String) : PatchBundle(patchBundlePath) {
|
||||
fun loadPatches() = loadPatches(
|
||||
URLClassLoader(
|
||||
arrayOf(this.toURI().toURL()),
|
||||
Thread.currentThread().contextClassLoader // TODO: find out why this is required
|
||||
),
|
||||
StringIterator(
|
||||
JarFile(this)
|
||||
.entries()
|
||||
.toList() // TODO: find a cleaner solution than that to filter non class files
|
||||
.filter {
|
||||
it.name.endsWith(".class") && !it.name.contains("$")
|
||||
}
|
||||
.iterator()
|
||||
) {
|
||||
it.realName.replace('/', '.').replace(".class", "")
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -8,34 +8,26 @@ import org.jf.dexlib2.iface.ClassDef
|
||||
*
|
||||
* A class proxy simply holds a reference to the original class
|
||||
* and allocates a mutable clone for the original class if needed.
|
||||
* @param immutableClass The class to proxy
|
||||
* @param immutableClass The class to proxy.
|
||||
*/
|
||||
class ClassProxy(
|
||||
class ClassProxy internal constructor(
|
||||
val immutableClass: ClassDef,
|
||||
) {
|
||||
internal var proxyUsed = false
|
||||
internal lateinit var mutatedClass: MutableClass
|
||||
|
||||
init {
|
||||
// in the instance, that a [MutableClass] is being proxied,
|
||||
// do not create an additional clone and reuse the [MutableClass] instance
|
||||
if (immutableClass is MutableClass) {
|
||||
mutatedClass = immutableClass
|
||||
proxyUsed = true
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Weather the proxy was actually used.
|
||||
*/
|
||||
internal var resolved = false
|
||||
|
||||
/**
|
||||
* Allocates and returns a mutable clone of the original class.
|
||||
* A patch should always use the original immutable class reference
|
||||
* to avoid unnecessary allocations for the mutable class.
|
||||
* @return A mutable clone of the original class.
|
||||
* The mutable clone of the original class.
|
||||
*
|
||||
* Note: This is only allocated if the proxy is actually used.
|
||||
*/
|
||||
fun resolve(): MutableClass {
|
||||
if (!proxyUsed) {
|
||||
proxyUsed = true
|
||||
mutatedClass = MutableClass(immutableClass)
|
||||
}
|
||||
return mutatedClass
|
||||
val mutableClass by lazy {
|
||||
resolved = true
|
||||
if (immutableClass is MutableClass) {
|
||||
immutableClass
|
||||
} else
|
||||
MutableClass(immutableClass)
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,22 @@ class MutableMethod(method: Method) : Method, BaseMethodReference() {
|
||||
private val _parameterTypes by lazy { method.parameterTypes.toMutableList() }
|
||||
private val _hiddenApiRestrictions by lazy { method.hiddenApiRestrictions }
|
||||
|
||||
fun setDefiningClass(definingClass: String) {
|
||||
this.definingClass = definingClass
|
||||
}
|
||||
|
||||
fun setName(name: String) {
|
||||
this.name = name
|
||||
}
|
||||
|
||||
fun setAccessFlags(accessFlags: Int) {
|
||||
this.accessFlags = accessFlags
|
||||
}
|
||||
|
||||
fun setReturnType(returnType: String) {
|
||||
this.returnType = returnType
|
||||
}
|
||||
|
||||
override fun getDefiningClass(): String {
|
||||
return definingClass
|
||||
}
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
package app.revanced.patcher.extensions
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.removeInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstructions
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||
import app.revanced.patcher.util.smali.ExternalLabel
|
||||
import org.jf.dexlib2.AccessFlags
|
||||
import org.jf.dexlib2.Opcode
|
||||
import org.jf.dexlib2.builder.BuilderOffsetInstruction
|
||||
import org.jf.dexlib2.builder.MutableMethodImplementation
|
||||
import org.jf.dexlib2.builder.instruction.BuilderInstruction21s
|
||||
import org.jf.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import org.jf.dexlib2.immutable.ImmutableMethod
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
private object InstructionExtensionsTest {
|
||||
private lateinit var testMethod: MutableMethod
|
||||
private lateinit var testMethodImplementation: MutableMethodImplementation
|
||||
|
||||
@BeforeEach
|
||||
fun createTestMethod() = ImmutableMethod(
|
||||
"TestClass;",
|
||||
"testMethod",
|
||||
null,
|
||||
"V",
|
||||
AccessFlags.PUBLIC.value,
|
||||
null,
|
||||
null,
|
||||
MutableMethodImplementation(16).also { testMethodImplementation = it }.apply {
|
||||
repeat(10) { i -> this.addInstruction(TestInstruction(i)) }
|
||||
},
|
||||
).let { testMethod = it.toMutable() }
|
||||
|
||||
@Test
|
||||
fun addInstructionsToImplementationIndexed() = applyToImplementation {
|
||||
addInstructions(5, getTestInstructions(5..6)).also {
|
||||
assertRegisterIs(5, 5)
|
||||
assertRegisterIs(6, 6)
|
||||
|
||||
assertRegisterIs(5, 7)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addInstructionsToImplementation() = applyToImplementation {
|
||||
addInstructions(getTestInstructions(10..11)).also {
|
||||
assertRegisterIs(10, 10)
|
||||
assertRegisterIs(11, 11)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removeInstructionsFromImplementationIndexed() = applyToImplementation {
|
||||
removeInstructions(5, 5).also { assertRegisterIs(4, 4) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removeInstructionsFromImplementation() = applyToImplementation {
|
||||
removeInstructions(0).also { assertRegisterIs(9, 9) }
|
||||
removeInstructions(1).also { assertRegisterIs(1, 0) }
|
||||
removeInstructions(2).also { assertRegisterIs(3, 0) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun replaceInstructionsInImplementationIndexed() = applyToImplementation {
|
||||
replaceInstructions(5, getTestInstructions(0..1)).also {
|
||||
assertRegisterIs(0, 5)
|
||||
assertRegisterIs(1, 6)
|
||||
assertRegisterIs(7, 7)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addInstructionToMethodIndexed() = applyToMethod {
|
||||
addInstruction(5, TestInstruction(0)).also { assertRegisterIs(0, 5) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addInstructionToMethod() = applyToMethod {
|
||||
addInstruction(TestInstruction(0)).also { assertRegisterIs(0, 10) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addSmaliInstructionToMethodIndexed() = applyToMethod {
|
||||
addInstruction(5, getTestSmaliInstruction(0)).also { assertRegisterIs(0, 5) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addSmaliInstructionToMethod() = applyToMethod {
|
||||
addInstruction(getTestSmaliInstruction(0)).also { assertRegisterIs(0, 10) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addInstructionsToMethodIndexed() = applyToMethod {
|
||||
addInstructions(5, getTestInstructions(0..1)).also {
|
||||
assertRegisterIs(0, 5)
|
||||
assertRegisterIs(1, 6)
|
||||
|
||||
assertRegisterIs(5, 7)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addInstructionsToMethod() = applyToMethod {
|
||||
addInstructions(getTestInstructions(0..1)).also {
|
||||
assertRegisterIs(0, 10)
|
||||
assertRegisterIs(1, 11)
|
||||
|
||||
assertRegisterIs(9, 9)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addSmaliInstructionsToMethodIndexed() = applyToMethod {
|
||||
addInstructionsWithLabels(5, getTestSmaliInstructions(0..1)).also {
|
||||
assertRegisterIs(0, 5)
|
||||
assertRegisterIs(1, 6)
|
||||
|
||||
assertRegisterIs(5, 7)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addSmaliInstructionsToMethod() = applyToMethod {
|
||||
addInstructions(getTestSmaliInstructions(0..1)).also {
|
||||
assertRegisterIs(0, 10)
|
||||
assertRegisterIs(1, 11)
|
||||
|
||||
assertRegisterIs(9, 9)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun addSmaliInstructionsWithExternalLabelToMethodIndexed() = applyToMethod {
|
||||
val label = ExternalLabel("testLabel", getInstruction(5))
|
||||
|
||||
addInstructionsWithLabels(
|
||||
5,
|
||||
getTestSmaliInstructions(0..1).plus("\n").plus("goto :${label.name}"),
|
||||
label
|
||||
).also {
|
||||
assertRegisterIs(0, 5)
|
||||
assertRegisterIs(1, 6)
|
||||
assertRegisterIs(5, 8)
|
||||
|
||||
val gotoTarget = getInstruction<BuilderOffsetInstruction>(7)
|
||||
.target.location.instruction as OneRegisterInstruction
|
||||
|
||||
assertEquals(5, gotoTarget.registerA)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removeInstructionFromMethodIndexed() = applyToMethod {
|
||||
removeInstruction(5).also {
|
||||
assertRegisterIs(4, 4)
|
||||
assertRegisterIs(6, 5)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removeInstructionsFromMethodIndexed() = applyToMethod {
|
||||
removeInstructions(5, 5).also { assertRegisterIs(4, 4) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removeInstructionsFromMethod() = applyToMethod {
|
||||
removeInstructions(0).also { assertRegisterIs(9, 9) }
|
||||
removeInstructions(1).also { assertRegisterIs(1, 0) }
|
||||
removeInstructions(2).also { assertRegisterIs(3, 0) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun replaceInstructionInMethodIndexed() = applyToMethod {
|
||||
replaceInstruction(5, TestInstruction(0)).also { assertRegisterIs(0, 5) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun replaceInstructionsInMethodIndexed() = applyToMethod {
|
||||
replaceInstructions(5, getTestInstructions(0..1)).also {
|
||||
assertRegisterIs(0, 5)
|
||||
assertRegisterIs(1, 6)
|
||||
assertRegisterIs(7, 7)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun replaceSmaliInstructionsInMethodIndexed() = applyToMethod {
|
||||
replaceInstructions(5, getTestSmaliInstructions(0..1)).also {
|
||||
assertRegisterIs(0, 5)
|
||||
assertRegisterIs(1, 6)
|
||||
assertRegisterIs(7, 7)
|
||||
}
|
||||
}
|
||||
|
||||
// region Helper methods
|
||||
|
||||
private fun applyToImplementation(block: MutableMethodImplementation.() -> Unit) {
|
||||
testMethodImplementation.apply(block)
|
||||
}
|
||||
|
||||
private fun applyToMethod(block: MutableMethod.() -> Unit) {
|
||||
testMethod.apply(block)
|
||||
}
|
||||
|
||||
private fun MutableMethodImplementation.assertRegisterIs(register: Int, atIndex: Int) = assertEquals(
|
||||
register, getInstruction<OneRegisterInstruction>(atIndex).registerA
|
||||
)
|
||||
|
||||
private fun MutableMethod.assertRegisterIs(register: Int, atIndex: Int) =
|
||||
implementation!!.assertRegisterIs(register, atIndex)
|
||||
|
||||
private fun getTestInstructions(range: IntRange) = range.map { TestInstruction(it) }
|
||||
|
||||
private fun getTestSmaliInstruction(register: Int) = "const/16 v$register, 0"
|
||||
|
||||
private fun getTestSmaliInstructions(range: IntRange) = range.joinToString("\n") {
|
||||
getTestSmaliInstruction(it)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
private class TestInstruction(register: Int) : BuilderInstruction21s(Opcode.CONST_16, register, 0)
|
||||
}
|
||||
@@ -9,6 +9,5 @@ import app.revanced.patcher.annotation.Package
|
||||
)]
|
||||
)
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
internal annotation class ExampleBytecodeCompatibility
|
||||
|
||||
|
||||
@@ -3,18 +3,15 @@ package app.revanced.patcher.usage.bytecode
|
||||
import app.revanced.patcher.annotation.Description
|
||||
import app.revanced.patcher.annotation.Name
|
||||
import app.revanced.patcher.annotation.Version
|
||||
import app.revanced.patcher.data.impl.BytecodeData
|
||||
import app.revanced.patcher.extensions.addInstructions
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.extensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.OptionsContainer
|
||||
import app.revanced.patcher.patch.PatchOption
|
||||
import app.revanced.patcher.patch.PatchResult
|
||||
import app.revanced.patcher.patch.PatchResultSuccess
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.*
|
||||
import app.revanced.patcher.patch.annotations.DependsOn
|
||||
import app.revanced.patcher.patch.annotations.Patch
|
||||
import app.revanced.patcher.patch.impl.BytecodePatch
|
||||
import app.revanced.patcher.usage.resource.annotation.ExampleResourceCompatibility
|
||||
import app.revanced.patcher.usage.resource.patch.ExampleResourcePatch
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||
import com.google.common.collect.ImmutableList
|
||||
@@ -39,11 +36,11 @@ import kotlin.io.path.Path
|
||||
@Description("Example demonstration of a bytecode patch.")
|
||||
@ExampleResourceCompatibility
|
||||
@Version("0.0.1")
|
||||
@DependsOn([ExampleBytecodePatch::class])
|
||||
@DependsOn([ExampleResourcePatch::class])
|
||||
class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) {
|
||||
// This function will be executed by the patcher.
|
||||
// You can treat it as a constructor
|
||||
override fun execute(data: BytecodeData): PatchResult {
|
||||
override fun execute(context: BytecodeContext): PatchResult {
|
||||
// Get the resolved method by its fingerprint from the resolver cache
|
||||
val result = ExampleFingerprint.result!!
|
||||
|
||||
@@ -63,9 +60,9 @@ class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) {
|
||||
implementation.replaceStringAt(startIndex, "Hello, ReVanced! Editing bytecode.")
|
||||
|
||||
// Get the class in which the method matching our fingerprint is defined in.
|
||||
val mainClass = data.findClass {
|
||||
val mainClass = context.findClass {
|
||||
it.type == result.classDef.type
|
||||
}!!.resolve()
|
||||
}!!.mutableClass
|
||||
|
||||
// Add a new method returning a string
|
||||
mainClass.methods.add(
|
||||
@@ -122,7 +119,7 @@ class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) {
|
||||
// For this sake of example I reuse the TestClass field dummyField inside the virtual register 0.
|
||||
//
|
||||
// Control flow instructions are not supported as of now.
|
||||
method.addInstructions(
|
||||
method.addInstructionsWithLabels(
|
||||
startIndex + 2,
|
||||
"""
|
||||
invoke-static { }, LTestClass;->returnHello()Ljava/lang/String;
|
||||
@@ -169,6 +166,7 @@ class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) {
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
companion object : OptionsContainer() {
|
||||
private var key1 by option(
|
||||
PatchOption.StringOption(
|
||||
|
||||
@@ -1,23 +1,11 @@
|
||||
package app.revanced.patcher.usage.bytecode
|
||||
|
||||
import app.revanced.patcher.annotation.Name
|
||||
import app.revanced.patcher.annotation.Version
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
|
||||
import app.revanced.patcher.fingerprint.method.annotation.MatchingMethod
|
||||
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
||||
import app.revanced.patcher.usage.bytecode.ExampleBytecodeCompatibility
|
||||
import org.jf.dexlib2.AccessFlags
|
||||
import org.jf.dexlib2.Opcode
|
||||
|
||||
@Name("example-fingerprint")
|
||||
@MatchingMethod(
|
||||
"LexampleClass;",
|
||||
"exampleMehod"
|
||||
)
|
||||
@FuzzyPatternScanMethod(2)
|
||||
@ExampleBytecodeCompatibility
|
||||
@Version("0.0.1")
|
||||
object ExampleFingerprint : MethodFingerprint(
|
||||
"V",
|
||||
AccessFlags.PUBLIC or AccessFlags.STATIC,
|
||||
|
||||
@@ -9,6 +9,5 @@ import app.revanced.patcher.annotation.Package
|
||||
)]
|
||||
)
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
internal annotation class ExampleResourceCompatibility
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@ package app.revanced.patcher.usage.resource.patch
|
||||
import app.revanced.patcher.annotation.Description
|
||||
import app.revanced.patcher.annotation.Name
|
||||
import app.revanced.patcher.annotation.Version
|
||||
import app.revanced.patcher.data.impl.ResourceData
|
||||
import app.revanced.patcher.data.ResourceContext
|
||||
import app.revanced.patcher.patch.PatchResult
|
||||
import app.revanced.patcher.patch.PatchResultSuccess
|
||||
import app.revanced.patcher.patch.ResourcePatch
|
||||
import app.revanced.patcher.patch.annotations.Patch
|
||||
import app.revanced.patcher.patch.impl.ResourcePatch
|
||||
import app.revanced.patcher.usage.resource.annotation.ExampleResourceCompatibility
|
||||
import org.w3c.dom.Element
|
||||
|
||||
@@ -16,9 +16,9 @@ import org.w3c.dom.Element
|
||||
@Description("Example demonstration of a resource patch.")
|
||||
@ExampleResourceCompatibility
|
||||
@Version("0.0.1")
|
||||
class ExampleResourcePatch : ResourcePatch() {
|
||||
override fun execute(data: ResourceData): PatchResult {
|
||||
data.xmlEditor["AndroidManifest.xml"].use { editor ->
|
||||
class ExampleResourcePatch : ResourcePatch {
|
||||
override fun execute(context: ResourceContext): PatchResult {
|
||||
context.xmlEditor["AndroidManifest.xml"].use { editor ->
|
||||
val element = editor // regular DomFileEditor
|
||||
.file
|
||||
.getElementsByTagName("application")
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
package app.revanced.patcher.util
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
import org.junit.jupiter.api.Assertions.*
|
||||
|
||||
internal class VersionReaderTest {
|
||||
@Test
|
||||
fun read() {
|
||||
val version = VersionReader.read()
|
||||
assertNotNull(version)
|
||||
assertTrue(version.isNotEmpty())
|
||||
val parts = version.split(".")
|
||||
assertEquals(3, parts.size)
|
||||
parts.forEach {
|
||||
assertTrue(it.toInt() >= 0)
|
||||
}
|
||||
println(version)
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
package app.revanced.patcher.util.smali
|
||||
|
||||
import app.revanced.patcher.extensions.addInstructions
|
||||
import app.revanced.patcher.extensions.instruction
|
||||
import app.revanced.patcher.extensions.label
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.newLabel
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||
import org.jf.dexlib2.AccessFlags
|
||||
import org.jf.dexlib2.Opcode
|
||||
@@ -35,7 +36,7 @@ internal class InlineSmaliCompilerTest {
|
||||
method.addInstructions(arrayOfNulls<String>(insnAmount).also {
|
||||
Arrays.fill(it, "const/4 v0, 0x0")
|
||||
}.joinToString("\n"))
|
||||
method.addInstructions(
|
||||
method.addInstructionsWithLabels(
|
||||
targetIndex,
|
||||
"""
|
||||
:test
|
||||
@@ -44,7 +45,7 @@ internal class InlineSmaliCompilerTest {
|
||||
"""
|
||||
)
|
||||
|
||||
val insn = method.instruction(insnIndex) as BuilderInstruction21t
|
||||
val insn = method.getInstruction<BuilderInstruction21t>(insnIndex)
|
||||
assertEquals(targetIndex, insn.target.location.index)
|
||||
}
|
||||
|
||||
@@ -61,19 +62,19 @@ internal class InlineSmaliCompilerTest {
|
||||
"""
|
||||
)
|
||||
|
||||
assertEquals(labelIndex, method.label(labelIndex).location.index)
|
||||
assertEquals(labelIndex, method.newLabel(labelIndex).location.index)
|
||||
|
||||
method.addInstructions(
|
||||
method.addInstructionsWithLabels(
|
||||
method.implementation!!.instructions.size,
|
||||
"""
|
||||
const/4 v0, 0x1
|
||||
if-eqz v0, :test
|
||||
return-void
|
||||
""", listOf(
|
||||
ExternalLabel("test",method.instruction(1))
|
||||
)
|
||||
""",
|
||||
ExternalLabel("test", method.getInstruction(1))
|
||||
)
|
||||
|
||||
val insn = method.instruction(insnIndex) as BuilderInstruction21t
|
||||
val insn = method.getInstruction<BuilderInstruction21t>(insnIndex)
|
||||
assertTrue(insn.target.isPlaced, "Label was not placed")
|
||||
assertEquals(labelIndex, insn.target.location.index)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user