mirror of
https://github.com/ReVanced/revanced-patcher.git
synced 2026-01-20 01:43:56 +00:00
Compare commits
121 Commits
v6.2.0
...
v11.0.2-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e681ed381 | ||
|
|
6e1b6479b6 | ||
|
|
f3c9e28a62 | ||
|
|
d5d6f85084 | ||
|
|
b8151ebccb | ||
|
|
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 |
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
|
||||||
31
.github/workflows/release.yml
vendored
31
.github/workflows/release.yml
vendored
@@ -1,4 +1,5 @@
|
|||||||
name: Release
|
name: Release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
@@ -9,6 +10,7 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- dev
|
- dev
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
name: Release
|
name: Release
|
||||||
@@ -17,24 +19,27 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
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
|
fetch-depth: 0
|
||||||
- name: Setup JDK
|
- name: Cache
|
||||||
uses: actions/setup-java@v3
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
java-version: '17'
|
path: |
|
||||||
distribution: 'temurin'
|
${{ runner.home }}/.gradle/caches
|
||||||
cache: gradle
|
${{ runner.home }}/.gradle/wrapper
|
||||||
- name: Setup Node.js
|
.gradle
|
||||||
uses: actions/setup-node@v3
|
build
|
||||||
with:
|
node_modules
|
||||||
node-version: "lts/*"
|
key: ${{ runner.os }}-gradle-npm-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', 'package-lock.json') }}
|
||||||
- name: Build with Gradle
|
- name: Build with Gradle
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: ./gradlew build
|
run: ./gradlew clean --no-daemon
|
||||||
- name: Setup semantic-release
|
- 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
|
- name: Release
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }}
|
||||||
run: npx semantic-release
|
run: npm exec semantic-release
|
||||||
|
|||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -74,6 +74,7 @@ cmake-build-*/
|
|||||||
|
|
||||||
# IntelliJ
|
# IntelliJ
|
||||||
out/
|
out/
|
||||||
|
.idea/
|
||||||
|
|
||||||
# mpeltonen/sbt-idea plugin
|
# mpeltonen/sbt-idea plugin
|
||||||
.idea_modules/
|
.idea_modules/
|
||||||
@@ -115,3 +116,9 @@ gradle-app.setting
|
|||||||
|
|
||||||
# Avoid ignoring test resources
|
# Avoid ignoring test resources
|
||||||
!src/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": [
|
"plugins": [
|
||||||
["@semantic-release/commit-analyzer", {
|
"@semantic-release/commit-analyzer",
|
||||||
"releaseRules": [
|
|
||||||
{"type": "build", "release": "patch"}
|
|
||||||
]
|
|
||||||
}],
|
|
||||||
"@semantic-release/release-notes-generator",
|
"@semantic-release/release-notes-generator",
|
||||||
"@semantic-release/changelog",
|
"@semantic-release/changelog",
|
||||||
"gradle-semantic-release-plugin",
|
"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
|
||||||
|
}
|
||||||
|
]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
308
CHANGELOG.md
308
CHANGELOG.md
@@ -1,3 +1,311 @@
|
|||||||
|
## [11.0.2-dev.2](https://github.com/revanced/revanced-patcher/compare/v11.0.2-dev.1...v11.0.2-dev.2) (2023-06-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* use `versionCode` if `versionName` is unavailable ([6e1b647](https://github.com/revanced/revanced-patcher/commit/6e1b6479b677657c226693e9cc6b63f4ef2ee060))
|
||||||
|
|
||||||
|
## [11.0.2-dev.1](https://github.com/revanced/revanced-patcher/compare/v11.0.1...v11.0.2-dev.1) (2023-06-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* catch exceptions from closing patches ([d5d6f85](https://github.com/revanced/revanced-patcher/commit/d5d6f85084c03ed9c776632823ca12394a716167))
|
||||||
|
* only close succeeded patches ([b8151eb](https://github.com/revanced/revanced-patcher/commit/b8151ebccb5b27dd9e06fa63235cf9baeef1c0ee))
|
||||||
|
|
||||||
|
## [11.0.1](https://github.com/revanced/revanced-patcher/compare/v11.0.0...v11.0.1) (2023-06-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* revert using `OutputStream.nullOutputStream` ([f02a426](https://github.com/revanced/revanced-patcher/commit/f02a42610b7698fc8cc6bc5183c9ccba2ed96cda))
|
||||||
|
|
||||||
|
## [11.0.1-dev.1](https://github.com/revanced/revanced-patcher/compare/v11.0.0...v11.0.1-dev.1) (2023-06-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* revert using `OutputStream.nullOutputStream` ([f02a426](https://github.com/revanced/revanced-patcher/commit/f02a42610b7698fc8cc6bc5183c9ccba2ed96cda))
|
||||||
|
|
||||||
|
# [11.0.0](https://github.com/revanced/revanced-patcher/compare/v10.0.0...v11.0.0) (2023-06-10)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add imports to fix failing tests ([43d6868](https://github.com/revanced/revanced-patcher/commit/43d6868d1f59922f9812f3e4a2a890f3b331def6))
|
||||||
|
|
||||||
|
|
||||||
|
* refactor!: move extension functions to their corresponding classes ([a12fe7d](https://github.com/revanced/revanced-patcher/commit/a12fe7dd9e976c38a0a82fe35e6650f58f815de4))
|
||||||
|
* refactor!: use proper extension function names ([efdd01a](https://github.com/revanced/revanced-patcher/commit/efdd01a9886b6f06af213731824621964367b2a3))
|
||||||
|
* fix!: implement extension functions consistently ([aacf900](https://github.com/revanced/revanced-patcher/commit/aacf9007647b1cc11bc40053625802573efda6ef))
|
||||||
|
|
||||||
|
|
||||||
|
### BREAKING CHANGES
|
||||||
|
|
||||||
|
* This changes the import paths for extension functions.
|
||||||
|
* This changes the names of extension functions
|
||||||
|
* This changes the name of functions
|
||||||
|
|
||||||
|
# [11.0.0-dev.2](https://github.com/revanced/revanced-patcher/compare/v11.0.0-dev.1...v11.0.0-dev.2) (2023-06-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add imports to fix failing tests ([43d6868](https://github.com/revanced/revanced-patcher/commit/43d6868d1f59922f9812f3e4a2a890f3b331def6))
|
||||||
|
|
||||||
|
# [11.0.0-dev.1](https://github.com/revanced/revanced-patcher/compare/v10.0.0...v11.0.0-dev.1) (2023-06-07)
|
||||||
|
|
||||||
|
|
||||||
|
* refactor!: move extension functions to their corresponding classes ([a12fe7d](https://github.com/revanced/revanced-patcher/commit/a12fe7dd9e976c38a0a82fe35e6650f58f815de4))
|
||||||
|
* refactor!: use proper extension function names ([efdd01a](https://github.com/revanced/revanced-patcher/commit/efdd01a9886b6f06af213731824621964367b2a3))
|
||||||
|
* fix!: implement extension functions consistently ([aacf900](https://github.com/revanced/revanced-patcher/commit/aacf9007647b1cc11bc40053625802573efda6ef))
|
||||||
|
|
||||||
|
|
||||||
|
### BREAKING CHANGES
|
||||||
|
|
||||||
|
* This changes the import paths for extension functions.
|
||||||
|
* This changes the names of extension functions
|
||||||
|
* This changes the name of functions
|
||||||
|
|
||||||
|
# [10.0.0](https://github.com/revanced/revanced-patcher/compare/v9.0.0...v10.0.0) (2023-06-07)
|
||||||
|
|
||||||
|
|
||||||
|
* fix!: check for two methods parameters orders (#183) ([b6d6a75](https://github.com/revanced/revanced-patcher/commit/b6d6a7591ba1c0b48ffdef52352709564da8d9be)), closes [#183](https://github.com/revanced/revanced-patcher/issues/183)
|
||||||
|
|
||||||
|
|
||||||
|
### BREAKING CHANGES
|
||||||
|
|
||||||
|
* This requires changes to `MethodFingerprint`
|
||||||
|
|
||||||
|
# [10.0.0-dev.1](https://github.com/revanced/revanced-patcher/compare/v9.0.0...v10.0.0-dev.1) (2023-06-07)
|
||||||
|
|
||||||
|
|
||||||
|
* fix!: check for two methods parameters orders (#183) ([b6d6a75](https://github.com/revanced/revanced-patcher/commit/b6d6a7591ba1c0b48ffdef52352709564da8d9be)), closes [#183](https://github.com/revanced/revanced-patcher/issues/183)
|
||||||
|
|
||||||
|
|
||||||
|
### BREAKING CHANGES
|
||||||
|
|
||||||
|
* This requires changes to `MethodFingerprint`
|
||||||
|
|
||||||
|
# [9.0.0](https://github.com/revanced/revanced-patcher/compare/v8.0.0...v9.0.0) (2023-05-23)
|
||||||
|
|
||||||
|
|
||||||
|
* refactor!: rename parameter ([526a3d7](https://github.com/revanced/revanced-patcher/commit/526a3d7c359e2d95d26756da0f88d5ce975f5d9b))
|
||||||
|
|
||||||
|
|
||||||
|
### BREAKING CHANGES
|
||||||
|
|
||||||
|
* This changes named parameters.
|
||||||
|
|
||||||
|
# [9.0.0-dev.1](https://github.com/revanced/revanced-patcher/compare/v8.0.0...v9.0.0-dev.1) (2023-05-23)
|
||||||
|
|
||||||
|
|
||||||
|
* refactor!: rename parameter ([526a3d7](https://github.com/revanced/revanced-patcher/commit/526a3d7c359e2d95d26756da0f88d5ce975f5d9b))
|
||||||
|
|
||||||
|
|
||||||
|
### BREAKING CHANGES
|
||||||
|
|
||||||
|
* This changes named parameters.
|
||||||
|
|
||||||
|
# [8.0.0](https://github.com/revanced/revanced-patcher/compare/v7.1.1...v8.0.0) (2023-05-13)
|
||||||
|
|
||||||
|
|
||||||
|
* feat!: add `classDef` parameter to `MethodFingerprint` (#175) ([a205220](https://github.com/revanced/revanced-patcher/commit/a2052202b23037150df6aadc47f6e91efcd481cf)), closes [#175](https://github.com/revanced/revanced-patcher/issues/175)
|
||||||
|
|
||||||
|
|
||||||
|
### BREAKING CHANGES
|
||||||
|
|
||||||
|
* This changes the signature of the `customFingerprint` function.
|
||||||
|
|
||||||
|
# [8.0.0-dev.1](https://github.com/revanced/revanced-patcher/compare/v7.1.1...v8.0.0-dev.1) (2023-05-10)
|
||||||
|
|
||||||
|
|
||||||
|
* feat!: add `classDef` parameter to `MethodFingerprint` (#175) ([a205220](https://github.com/revanced/revanced-patcher/commit/a2052202b23037150df6aadc47f6e91efcd481cf)), closes [#175](https://github.com/revanced/revanced-patcher/issues/175)
|
||||||
|
|
||||||
|
|
||||||
|
### BREAKING CHANGES
|
||||||
|
|
||||||
|
* This changes the signature of the `customFingerprint` function.
|
||||||
|
|
||||||
|
## [7.1.1](https://github.com/revanced/revanced-patcher/compare/v7.1.0...v7.1.1) (2023-05-07)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* remove `count` instead of `count + 1` instructions with `removeInstructions` ([#167](https://github.com/revanced/revanced-patcher/issues/167)) ([98f8eed](https://github.com/revanced/revanced-patcher/commit/98f8eedecd72b0afe6a0f099a3641a1cc6be2698))
|
||||||
|
|
||||||
|
## [7.1.1-dev.1](https://github.com/revanced/revanced-patcher/compare/v7.1.0...v7.1.1-dev.1) (2023-05-07)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* remove `count` instead of `count + 1` instructions with `removeInstructions` ([#167](https://github.com/revanced/revanced-patcher/issues/167)) ([98f8eed](https://github.com/revanced/revanced-patcher/commit/98f8eedecd72b0afe6a0f099a3641a1cc6be2698))
|
||||||
|
|
||||||
|
# [7.1.0](https://github.com/revanced/revanced-patcher/compare/v7.0.0...v7.1.0) (2023-05-05)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add appreciation message for new contributors ([d674362](https://github.com/revanced/revanced-patcher/commit/d67436271ddca9ccfe008272c1ca82c6123ae7ee))
|
||||||
|
* add overload to get instruction as type ([49c173d](https://github.com/revanced/revanced-patcher/commit/49c173dc14137ddd198a611e9882dc71300831ee))
|
||||||
|
|
||||||
|
# [7.1.0-dev.2](https://github.com/revanced/revanced-patcher/compare/v7.1.0-dev.1...v7.1.0-dev.2) (2023-05-05)
|
||||||
|
|
||||||
|
|
||||||
|
### 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)
|
# [6.2.0](https://github.com/revanced/revanced-patcher/compare/v6.1.1...v6.2.0) (2022-12-02)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
# Patcher
|
# 💉 ReVanced Patcher
|
||||||
Patcher framework used in the ReVanced project.
|
|
||||||
|
ReVanced Patcher used to patch Android applications.
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm") version "1.7.0"
|
kotlin("jvm") version "1.8.10"
|
||||||
java
|
|
||||||
`maven-publish`
|
`maven-publish`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,12 +21,12 @@ repositories {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("xpp3:xpp3:1.1.4c")
|
implementation("xpp3:xpp3:1.1.4c")
|
||||||
implementation("org.smali:smali:2.5.2")
|
implementation("app.revanced:smali:2.5.3-a3836654")
|
||||||
implementation("app.revanced:multidexlib2:2.5.2.r2")
|
implementation("app.revanced:multidexlib2:2.5.3-a3836654")
|
||||||
implementation("org.apktool:apktool-lib:2.8.1-SNAPSHOT")
|
implementation("app.revanced:apktool-lib:2.7.0")
|
||||||
|
|
||||||
implementation(kotlin("reflect"))
|
implementation("org.jetbrains.kotlin:kotlin-reflect:1.8.20-RC")
|
||||||
testImplementation(kotlin("test"))
|
testImplementation("org.jetbrains.kotlin:kotlin-test:1.8.20-RC")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks {
|
tasks {
|
||||||
@@ -46,6 +45,10 @@ java {
|
|||||||
withSourcesJar()
|
withSourcesJar()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
jvmToolchain(11)
|
||||||
|
}
|
||||||
|
|
||||||
publishing {
|
publishing {
|
||||||
repositories {
|
repositories {
|
||||||
if (System.getenv("GITHUB_ACTOR") != null)
|
if (System.getenv("GITHUB_ACTOR") != null)
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
kotlin.code.style = official
|
kotlin.code.style = official
|
||||||
version = 6.2.0
|
version = 11.0.2-dev.2
|
||||||
|
|||||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
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
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
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,17 +1,12 @@
|
|||||||
package app.revanced.patcher
|
package app.revanced.patcher
|
||||||
|
|
||||||
import app.revanced.patcher.data.Context
|
import app.revanced.patcher.data.Context
|
||||||
import app.revanced.patcher.data.findIndexed
|
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.dependencies
|
import app.revanced.patcher.extensions.PatchExtensions.dependencies
|
||||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||||
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.fingerprint.method.impl.MethodFingerprint.Companion.resolve
|
||||||
import app.revanced.patcher.patch.*
|
import app.revanced.patcher.patch.*
|
||||||
import app.revanced.patcher.util.VersionReader
|
import app.revanced.patcher.util.VersionReader
|
||||||
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.Companion.toMutable
|
|
||||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
|
||||||
import brut.androlib.Androlib
|
import brut.androlib.Androlib
|
||||||
import brut.androlib.meta.UsesFramework
|
import brut.androlib.meta.UsesFramework
|
||||||
import brut.androlib.options.BuildOptions
|
import brut.androlib.options.BuildOptions
|
||||||
@@ -26,14 +21,14 @@ import lanchon.multidexlib2.BasicDexFileNamer
|
|||||||
import lanchon.multidexlib2.DexIO
|
import lanchon.multidexlib2.DexIO
|
||||||
import lanchon.multidexlib2.MultiDexIO
|
import lanchon.multidexlib2.MultiDexIO
|
||||||
import org.jf.dexlib2.Opcodes
|
import org.jf.dexlib2.Opcodes
|
||||||
import org.jf.dexlib2.iface.ClassDef
|
|
||||||
import org.jf.dexlib2.iface.DexFile
|
import org.jf.dexlib2.iface.DexFile
|
||||||
import org.jf.dexlib2.util.MethodUtil
|
|
||||||
import org.jf.dexlib2.writer.io.MemoryDataStore
|
import org.jf.dexlib2.writer.io.MemoryDataStore
|
||||||
|
import java.io.Closeable
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.OutputStream
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
|
|
||||||
private val NAMER = BasicDexFileNamer()
|
internal val NAMER = BasicDexFileNamer()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ReVanced Patcher.
|
* The ReVanced Patcher.
|
||||||
@@ -43,6 +38,7 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
private val logger = options.logger
|
private val logger = options.logger
|
||||||
private val opcodes: Opcodes
|
private val opcodes: Opcodes
|
||||||
private var resourceDecodingMode = ResourceDecodingMode.MANIFEST_ONLY
|
private var resourceDecodingMode = ResourceDecodingMode.MANIFEST_ONLY
|
||||||
|
private var mergeIntegrations = false
|
||||||
val context: PatcherContext
|
val context: PatcherContext
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -69,87 +65,19 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add additional dex file container to the patcher.
|
* Add integrations to be merged by the patcher.
|
||||||
* @param files The dex file containers to add to the patcher.
|
* The integrations will only be merged, if necessary.
|
||||||
* @param process The callback for [files] which are being added.
|
*
|
||||||
|
* @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(
|
fun addIntegrations(
|
||||||
files: List<File>,
|
integrations: List<File>,
|
||||||
process: (File) -> Unit
|
callback: (File) -> Unit
|
||||||
) {
|
) {
|
||||||
with(context.bytecodeContext.classes) {
|
context.integrations.apply integrations@{
|
||||||
for (file in files) {
|
add(integrations)
|
||||||
process(file)
|
this@integrations.callback = callback
|
||||||
for (classDef in MultiDexIO.readDexFile(true, file, 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)
|
|
||||||
} else {
|
|
||||||
val (existingClass, existingClassIndex) = result
|
|
||||||
|
|
||||||
logger.trace("Type $type exists. Adding missing methods and fields.")
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add missing fields and methods from [from].
|
|
||||||
*
|
|
||||||
* @param from The class to add methods and fields from.
|
|
||||||
*/
|
|
||||||
fun ClassDef.addMissingFrom(from: ClassDef) {
|
|
||||||
var changed = false
|
|
||||||
fun <T> ClassDef.transformClass(transform: (MutableClass) -> T): T {
|
|
||||||
fun toMutableClass() =
|
|
||||||
if (this@transformClass is MutableClass) this else this.toMutable()
|
|
||||||
return transform(toMutableClass())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ClassDef.addMissingMethods(): ClassDef {
|
|
||||||
fun getMissingMethods() = from.methods.filterNot {
|
|
||||||
this@addMissingMethods.methods.any { original ->
|
|
||||||
MethodUtil.methodSignaturesMatch(original, it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return getMissingMethods()
|
|
||||||
.apply {
|
|
||||||
if (isEmpty()) return@addMissingMethods this@addMissingMethods else changed =
|
|
||||||
true
|
|
||||||
}
|
|
||||||
.map { it.toMutable() }
|
|
||||||
.let { missingMethods ->
|
|
||||||
this@addMissingMethods.transformClass { classDef ->
|
|
||||||
classDef.apply { methods.addAll(missingMethods) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ClassDef.addMissingFields(): ClassDef {
|
|
||||||
fun getMissingFields() = from.fields.filterNot {
|
|
||||||
this@addMissingFields.fields.any { original -> original.name == it.name }
|
|
||||||
}
|
|
||||||
|
|
||||||
return getMissingFields()
|
|
||||||
.apply {
|
|
||||||
if (isEmpty()) return@addMissingFields this@addMissingFields else changed = true
|
|
||||||
}
|
|
||||||
.map { it.toMutable() }
|
|
||||||
.let { missingFields ->
|
|
||||||
this@addMissingFields.transformClass { classDef ->
|
|
||||||
classDef.apply { fields.addAll(missingFields) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
classes[existingClassIndex] = addMissingMethods().addMissingFields()
|
|
||||||
.apply { if (!changed) return }
|
|
||||||
}
|
|
||||||
|
|
||||||
existingClass.addMissingFrom(classDef)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,18 +166,29 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
*/
|
*/
|
||||||
fun addPatches(patches: Iterable<Class<out Patch<Context>>>) {
|
fun addPatches(patches: Iterable<Class<out Patch<Context>>>) {
|
||||||
/**
|
/**
|
||||||
* Fill the cache with the instances of the [Patch]es for later use.
|
* Returns true if at least one patches or its dependencies matches the given predicate.
|
||||||
* Note: Dependencies of the [Patch] will be cached as well.
|
|
||||||
*/
|
*/
|
||||||
fun Class<out Patch<Context>>.isResource() {
|
fun Class<out Patch<Context>>.anyRecursively(predicate: (Class<out Patch<Context>>) -> Boolean): Boolean =
|
||||||
this.also {
|
predicate(this) || dependencies?.any { it.java.anyRecursively(predicate) } == true
|
||||||
if (!ResourcePatch::class.java.isAssignableFrom(it)) return@also
|
|
||||||
// set the mode to decode all resources before running the patches
|
|
||||||
|
// Determine if resource patching is required.
|
||||||
|
for (patch in patches) {
|
||||||
|
if (patch.anyRecursively { ResourcePatch::class.java.isAssignableFrom(it) }) {
|
||||||
resourceDecodingMode = ResourceDecodingMode.FULL
|
resourceDecodingMode = ResourceDecodingMode.FULL
|
||||||
}.dependencies?.forEach { it.java.isResource() }
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context.patches.addAll(patches.onEach(Class<out Patch<Context>>::isResource))
|
// Determine if merging integrations is required.
|
||||||
|
for (patch in patches) {
|
||||||
|
if (patch.anyRecursively { it.requiresIntegrations }) {
|
||||||
|
mergeIntegrations = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.patches.addAll(patches)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -309,7 +248,13 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
XmlPullStreamDecoder(
|
XmlPullStreamDecoder(
|
||||||
axmlParser, AndrolibResources().resXmlSerializer
|
axmlParser, AndrolibResources().resXmlSerializer
|
||||||
).decodeManifest(
|
).decodeManifest(
|
||||||
extInputFile.directory.getFileInput("AndroidManifest.xml"), nullOutputStream
|
extInputFile.directory.getFileInput("AndroidManifest.xml"),
|
||||||
|
// Older Android versions do not support OutputStream.nullOutputStream()
|
||||||
|
object : OutputStream() {
|
||||||
|
override fun write(b: Int) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -317,7 +262,7 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
// read of the resourceTable which is created by reading the manifest file
|
// read of the resourceTable which is created by reading the manifest file
|
||||||
context.packageMetadata.let { metadata ->
|
context.packageMetadata.let { metadata ->
|
||||||
metadata.packageName = resourceTable.currentResPackage.name
|
metadata.packageName = resourceTable.currentResPackage.name
|
||||||
metadata.packageVersion = resourceTable.versionInfo.versionName
|
metadata.packageVersion = resourceTable.versionInfo.versionName ?: resourceTable.versionInfo.versionCode
|
||||||
metadata.metaInfo.versionInfo = resourceTable.versionInfo
|
metadata.metaInfo.versionInfo = resourceTable.versionInfo
|
||||||
metadata.metaInfo.sdkInfo = resourceTable.sdkInfo
|
metadata.metaInfo.sdkInfo = resourceTable.sdkInfo
|
||||||
}
|
}
|
||||||
@@ -363,9 +308,10 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
val result = executePatch(dependency, executedPatches)
|
val result = executePatch(dependency, executedPatches)
|
||||||
if (result.isSuccess()) return@forEach
|
if (result.isSuccess()) return@forEach
|
||||||
|
|
||||||
val error = result.error()!!
|
return PatchResultError(
|
||||||
val errorMessage = error.cause ?: error.message
|
"'$patchName' depends on '${dependency.patchName}' but the following error was raised: " +
|
||||||
return PatchResultError("'$patchName' depends on '${dependency.patchName}' but the following error was raised: $errorMessage")
|
result.error()!!.let { it.cause?.stackTraceToString() ?: it.message }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val isResourcePatch = ResourcePatch::class.java.isAssignableFrom(patchClass)
|
val isResourcePatch = ResourcePatch::class.java.isAssignableFrom(patchClass)
|
||||||
@@ -397,6 +343,8 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return sequence {
|
return sequence {
|
||||||
|
if (mergeIntegrations) context.integrations.merge(logger)
|
||||||
|
|
||||||
// prevent from decoding the manifest twice if it is not needed
|
// prevent from decoding the manifest twice if it is not needed
|
||||||
if (resourceDecodingMode == ResourceDecodingMode.FULL) decodeResources(ResourceDecodingMode.FULL)
|
if (resourceDecodingMode == ResourceDecodingMode.FULL) decodeResources(ResourceDecodingMode.FULL)
|
||||||
|
|
||||||
@@ -404,24 +352,42 @@ class Patcher(private val options: PatcherOptions) {
|
|||||||
|
|
||||||
val executedPatches = LinkedHashMap<String, ExecutedPatch>() // first is name
|
val executedPatches = LinkedHashMap<String, ExecutedPatch>() // first is name
|
||||||
|
|
||||||
try {
|
|
||||||
context.patches.forEach { patch ->
|
|
||||||
val patchResult = executePatch(patch, executedPatches)
|
|
||||||
|
|
||||||
val result = if (patchResult.isSuccess()) {
|
context.patches.forEach { patch ->
|
||||||
Result.success(patchResult.success()!!)
|
val patchResult = executePatch(patch, executedPatches)
|
||||||
} else {
|
|
||||||
Result.failure(patchResult.error()!!)
|
|
||||||
}
|
|
||||||
|
|
||||||
yield(patch.patchName to result)
|
val result = if (patchResult.isSuccess()) {
|
||||||
if (stopOnError && patchResult.isError()) return@sequence
|
Result.success(patchResult.success()!!)
|
||||||
}
|
} else {
|
||||||
} finally {
|
Result.failure(patchResult.error()!!)
|
||||||
executedPatches.values.reversed().forEach { (patch, _) ->
|
|
||||||
patch.close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: This prints before the patch really finishes in case it is a Closeable
|
||||||
|
// because the Closeable is closed after all patches are executed.
|
||||||
|
yield(patch.patchName to result)
|
||||||
|
|
||||||
|
if (stopOnError && patchResult.isError()) return@sequence
|
||||||
}
|
}
|
||||||
|
|
||||||
|
executedPatches.values
|
||||||
|
.filter(ExecutedPatch::success)
|
||||||
|
.map(ExecutedPatch::patchInstance)
|
||||||
|
.filterIsInstance(Closeable::class.java)
|
||||||
|
.asReversed().forEach {
|
||||||
|
try {
|
||||||
|
it.close()
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
val patchName = (it as Patch<Context>).javaClass.patchName
|
||||||
|
|
||||||
|
logger.error("Failed to close '$patchName': ${exception.stackTraceToString()}")
|
||||||
|
|
||||||
|
yield(patchName to Result.failure(exception))
|
||||||
|
|
||||||
|
// This is not failsafe. If a patch throws an exception while closing,
|
||||||
|
// the other patches that depend on it may fail.
|
||||||
|
if (stopOnError) return@sequence
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
package app.revanced.patcher
|
package app.revanced.patcher
|
||||||
|
|
||||||
import app.revanced.patcher.data.BytecodeContext
|
import app.revanced.patcher.data.*
|
||||||
import app.revanced.patcher.data.Context
|
import app.revanced.patcher.logging.Logger
|
||||||
import app.revanced.patcher.data.PackageMetadata
|
|
||||||
import app.revanced.patcher.data.ResourceContext
|
|
||||||
import app.revanced.patcher.patch.Patch
|
import app.revanced.patcher.patch.Patch
|
||||||
|
import app.revanced.patcher.util.ClassMerger.merge
|
||||||
import org.jf.dexlib2.iface.ClassDef
|
import org.jf.dexlib2.iface.ClassDef
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@@ -14,6 +13,52 @@ data class PatcherContext(
|
|||||||
) {
|
) {
|
||||||
val packageMetadata = PackageMetadata()
|
val packageMetadata = PackageMetadata()
|
||||||
internal val patches = mutableListOf<Class<out Patch<Context>>>()
|
internal val patches = mutableListOf<Class<out Patch<Context>>>()
|
||||||
|
internal val integrations = Integrations(this)
|
||||||
internal val bytecodeContext = BytecodeContext(classes)
|
internal val bytecodeContext = BytecodeContext(classes)
|
||||||
internal val resourceContext = ResourceContext(resourceCacheDirectory)
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -15,7 +15,7 @@ import java.io.File
|
|||||||
data class PatcherOptions(
|
data class PatcherOptions(
|
||||||
internal val inputFile: File,
|
internal val inputFile: File,
|
||||||
internal val resourceCacheDirectory: String,
|
internal val resourceCacheDirectory: String,
|
||||||
internal val aaptPath: String = "",
|
internal val aaptPath: String? = null,
|
||||||
internal val frameworkFolderLocation: String? = null,
|
internal val frameworkFolderLocation: String? = null,
|
||||||
internal val logger: Logger = NopLogger
|
internal val logger: Logger = NopLogger
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ import app.revanced.patcher.patch.Patch
|
|||||||
* @param compatiblePackages A list of packages a [Patch] is compatible with.
|
* @param compatiblePackages A list of packages a [Patch] is compatible with.
|
||||||
*/
|
*/
|
||||||
@Target(AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.CLASS)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@MustBeDocumented
|
|
||||||
annotation class Compatibility(
|
annotation class Compatibility(
|
||||||
val compatiblePackages: Array<Package>,
|
val compatiblePackages: Array<Package>,
|
||||||
)
|
)
|
||||||
@@ -19,8 +17,6 @@ annotation class Compatibility(
|
|||||||
* @param versions The versions of the package the [Patch] is compatible with.
|
* @param versions The versions of the package the [Patch] is compatible with.
|
||||||
*/
|
*/
|
||||||
@Target()
|
@Target()
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@MustBeDocumented
|
|
||||||
annotation class Package(
|
annotation class Package(
|
||||||
val name: String,
|
val name: String,
|
||||||
val versions: Array<String> = [],
|
val versions: Array<String> = [],
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ import app.revanced.patcher.patch.Patch
|
|||||||
* @param name A suggestive name for the [Patch].
|
* @param name A suggestive name for the [Patch].
|
||||||
*/
|
*/
|
||||||
@Target(AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.CLASS)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@MustBeDocumented
|
|
||||||
annotation class Name(
|
annotation class Name(
|
||||||
val name: String,
|
val name: String,
|
||||||
)
|
)
|
||||||
@@ -18,8 +16,6 @@ annotation class Name(
|
|||||||
* @param description A description for the [Patch].
|
* @param description A description for the [Patch].
|
||||||
*/
|
*/
|
||||||
@Target(AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.CLASS)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@MustBeDocumented
|
|
||||||
annotation class Description(
|
annotation class Description(
|
||||||
val description: String,
|
val description: String,
|
||||||
)
|
)
|
||||||
@@ -30,8 +26,6 @@ annotation class Description(
|
|||||||
* @param version The version of a [Patch].
|
* @param version The version of a [Patch].
|
||||||
*/
|
*/
|
||||||
@Target(AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.CLASS)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@MustBeDocumented
|
|
||||||
annotation class Version(
|
annotation class Version(
|
||||||
val version: String,
|
val version: String,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -54,17 +54,6 @@ class BytecodeContext internal constructor(classes: MutableList<ClassDef>) : Con
|
|||||||
}
|
}
|
||||||
return proxy
|
return proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
private companion object {
|
|
||||||
inline fun <reified T> Iterable<T>.find(predicate: (T) -> Boolean): T? {
|
|
||||||
for (element in this) {
|
|
||||||
if (predicate(element)) {
|
|
||||||
return element
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,79 +1,33 @@
|
|||||||
package app.revanced.patcher.extensions
|
package app.revanced.patcher.extensions
|
||||||
|
|
||||||
import app.revanced.patcher.annotation.*
|
|
||||||
import app.revanced.patcher.data.Context
|
|
||||||
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
|
|
||||||
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 app.revanced.patcher.patch.annotations.DependsOn
|
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import kotlin.reflect.KVisibility
|
|
||||||
import kotlin.reflect.full.companionObject
|
|
||||||
import kotlin.reflect.full.companionObjectInstance
|
|
||||||
|
|
||||||
/**
|
internal object AnnotationExtensions {
|
||||||
* Recursively find a given annotation on a class.
|
/**
|
||||||
* @param targetAnnotation The annotation to find.
|
* Recursively find a given annotation on a class.
|
||||||
* @return The annotation.
|
*
|
||||||
*/
|
* @param targetAnnotation The annotation to find.
|
||||||
private fun <T : Annotation> Class<*>.findAnnotationRecursively(targetAnnotation: KClass<T>) =
|
* @return The annotation.
|
||||||
this.findAnnotationRecursively(targetAnnotation.java, mutableSetOf())
|
*/
|
||||||
|
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(
|
for (annotation in this.annotations) {
|
||||||
targetAnnotation: Class<T>, traversed: MutableSet<Annotation>
|
if (traversed.contains(annotation)) continue
|
||||||
): T? {
|
traversed.add(annotation)
|
||||||
val found = this.annotations.firstOrNull { it.annotationClass.java.name == targetAnnotation.name }
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST") if (found != null) return found as T
|
return (annotation.annotationClass.java.findAnnotationRecursively(targetAnnotation, traversed))
|
||||||
|
?: continue
|
||||||
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<out Patch<Context>>.patchName: String
|
|
||||||
get() = findAnnotationRecursively(Name::class)?.name ?: this.simpleName
|
|
||||||
|
|
||||||
val Class<out Patch<Context>>.version
|
|
||||||
get() = findAnnotationRecursively(Version::class)?.version
|
|
||||||
|
|
||||||
val Class<out Patch<Context>>.include
|
|
||||||
get() = findAnnotationRecursively(app.revanced.patcher.patch.annotations.Patch::class)!!.include
|
|
||||||
|
|
||||||
val Class<out Patch<Context>>.description
|
|
||||||
get() = findAnnotationRecursively(Description::class)?.description
|
|
||||||
|
|
||||||
val Class<out Patch<Context>>.dependencies
|
|
||||||
get() = findAnnotationRecursively(DependsOn::class)?.dependencies
|
|
||||||
|
|
||||||
val Class<out Patch<Context>>.compatiblePackages
|
|
||||||
get() = findAnnotationRecursively(Compatibility::class)?.compatiblePackages
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
object MethodFingerprintExtensions {
|
return this.findAnnotationRecursively(targetAnnotation.java, mutableSetOf())
|
||||||
val MethodFingerprint.name: String
|
}
|
||||||
get() = this.javaClass.simpleName
|
|
||||||
|
|
||||||
val MethodFingerprint.fuzzyPatternScanMethod
|
|
||||||
get() = javaClass.findAnnotationRecursively(FuzzyPatternScanMethod::class)
|
|
||||||
|
|
||||||
val MethodFingerprint.fuzzyScanThreshold
|
|
||||||
get() = fuzzyPatternScanMethod?.threshold ?: 0
|
|
||||||
}
|
}
|
||||||
@@ -1,247 +1,33 @@
|
|||||||
package app.revanced.patcher.extensions
|
package app.revanced.patcher.extensions
|
||||||
|
|
||||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
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.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.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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clones the method.
|
* Create a label for the instruction at given index.
|
||||||
* @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.
|
|
||||||
* @param index The index to create the label for the instruction at.
|
* @param index The index to create the label for the instruction at.
|
||||||
* @return The label.
|
* @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.
|
* Perform a bitwise OR operation between two [AccessFlags].
|
||||||
* @param index The index to get the instruction at.
|
*
|
||||||
* @return The instruction.
|
* @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.
|
* Perform a bitwise OR operation between an [AccessFlags] and an [Int].
|
||||||
* @param index The index to insert the instructions at.
|
*
|
||||||
* @param smali The smali instructions to add.
|
* @param other The [Int] to perform the operation with.
|
||||||
* @param externalLabels A list of [ExternalLabel] representing a list of labels for instructions which are not in the method to compile.
|
|
||||||
*/
|
*/
|
||||||
|
infix fun Int.or(other: AccessFlags) = this or other.value
|
||||||
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.subList(0, compiledInstructions.size - externalLabels.size))
|
|
||||||
|
|
||||||
val methodInstructions = methodImplementation.instructions
|
|
||||||
methodInstructions.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() {
|
|
||||||
// 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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add smali instructions to the end of the method.
|
* Perform a bitwise OR operation between an [Int] and an [AccessFlags].
|
||||||
* @param instructions The smali instructions to add.
|
*
|
||||||
|
* @param other The [AccessFlags] to perform the operation with.
|
||||||
*/
|
*/
|
||||||
fun MutableMethod.addInstructions(instructions: String, labels: List<ExternalLabel> = emptyList()) =
|
infix fun AccessFlags.or(other: Int) = value or other
|
||||||
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
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,6 @@ import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
|||||||
* @param threshold if [threshold] or more of the opcodes do not match, skip.
|
* @param threshold if [threshold] or more of the opcodes do not match, skip.
|
||||||
*/
|
*/
|
||||||
@Target(AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.CLASS)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
annotation class FuzzyPatternScanMethod(
|
annotation class FuzzyPatternScanMethod(
|
||||||
val threshold: Int = 1
|
val threshold: Int = 1
|
||||||
)
|
)
|
||||||
@@ -2,8 +2,6 @@ package app.revanced.patcher.fingerprint.method.impl
|
|||||||
|
|
||||||
import app.revanced.patcher.data.BytecodeContext
|
import app.revanced.patcher.data.BytecodeContext
|
||||||
import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyPatternScanMethod
|
import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyPatternScanMethod
|
||||||
import app.revanced.patcher.extensions.MethodFingerprintExtensions.fuzzyScanThreshold
|
|
||||||
import app.revanced.patcher.extensions.parametersEqual
|
|
||||||
import app.revanced.patcher.fingerprint.Fingerprint
|
import app.revanced.patcher.fingerprint.Fingerprint
|
||||||
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
|
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
|
||||||
import app.revanced.patcher.util.proxy.ClassProxy
|
import app.revanced.patcher.util.proxy.ClassProxy
|
||||||
@@ -15,10 +13,13 @@ import org.jf.dexlib2.iface.instruction.ReferenceInstruction
|
|||||||
import org.jf.dexlib2.iface.reference.StringReference
|
import org.jf.dexlib2.iface.reference.StringReference
|
||||||
import org.jf.dexlib2.util.MethodUtil
|
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.
|
* Represents the [MethodFingerprint] for a method.
|
||||||
* @param returnType The return type of the 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 parameters The parameters of the method.
|
||||||
* @param opcodes The list of opcodes of the method.
|
* @param opcodes The list of opcodes of the method.
|
||||||
* @param strings A list of strings which a method contains.
|
* @param strings A list of strings which a method contains.
|
||||||
@@ -27,14 +28,14 @@ import org.jf.dexlib2.util.MethodUtil
|
|||||||
*/
|
*/
|
||||||
abstract class MethodFingerprint(
|
abstract class MethodFingerprint(
|
||||||
internal val returnType: String? = null,
|
internal val returnType: String? = null,
|
||||||
internal val access: Int? = null,
|
internal val accessFlags: Int? = null,
|
||||||
internal val parameters: Iterable<String>? = null,
|
internal val parameters: Iterable<String>? = null,
|
||||||
internal val opcodes: Iterable<Opcode?>? = null,
|
internal val opcodes: Iterable<Opcode?>? = null,
|
||||||
internal val strings: Iterable<String>? = null,
|
internal val strings: Iterable<String>? = null,
|
||||||
internal val customFingerprint: ((methodDef: Method) -> Boolean)? = null
|
internal val customFingerprint: ((methodDef: Method, classDef: ClassDef) -> Boolean)? = null
|
||||||
) : Fingerprint {
|
) : Fingerprint {
|
||||||
/**
|
/**
|
||||||
* The result of the [MethodFingerprint] the [Method].
|
* The result of the [MethodFingerprint].
|
||||||
*/
|
*/
|
||||||
var result: MethodFingerprintResult? = null
|
var result: MethodFingerprintResult? = null
|
||||||
|
|
||||||
@@ -83,10 +84,21 @@ abstract class MethodFingerprint(
|
|||||||
if (methodFingerprint.returnType != null && !method.returnType.startsWith(methodFingerprint.returnType))
|
if (methodFingerprint.returnType != null && !method.returnType.startsWith(methodFingerprint.returnType))
|
||||||
return false
|
return false
|
||||||
|
|
||||||
if (methodFingerprint.access != null && methodFingerprint.access != method.accessFlags)
|
if (methodFingerprint.accessFlags != null && methodFingerprint.accessFlags != method.accessFlags)
|
||||||
return false
|
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(
|
if (methodFingerprint.parameters != null && !parametersEqual(
|
||||||
methodFingerprint.parameters, // TODO: parseParameters()
|
methodFingerprint.parameters, // TODO: parseParameters()
|
||||||
method.parameterTypes
|
method.parameterTypes
|
||||||
@@ -94,7 +106,7 @@ abstract class MethodFingerprint(
|
|||||||
) return false
|
) return false
|
||||||
|
|
||||||
@Suppress("UNNECESSARY_NOT_NULL_ASSERTION")
|
@Suppress("UNNECESSARY_NOT_NULL_ASSERTION")
|
||||||
if (methodFingerprint.customFingerprint != null && !methodFingerprint.customFingerprint!!(method))
|
if (methodFingerprint.customFingerprint != null && !methodFingerprint.customFingerprint!!(method, forClass))
|
||||||
return false
|
return false
|
||||||
|
|
||||||
val stringsScanResult: StringsScanResult? =
|
val stringsScanResult: StringsScanResult? =
|
||||||
@@ -106,10 +118,13 @@ abstract class MethodFingerprint(
|
|||||||
val stringsList = methodFingerprint.strings.toMutableList()
|
val stringsList = methodFingerprint.strings.toMutableList()
|
||||||
|
|
||||||
implementation.instructions.forEachIndexed { instructionIndex, instruction ->
|
implementation.instructions.forEachIndexed { instructionIndex, instruction ->
|
||||||
if (instruction.opcode.ordinal != Opcode.CONST_STRING.ordinal) return@forEachIndexed
|
if (
|
||||||
|
instruction.opcode != Opcode.CONST_STRING &&
|
||||||
|
instruction.opcode != Opcode.CONST_STRING_JUMBO
|
||||||
|
) return@forEachIndexed
|
||||||
|
|
||||||
val string = ((instruction as ReferenceInstruction).reference as StringReference).string
|
val string = ((instruction as ReferenceInstruction).reference as StringReference).string
|
||||||
val index = stringsList.indexOfFirst { it == string }
|
val index = stringsList.indexOfFirst(string::contains)
|
||||||
if (index == -1) return@forEachIndexed
|
if (index == -1) return@forEachIndexed
|
||||||
|
|
||||||
add(
|
add(
|
||||||
@@ -149,7 +164,7 @@ abstract class MethodFingerprint(
|
|||||||
fingerprint: MethodFingerprint
|
fingerprint: MethodFingerprint
|
||||||
): MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult? {
|
): MethodFingerprintResult.MethodFingerprintScanResult.PatternScanResult? {
|
||||||
val instructions = this.implementation!!.instructions
|
val instructions = this.implementation!!.instructions
|
||||||
val fingerprintFuzzyPatternScanThreshold = fingerprint.fuzzyScanThreshold
|
val fingerprintFuzzyPatternScanThreshold = fingerprint.fuzzyPatternScanMethod?.threshold ?: 0
|
||||||
|
|
||||||
val pattern = fingerprint.opcodes!!
|
val pattern = fingerprint.opcodes!!
|
||||||
val instructionLength = instructions.count()
|
val instructionLength = instructions.count()
|
||||||
@@ -213,9 +228,6 @@ abstract class MethodFingerprint(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private typealias StringMatch = MethodFingerprintResult.MethodFingerprintScanResult.StringsScanResult.StringMatch
|
|
||||||
private typealias StringsScanResult = MethodFingerprintResult.MethodFingerprintScanResult.StringsScanResult
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the result of a [MethodFingerprintResult].
|
* Represents the result of a [MethodFingerprintResult].
|
||||||
*
|
*
|
||||||
@@ -230,6 +242,26 @@ data class MethodFingerprintResult(
|
|||||||
val scanResult: MethodFingerprintScanResult,
|
val scanResult: MethodFingerprintScanResult,
|
||||||
internal val context: BytecodeContext
|
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].
|
* The result of scanning on the [MethodFingerprint].
|
||||||
@@ -244,7 +276,7 @@ data class MethodFingerprintResult(
|
|||||||
* The result of scanning strings on the [MethodFingerprint].
|
* The result of scanning strings on the [MethodFingerprint].
|
||||||
* @param matches The list of strings that were matched.
|
* @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.
|
* Represents a match for a string at an index.
|
||||||
* @param string The string that was matched.
|
* @param string The string that was matched.
|
||||||
@@ -279,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 { 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,7 @@ import java.io.Closeable
|
|||||||
* If it implements [Closeable], it will be closed after all patches have been executed.
|
* If it implements [Closeable], it will be closed after all patches have been executed.
|
||||||
* Closing will be done in reverse execution order.
|
* Closing will be done in reverse execution order.
|
||||||
*/
|
*/
|
||||||
sealed interface Patch<out T : Context> : Closeable {
|
sealed interface Patch<out T : Context> {
|
||||||
/**
|
/**
|
||||||
* The main function of the [Patch] which the patcher will call.
|
* The main function of the [Patch] which the patcher will call.
|
||||||
*
|
*
|
||||||
@@ -20,13 +20,6 @@ sealed interface Patch<out T : Context> : Closeable {
|
|||||||
* @return The result of executing the patch.
|
* @return The result of executing the patch.
|
||||||
*/
|
*/
|
||||||
fun execute(context: @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() {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -5,20 +5,23 @@ import app.revanced.patcher.patch.Patch
|
|||||||
import kotlin.reflect.KClass
|
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.
|
* @param include If false, the patch should be treated as optional by default.
|
||||||
*/
|
*/
|
||||||
@Target(AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.CLASS)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@MustBeDocumented
|
|
||||||
annotation class Patch(val include: Boolean = true)
|
annotation class Patch(val include: Boolean = true)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Annotation for dependencies of [Patch]es .
|
* Annotation for dependencies of [Patch]es.
|
||||||
*/
|
*/
|
||||||
@Target(AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.CLASS)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@MustBeDocumented
|
|
||||||
annotation class DependsOn(
|
annotation class DependsOn(
|
||||||
val dependencies: Array<KClass<out Patch<Context>>> = []
|
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
|
||||||
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
package app.revanced.patcher.util.patch
|
package app.revanced.patcher.util.patch
|
||||||
|
|
||||||
import app.revanced.patcher.data.Context
|
import app.revanced.patcher.data.Context
|
||||||
|
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||||
import app.revanced.patcher.patch.Patch
|
import app.revanced.patcher.patch.Patch
|
||||||
import org.jf.dexlib2.DexFileFactory
|
import org.jf.dexlib2.DexFileFactory
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -16,12 +17,12 @@ import java.util.jar.JarFile
|
|||||||
*/
|
*/
|
||||||
sealed class PatchBundle(path: String) : File(path) {
|
sealed class PatchBundle(path: String) : File(path) {
|
||||||
internal fun loadPatches(classLoader: ClassLoader, classNames: Iterator<String>) = buildList {
|
internal fun loadPatches(classLoader: ClassLoader, classNames: Iterator<String>) = buildList {
|
||||||
for (className in classNames) {
|
classNames.forEach { className ->
|
||||||
val clazz = classLoader.loadClass(className)
|
val clazz = classLoader.loadClass(className)
|
||||||
if (!clazz.isAnnotationPresent(app.revanced.patcher.patch.annotations.Patch::class.java)) continue
|
if (!clazz.isAnnotationPresent(app.revanced.patcher.patch.annotations.Patch::class.java)) return@forEach
|
||||||
@Suppress("UNCHECKED_CAST") this.add(clazz as Class<out Patch<Context>>)
|
@Suppress("UNCHECKED_CAST") this.add(clazz as Class<out Patch<Context>>)
|
||||||
}
|
}
|
||||||
}
|
}.sortedBy { it.patchName }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A patch bundle of type [Jar].
|
* A patch bundle of type [Jar].
|
||||||
@@ -40,18 +41,13 @@ sealed class PatchBundle(path: String) : File(path) {
|
|||||||
arrayOf(this.toURI().toURL()),
|
arrayOf(this.toURI().toURL()),
|
||||||
Thread.currentThread().contextClassLoader // TODO: find out why this is required
|
Thread.currentThread().contextClassLoader // TODO: find out why this is required
|
||||||
),
|
),
|
||||||
StringIterator(
|
JarFile(this)
|
||||||
JarFile(this)
|
.stream()
|
||||||
.entries()
|
.filter {it.name.endsWith(".class") && !it.name.contains("$")}
|
||||||
.toList() // TODO: find a cleaner solution than that to filter non class files
|
.map({it -> it.realName.replace('/', '.').replace(".class", "")}).iterator()
|
||||||
.filter {
|
)
|
||||||
it.name.endsWith(".class") && !it.name.contains("$")
|
|
||||||
}
|
|
||||||
.iterator()
|
|
||||||
) {
|
|
||||||
it.realName.replace('/', '.').replace(".class", "")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -67,8 +63,8 @@ sealed class PatchBundle(path: String) : File(path) {
|
|||||||
* Patches will be loaded to the provided [dexClassLoader].
|
* Patches will be loaded to the provided [dexClassLoader].
|
||||||
*/
|
*/
|
||||||
fun loadPatches() = loadPatches(dexClassLoader,
|
fun loadPatches() = loadPatches(dexClassLoader,
|
||||||
StringIterator(DexFileFactory.loadDexFile(path, null).classes.iterator()) { classDef ->
|
DexFileFactory.loadDexFile(path, null).classes.asSequence().map({ classDef ->
|
||||||
classDef.type.substring(1, classDef.length - 1).replace('/', '.')
|
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())
|
|
||||||
}
|
|
||||||
@@ -20,6 +20,22 @@ class MutableMethod(method: Method) : Method, BaseMethodReference() {
|
|||||||
private val _parameterTypes by lazy { method.parameterTypes.toMutableList() }
|
private val _parameterTypes by lazy { method.parameterTypes.toMutableList() }
|
||||||
private val _hiddenApiRestrictions by lazy { method.hiddenApiRestrictions }
|
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 {
|
override fun getDefiningClass(): String {
|
||||||
return definingClass
|
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)
|
@Target(AnnotationTarget.CLASS)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
internal annotation class ExampleBytecodeCompatibility
|
internal annotation class ExampleBytecodeCompatibility
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import app.revanced.patcher.annotation.Description
|
|||||||
import app.revanced.patcher.annotation.Name
|
import app.revanced.patcher.annotation.Name
|
||||||
import app.revanced.patcher.annotation.Version
|
import app.revanced.patcher.annotation.Version
|
||||||
import app.revanced.patcher.data.BytecodeContext
|
import app.revanced.patcher.data.BytecodeContext
|
||||||
import app.revanced.patcher.extensions.addInstructions
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
||||||
import app.revanced.patcher.extensions.or
|
import app.revanced.patcher.extensions.or
|
||||||
import app.revanced.patcher.extensions.replaceInstruction
|
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||||
import app.revanced.patcher.patch.*
|
import app.revanced.patcher.patch.*
|
||||||
import app.revanced.patcher.patch.annotations.DependsOn
|
import app.revanced.patcher.patch.annotations.DependsOn
|
||||||
import app.revanced.patcher.patch.annotations.Patch
|
import app.revanced.patcher.patch.annotations.Patch
|
||||||
@@ -119,7 +119,7 @@ class ExampleBytecodePatch : BytecodePatch(listOf(ExampleFingerprint)) {
|
|||||||
// For this sake of example I reuse the TestClass field dummyField inside the virtual register 0.
|
// 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.
|
// Control flow instructions are not supported as of now.
|
||||||
method.addInstructions(
|
method.addInstructionsWithLabels(
|
||||||
startIndex + 2,
|
startIndex + 2,
|
||||||
"""
|
"""
|
||||||
invoke-static { }, LTestClass;->returnHello()Ljava/lang/String;
|
invoke-static { }, LTestClass;->returnHello()Ljava/lang/String;
|
||||||
|
|||||||
@@ -9,6 +9,5 @@ import app.revanced.patcher.annotation.Package
|
|||||||
)]
|
)]
|
||||||
)
|
)
|
||||||
@Target(AnnotationTarget.CLASS)
|
@Target(AnnotationTarget.CLASS)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
internal annotation class ExampleResourceCompatibility
|
internal annotation class ExampleResourceCompatibility
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
package app.revanced.patcher.util
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Assertions.*
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
|
|
||||||
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
|
package app.revanced.patcher.util.smali
|
||||||
|
|
||||||
import app.revanced.patcher.extensions.addInstructions
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||||
import app.revanced.patcher.extensions.instruction
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
||||||
import app.revanced.patcher.extensions.label
|
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||||
|
import app.revanced.patcher.extensions.newLabel
|
||||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||||
import org.jf.dexlib2.AccessFlags
|
import org.jf.dexlib2.AccessFlags
|
||||||
import org.jf.dexlib2.Opcode
|
import org.jf.dexlib2.Opcode
|
||||||
@@ -35,7 +36,7 @@ internal class InlineSmaliCompilerTest {
|
|||||||
method.addInstructions(arrayOfNulls<String>(insnAmount).also {
|
method.addInstructions(arrayOfNulls<String>(insnAmount).also {
|
||||||
Arrays.fill(it, "const/4 v0, 0x0")
|
Arrays.fill(it, "const/4 v0, 0x0")
|
||||||
}.joinToString("\n"))
|
}.joinToString("\n"))
|
||||||
method.addInstructions(
|
method.addInstructionsWithLabels(
|
||||||
targetIndex,
|
targetIndex,
|
||||||
"""
|
"""
|
||||||
:test
|
: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)
|
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
|
const/4 v0, 0x1
|
||||||
if-eqz v0, :test
|
if-eqz v0, :test
|
||||||
return-void
|
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")
|
assertTrue(insn.target.isPlaced, "Label was not placed")
|
||||||
assertEquals(labelIndex, insn.target.location.index)
|
assertEquals(labelIndex, insn.target.location.index)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user