You've already forked revanced-patcher
mirror of
https://github.com/revanced/revanced-patcher
synced 2025-09-06 16:38:50 +02:00
Compare commits
240 Commits
v8.0.0-dev
...
v19.0.0-de
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f05a404e48 | ||
![]() |
a46e948b5a | ||
![]() |
dc09ea639f | ||
![]() |
49ed096e85 | ||
![]() |
167bd83f4e | ||
![]() |
aed1eac315 | ||
![]() |
54a2f8f16f | ||
![]() |
2ca543ffb9 | ||
![]() |
58e7f815a5 | ||
![]() |
15b38fc841 | ||
![]() |
e2ca50729d | ||
![]() |
6192089b71 | ||
![]() |
a4212f6bf9 | ||
![]() |
124a2e9d3e | ||
![]() |
f77624b3b9 | ||
![]() |
a76ac04214 | ||
![]() |
0447fa9c28 | ||
![]() |
54ac1394a9 | ||
![]() |
0b04c73ac5 | ||
![]() |
079de45238 | ||
![]() |
56ce9ec2f9 | ||
![]() |
1b52e4b0f9 | ||
![]() |
098c2c1efa | ||
![]() |
64343e5a7c | ||
![]() |
0caf6caeb9 | ||
![]() |
55f6c2a9fc | ||
![]() |
c6095bc38a | ||
![]() |
09cd6aa568 | ||
![]() |
ebbaafb78e | ||
![]() |
e6de90d300 | ||
![]() |
c7922e90d0 | ||
![]() |
c1f4c0445a | ||
![]() |
caa634fac6 | ||
![]() |
f28bfe0dbd | ||
![]() |
155e787ff4 | ||
![]() |
c38f0ef42a | ||
![]() |
4456031459 | ||
![]() |
5fb59a227f | ||
![]() |
d9fb241d57 | ||
![]() |
642c4ea97e | ||
![]() |
8c8a251626 | ||
![]() |
5953d6cfb5 | ||
![]() |
a1962fe600 | ||
![]() |
77dbee3d6a | ||
![]() |
cb5e39d73e | ||
![]() |
38ef2f470a | ||
![]() |
129d84e108 | ||
![]() |
affeba76b8 | ||
![]() |
6059d3ca26 | ||
![]() |
444dee5a16 | ||
![]() |
d314466ce2 | ||
![]() |
fdaf9c21c8 | ||
![]() |
06c2b76f11 | ||
![]() |
3896b30738 | ||
![]() |
2c4b88e1a0 | ||
![]() |
dfc7e1596b | ||
![]() |
f590436399 | ||
![]() |
cbfb9ba02f | ||
![]() |
b4cfe80ad5 | ||
![]() |
b37906fa35 | ||
![]() |
356f1f1553 | ||
![]() |
e882af74ee | ||
![]() |
46875fb28e | ||
![]() |
417c3e4234 | ||
![]() |
6d2c28807b | ||
![]() |
4d6e08a650 | ||
![]() |
5cebc1fd30 | ||
![]() |
ac61731dc6 | ||
![]() |
9e4ffabd5c | ||
![]() |
3f410bd39f | ||
![]() |
d51bc32e37 | ||
![]() |
b7f6aa94cc | ||
![]() |
ff965e6953 | ||
![]() |
468d5d7421 | ||
![]() |
fc95b28c49 | ||
![]() |
69184187d9 | ||
![]() |
a802d0df46 | ||
![]() |
8de30633ae | ||
![]() |
a1fbb7990f | ||
![]() |
aa71146b1b | ||
![]() |
9fdb8f087f | ||
![]() |
670f0153de | ||
![]() |
1d7aeca696 | ||
![]() |
4e7811ea07 | ||
![]() |
e11283744a | ||
![]() |
91cdfd53ef | ||
![]() |
bc7d6b9941 | ||
![]() |
6b1e0a1656 | ||
![]() |
72c9eb2129 | ||
![]() |
4bc4b0dc01 | ||
![]() |
637d48746f | ||
![]() |
9a109c129b | ||
![]() |
d49e4ee5ea | ||
![]() |
30f0ea29a3 | ||
![]() |
49930f6565 | ||
![]() |
909d89fa8d | ||
![]() |
81d1d7f544 | ||
![]() |
67b7dff67a | ||
![]() |
4b76d19596 | ||
![]() |
080fbe9feb | ||
![]() |
d3721229bf | ||
![]() |
86c1c9c772 | ||
![]() |
c299817193 | ||
![]() |
fcc1de45ed | ||
![]() |
a29931f2ec | ||
![]() |
3fc6a139ee | ||
![]() |
4dd04975d9 | ||
![]() |
3b4db3ddb7 | ||
![]() |
c4a7117ee8 | ||
![]() |
b4e900fde8 | ||
![]() |
9818d730e4 | ||
![]() |
11a3378659 | ||
![]() |
1bb05f22d3 | ||
![]() |
26b70554c4 | ||
![]() |
93b29d2e83 | ||
![]() |
072986374a | ||
![]() |
2c590d212a | ||
![]() |
6cc863efb3 | ||
![]() |
b832812767 | ||
![]() |
c44558cacd | ||
![]() |
6d83a720cd | ||
![]() |
8d0dd9c448 | ||
![]() |
64020eec49 | ||
![]() |
4dedfb85cb | ||
![]() |
55d694579a | ||
![]() |
86db64edff | ||
![]() |
983563efb6 | ||
![]() |
37abb2db99 | ||
![]() |
5ba0b47e60 | ||
![]() |
e8f2087a6f | ||
![]() |
6ce99f5cdf | ||
![]() |
13c0c9cdd3 | ||
![]() |
58ffdb60d7 | ||
![]() |
ba56a6a2ee | ||
![]() |
ccccf5b1d2 | ||
![]() |
b507ac0a54 | ||
![]() |
e985676c2d | ||
![]() |
f7f4ba6c55 | ||
![]() |
4292f43814 | ||
![]() |
30bd4fd9fe | ||
![]() |
76de39369d | ||
![]() |
88a703ce36 | ||
![]() |
5938f6b7ea | ||
![]() |
5c0c0d6c37 | ||
![]() |
0f15077225 | ||
![]() |
273dd8d388 | ||
![]() |
1795f376ef | ||
![]() |
e7360a7692 | ||
![]() |
e1fc86934f | ||
![]() |
6b8977f178 | ||
![]() |
12c6c73de0 | ||
![]() |
db62a1607b | ||
![]() |
58bb879ef5 | ||
![]() |
254912438a | ||
![]() |
0e48918bcc | ||
![]() |
783ccf8529 | ||
![]() |
8fb2f2dc1d | ||
![]() |
2a8cc283c7 | ||
![]() |
433fe3af9f | ||
![]() |
c2d89c622e | ||
![]() |
02d6ff15fe | ||
![]() |
f2cb7ee7df | ||
![]() |
a2ac44dcc1 | ||
![]() |
3cf9d74efa | ||
![]() |
d5f89a903f | ||
![]() |
496c2242bc | ||
![]() |
98fbff87df | ||
![]() |
ddb51a1c45 | ||
![]() |
8df1155215 | ||
![]() |
53f2a61409 | ||
![]() |
746544f9d5 | ||
![]() |
c65c3df11c | ||
![]() |
b29b8f12b3 | ||
![]() |
d6945677c4 | ||
![]() |
aedf4aea08 | ||
![]() |
dc28d414dc | ||
![]() |
9755bab298 | ||
![]() |
fae4029cfc | ||
![]() |
1790f0d706 | ||
![]() |
0ba2c51676 | ||
![]() |
03cd97b49c | ||
![]() |
16a162c1dd | ||
![]() |
c9bbcf2bf2 | ||
![]() |
86e1bf6078 | ||
![]() |
1bca84ef0b | ||
![]() |
e0f8e1b71a | ||
![]() |
416d69142f | ||
![]() |
426807aeaa | ||
![]() |
90cb075a97 | ||
![]() |
ac2ca8fbd3 | ||
![]() |
69e4a49065 | ||
![]() |
a4a030f2b2 | ||
![]() |
dcc4ecd237 | ||
![]() |
57f3036a96 | ||
![]() |
753e55dfc3 | ||
![]() |
9d81baf4b4 | ||
![]() |
7cb4d4c596 | ||
![]() |
2c8565508e | ||
![]() |
c7f156e4c9 | ||
![]() |
fcef4342e8 | ||
![]() |
72783a5e74 | ||
![]() |
a379b69eeb | ||
![]() |
0a8ccba33e | ||
![]() |
519359a9eb | ||
![]() |
b615ed6aab | ||
![]() |
d718134ab2 | ||
![]() |
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 |
9
.gitattributes
vendored
Normal file
9
.gitattributes
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#
|
||||||
|
# https://help.github.com/articles/dealing-with-line-endings/
|
||||||
|
#
|
||||||
|
# Linux start script should use lf
|
||||||
|
/gradlew text eol=lf
|
||||||
|
|
||||||
|
# These are Windows script files and should use crlf
|
||||||
|
*.bat text eol=crlf
|
||||||
|
|
6
.github/workflows/pull_request.yml
vendored
6
.github/workflows/pull_request.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: PR to main
|
name: Open a PR to main
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -7,7 +7,7 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
MESSAGE: merge branch `${{ github.head_ref || github.ref_name }}` to `main`
|
MESSAGE: Merge branch `${{ github.head_ref || github.ref_name }}` to `main`
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pull-request:
|
pull-request:
|
||||||
@@ -15,7 +15,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- name: Open pull request
|
- name: Open pull request
|
||||||
uses: repo-sync/pull-request@v2
|
uses: repo-sync/pull-request@v2
|
||||||
with:
|
with:
|
||||||
|
25
.github/workflows/release.yml
vendored
25
.github/workflows/release.yml
vendored
@@ -17,23 +17,26 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
# Make sure the release step uses its own credentials:
|
# Make sure the release step uses its own credentials:
|
||||||
# https://github.com/cycjimmy/semantic-release-action#private-packages
|
# https://github.com/cycjimmy/semantic-release-action#private-packages
|
||||||
persist-credentials: false
|
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: 'zulu'
|
${{ 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: "18"
|
key: ${{ runner.os }}-gradle-npm-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', 'package-lock.json') }}
|
||||||
cache: 'npm'
|
- name: Build with Gradle
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: ./gradlew build clean --no-daemon
|
||||||
- name: Setup semantic-release
|
- name: Setup semantic-release
|
||||||
run: npm install
|
run: npm install
|
||||||
- name: Release
|
- name: Release
|
||||||
|
@@ -7,7 +7,13 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"@semantic-release/commit-analyzer",
|
[
|
||||||
|
"@semantic-release/commit-analyzer", {
|
||||||
|
"releaseRules": [
|
||||||
|
{ "type": "build", "scope": "Needs bump", "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",
|
||||||
|
826
CHANGELOG.md
826
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
770
api/revanced-patcher.api
Normal file
770
api/revanced-patcher.api
Normal file
File diff suppressed because it is too large
Load Diff
101
build.gradle.kts
101
build.gradle.kts
@@ -1,47 +1,48 @@
|
|||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm") version "1.8.10"
|
kotlin("jvm") version "1.9.10"
|
||||||
|
alias(libs.plugins.binary.compatibility.validator)
|
||||||
`maven-publish`
|
`maven-publish`
|
||||||
|
signing
|
||||||
|
java
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "app.revanced"
|
group = "app.revanced"
|
||||||
|
|
||||||
val githubUsername: String = project.findProperty("gpr.user") as? String ?: System.getenv("GITHUB_ACTOR")
|
|
||||||
val githubPassword: String = project.findProperty("gpr.key") as? String ?: System.getenv("GITHUB_TOKEN")
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
maven {
|
|
||||||
url = uri("https://maven.pkg.github.com/revanced/multidexlib2")
|
|
||||||
credentials {
|
|
||||||
username = githubUsername
|
|
||||||
password = githubPassword
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation("xpp3:xpp3:1.1.4c")
|
|
||||||
implementation("app.revanced:smali:2.5.3-a3836654")
|
|
||||||
implementation("app.revanced:multidexlib2:2.5.3-a3836654")
|
|
||||||
implementation("app.revanced:apktool-lib:2.7.0")
|
|
||||||
|
|
||||||
implementation("org.jetbrains.kotlin:kotlin-reflect:1.8.20-RC")
|
|
||||||
testImplementation("org.jetbrains.kotlin:kotlin-test:1.8.20-RC")
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks {
|
tasks {
|
||||||
|
processResources {
|
||||||
|
expand("projectVersion" to project.version)
|
||||||
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
testLogging {
|
testLogging {
|
||||||
events("PASSED", "SKIPPED", "FAILED")
|
events("PASSED", "SKIPPED", "FAILED")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
processResources {
|
}
|
||||||
expand("projectVersion" to project.version)
|
|
||||||
}
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
mavenLocal()
|
||||||
|
maven { url = uri("https://jitpack.io") }
|
||||||
|
google()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(libs.kotlinx.coroutines.core)
|
||||||
|
implementation(libs.xpp3)
|
||||||
|
implementation(libs.smali)
|
||||||
|
implementation(libs.multidexlib2)
|
||||||
|
implementation(libs.apktool.lib)
|
||||||
|
implementation(libs.kotlin.reflect)
|
||||||
|
|
||||||
|
compileOnly(libs.android)
|
||||||
|
|
||||||
|
testImplementation(libs.kotlin.test)
|
||||||
}
|
}
|
||||||
|
|
||||||
java {
|
java {
|
||||||
|
withJavadocJar()
|
||||||
withSourcesJar()
|
withSourcesJar()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,22 +51,36 @@ kotlin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
publishing {
|
publishing {
|
||||||
repositories {
|
publications {
|
||||||
if (System.getenv("GITHUB_ACTOR") != null)
|
create<MavenPublication>("revanced-patcher-publication") {
|
||||||
maven {
|
from(components["java"])
|
||||||
name = "GitHubPackages"
|
|
||||||
url = uri("https://maven.pkg.github.com/revanced/revanced-patcher")
|
version = project.version.toString()
|
||||||
credentials {
|
|
||||||
username = System.getenv("GITHUB_ACTOR")
|
pom {
|
||||||
password = System.getenv("GITHUB_TOKEN")
|
name = "ReVanced Patcher"
|
||||||
|
description = "Patcher used by ReVanced."
|
||||||
|
url = "https://revanced.app"
|
||||||
|
|
||||||
|
licenses {
|
||||||
|
license {
|
||||||
|
name = "GNU General Public License v3.0"
|
||||||
|
url = "https://www.gnu.org/licenses/gpl-3.0.en.html"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
developers {
|
||||||
|
developer {
|
||||||
|
id = "ReVanced"
|
||||||
|
name = "ReVanced"
|
||||||
|
email = "contact@revanced.app"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scm {
|
||||||
|
connection = "scm:git:git://github.com/revanced/revanced-patcher.git"
|
||||||
|
developerConnection = "scm:git:git@github.com:revanced/revanced-patcher.git"
|
||||||
|
url = "https://github.com/revanced/revanced-patcher"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
mavenLocal()
|
|
||||||
}
|
|
||||||
publications {
|
|
||||||
register<MavenPublication>("gpr") {
|
|
||||||
from(components["java"])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,2 +1,4 @@
|
|||||||
|
org.gradle.parallel = true
|
||||||
|
org.gradle.caching = true
|
||||||
kotlin.code.style = official
|
kotlin.code.style = official
|
||||||
version = 8.0.0-dev.1
|
version = 19.0.0-dev.1
|
||||||
|
23
gradle/libs.versions.toml
Normal file
23
gradle/libs.versions.toml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
[versions]
|
||||||
|
android = "4.1.1.4"
|
||||||
|
kotlin-reflect = "1.9.0"
|
||||||
|
apktool-lib = "2.9.1"
|
||||||
|
kotlin-test = "1.8.20-RC"
|
||||||
|
kotlinx-coroutines-core = "1.7.3"
|
||||||
|
multidexlib2 = "3.0.3.r3"
|
||||||
|
smali = "3.0.3"
|
||||||
|
xpp3 = "1.1.4c"
|
||||||
|
binary-compatibility-validator = "0.13.2"
|
||||||
|
|
||||||
|
[libraries]
|
||||||
|
android = { module = "com.google.android:android", version.ref = "android" }
|
||||||
|
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin-reflect" }
|
||||||
|
apktool-lib = { module = "app.revanced:apktool", version.ref = "apktool-lib" }
|
||||||
|
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin-test" }
|
||||||
|
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines-core" }
|
||||||
|
multidexlib2 = { module = "app.revanced:multidexlib2", version.ref = "multidexlib2" }
|
||||||
|
smali = { module = "com.android.tools.smali:smali", version.ref = "smali" }
|
||||||
|
xpp3 = { module = "xpp3:xpp3", version.ref = "xpp3" }
|
||||||
|
|
||||||
|
[plugins]
|
||||||
|
binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binary-compatibility-validator" }
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,7 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
|
||||||
|
networkTimeout=10000
|
||||||
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
40
gradlew
vendored
40
gradlew
vendored
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
#
|
#
|
||||||
# Copyright © 2015-2021 the original authors.
|
# Copyright © 2015-2021 the original authors.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -32,10 +32,10 @@
|
|||||||
# Busybox and similar reduced shells will NOT work, because this script
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
# requires all of these POSIX shell features:
|
# requires all of these POSIX shell features:
|
||||||
# * functions;
|
# * functions;
|
||||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||||
# * compound commands having a testable exit status, especially «case»;
|
# * compound commands having a testable exit status, especially «case»;
|
||||||
# * various built-in commands including «command», «set», and «ulimit».
|
# * various built-in commands including «command», «set», and «ulimit».
|
||||||
#
|
#
|
||||||
# Important for patching:
|
# Important for patching:
|
||||||
#
|
#
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (3) This script is generated from the Groovy template
|
||||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
# within the Gradle project.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
@@ -80,13 +80,10 @@ do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
# This is normally unused
|
||||||
|
# shellcheck disable=SC2034
|
||||||
APP_NAME="Gradle"
|
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
|
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
@@ -133,22 +130,29 @@ location of your Java installation."
|
|||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
JAVACMD=java
|
JAVACMD=java
|
||||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
if ! command -v java >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
max*)
|
max*)
|
||||||
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC3045
|
||||||
MAX_FD=$( ulimit -H -n ) ||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
warn "Could not query maximum file descriptor limit"
|
warn "Could not query maximum file descriptor limit"
|
||||||
esac
|
esac
|
||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
'' | soft) :;; #(
|
'' | soft) :;; #(
|
||||||
*)
|
*)
|
||||||
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC3045
|
||||||
ulimit -n "$MAX_FD" ||
|
ulimit -n "$MAX_FD" ||
|
||||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
esac
|
esac
|
||||||
@@ -193,6 +197,10 @@ if "$cygwin" || "$msys" ; then
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
# Collect all arguments for the java command;
|
# Collect all arguments for the java command;
|
||||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||||
# shell script including quotes and variable substitutions, so put them in
|
# shell script including quotes and variable substitutions, so put them in
|
||||||
@@ -205,6 +213,12 @@ set -- \
|
|||||||
org.gradle.wrapper.GradleWrapperMain \
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
"$@"
|
"$@"
|
||||||
|
|
||||||
|
# Stop when "xargs" is not available.
|
||||||
|
if ! command -v xargs >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "xargs is not available"
|
||||||
|
fi
|
||||||
|
|
||||||
# Use "xargs" to parse quoted args.
|
# Use "xargs" to parse quoted args.
|
||||||
#
|
#
|
||||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
15
gradlew.bat
vendored
15
gradlew.bat
vendored
@@ -14,7 +14,7 @@
|
|||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%" == "" @echo off
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@rem Gradle startup script for Windows
|
@rem Gradle startup script for Windows
|
||||||
@@ -25,7 +25,8 @@
|
|||||||
if "%OS%"=="Windows_NT" setlocal
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%" == "" set DIRNAME=.
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
|||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if "%ERRORLEVEL%" == "0" goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
@@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
|||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||||
|
|
||||||
:fail
|
:fail
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
rem the _cmd.exe /c_ return code!
|
rem the _cmd.exe /c_ return code!
|
||||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
set EXIT_CODE=%ERRORLEVEL%
|
||||||
exit /b 1
|
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||||
|
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||||
|
exit /b %EXIT_CODE%
|
||||||
|
|
||||||
:mainEnd
|
:mainEnd
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
@@ -1 +1 @@
|
|||||||
rootProject.name = "revanced-patcher"
|
rootProject.name = "revanced-patcher"
|
@@ -0,0 +1,8 @@
|
|||||||
|
package app.revanced.patcher
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface IntegrationsConsumer {
|
||||||
|
fun acceptIntegrations(integrations: List<File>)
|
||||||
|
}
|
14
src/main/kotlin/app/revanced/patcher/PackageMetadata.kt
Normal file
14
src/main/kotlin/app/revanced/patcher/PackageMetadata.kt
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package app.revanced.patcher
|
||||||
|
|
||||||
|
import brut.androlib.apk.ApkInfo
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metadata about a package.
|
||||||
|
*/
|
||||||
|
class PackageMetadata internal constructor(internal val apkInfo: ApkInfo) {
|
||||||
|
lateinit var packageName: String
|
||||||
|
internal set
|
||||||
|
|
||||||
|
lateinit var packageVersion: String
|
||||||
|
internal set
|
||||||
|
}
|
127
src/main/kotlin/app/revanced/patcher/PatchBundleLoader.kt
Normal file
127
src/main/kotlin/app/revanced/patcher/PatchBundleLoader.kt
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
@file:Suppress("unused")
|
||||||
|
|
||||||
|
package app.revanced.patcher
|
||||||
|
|
||||||
|
import app.revanced.patcher.patch.Patch
|
||||||
|
import dalvik.system.DexClassLoader
|
||||||
|
import lanchon.multidexlib2.BasicDexFileNamer
|
||||||
|
import lanchon.multidexlib2.MultiDexIO
|
||||||
|
import java.io.File
|
||||||
|
import java.net.URLClassLoader
|
||||||
|
import java.util.jar.JarFile
|
||||||
|
import java.util.logging.Logger
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A set of [Patch]es.
|
||||||
|
*/
|
||||||
|
typealias PatchSet = Set<Patch<*>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [Patch] class.
|
||||||
|
*/
|
||||||
|
typealias PatchClass = KClass<out Patch<*>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A loader of [Patch]es from patch bundles.
|
||||||
|
* This will load all [Patch]es from the given patch bundles that have a name.
|
||||||
|
*
|
||||||
|
* @param getBinaryClassNames A function that returns the binary names of all classes in a patch bundle.
|
||||||
|
* @param classLoader The [ClassLoader] to use for loading the classes.
|
||||||
|
* @param patchBundles A set of patches to initialize this instance with.
|
||||||
|
*/
|
||||||
|
sealed class PatchBundleLoader private constructor(
|
||||||
|
classLoader: ClassLoader,
|
||||||
|
patchBundles: Array<out File>,
|
||||||
|
getBinaryClassNames: (patchBundle: File) -> List<String>,
|
||||||
|
// This constructor parameter is unfortunately necessary,
|
||||||
|
// so that a reference to the mutable set is present in the constructor to be able to add patches to it.
|
||||||
|
// because the instance itself is a PatchSet, which is immutable, that is delegated by the parameter.
|
||||||
|
private val patchSet: MutableSet<Patch<*>> = mutableSetOf()
|
||||||
|
) : PatchSet by patchSet {
|
||||||
|
private val logger = Logger.getLogger(PatchBundleLoader::class.java.name)
|
||||||
|
|
||||||
|
init {
|
||||||
|
patchBundles.flatMap(getBinaryClassNames).asSequence().map {
|
||||||
|
classLoader.loadClass(it)
|
||||||
|
}.filter {
|
||||||
|
Patch::class.java.isAssignableFrom(it)
|
||||||
|
}.mapNotNull { patchClass ->
|
||||||
|
patchClass.getInstance(logger, silent = true)
|
||||||
|
}.filter {
|
||||||
|
it.name != null
|
||||||
|
}.let { patches ->
|
||||||
|
patchSet.addAll(patches)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal companion object Utils {
|
||||||
|
/**
|
||||||
|
* Instantiates a [Patch]. If the class is a singleton, the INSTANCE field will be used.
|
||||||
|
*
|
||||||
|
* @param logger The [Logger] to use for logging.
|
||||||
|
* @param silent Whether to suppress logging.
|
||||||
|
* @return The instantiated [Patch] or `null` if the [Patch] could not be instantiated.
|
||||||
|
*/
|
||||||
|
internal fun Class<*>.getInstance(logger: Logger, silent: Boolean = false): Patch<*>? {
|
||||||
|
return try {
|
||||||
|
getField("INSTANCE").get(null)
|
||||||
|
} catch (exception: NoSuchFieldException) {
|
||||||
|
if (!silent) logger.fine(
|
||||||
|
"Patch class '${name}' has no INSTANCE field, therefor not a singleton. " +
|
||||||
|
"Will try to instantiate it."
|
||||||
|
)
|
||||||
|
|
||||||
|
try {
|
||||||
|
getDeclaredConstructor().newInstance()
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
if (!silent) logger.severe(
|
||||||
|
"Patch class '${name}' is not singleton and has no suitable constructor, " +
|
||||||
|
"therefor cannot be instantiated and will be ignored."
|
||||||
|
)
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
} as Patch<*>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [PatchBundleLoader] for JAR files.
|
||||||
|
*
|
||||||
|
* @param patchBundles The path to patch bundles of JAR format.
|
||||||
|
*/
|
||||||
|
class Jar(vararg patchBundles: File) : PatchBundleLoader(
|
||||||
|
URLClassLoader(patchBundles.map { it.toURI().toURL() }.toTypedArray()),
|
||||||
|
patchBundles,
|
||||||
|
{ patchBundle ->
|
||||||
|
JarFile(patchBundle).entries().toList().filter { it.name.endsWith(".class") }
|
||||||
|
.map { it.name.replace('/', '.').replace(".class", "") }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [PatchBundleLoader] for [Dex] files.
|
||||||
|
*
|
||||||
|
* @param patchBundles The path to patch bundles of DEX format.
|
||||||
|
* @param optimizedDexDirectory The directory to store optimized DEX files in.
|
||||||
|
* This parameter is deprecated and has no effect since API level 26.
|
||||||
|
*/
|
||||||
|
class Dex(vararg patchBundles: File, optimizedDexDirectory: File? = null) : PatchBundleLoader(
|
||||||
|
DexClassLoader(
|
||||||
|
patchBundles.joinToString(File.pathSeparator) { it.absolutePath }, optimizedDexDirectory?.absolutePath,
|
||||||
|
null,
|
||||||
|
PatchBundleLoader::class.java.classLoader
|
||||||
|
),
|
||||||
|
patchBundles,
|
||||||
|
{ patchBundle ->
|
||||||
|
MultiDexIO.readDexFile(true, patchBundle, BasicDexFileNamer(), null, null).classes
|
||||||
|
.map { classDef ->
|
||||||
|
classDef.type.substring(1, classDef.length - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
@Deprecated("This constructor is deprecated. Use the constructor with the second parameter instead.")
|
||||||
|
constructor(vararg patchBundles: File) : this(*patchBundles, optimizedDexDirectory = null)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,8 @@
|
|||||||
|
package app.revanced.patcher
|
||||||
|
|
||||||
|
import app.revanced.patcher.patch.PatchResult
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import java.util.function.Function
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface PatchExecutorFunction : Function<Boolean, Flow<PatchResult>>
|
File diff suppressed because it is too large
Load Diff
@@ -1,64 +1,41 @@
|
|||||||
package app.revanced.patcher
|
package app.revanced.patcher
|
||||||
|
|
||||||
import app.revanced.patcher.data.*
|
import app.revanced.patcher.data.BytecodeContext
|
||||||
import app.revanced.patcher.logging.Logger
|
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 brut.androlib.apk.ApkInfo
|
||||||
import org.jf.dexlib2.iface.ClassDef
|
import brut.directory.ExtFile
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
data class PatcherContext(
|
/**
|
||||||
val classes: MutableList<ClassDef>,
|
* A context for ReVanced [Patcher].
|
||||||
val resourceCacheDirectory: File,
|
*
|
||||||
) {
|
* @param options The [PatcherOptions] used to create this context.
|
||||||
val packageMetadata = PackageMetadata()
|
*/
|
||||||
internal val patches = mutableListOf<Class<out Patch<Context>>>()
|
class PatcherContext internal constructor(options: PatcherOptions) {
|
||||||
internal val integrations = Integrations(this)
|
/**
|
||||||
internal val bytecodeContext = BytecodeContext(classes)
|
* [PackageMetadata] of the supplied [PatcherOptions.inputFile].
|
||||||
internal val resourceContext = ResourceContext(resourceCacheDirectory)
|
*/
|
||||||
|
val packageMetadata = PackageMetadata(ApkInfo(ExtFile(options.inputFile)))
|
||||||
|
|
||||||
internal class Integrations(val context: PatcherContext) {
|
/**
|
||||||
var callback: ((File) -> Unit)? = null
|
* The map of [Patch]es associated by their [PatchClass].
|
||||||
private val integrations: MutableList<File> = mutableListOf()
|
*/
|
||||||
|
internal val executablePatches = mutableMapOf<PatchClass, Patch<*>>()
|
||||||
|
|
||||||
fun add(integrations: List<File>) = this@Integrations.integrations.addAll(integrations)
|
/**
|
||||||
|
* The map of all [Patch]es and their dependencies associated by their [PatchClass].
|
||||||
|
*/
|
||||||
|
internal val allPatches = mutableMapOf<PatchClass, Patch<*>>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Merge integrations.
|
* The [ResourceContext] of this [PatcherContext].
|
||||||
* @param logger A logger.
|
* This holds the current state of the resources.
|
||||||
*/
|
*/
|
||||||
fun merge(logger: Logger) {
|
internal val resourceContext = ResourceContext(this, options)
|
||||||
with(context.bytecodeContext.classes) {
|
|
||||||
for (integrations in integrations) {
|
|
||||||
callback?.let { it(integrations) }
|
|
||||||
|
|
||||||
for (classDef in lanchon.multidexlib2.MultiDexIO.readDexFile(
|
/**
|
||||||
true,
|
* The [BytecodeContext] of this [PatcherContext].
|
||||||
integrations,
|
* This holds the current state of the bytecode.
|
||||||
NAMER,
|
*/
|
||||||
null,
|
internal val bytecodeContext = BytecodeContext(options)
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
16
src/main/kotlin/app/revanced/patcher/PatcherException.kt
Normal file
16
src/main/kotlin/app/revanced/patcher/PatcherException.kt
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package app.revanced.patcher
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An exception thrown by ReVanced [Patcher].
|
||||||
|
*
|
||||||
|
* @param errorMessage The exception message.
|
||||||
|
* @param cause The corresponding [Throwable].
|
||||||
|
*/
|
||||||
|
sealed class PatcherException(errorMessage: String?, cause: Throwable?) : Exception(errorMessage, cause) {
|
||||||
|
constructor(errorMessage: String) : this(errorMessage, null)
|
||||||
|
|
||||||
|
|
||||||
|
class CircularDependencyException internal constructor(dependant: String) : PatcherException(
|
||||||
|
"Patch '$dependant' causes a circular dependency"
|
||||||
|
)
|
||||||
|
}
|
@@ -1,21 +1,51 @@
|
|||||||
package app.revanced.patcher
|
package app.revanced.patcher
|
||||||
|
|
||||||
import app.revanced.patcher.logging.Logger
|
import app.revanced.patcher.data.ResourceContext
|
||||||
import app.revanced.patcher.logging.impl.NopLogger
|
import brut.androlib.Config
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.util.logging.Logger
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options for the [Patcher].
|
* Options for ReVanced [Patcher].
|
||||||
* @param inputFile The input file (usually an apk file).
|
* @param inputFile The input file to patch.
|
||||||
* @param resourceCacheDirectory Directory to cache resources.
|
* @param resourceCachePath The path to the directory to use for caching resources.
|
||||||
* @param aaptPath Optional path to a custom aapt binary.
|
* @param aaptBinaryPath The path to a custom aapt binary.
|
||||||
* @param frameworkFolderLocation Optional path to a custom framework folder.
|
* @param frameworkFileDirectory The path to the directory to cache the framework file in.
|
||||||
* @param logger Custom logger implementation for the [Patcher].
|
* @param multithreadingDexFileWriter Whether to use multiple threads for writing dex files.
|
||||||
|
* This can impact memory usage.
|
||||||
*/
|
*/
|
||||||
data class PatcherOptions(
|
data class PatcherOptions(
|
||||||
internal val inputFile: File,
|
internal val inputFile: File,
|
||||||
internal val resourceCacheDirectory: String,
|
internal val resourceCachePath: File = File("revanced-resource-cache"),
|
||||||
internal val aaptPath: String? = null,
|
internal val aaptBinaryPath: String? = null,
|
||||||
internal val frameworkFolderLocation: String? = null,
|
internal val frameworkFileDirectory: String? = null,
|
||||||
internal val logger: Logger = NopLogger
|
internal val multithreadingDexFileWriter: Boolean = false,
|
||||||
)
|
) {
|
||||||
|
private val logger = Logger.getLogger(PatcherOptions::class.java.name)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The mode to use for resource decoding.
|
||||||
|
* @see ResourceContext.ResourceDecodingMode
|
||||||
|
*/
|
||||||
|
internal var resourceDecodingMode = ResourceContext.ResourceDecodingMode.MANIFEST_ONLY
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The configuration to use for resource decoding and compiling.
|
||||||
|
*/
|
||||||
|
internal val resourceConfig = Config.getDefaultConfig().apply {
|
||||||
|
useAapt2 = true
|
||||||
|
aaptPath = aaptBinaryPath ?: ""
|
||||||
|
frameworkDirectory = frameworkFileDirectory
|
||||||
|
}
|
||||||
|
|
||||||
|
fun recreateResourceCacheDirectory() = resourceCachePath.also {
|
||||||
|
if (it.exists()) {
|
||||||
|
logger.info("Deleting existing resource cache directory")
|
||||||
|
|
||||||
|
if (!it.deleteRecursively())
|
||||||
|
logger.severe("Failed to delete existing resource cache directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
it.mkdirs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,16 +1,23 @@
|
|||||||
package app.revanced.patcher
|
package app.revanced.patcher
|
||||||
|
|
||||||
import app.revanced.patcher.util.dex.DexFile
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The result of a patcher.
|
* The result of a patcher.
|
||||||
* @param dexFiles The patched dex files.
|
* @param dexFiles The patched dex files.
|
||||||
* @param doNotCompress List of relative paths to files to exclude from compressing.
|
|
||||||
* @param resourceFile File containing resources that need to be extracted into the APK.
|
* @param resourceFile File containing resources that need to be extracted into the APK.
|
||||||
|
* @param doNotCompress List of relative paths of files to exclude from compressing.
|
||||||
*/
|
*/
|
||||||
data class PatcherResult(
|
data class PatcherResult(
|
||||||
val dexFiles: List<DexFile>,
|
val dexFiles: List<PatchedDexFile>,
|
||||||
val doNotCompress: List<String>? = null,
|
val resourceFile: File?,
|
||||||
val resourceFile: File?
|
val doNotCompress: List<String>? = null
|
||||||
)
|
) {
|
||||||
|
/**
|
||||||
|
* Wrapper for dex files.
|
||||||
|
* @param name The original name of the dex file.
|
||||||
|
* @param stream The dex file as [InputStream].
|
||||||
|
*/
|
||||||
|
class PatchedDexFile(val name: String, val stream: InputStream)
|
||||||
|
}
|
8
src/main/kotlin/app/revanced/patcher/PatchesConsumer.kt
Normal file
8
src/main/kotlin/app/revanced/patcher/PatchesConsumer.kt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package app.revanced.patcher
|
||||||
|
|
||||||
|
import app.revanced.patcher.patch.Patch
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface PatchesConsumer {
|
||||||
|
fun acceptPatches(patches: List<Patch<*>>)
|
||||||
|
}
|
@@ -1,23 +0,0 @@
|
|||||||
package app.revanced.patcher.annotation
|
|
||||||
|
|
||||||
import app.revanced.patcher.patch.Patch
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Annotation to constrain a [Patch] to compatible packages.
|
|
||||||
* @param compatiblePackages A list of packages a [Patch] is compatible with.
|
|
||||||
*/
|
|
||||||
@Target(AnnotationTarget.CLASS)
|
|
||||||
annotation class Compatibility(
|
|
||||||
val compatiblePackages: Array<Package>,
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Annotation to represent packages a patch can be compatible with.
|
|
||||||
* @param name The package identifier name.
|
|
||||||
* @param versions The versions of the package the [Patch] is compatible with.
|
|
||||||
*/
|
|
||||||
@Target()
|
|
||||||
annotation class Package(
|
|
||||||
val name: String,
|
|
||||||
val versions: Array<String> = [],
|
|
||||||
)
|
|
@@ -1,31 +0,0 @@
|
|||||||
package app.revanced.patcher.annotation
|
|
||||||
|
|
||||||
import app.revanced.patcher.patch.Patch
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Annotation to name a [Patch].
|
|
||||||
* @param name A suggestive name for the [Patch].
|
|
||||||
*/
|
|
||||||
@Target(AnnotationTarget.CLASS)
|
|
||||||
annotation class Name(
|
|
||||||
val name: String,
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Annotation to describe a [Patch].
|
|
||||||
* @param description A description for the [Patch].
|
|
||||||
*/
|
|
||||||
@Target(AnnotationTarget.CLASS)
|
|
||||||
annotation class Description(
|
|
||||||
val description: String,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Annotation to version a [Patch].
|
|
||||||
* @param version The version of a [Patch].
|
|
||||||
*/
|
|
||||||
@Target(AnnotationTarget.CLASS)
|
|
||||||
annotation class Version(
|
|
||||||
val version: String,
|
|
||||||
)
|
|
168
src/main/kotlin/app/revanced/patcher/data/BytecodeContext.kt
Normal file
168
src/main/kotlin/app/revanced/patcher/data/BytecodeContext.kt
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
package app.revanced.patcher.data
|
||||||
|
|
||||||
|
import app.revanced.patcher.PatcherContext
|
||||||
|
import app.revanced.patcher.PatcherOptions
|
||||||
|
import app.revanced.patcher.PatcherResult
|
||||||
|
import app.revanced.patcher.patch.Patch
|
||||||
|
import app.revanced.patcher.util.ClassMerger.merge
|
||||||
|
import app.revanced.patcher.util.ProxyClassList
|
||||||
|
import app.revanced.patcher.util.method.MethodWalker
|
||||||
|
import app.revanced.patcher.util.proxy.ClassProxy
|
||||||
|
import com.android.tools.smali.dexlib2.Opcodes
|
||||||
|
import com.android.tools.smali.dexlib2.iface.ClassDef
|
||||||
|
import com.android.tools.smali.dexlib2.iface.DexFile
|
||||||
|
import com.android.tools.smali.dexlib2.iface.Method
|
||||||
|
import lanchon.multidexlib2.BasicDexFileNamer
|
||||||
|
import lanchon.multidexlib2.DexIO
|
||||||
|
import lanchon.multidexlib2.MultiDexIO
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileFilter
|
||||||
|
import java.io.Flushable
|
||||||
|
import java.util.logging.Logger
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A context for bytecode.
|
||||||
|
* This holds the current state of the bytecode.
|
||||||
|
*
|
||||||
|
* @param options The [PatcherOptions] used to create this context.
|
||||||
|
*/
|
||||||
|
class BytecodeContext internal constructor(private val options: PatcherOptions) :
|
||||||
|
Context<List<PatcherResult.PatchedDexFile>> {
|
||||||
|
private val logger = Logger.getLogger(BytecodeContext::class.java.name)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Opcodes] of the supplied [PatcherOptions.inputFile].
|
||||||
|
*/
|
||||||
|
internal lateinit var opcodes: Opcodes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of classes.
|
||||||
|
*/
|
||||||
|
val classes by lazy {
|
||||||
|
ProxyClassList(
|
||||||
|
MultiDexIO.readDexFile(
|
||||||
|
true, options.inputFile, BasicDexFileNamer(), null, null
|
||||||
|
).also { opcodes = it.opcodes }.classes.toMutableSet()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [Integrations] of this [PatcherContext].
|
||||||
|
*/
|
||||||
|
internal val integrations = Integrations()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a class by a given class name.
|
||||||
|
*
|
||||||
|
* @param className The name of the class.
|
||||||
|
* @return A proxy for the first class that matches the class name.
|
||||||
|
*/
|
||||||
|
fun findClass(className: String) = findClass { it.type.contains(className) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a class by a given predicate.
|
||||||
|
*
|
||||||
|
* @param predicate A predicate to match the class.
|
||||||
|
* @return A proxy for the first class that matches the predicate.
|
||||||
|
*/
|
||||||
|
fun findClass(predicate: (ClassDef) -> Boolean) =
|
||||||
|
// if we already proxied the class matching the predicate...
|
||||||
|
classes.proxies.firstOrNull { predicate(it.immutableClass) } ?:
|
||||||
|
// else resolve the class to a proxy and return it, if the predicate is matching a class
|
||||||
|
classes.find(predicate)?.let { proxy(it) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proxy a class.
|
||||||
|
* This will allow the class to be modified.
|
||||||
|
*
|
||||||
|
* @param classDef The class to proxy.
|
||||||
|
* @return A proxy for the class.
|
||||||
|
*/
|
||||||
|
fun proxy(classDef: ClassDef) = this.classes.proxies.find { it.immutableClass.type == classDef.type } ?: let {
|
||||||
|
ClassProxy(classDef).also { this.classes.add(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a [MethodWalker] instance for the current [BytecodeContext].
|
||||||
|
*
|
||||||
|
* @param startMethod The method to start at.
|
||||||
|
* @return A [MethodWalker] instance.
|
||||||
|
*/
|
||||||
|
fun toMethodWalker(startMethod: Method) = MethodWalker(this, startMethod)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compile bytecode from the [BytecodeContext].
|
||||||
|
*
|
||||||
|
* @return The compiled bytecode.
|
||||||
|
*/
|
||||||
|
override fun get(): List<PatcherResult.PatchedDexFile> {
|
||||||
|
logger.info("Compiling patched dex files")
|
||||||
|
|
||||||
|
val patchedDexFileResults = options.resourceCachePath.resolve("dex").also {
|
||||||
|
it.deleteRecursively() // Make sure the directory is empty.
|
||||||
|
it.mkdirs()
|
||||||
|
}.apply {
|
||||||
|
MultiDexIO.writeDexFile(
|
||||||
|
true,
|
||||||
|
if (options.multithreadingDexFileWriter) -1 else 1,
|
||||||
|
this,
|
||||||
|
BasicDexFileNamer(),
|
||||||
|
object : DexFile {
|
||||||
|
override fun getClasses() = this@BytecodeContext.classes.also(ProxyClassList::replaceClasses)
|
||||||
|
override fun getOpcodes() = this@BytecodeContext.opcodes
|
||||||
|
},
|
||||||
|
DexIO.DEFAULT_MAX_DEX_POOL_SIZE
|
||||||
|
) { _, entryName, _ -> logger.info("Compiled $entryName") }
|
||||||
|
}.listFiles(FileFilter { it.isFile })!!.map { PatcherResult.PatchedDexFile(it.name, it.inputStream()) }
|
||||||
|
|
||||||
|
System.gc()
|
||||||
|
|
||||||
|
return patchedDexFileResults
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The integrations of a [PatcherContext].
|
||||||
|
*/
|
||||||
|
internal inner class Integrations : MutableList<File> by mutableListOf(), Flushable {
|
||||||
|
/**
|
||||||
|
* Whether to merge integrations.
|
||||||
|
* Set to true, if the field requiresIntegrations of any supplied [Patch] is true.
|
||||||
|
*/
|
||||||
|
var merge = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge integrations into the [BytecodeContext] and flush all [Integrations].
|
||||||
|
*/
|
||||||
|
override fun flush() {
|
||||||
|
if (!merge) return
|
||||||
|
|
||||||
|
logger.info("Merging integrations")
|
||||||
|
|
||||||
|
val classMap = classes.associateBy { it.type }
|
||||||
|
|
||||||
|
this@Integrations.forEach { integrations ->
|
||||||
|
MultiDexIO.readDexFile(
|
||||||
|
true,
|
||||||
|
integrations, BasicDexFileNamer(),
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
).classes.forEach classDef@{ classDef ->
|
||||||
|
val existingClass = classMap[classDef.type] ?: run {
|
||||||
|
logger.fine("Adding $classDef")
|
||||||
|
classes.add(classDef)
|
||||||
|
return@classDef
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.fine("$classDef exists. Adding missing methods and fields.")
|
||||||
|
|
||||||
|
existingClass.merge(classDef, this@BytecodeContext).let { mergedClass ->
|
||||||
|
// If the class was merged, replace the original class with the merged class.
|
||||||
|
if (mergedClass === existingClass) return@let
|
||||||
|
classes.apply { remove(existingClass); add(mergedClass) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,181 +1,9 @@
|
|||||||
package app.revanced.patcher.data
|
package app.revanced.patcher.data
|
||||||
|
|
||||||
import app.revanced.patcher.util.ProxyBackedClassList
|
import java.util.function.Supplier
|
||||||
import app.revanced.patcher.util.method.MethodWalker
|
|
||||||
import org.jf.dexlib2.iface.ClassDef
|
|
||||||
import org.jf.dexlib2.iface.Method
|
|
||||||
import org.w3c.dom.Document
|
|
||||||
import java.io.Closeable
|
|
||||||
import java.io.File
|
|
||||||
import java.io.InputStream
|
|
||||||
import java.io.OutputStream
|
|
||||||
import javax.xml.parsers.DocumentBuilderFactory
|
|
||||||
import javax.xml.transform.TransformerFactory
|
|
||||||
import javax.xml.transform.dom.DOMSource
|
|
||||||
import javax.xml.transform.stream.StreamResult
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A common interface to constrain [Context] to [BytecodeContext] and [ResourceContext].
|
* A common interface for contexts such as [ResourceContext] and [BytecodeContext].
|
||||||
*/
|
*/
|
||||||
|
|
||||||
sealed interface Context
|
sealed interface Context<T> : Supplier<T>
|
||||||
|
|
||||||
class BytecodeContext internal constructor(classes: MutableList<ClassDef>) : Context {
|
|
||||||
/**
|
|
||||||
* The list of classes.
|
|
||||||
*/
|
|
||||||
val classes = ProxyBackedClassList(classes)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find a class by a given class name.
|
|
||||||
*
|
|
||||||
* @param className The name of the class.
|
|
||||||
* @return A proxy for the first class that matches the class name.
|
|
||||||
*/
|
|
||||||
fun findClass(className: String) = findClass { it.type.contains(className) }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find a class by a given predicate.
|
|
||||||
*
|
|
||||||
* @param predicate A predicate to match the class.
|
|
||||||
* @return A proxy for the first class that matches the predicate.
|
|
||||||
*/
|
|
||||||
fun findClass(predicate: (ClassDef) -> Boolean) =
|
|
||||||
// if we already proxied the class matching the predicate...
|
|
||||||
classes.proxies.firstOrNull { predicate(it.immutableClass) } ?:
|
|
||||||
// else resolve the class to a proxy and return it, if the predicate is matching a class
|
|
||||||
classes.find(predicate)?.let { proxy(it) }
|
|
||||||
|
|
||||||
fun proxy(classDef: ClassDef): app.revanced.patcher.util.proxy.ClassProxy {
|
|
||||||
var proxy = this.classes.proxies.find { it.immutableClass.type == classDef.type }
|
|
||||||
if (proxy == null) {
|
|
||||||
proxy = app.revanced.patcher.util.proxy.ClassProxy(classDef)
|
|
||||||
this.classes.add(proxy)
|
|
||||||
}
|
|
||||||
return proxy
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a [MethodWalker] instance for the current [BytecodeContext].
|
|
||||||
*
|
|
||||||
* @param startMethod The method to start at.
|
|
||||||
* @return A [MethodWalker] instance.
|
|
||||||
*/
|
|
||||||
fun BytecodeContext.toMethodWalker(startMethod: Method): MethodWalker {
|
|
||||||
return MethodWalker(this, startMethod)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal inline fun <T> Iterable<T>.findIndexed(predicate: (T) -> Boolean): Pair<T, Int>? {
|
|
||||||
for ((index, element) in this.withIndex()) {
|
|
||||||
if (predicate(element)) {
|
|
||||||
return element to index
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
class ResourceContext internal constructor(private val resourceCacheDirectory: File) : Context, Iterable<File> {
|
|
||||||
val xmlEditor = XmlFileHolder()
|
|
||||||
|
|
||||||
operator fun get(path: String) = resourceCacheDirectory.resolve(path)
|
|
||||||
|
|
||||||
override fun iterator() = resourceCacheDirectory.walkTopDown().iterator()
|
|
||||||
|
|
||||||
inner class XmlFileHolder {
|
|
||||||
operator fun get(inputStream: InputStream) =
|
|
||||||
DomFileEditor(inputStream)
|
|
||||||
|
|
||||||
operator fun get(path: String): DomFileEditor {
|
|
||||||
return DomFileEditor(this@ResourceContext[path])
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wrapper for a file that can be edited as a dom document.
|
|
||||||
*
|
|
||||||
* This constructor does not check for locks to the file when writing.
|
|
||||||
* Use the secondary constructor.
|
|
||||||
*
|
|
||||||
* @param inputStream the input stream to read the xml file from.
|
|
||||||
* @param outputStream the output stream to write the xml file to. If null, the file will be read only.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
class DomFileEditor internal constructor(
|
|
||||||
private val inputStream: InputStream,
|
|
||||||
private val outputStream: Lazy<OutputStream>? = null,
|
|
||||||
) : Closeable {
|
|
||||||
// path to the xml file to unlock the resource when closing the editor
|
|
||||||
private var filePath: String? = null
|
|
||||||
private var closed: Boolean = false
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The document of the xml file
|
|
||||||
*/
|
|
||||||
val file: Document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream)
|
|
||||||
.also(Document::normalize)
|
|
||||||
|
|
||||||
|
|
||||||
// lazily open an output stream
|
|
||||||
// this is required because when constructing a DomFileEditor the output stream is created along with the input stream, which is not allowed
|
|
||||||
// the workaround is to lazily create the output stream. This way it would be used after the input stream is closed, which happens in the constructor
|
|
||||||
constructor(file: File) : this(file.inputStream(), lazy { file.outputStream() }) {
|
|
||||||
// increase the lock
|
|
||||||
locks.merge(file.path, 1, Integer::sum)
|
|
||||||
filePath = file.path
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the editor. Write backs and decreases the lock count.
|
|
||||||
*
|
|
||||||
* Will not write back to the file if the file is still locked.
|
|
||||||
*/
|
|
||||||
override fun close() {
|
|
||||||
if (closed) return
|
|
||||||
|
|
||||||
inputStream.close()
|
|
||||||
|
|
||||||
// if the output stream is not null, do not close it
|
|
||||||
outputStream?.let {
|
|
||||||
// prevent writing to same file, if it is being locked
|
|
||||||
// isLocked will be false if the editor was created through a stream
|
|
||||||
val isLocked = filePath?.let { path ->
|
|
||||||
val isLocked = locks[path]!! > 1
|
|
||||||
// decrease the lock count if the editor was opened for a file
|
|
||||||
locks.merge(path, -1, Integer::sum)
|
|
||||||
isLocked
|
|
||||||
} ?: false
|
|
||||||
|
|
||||||
// if unlocked, write back to the file
|
|
||||||
if (!isLocked) {
|
|
||||||
it.value.use { stream ->
|
|
||||||
val result = StreamResult(stream)
|
|
||||||
TransformerFactory.newInstance().newTransformer().transform(DOMSource(file), result)
|
|
||||||
}
|
|
||||||
|
|
||||||
it.value.close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
closed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
private companion object {
|
|
||||||
// map of concurrent open files
|
|
||||||
val locks = mutableMapOf<String, Int>()
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,13 +0,0 @@
|
|||||||
package app.revanced.patcher.data
|
|
||||||
|
|
||||||
import brut.androlib.meta.MetaInfo
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Metadata about a package.
|
|
||||||
*/
|
|
||||||
class PackageMetadata {
|
|
||||||
lateinit var packageName: String
|
|
||||||
lateinit var packageVersion: String
|
|
||||||
|
|
||||||
internal val metaInfo: MetaInfo = MetaInfo()
|
|
||||||
}
|
|
170
src/main/kotlin/app/revanced/patcher/data/ResourceContext.kt
Normal file
170
src/main/kotlin/app/revanced/patcher/data/ResourceContext.kt
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
package app.revanced.patcher.data
|
||||||
|
|
||||||
|
import app.revanced.patcher.PatcherContext
|
||||||
|
import app.revanced.patcher.PatcherOptions
|
||||||
|
import app.revanced.patcher.util.DomFileEditor
|
||||||
|
import brut.androlib.AaptInvoker
|
||||||
|
import brut.androlib.ApkDecoder
|
||||||
|
import brut.androlib.apk.UsesFramework
|
||||||
|
import brut.androlib.res.Framework
|
||||||
|
import brut.androlib.res.ResourcesDecoder
|
||||||
|
import brut.androlib.res.decoder.AndroidManifestResourceParser
|
||||||
|
import brut.androlib.res.decoder.XmlPullStreamDecoder
|
||||||
|
import brut.androlib.res.xml.ResXmlPatcher
|
||||||
|
import brut.directory.ExtFile
|
||||||
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.util.logging.Logger
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A context for resources.
|
||||||
|
* This holds the current state of the resources.
|
||||||
|
*
|
||||||
|
* @param context The [PatcherContext] to create the context for.
|
||||||
|
*/
|
||||||
|
class ResourceContext internal constructor(
|
||||||
|
private val context: PatcherContext,
|
||||||
|
private val options: PatcherOptions
|
||||||
|
) : Context<File?>, Iterable<File> {
|
||||||
|
private val logger = Logger.getLogger(ResourceContext::class.java.name)
|
||||||
|
|
||||||
|
val xmlEditor = XmlFileHolder()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode resources for the patcher.
|
||||||
|
*
|
||||||
|
* @param mode The [ResourceDecodingMode] to use when decoding.
|
||||||
|
*/
|
||||||
|
internal fun decodeResources(mode: ResourceDecodingMode) = with(context.packageMetadata.apkInfo) {
|
||||||
|
// Needed to decode resources.
|
||||||
|
val resourcesDecoder = ResourcesDecoder(options.resourceConfig, this)
|
||||||
|
|
||||||
|
when (mode) {
|
||||||
|
ResourceDecodingMode.FULL -> {
|
||||||
|
val outDir = options.recreateResourceCacheDirectory()
|
||||||
|
|
||||||
|
logger.info("Decoding resources")
|
||||||
|
|
||||||
|
resourcesDecoder.decodeResources(outDir)
|
||||||
|
resourcesDecoder.decodeManifest(outDir)
|
||||||
|
|
||||||
|
// Needed to record uncompressed files.
|
||||||
|
val apkDecoder = ApkDecoder(options.resourceConfig, this)
|
||||||
|
apkDecoder.recordUncompressedFiles(resourcesDecoder.resFileMapping)
|
||||||
|
|
||||||
|
usesFramework = UsesFramework().apply {
|
||||||
|
ids = resourcesDecoder.resTable.listFramePackages().map { it.id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceDecodingMode.MANIFEST_ONLY -> {
|
||||||
|
logger.info("Decoding app manifest")
|
||||||
|
|
||||||
|
// Decode manually instead of using resourceDecoder.decodeManifest
|
||||||
|
// because it does not support decoding to an OutputStream.
|
||||||
|
XmlPullStreamDecoder(
|
||||||
|
AndroidManifestResourceParser(resourcesDecoder.resTable),
|
||||||
|
resourcesDecoder.resXmlSerializer
|
||||||
|
).decodeManifest(
|
||||||
|
apkFile.directory.getFileInput("AndroidManifest.xml"),
|
||||||
|
// Older Android versions do not support OutputStream.nullOutputStream()
|
||||||
|
object : OutputStream() {
|
||||||
|
override fun write(b: Int) { /* do nothing */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get the package name and version from the manifest using the XmlPullStreamDecoder.
|
||||||
|
// XmlPullStreamDecoder.decodeManifest() sets metadata.apkInfo.
|
||||||
|
context.packageMetadata.let { metadata ->
|
||||||
|
metadata.packageName = resourcesDecoder.resTable.packageRenamed
|
||||||
|
versionInfo.let {
|
||||||
|
metadata.packageVersion = it.versionName ?: it.versionCode
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
The ResTable if flagged as sparse if the main package is not loaded, which is the case here,
|
||||||
|
because ResourcesDecoder.decodeResources loads the main package
|
||||||
|
and not XmlPullStreamDecoder.decodeManifest.
|
||||||
|
See ARSCDecoder.readTableType for more info.
|
||||||
|
|
||||||
|
Set this to false again to prevent the ResTable from being flagged as sparse falsely.
|
||||||
|
*/
|
||||||
|
metadata.apkInfo.sparseResources = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun get(path: String) = options.resourceCachePath.resolve(path)
|
||||||
|
|
||||||
|
override fun iterator() = options.resourceCachePath.walkTopDown().iterator()
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compile resources from the [ResourceContext].
|
||||||
|
*
|
||||||
|
* @return The compiled resources.
|
||||||
|
*/
|
||||||
|
override fun get(): File? {
|
||||||
|
var resourceFile: File? = null
|
||||||
|
|
||||||
|
if (options.resourceDecodingMode == ResourceDecodingMode.FULL) {
|
||||||
|
logger.info("Compiling modified resources")
|
||||||
|
|
||||||
|
val cacheDirectory = ExtFile(options.resourceCachePath)
|
||||||
|
val aaptFile = cacheDirectory.resolve("aapt_temp_file").also {
|
||||||
|
Files.deleteIfExists(it.toPath())
|
||||||
|
}.also { resourceFile = it }
|
||||||
|
|
||||||
|
try {
|
||||||
|
AaptInvoker(
|
||||||
|
options.resourceConfig, context.packageMetadata.apkInfo
|
||||||
|
).invokeAapt(aaptFile,
|
||||||
|
cacheDirectory.resolve("AndroidManifest.xml").also {
|
||||||
|
ResXmlPatcher.fixingPublicAttrsInProviderAttributes(it)
|
||||||
|
},
|
||||||
|
cacheDirectory.resolve("res"),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
context.packageMetadata.apkInfo.usesFramework.let { usesFramework ->
|
||||||
|
usesFramework.ids.map { id ->
|
||||||
|
Framework(options.resourceConfig).getFrameworkApk(id, usesFramework.tag)
|
||||||
|
}.toTypedArray()
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
cacheDirectory.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceFile
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of decoding the resources.
|
||||||
|
*/
|
||||||
|
internal enum class ResourceDecodingMode {
|
||||||
|
/**
|
||||||
|
* Decode all resources.
|
||||||
|
*/
|
||||||
|
FULL,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode the manifest file only.
|
||||||
|
*/
|
||||||
|
MANIFEST_ONLY,
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class XmlFileHolder {
|
||||||
|
operator fun get(inputStream: InputStream) =
|
||||||
|
DomFileEditor(inputStream)
|
||||||
|
|
||||||
|
operator fun get(path: String): DomFileEditor {
|
||||||
|
return DomFileEditor(this@ResourceContext[path])
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@@ -1,83 +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 app.revanced.patcher.patch.annotations.RequiresIntegrations
|
|
||||||
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
|
|
||||||
|
|
||||||
internal val Class<out Patch<Context>>.requiresIntegrations
|
|
||||||
get() = findAnnotationRecursively(RequiresIntegrations::class) != null
|
|
||||||
|
|
||||||
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,238 +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 com.android.tools.smali.dexlib2.AccessFlags
|
||||||
import app.revanced.patcher.util.smali.ExternalLabel
|
|
||||||
import app.revanced.patcher.util.smali.toInstruction
|
|
||||||
import app.revanced.patcher.util.smali.toInstructions
|
|
||||||
import org.jf.dexlib2.AccessFlags
|
|
||||||
import org.jf.dexlib2.builder.BuilderInstruction
|
|
||||||
import org.jf.dexlib2.builder.BuilderOffsetInstruction
|
|
||||||
import org.jf.dexlib2.builder.Label
|
|
||||||
import org.jf.dexlib2.builder.MutableMethodImplementation
|
|
||||||
import org.jf.dexlib2.builder.instruction.*
|
|
||||||
import org.jf.dexlib2.iface.Method
|
|
||||||
import org.jf.dexlib2.iface.instruction.Instruction
|
|
||||||
import org.jf.dexlib2.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 - 1 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 an instruction at the 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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get an instruction at the given index in the method's implementation.
|
* Perform a bitwise OR operation between an [AccessFlags] and an [Int].
|
||||||
* @param index The index to get the instruction at.
|
*
|
||||||
* @param T The type of instruction to return.
|
* @param other The [Int] to perform the operation with.
|
||||||
* @return The instruction.
|
|
||||||
*/
|
*/
|
||||||
fun <T> MutableMethod.instruction(index: Int): T = instruction(index) as T
|
infix fun Int.or(other: AccessFlags) = this or other.value
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add smali instructions to the method.
|
* Perform a bitwise OR operation between an [Int] and an [AccessFlags].
|
||||||
* @param index The index to insert the instructions at.
|
*
|
||||||
* @param smali The smali instructions to add.
|
* @param other The [AccessFlags] 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 AccessFlags.or(other: Int) = value or other
|
||||||
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.
|
|
||||||
* @param instructions The smali instructions to add.
|
|
||||||
*/
|
|
||||||
fun MutableMethod.addInstructions(instructions: String, labels: List<ExternalLabel> = emptyList()) =
|
|
||||||
this.addInstructions(this.implementation!!.instructions.size, instructions, labels)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replace smali instructions within the method.
|
|
||||||
* @param index The index to replace the instructions at.
|
|
||||||
* @param instructions The smali instructions to place.
|
|
||||||
*/
|
|
||||||
fun MutableMethod.replaceInstructions(index: Int, instructions: String) =
|
|
||||||
this.implementation!!.replaceInstructions(index, instructions.toInstructions(this))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove smali instructions from the method.
|
|
||||||
* @param index The index to remove the instructions at.
|
|
||||||
* @param count The amount of instructions to remove.
|
|
||||||
*/
|
|
||||||
fun MutableMethod.removeInstructions(index: Int, count: Int) = this.implementation!!.removeInstructions(index, count)
|
|
||||||
|
|
||||||
private fun replaceOffset(
|
|
||||||
i: BuilderOffsetInstruction, label: Label
|
|
||||||
): BuilderOffsetInstruction {
|
|
||||||
return when (i) {
|
|
||||||
is BuilderInstruction10t -> BuilderInstruction10t(i.opcode, label)
|
|
||||||
is BuilderInstruction20t -> BuilderInstruction20t(i.opcode, label)
|
|
||||||
is BuilderInstruction21t -> BuilderInstruction21t(i.opcode, i.registerA, label)
|
|
||||||
is BuilderInstruction22t -> BuilderInstruction22t(i.opcode, i.registerA, i.registerB, label)
|
|
||||||
is BuilderInstruction30t -> BuilderInstruction30t(i.opcode, label)
|
|
||||||
is BuilderInstruction31t -> BuilderInstruction31t(i.opcode, i.registerA, label)
|
|
||||||
else -> throw IllegalStateException("A non-offset instruction was given, this should never happen!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clones the method.
|
|
||||||
* @param registerCount This parameter allows you to change the register count of the method.
|
|
||||||
* This may be a positive or negative number.
|
|
||||||
* @return The **mutable** cloned method. Call [clone] to get an **immutable** copy.
|
|
||||||
*/
|
|
||||||
internal fun Method.cloneMutable(registerCount: Int = 0) = clone(registerCount).toMutable()
|
|
||||||
|
|
||||||
// FIXME: also check the order of parameters as different order equals different method overload
|
|
||||||
internal fun parametersEqual(
|
|
||||||
parameters1: Iterable<CharSequence>, parameters2: Iterable<CharSequence>
|
|
||||||
): Boolean {
|
|
||||||
return parameters1.count() == parameters2.count() && parameters1.all { parameter ->
|
|
||||||
parameters2.any {
|
|
||||||
it.startsWith(
|
|
||||||
parameter
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal val nullOutputStream = object : OutputStream() {
|
|
||||||
override fun write(b: Int) {}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user