mirror of
https://github.com/revanced/revanced-patches
synced 2024-11-19 03:57:27 +01:00
build: Bump ReVanced Patcher
BREAKING CHANGE: Various APIs have been changed or removed.
This commit is contained in:
parent
5848269c2e
commit
eee1692277
6
.github/workflows/build_pull_request.yml
vendored
6
.github/workflows/build_pull_request.yml
vendored
@ -16,6 +16,12 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: "17"
|
||||
|
||||
- name: Cache Gradle
|
||||
uses: burrunan/gradle-cache-action@v1
|
||||
|
||||
|
5
.github/workflows/open_pull_request.yml
vendored
5
.github/workflows/open_pull_request.yml
vendored
@ -20,13 +20,12 @@ jobs:
|
||||
- name: Open pull request
|
||||
uses: repo-sync/pull-request@v2
|
||||
with:
|
||||
destination_branch: "main"
|
||||
pr_title: "chore: ${{ env.MESSAGE }}"
|
||||
destination_branch: main
|
||||
pr_title: 'chore: ${{ env.MESSAGE }}'
|
||||
pr_body: |
|
||||
This pull request will ${{ env.MESSAGE }}.
|
||||
|
||||
## Before merging this PR
|
||||
|
||||
- [ ] Remember about https://github.com/revanced/revanced-integrations
|
||||
- [ ] Pull translations from Crowdin
|
||||
pr_draft: true
|
||||
|
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@ -23,13 +23,19 @@ jobs:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: "17"
|
||||
|
||||
- name: Cache Gradle
|
||||
uses: burrunan/gradle-cache-action@v1
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: ./gradlew generateMeta clean
|
||||
run: ./gradlew build clean
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -122,5 +122,8 @@ gradle-app.setting
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
|
||||
# gradle properties, due to Github token
|
||||
# Gradle properties, due to Github token
|
||||
./gradle.properties
|
||||
|
||||
# One package is called the same as the Gradle build folder
|
||||
!**/src/**/build/
|
@ -4,5 +4,5 @@
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
<file type="web" url="file://$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="azul-17" project-jdk-type="JavaSDK" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="azul-17" project-jdk-type="JavaSDK" />
|
||||
</project>
|
@ -23,7 +23,6 @@
|
||||
"assets": [
|
||||
"CHANGELOG.md",
|
||||
"gradle.properties",
|
||||
"patches.json"
|
||||
],
|
||||
"message": "chore: Release v${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
|
||||
}
|
||||
@ -33,11 +32,8 @@
|
||||
{
|
||||
"assets": [
|
||||
{
|
||||
"path": "build/libs/revanced-patches*"
|
||||
"path": "patches/build/libs/patches-!(*sources*|*javadoc*).rvp?(.asc)"
|
||||
},
|
||||
{
|
||||
"path": "patches.json"
|
||||
}
|
||||
],
|
||||
successComment: false
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
155
build.gradle.kts
155
build.gradle.kts
@ -1,155 +0,0 @@
|
||||
import org.gradle.kotlin.dsl.support.listFilesOrdered
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin)
|
||||
alias(libs.plugins.binary.compatibility.validator)
|
||||
`maven-publish`
|
||||
signing
|
||||
}
|
||||
|
||||
group = "app.revanced"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
google()
|
||||
maven {
|
||||
// A repository must be specified for some reason. "registry" is a dummy.
|
||||
url = uri("https://maven.pkg.github.com/revanced/registry")
|
||||
credentials {
|
||||
username = project.findProperty("gpr.user") as String? ?: System.getenv("GITHUB_ACTOR")
|
||||
password = project.findProperty("gpr.key") as String? ?: System.getenv("GITHUB_TOKEN")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.revanced.patcher)
|
||||
implementation(libs.smali)
|
||||
// TODO: Required because build fails without it. Find a way to remove this dependency.
|
||||
implementation(libs.guava)
|
||||
// Used in JsonGenerator.
|
||||
implementation(libs.gson)
|
||||
// Android API stubs defined here.
|
||||
compileOnly(project(":stub"))
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
jvmTarget.set(JvmTarget.JVM_11)
|
||||
}
|
||||
}
|
||||
|
||||
java {
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
tasks {
|
||||
withType(Jar::class) {
|
||||
exclude("app/revanced/meta")
|
||||
|
||||
manifest {
|
||||
attributes["Name"] = "ReVanced Patches"
|
||||
attributes["Description"] = "Patches for ReVanced."
|
||||
attributes["Version"] = version
|
||||
attributes["Timestamp"] = System.currentTimeMillis().toString()
|
||||
attributes["Source"] = "git@github.com:revanced/revanced-patches.git"
|
||||
attributes["Author"] = "ReVanced"
|
||||
attributes["Contact"] = "contact@revanced.app"
|
||||
attributes["Origin"] = "https://revanced.app"
|
||||
attributes["License"] = "GNU General Public License v3.0"
|
||||
}
|
||||
}
|
||||
|
||||
register("buildDexJar") {
|
||||
description = "Build and add a DEX to the JAR file"
|
||||
group = "build"
|
||||
|
||||
dependsOn(build)
|
||||
|
||||
doLast {
|
||||
val d8 = File(System.getenv("ANDROID_HOME")).resolve("build-tools")
|
||||
.listFilesOrdered().last().resolve("d8").absolutePath
|
||||
|
||||
val patchesJar = configurations.archives.get().allArtifacts.files.files.first().absolutePath
|
||||
val workingDirectory = layout.buildDirectory.dir("libs").get().asFile
|
||||
|
||||
exec {
|
||||
workingDir = workingDirectory
|
||||
commandLine = listOf(d8, "--release", patchesJar)
|
||||
}
|
||||
|
||||
exec {
|
||||
workingDir = workingDirectory
|
||||
commandLine = listOf("zip", "-u", patchesJar, "classes.dex")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
register<JavaExec>("generatePatchesFiles") {
|
||||
description = "Generate patches files"
|
||||
|
||||
dependsOn(build)
|
||||
|
||||
classpath = sourceSets["main"].runtimeClasspath
|
||||
mainClass.set("app.revanced.generator.MainKt")
|
||||
}
|
||||
|
||||
// Needed by gradle-semantic-release-plugin.
|
||||
// Tracking: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435
|
||||
publish {
|
||||
dependsOn("buildDexJar")
|
||||
dependsOn("generatePatchesFiles")
|
||||
}
|
||||
}
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
maven {
|
||||
name = "GitHubPackages"
|
||||
url = uri("https://maven.pkg.github.com/revanced/revanced-patches")
|
||||
credentials {
|
||||
username = System.getenv("GITHUB_ACTOR")
|
||||
password = System.getenv("GITHUB_TOKEN")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
publications {
|
||||
create<MavenPublication>("revanced-patches-publication") {
|
||||
from(components["java"])
|
||||
|
||||
pom {
|
||||
name = "ReVanced Patches"
|
||||
description = "Patches for 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-patches.git"
|
||||
developerConnection = "scm:git:git@github.com:revanced/revanced-patches.git"
|
||||
url = "https://github.com/revanced/revanced-patches"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
signing {
|
||||
useGpgCmd()
|
||||
|
||||
sign(publishing.publications["revanced-patches-publication"])
|
||||
}
|
@ -3,6 +3,6 @@ api_token_env: "CROWDIN_PERSONAL_TOKEN"
|
||||
|
||||
preserve_hierarchy: false
|
||||
files:
|
||||
- source: src/main/resources/addresources/values/strings.xml
|
||||
translation: src/main/resources/addresources/values-%android_code%/strings.xml
|
||||
- source: patches/src/main/resources/addresources/values/strings.xml
|
||||
translation: patches/src/main/resources/addresources/values-%android_code%/strings.xml
|
||||
skip_untranslated_strings: true
|
||||
|
@ -1,4 +1,5 @@
|
||||
org.gradle.parallel = true
|
||||
org.gradle.caching = true
|
||||
android.useAndroidX=true
|
||||
kotlin.code.style = official
|
||||
version = 4.18.0-dev.6
|
||||
|
@ -1,18 +1,26 @@
|
||||
[versions]
|
||||
revanced-patcher = "19.3.1"
|
||||
revanced-patcher = "20.0.2"
|
||||
# Tracking https://github.com/google/smali/issues/64.
|
||||
#noinspection GradleDependency
|
||||
smali = "3.0.5" # 3.0.7 breaks binary compatibility. Tracking https://github.com/google/smali/issues/58.
|
||||
guava = "33.2.1-jre"
|
||||
smali = "3.0.5"
|
||||
gson = "2.11.0"
|
||||
binary-compatibility-validator = "0.15.1"
|
||||
kotlin = "2.0.0"
|
||||
# 8.3.0 causes java verifier error: https://github.com/ReVanced/revanced-patches/issues/2818.
|
||||
#noinspection GradleDependency
|
||||
agp = "8.2.2"
|
||||
annotation = "1.9.0"
|
||||
appcompat = "1.7.0"
|
||||
okhttp = "5.0.0-alpha.14"
|
||||
retrofit = "2.11.0"
|
||||
guava = "33.2.1-jre"
|
||||
|
||||
[libraries]
|
||||
revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" }
|
||||
smali = { module = "com.android.tools.smali:smali", version.ref = "smali" }
|
||||
guava = { module = "com.google.guava:guava", version.ref = "guava" }
|
||||
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
|
||||
annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" }
|
||||
appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
||||
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
|
||||
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
|
||||
guava = { module = "com.google.guava:guava", version.ref = "guava" }
|
||||
|
||||
|
||||
[plugins]
|
||||
binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binary-compatibility-validator" }
|
||||
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
||||
android-library = { id = "com.android.library", version.ref = "agp" }
|
||||
|
1556
patches/api/patches.api
Normal file
1556
patches/api/patches.api
Normal file
File diff suppressed because it is too large
Load Diff
35
patches/build.gradle.kts
Normal file
35
patches/build.gradle.kts
Normal file
@ -0,0 +1,35 @@
|
||||
group = "app.revanced"
|
||||
|
||||
patches {
|
||||
about {
|
||||
name = "ReVanced Patches"
|
||||
description = "Patches for ReVanced"
|
||||
source = "git@github.com:revanced/revanced-patches.git"
|
||||
author = "ReVanced"
|
||||
contact = "contact@revanced.app"
|
||||
website = "https://revanced.app"
|
||||
license = "GNU General Public License v3.0"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Used by JsonGenerator.
|
||||
implementation(libs.gson)
|
||||
// Required due to smali, or build fails. Can be removed once smali is bumped.
|
||||
implementation(libs.guava)
|
||||
// Android API stubs defined here.
|
||||
compileOnly(project(":patches:stub"))
|
||||
}
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
maven {
|
||||
name = "GitHubPackages"
|
||||
url = uri("https://maven.pkg.github.com/revanced/revanced-patches")
|
||||
credentials {
|
||||
username = System.getenv("GITHUB_ACTOR")
|
||||
password = System.getenv("GITHUB_TOKEN")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,27 +1,22 @@
|
||||
package app.revanced.patches.all.activity.exportall
|
||||
package app.revanced.patches.all.misc.activity.exportall
|
||||
|
||||
import app.revanced.patcher.data.ResourceContext
|
||||
import app.revanced.patcher.patch.ResourcePatch
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
|
||||
@Patch(
|
||||
@Suppress("unused")
|
||||
val exportAllActivitiesPatch = resourcePatch(
|
||||
name = "Export all activities",
|
||||
description = "Makes all app activities exportable.",
|
||||
use = false,
|
||||
)
|
||||
@Suppress("unused")
|
||||
object ExportAllActivitiesPatch : ResourcePatch() {
|
||||
private const val EXPORTED_FLAG = "android:exported"
|
||||
|
||||
override fun execute(context: ResourceContext) {
|
||||
context.xmlEditor["AndroidManifest.xml"].use { editor ->
|
||||
val document = editor.file
|
||||
) {
|
||||
execute { context ->
|
||||
val exportedFlag = "android:exported"
|
||||
|
||||
context.document["AndroidManifest.xml"].use { document ->
|
||||
val activities = document.getElementsByTagName("activity")
|
||||
|
||||
for (i in 0..activities.length) {
|
||||
activities.item(i)?.apply {
|
||||
val exportedAttribute = attributes.getNamedItem(EXPORTED_FLAG)
|
||||
val exportedAttribute = attributes.getNamedItem(exportedFlag)
|
||||
|
||||
if (exportedAttribute != null) {
|
||||
if (exportedAttribute.nodeValue != "true") {
|
||||
@ -31,7 +26,7 @@ object ExportAllActivitiesPatch : ResourcePatch() {
|
||||
// Reason why the attribute is added in the case it does not exist:
|
||||
// https://github.com/revanced/revanced-patches/pull/1751/files#r1141481604
|
||||
else {
|
||||
document.createAttribute(EXPORTED_FLAG)
|
||||
document.createAttribute(exportedFlag)
|
||||
.apply { value = "true" }
|
||||
.let(attributes::setNamedItem)
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
package app.revanced.patches.all.misc.build
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.all.misc.transformation.transformInstructionsPatch
|
||||
import app.revanced.util.getReference
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
|
||||
private const val BUILD_CLASS_DESCRIPTOR = "Landroid/os/Build;"
|
||||
|
||||
class BuildInfo(
|
||||
// The build information supported32BitAbis, supported64BitAbis, and supportedAbis are not supported for now,
|
||||
// because initializing an array in transform is a bit more complex.
|
||||
val board: String? = null,
|
||||
val bootloader: String? = null,
|
||||
val brand: String? = null,
|
||||
val cpuAbi: String? = null,
|
||||
val cpuAbi2: String? = null,
|
||||
val device: String? = null,
|
||||
val display: String? = null,
|
||||
val fingerprint: String? = null,
|
||||
val hardware: String? = null,
|
||||
val host: String? = null,
|
||||
val id: String? = null,
|
||||
val manufacturer: String? = null,
|
||||
val model: String? = null,
|
||||
val odmSku: String? = null,
|
||||
val product: String? = null,
|
||||
val radio: String? = null,
|
||||
val serial: String? = null,
|
||||
val sku: String? = null,
|
||||
val socManufacturer: String? = null,
|
||||
val socModel: String? = null,
|
||||
val tags: String? = null,
|
||||
val time: Long? = null,
|
||||
val type: String? = null,
|
||||
val user: String? = null,
|
||||
)
|
||||
|
||||
fun baseSpoofBuildInfoPatch(buildInfoSupplier: () -> BuildInfo) = bytecodePatch {
|
||||
// Lazy, so that patch options above are initialized before they are accessed.
|
||||
val replacements by lazy {
|
||||
with(buildInfoSupplier()) {
|
||||
buildMap {
|
||||
if (board != null) put("BOARD", "const-string" to "\"$board\"")
|
||||
if (bootloader != null) put("BOOTLOADER", "const-string" to "\"$bootloader\"")
|
||||
if (brand != null) put("BRAND", "const-string" to "\"$brand\"")
|
||||
if (cpuAbi != null) put("CPU_ABI", "const-string" to "\"$cpuAbi\"")
|
||||
if (cpuAbi2 != null) put("CPU_ABI2", "const-string" to "\"$cpuAbi2\"")
|
||||
if (device != null) put("DEVICE", "const-string" to "\"$device\"")
|
||||
if (display != null) put("DISPLAY", "const-string" to "\"$display\"")
|
||||
if (fingerprint != null) put("FINGERPRINT", "const-string" to "\"$fingerprint\"")
|
||||
if (hardware != null) put("HARDWARE", "const-string" to "\"$hardware\"")
|
||||
if (host != null) put("HOST", "const-string" to "\"$host\"")
|
||||
if (id != null) put("ID", "const-string" to "\"$id\"")
|
||||
if (manufacturer != null) put("MANUFACTURER", "const-string" to "\"$manufacturer\"")
|
||||
if (model != null) put("MODEL", "const-string" to "\"$model\"")
|
||||
if (odmSku != null) put("ODM_SKU", "const-string" to "\"$odmSku\"")
|
||||
if (product != null) put("PRODUCT", "const-string" to "\"$product\"")
|
||||
if (radio != null) put("RADIO", "const-string" to "\"$radio\"")
|
||||
if (serial != null) put("SERIAL", "const-string" to "\"$serial\"")
|
||||
if (sku != null) put("SKU", "const-string" to "\"$sku\"")
|
||||
if (socManufacturer != null) put("SOC_MANUFACTURER", "const-string" to "\"$socManufacturer\"")
|
||||
if (socModel != null) put("SOC_MODEL", "const-string" to "\"$socModel\"")
|
||||
if (tags != null) put("TAGS", "const-string" to "\"$tags\"")
|
||||
if (time != null) put("TIME", "const-wide" to "$time")
|
||||
if (type != null) put("TYPE", "const-string" to "\"$type\"")
|
||||
if (user != null) put("USER", "const-string" to "\"$user\"")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependsOn(
|
||||
transformInstructionsPatch(
|
||||
filterMap = filterMap@{ _, _, instruction, instructionIndex ->
|
||||
val reference = instruction.getReference<FieldReference>() ?: return@filterMap null
|
||||
if (reference.definingClass != BUILD_CLASS_DESCRIPTOR) return@filterMap null
|
||||
|
||||
return@filterMap replacements[reference.name]?.let { instructionIndex to it }
|
||||
},
|
||||
transform = { mutableMethod, entry ->
|
||||
val (index, replacement) = entry
|
||||
val (opcode, operand) = replacement
|
||||
val register = mutableMethod.getInstruction<OneRegisterInstruction>(index).registerA
|
||||
|
||||
mutableMethod.replaceInstruction(index, "$opcode v$register, $operand")
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
@ -1,183 +1,214 @@
|
||||
package app.revanced.patches.all.misc.build
|
||||
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.longPatchOption
|
||||
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.patch.longOption
|
||||
import app.revanced.patcher.patch.stringOption
|
||||
|
||||
@Patch(
|
||||
@Suppress("unused")
|
||||
val spoofBuildInfoPatch = bytecodePatch(
|
||||
name = "Spoof build info",
|
||||
description = "Spoof the information about the current build.",
|
||||
use = false
|
||||
)
|
||||
@Suppress("unused")
|
||||
class SpoofBuildInfoPatch : BaseSpoofBuildInfoPatch() {
|
||||
override val board by stringPatchOption(
|
||||
use = false,
|
||||
) {
|
||||
val board by stringOption(
|
||||
key = "board",
|
||||
default = null,
|
||||
title = "Board",
|
||||
description = "The name of the underlying board, like \"goldfish\"."
|
||||
description = "The name of the underlying board, like \"goldfish\".",
|
||||
)
|
||||
|
||||
override val bootloader by stringPatchOption(
|
||||
val bootloader by stringOption(
|
||||
key = "bootloader",
|
||||
default = null,
|
||||
title = "Bootloader",
|
||||
description = "The system bootloader version number."
|
||||
description = "The system bootloader version number.",
|
||||
)
|
||||
|
||||
override val brand by stringPatchOption(
|
||||
val brand by stringOption(
|
||||
key = "brand",
|
||||
default = null,
|
||||
title = "Brand",
|
||||
description = "The consumer-visible brand with which the product/hardware will be associated, if any."
|
||||
description = "The consumer-visible brand with which the product/hardware will be associated, if any.",
|
||||
)
|
||||
|
||||
override val cpuAbi by stringPatchOption(
|
||||
val cpuAbi by stringOption(
|
||||
key = "cpu-abi",
|
||||
default = null,
|
||||
title = "CPU ABI",
|
||||
description = "This field was deprecated in API level 21. Use SUPPORTED_ABIS instead."
|
||||
description = "This field was deprecated in API level 21. Use SUPPORTED_ABIS instead.",
|
||||
)
|
||||
|
||||
override val cpuAbi2 by stringPatchOption(
|
||||
val cpuAbi2 by stringOption(
|
||||
key = "cpu-abi-2",
|
||||
default = null,
|
||||
title = "CPU ABI 2",
|
||||
description = "This field was deprecated in API level 21. Use SUPPORTED_ABIS instead."
|
||||
description = "This field was deprecated in API level 21. Use SUPPORTED_ABIS instead.",
|
||||
)
|
||||
|
||||
override val device by stringPatchOption(
|
||||
val device by stringOption(
|
||||
key = "device",
|
||||
default = null,
|
||||
title = "Device",
|
||||
description = "The name of the industrial design."
|
||||
description = "The name of the industrial design.",
|
||||
)
|
||||
|
||||
override val display by stringPatchOption(
|
||||
val display by stringOption(
|
||||
key = "display",
|
||||
default = null,
|
||||
title = "Display",
|
||||
description = "A build ID string meant for displaying to the user."
|
||||
description = "A build ID string meant for displaying to the user.",
|
||||
)
|
||||
|
||||
override val fingerprint by stringPatchOption(
|
||||
val fingerprint by stringOption(
|
||||
key = "fingerprint",
|
||||
default = null,
|
||||
title = "Fingerprint",
|
||||
description = "A string that uniquely identifies this build."
|
||||
description = "A string that uniquely identifies this build.",
|
||||
)
|
||||
|
||||
override val hardware by stringPatchOption(
|
||||
val hardware by stringOption(
|
||||
key = "hardware",
|
||||
default = null,
|
||||
title = "Hardware",
|
||||
description = "The name of the hardware (from the kernel command line or /proc)."
|
||||
description = "The name of the hardware (from the kernel command line or /proc).",
|
||||
)
|
||||
|
||||
override val host by stringPatchOption(
|
||||
val host by stringOption(
|
||||
key = "host",
|
||||
default = null,
|
||||
title = "Host",
|
||||
description = "The host."
|
||||
description = "The host.",
|
||||
)
|
||||
|
||||
override val id by stringPatchOption(
|
||||
val id by stringOption(
|
||||
key = "id",
|
||||
default = null,
|
||||
title = "ID",
|
||||
description = "Either a changelist number, or a label like \"M4-rc20\"."
|
||||
description = "Either a changelist number, or a label like \"M4-rc20\".",
|
||||
)
|
||||
|
||||
override val manufacturer by stringPatchOption(
|
||||
val manufacturer by stringOption(
|
||||
key = "manufacturer",
|
||||
default = null,
|
||||
title = "Manufacturer",
|
||||
description = "The manufacturer of the product/hardware."
|
||||
description = "The manufacturer of the product/hardware.",
|
||||
)
|
||||
|
||||
override val model by stringPatchOption(
|
||||
val model by stringOption(
|
||||
key = "model",
|
||||
default = null,
|
||||
title = "Model",
|
||||
description = "The end-user-visible name for the end product."
|
||||
description = "The end-user-visible name for the end product.",
|
||||
)
|
||||
|
||||
override val odmSku by stringPatchOption(
|
||||
val odmSku by stringOption(
|
||||
key = "odm-sku",
|
||||
default = null,
|
||||
title = "ODM SKU",
|
||||
description = "The SKU of the device as set by the original design manufacturer (ODM)."
|
||||
description = "The SKU of the device as set by the original design manufacturer (ODM).",
|
||||
)
|
||||
|
||||
override val product by stringPatchOption(
|
||||
val product by stringOption(
|
||||
key = "product",
|
||||
default = null,
|
||||
title = "Product",
|
||||
description = "The name of the overall product."
|
||||
description = "The name of the overall product.",
|
||||
)
|
||||
|
||||
override val radio by stringPatchOption(
|
||||
val radio by stringOption(
|
||||
key = "radio",
|
||||
default = null,
|
||||
title = "Radio",
|
||||
description = "This field was deprecated in API level 15. " +
|
||||
"The radio firmware version is frequently not available when this class is initialized, " +
|
||||
"leading to a blank or \"unknown\" value for this string. Use getRadioVersion() instead."
|
||||
"The radio firmware version is frequently not available when this class is initialized, " +
|
||||
"leading to a blank or \"unknown\" value for this string. Use getRadioVersion() instead.",
|
||||
)
|
||||
|
||||
override val serial by stringPatchOption(
|
||||
val serial by stringOption(
|
||||
key = "serial",
|
||||
default = null,
|
||||
title = "Serial",
|
||||
description = "This field was deprecated in API level 26. Use getSerial() instead."
|
||||
description = "This field was deprecated in API level 26. Use getSerial() instead.",
|
||||
)
|
||||
|
||||
override val sku by stringPatchOption(
|
||||
val sku by stringOption(
|
||||
key = "sku",
|
||||
default = null,
|
||||
title = "SKU",
|
||||
description = "The SKU of the hardware (from the kernel command line)."
|
||||
description = "The SKU of the hardware (from the kernel command line).",
|
||||
)
|
||||
|
||||
override val socManufacturer by stringPatchOption(
|
||||
val socManufacturer by stringOption(
|
||||
key = "soc-manufacturer",
|
||||
default = null,
|
||||
title = "SOC Manufacturer",
|
||||
description = "The manufacturer of the device's primary system-on-chip."
|
||||
description = "The manufacturer of the device's primary system-on-chip.",
|
||||
)
|
||||
|
||||
override val socModel by stringPatchOption(
|
||||
val socModel by stringOption(
|
||||
key = "soc-model",
|
||||
default = null,
|
||||
title = "SOC Model",
|
||||
description = "The model name of the device's primary system-on-chip."
|
||||
description = "The model name of the device's primary system-on-chip.",
|
||||
)
|
||||
|
||||
override val tags by stringPatchOption(
|
||||
val tags by stringOption(
|
||||
key = "tags",
|
||||
default = null,
|
||||
title = "Tags",
|
||||
description = "Comma-separated tags describing the build, like \"unsigned,debug\"."
|
||||
description = "Comma-separated tags describing the build, like \"unsigned,debug\".",
|
||||
)
|
||||
|
||||
override val time by longPatchOption(
|
||||
val time by longOption(
|
||||
key = "time",
|
||||
default = null,
|
||||
title = "Time",
|
||||
description = "The time at which the build was produced, given in milliseconds since the UNIX epoch."
|
||||
description = "The time at which the build was produced, given in milliseconds since the UNIX epoch.",
|
||||
)
|
||||
|
||||
override val type by stringPatchOption(
|
||||
val type by stringOption(
|
||||
key = "type",
|
||||
default = null,
|
||||
title = "Type",
|
||||
description = "The type of build, like \"user\" or \"eng\"."
|
||||
description = "The type of build, like \"user\" or \"eng\".",
|
||||
)
|
||||
|
||||
override val user by stringPatchOption(
|
||||
val user by stringOption(
|
||||
key = "user",
|
||||
default = null,
|
||||
title = "User",
|
||||
description = "The user."
|
||||
description = "The user.",
|
||||
)
|
||||
|
||||
dependsOn(
|
||||
baseSpoofBuildInfoPatch {
|
||||
BuildInfo(
|
||||
board,
|
||||
bootloader,
|
||||
brand,
|
||||
cpuAbi,
|
||||
cpuAbi2,
|
||||
device,
|
||||
display,
|
||||
fingerprint,
|
||||
hardware,
|
||||
host,
|
||||
id,
|
||||
manufacturer,
|
||||
model,
|
||||
odmSku,
|
||||
product,
|
||||
radio,
|
||||
serial,
|
||||
sku,
|
||||
socManufacturer,
|
||||
socModel,
|
||||
tags,
|
||||
time,
|
||||
type,
|
||||
user,
|
||||
)
|
||||
},
|
||||
|
||||
)
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
@file:Suppress("unused")
|
||||
|
||||
package app.revanced.patches.all.misc.connectivity.location.hide
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.all.misc.transformation.IMethodCall
|
||||
import app.revanced.patches.all.misc.transformation.fromMethodReference
|
||||
import app.revanced.patches.all.misc.transformation.transformInstructionsPatch
|
||||
import app.revanced.util.getReference
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
@Suppress("unused")
|
||||
val hideMockLocationPatch = bytecodePatch(
|
||||
name = "Hide mock location",
|
||||
description = "Prevents the app from knowing the device location is being mocked by a third party app.",
|
||||
use = false,
|
||||
) {
|
||||
dependsOn(
|
||||
transformInstructionsPatch(
|
||||
filterMap = filter@{ _, _, instruction, instructionIndex ->
|
||||
val reference = instruction.getReference<MethodReference>() ?: return@filter null
|
||||
if (fromMethodReference<MethodCall>(reference) == null) return@filter null
|
||||
|
||||
instruction to instructionIndex
|
||||
},
|
||||
transform = { method, entry ->
|
||||
val (instruction, index) = entry
|
||||
instruction as FiveRegisterInstruction
|
||||
|
||||
// Replace return value with a constant `false` boolean.
|
||||
method.replaceInstruction(
|
||||
index + 1,
|
||||
"const/4 v${instruction.registerC}, 0x0",
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private enum class MethodCall(
|
||||
override val definedClassName: String,
|
||||
override val methodName: String,
|
||||
override val methodParams: Array<String>,
|
||||
override val returnType: String,
|
||||
) : IMethodCall {
|
||||
IsMock("Landroid/location/Location;", "isMock", emptyArray(), "Z"),
|
||||
IsFromMockProvider("Landroid/location/Location;", "isFromMockProvider", emptyArray(), "Z"),
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
package app.revanced.patches.all.misc.connectivity.telephony.sim.spoof
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.patch.stringOption
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patches.all.misc.transformation.transformInstructionsPatch
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableMethodReference
|
||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||
import java.util.*
|
||||
|
||||
@Suppress("unused")
|
||||
val spoofSimCountryPatch = bytecodePatch(
|
||||
name = "Spoof SIM country",
|
||||
description = "Spoofs country information returned by the SIM card provider.",
|
||||
use = false,
|
||||
) {
|
||||
val countries = Locale.getISOCountries().associateBy { Locale("", it).displayCountry }
|
||||
|
||||
fun isoCountryPatchOption(
|
||||
key: String,
|
||||
title: String,
|
||||
) = stringOption(
|
||||
key,
|
||||
null,
|
||||
countries,
|
||||
title,
|
||||
"ISO-3166-1 alpha-2 country code equivalent for the SIM provider's country code.",
|
||||
false,
|
||||
validator = { it: String? -> it == null || it.uppercase() in countries.values },
|
||||
)
|
||||
|
||||
val networkCountryIso by isoCountryPatchOption(
|
||||
"networkCountryIso",
|
||||
"Network ISO Country Code",
|
||||
)
|
||||
|
||||
val simCountryIso by isoCountryPatchOption(
|
||||
"simCountryIso",
|
||||
"Sim ISO Country Code",
|
||||
)
|
||||
|
||||
dependsOn(
|
||||
transformInstructionsPatch(
|
||||
filterMap = { _, _, instruction, instructionIndex ->
|
||||
if (instruction !is ReferenceInstruction) return@transformInstructionsPatch null
|
||||
|
||||
val reference = instruction.reference as? MethodReference ?: return@transformInstructionsPatch null
|
||||
|
||||
val match = MethodCall.entries.firstOrNull { search ->
|
||||
MethodUtil.methodSignaturesMatch(reference, search.reference)
|
||||
} ?: return@transformInstructionsPatch null
|
||||
|
||||
val iso = when (match) {
|
||||
MethodCall.NetworkCountryIso -> networkCountryIso
|
||||
MethodCall.SimCountryIso -> simCountryIso
|
||||
}?.lowercase()
|
||||
|
||||
iso?.let { instructionIndex to it }
|
||||
},
|
||||
transform = { mutableMethod, entry: Pair<Int, String> ->
|
||||
transformMethodCall(entry, mutableMethod)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun transformMethodCall(
|
||||
entry: Pair<Int, String>,
|
||||
mutableMethod: MutableMethod,
|
||||
) {
|
||||
val (instructionIndex, methodCallValue) = entry
|
||||
|
||||
val register = mutableMethod.getInstruction<OneRegisterInstruction>(instructionIndex + 1).registerA
|
||||
|
||||
mutableMethod.replaceInstruction(
|
||||
instructionIndex + 1,
|
||||
"const-string v$register, \"$methodCallValue\"",
|
||||
)
|
||||
}
|
||||
|
||||
private enum class MethodCall(
|
||||
val reference: MethodReference,
|
||||
) {
|
||||
NetworkCountryIso(
|
||||
ImmutableMethodReference(
|
||||
"Landroid/telephony/TelephonyManager;",
|
||||
"getNetworkCountryIso",
|
||||
emptyList(),
|
||||
"Ljava/lang/String;",
|
||||
),
|
||||
),
|
||||
SimCountryIso(
|
||||
ImmutableMethodReference(
|
||||
"Landroid/telephony/TelephonyManager;",
|
||||
"getSimCountryIso",
|
||||
emptyList(),
|
||||
"Ljava/lang/String;",
|
||||
),
|
||||
),
|
||||
}
|
@ -0,0 +1,224 @@
|
||||
package app.revanced.patches.all.misc.connectivity.wifi.spoof
|
||||
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.all.misc.transformation.IMethodCall
|
||||
import app.revanced.patches.all.misc.transformation.filterMapInstruction35c
|
||||
import app.revanced.patches.all.misc.transformation.transformInstructionsPatch
|
||||
|
||||
internal const val EXTENSION_CLASS_DESCRIPTOR_PREFIX = "Lapp/revanced/extension/all/connectivity/wifi/spoof/SpoofWifiPatch"
|
||||
|
||||
internal const val EXTENSION_CLASS_DESCRIPTOR = "$EXTENSION_CLASS_DESCRIPTOR_PREFIX;"
|
||||
|
||||
@Suppress("unused")
|
||||
val spoofWifiPatch = bytecodePatch(
|
||||
name = "Spoof Wi-Fi connection",
|
||||
description = "Spoofs an existing Wi-Fi connection.",
|
||||
use = false,
|
||||
) {
|
||||
extendWith("extensions/all/connectivity/wifi/spoof/spoof-wifi.rve")
|
||||
|
||||
dependsOn(
|
||||
transformInstructionsPatch(
|
||||
filterMap = { classDef, _, instruction, instructionIndex ->
|
||||
filterMapInstruction35c<MethodCall>(
|
||||
EXTENSION_CLASS_DESCRIPTOR_PREFIX,
|
||||
classDef,
|
||||
instruction,
|
||||
instructionIndex,
|
||||
)
|
||||
},
|
||||
transform = { method, entry ->
|
||||
val (methodType, instruction, instructionIndex) = entry
|
||||
methodType.replaceInvokeVirtualWithExtension(
|
||||
EXTENSION_CLASS_DESCRIPTOR,
|
||||
method,
|
||||
instruction,
|
||||
instructionIndex,
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Information about method calls we want to replace
|
||||
@Suppress("unused")
|
||||
private enum class MethodCall(
|
||||
override val definedClassName: String,
|
||||
override val methodName: String,
|
||||
override val methodParams: Array<String>,
|
||||
override val returnType: String,
|
||||
) : IMethodCall {
|
||||
GetSystemService1(
|
||||
"Landroid/content/Context;",
|
||||
"getSystemService",
|
||||
arrayOf("Ljava/lang/String;"),
|
||||
"Ljava/lang/Object;",
|
||||
),
|
||||
GetSystemService2(
|
||||
"Landroid/content/Context;",
|
||||
"getSystemService",
|
||||
arrayOf("Ljava/lang/Class;"),
|
||||
"Ljava/lang/Object;",
|
||||
),
|
||||
GetActiveNetworkInfo(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"getActiveNetworkInfo",
|
||||
arrayOf(),
|
||||
"Landroid/net/NetworkInfo;",
|
||||
),
|
||||
IsConnected(
|
||||
"Landroid/net/NetworkInfo;",
|
||||
"isConnected",
|
||||
arrayOf(),
|
||||
"Z",
|
||||
),
|
||||
IsConnectedOrConnecting(
|
||||
"Landroid/net/NetworkInfo;",
|
||||
"isConnectedOrConnecting",
|
||||
arrayOf(),
|
||||
"Z",
|
||||
),
|
||||
IsAvailable(
|
||||
"Landroid/net/NetworkInfo;",
|
||||
"isAvailable",
|
||||
arrayOf(),
|
||||
"Z",
|
||||
),
|
||||
GetState(
|
||||
"Landroid/net/NetworkInfo;",
|
||||
"getState",
|
||||
arrayOf(),
|
||||
"Landroid/net/NetworkInfo\$State;",
|
||||
),
|
||||
GetDetailedState(
|
||||
"Landroid/net/NetworkInfo;",
|
||||
"getDetailedState",
|
||||
arrayOf(),
|
||||
"Landroid/net/NetworkInfo\$DetailedState;",
|
||||
),
|
||||
IsActiveNetworkMetered(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"isActiveNetworkMetered",
|
||||
arrayOf(),
|
||||
"Z",
|
||||
),
|
||||
GetActiveNetwork(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"getActiveNetwork",
|
||||
arrayOf(),
|
||||
"Landroid/net/Network;",
|
||||
),
|
||||
GetNetworkInfo(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"getNetworkInfo",
|
||||
arrayOf("Landroid/net/Network;"),
|
||||
"Landroid/net/NetworkInfo;",
|
||||
),
|
||||
HasTransport(
|
||||
"Landroid/net/NetworkCapabilities;",
|
||||
"hasTransport",
|
||||
arrayOf("I"),
|
||||
"Z",
|
||||
),
|
||||
HasCapability(
|
||||
"Landroid/net/NetworkCapabilities;",
|
||||
"hasCapability",
|
||||
arrayOf("I"),
|
||||
"Z",
|
||||
),
|
||||
RegisterBestMatchingNetworkCallback(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"registerBestMatchingNetworkCallback",
|
||||
arrayOf(
|
||||
"Landroid/net/NetworkRequest;",
|
||||
"Landroid/net/ConnectivityManager\$NetworkCallback;",
|
||||
"Landroid/os/Handler;",
|
||||
),
|
||||
"V",
|
||||
),
|
||||
RegisterDefaultNetworkCallback1(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"registerDefaultNetworkCallback",
|
||||
arrayOf("Landroid/net/ConnectivityManager\$NetworkCallback;"),
|
||||
"V",
|
||||
),
|
||||
RegisterDefaultNetworkCallback2(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"registerDefaultNetworkCallback",
|
||||
arrayOf("Landroid/net/ConnectivityManager\$NetworkCallback;", "Landroid/os/Handler;"),
|
||||
"V",
|
||||
),
|
||||
RegisterNetworkCallback1(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"registerNetworkCallback",
|
||||
arrayOf("Landroid/net/NetworkRequest;", "Landroid/net/ConnectivityManager\$NetworkCallback;"),
|
||||
"V",
|
||||
),
|
||||
RegisterNetworkCallback2(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"registerNetworkCallback",
|
||||
arrayOf("Landroid/net/NetworkRequest;", "Landroid/app/PendingIntent;"),
|
||||
"V",
|
||||
),
|
||||
RegisterNetworkCallback3(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"registerNetworkCallback",
|
||||
arrayOf(
|
||||
"Landroid/net/NetworkRequest;",
|
||||
"Landroid/net/ConnectivityManager\$NetworkCallback;",
|
||||
"Landroid/os/Handler;",
|
||||
),
|
||||
"V",
|
||||
),
|
||||
RequestNetwork1(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"requestNetwork",
|
||||
arrayOf("Landroid/net/NetworkRequest;", "Landroid/net/ConnectivityManager\$NetworkCallback;"),
|
||||
"V",
|
||||
),
|
||||
RequestNetwork2(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"requestNetwork",
|
||||
arrayOf("Landroid/net/NetworkRequest;", "Landroid/net/ConnectivityManager\$NetworkCallback;", "I"),
|
||||
"V",
|
||||
),
|
||||
RequestNetwork3(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"requestNetwork",
|
||||
arrayOf(
|
||||
"Landroid/net/NetworkRequest;",
|
||||
"Landroid/net/ConnectivityManager\$NetworkCallback;",
|
||||
"Landroid/os/Handler;",
|
||||
),
|
||||
"V",
|
||||
),
|
||||
RequestNetwork4(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"requestNetwork",
|
||||
arrayOf("Landroid/net/NetworkRequest;", "Landroid/app/PendingIntent;"),
|
||||
"V",
|
||||
),
|
||||
RequestNetwork5(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"requestNetwork",
|
||||
arrayOf(
|
||||
"Landroid/net/NetworkRequest;",
|
||||
"Landroid/net/ConnectivityManager\$NetworkCallback;",
|
||||
"Landroid/os/Handler;",
|
||||
"I",
|
||||
),
|
||||
"V",
|
||||
),
|
||||
UnregisterNetworkCallback1(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"unregisterNetworkCallback",
|
||||
arrayOf("Landroid/net/ConnectivityManager\$NetworkCallback;"),
|
||||
"V",
|
||||
),
|
||||
UnregisterNetworkCallback2(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"unregisterNetworkCallback",
|
||||
arrayOf("Landroid/app/PendingIntent;"),
|
||||
"V",
|
||||
),
|
||||
}
|
@ -1,21 +1,16 @@
|
||||
package app.revanced.patches.all.misc.debugging
|
||||
|
||||
import app.revanced.patcher.data.ResourceContext
|
||||
import app.revanced.patcher.patch.ResourcePatch
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import org.w3c.dom.Element
|
||||
|
||||
@Patch(
|
||||
@Suppress("unused")
|
||||
val enableAndroidDebuggingPatch = resourcePatch(
|
||||
name = "Enable Android debugging",
|
||||
description = "Enables Android debugging capabilities. This can slow down the app.",
|
||||
use = false,
|
||||
)
|
||||
@Suppress("unused")
|
||||
object EnableAndroidDebuggingPatch : ResourcePatch() {
|
||||
override fun execute(context: ResourceContext) {
|
||||
context.xmlEditor["AndroidManifest.xml"].use { editor ->
|
||||
val document = editor.file
|
||||
|
||||
) {
|
||||
execute { context ->
|
||||
context.document["AndroidManifest.xml"].use { document ->
|
||||
val applicationNode =
|
||||
document
|
||||
.getElementsByTagName("application")
|
@ -0,0 +1,58 @@
|
||||
package app.revanced.patches.all.misc.directory
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.all.misc.transformation.transformInstructionsPatch
|
||||
import app.revanced.util.getReference
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableMethodReference
|
||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||
|
||||
@Suppress("unused")
|
||||
val changeDataDirectoryLocationPatch = bytecodePatch(
|
||||
name = "Change data directory location",
|
||||
description = "Changes the data directory in the application from " +
|
||||
"the app internal storage directory to /sdcard/android/data accessible by root-less devices." +
|
||||
"Using this patch can cause unexpected issues with some apps.",
|
||||
use = false,
|
||||
) {
|
||||
dependsOn(
|
||||
transformInstructionsPatch(
|
||||
filterMap = filter@{ _, _, instruction, instructionIndex ->
|
||||
val reference = instruction.getReference<MethodReference>() ?: return@filter null
|
||||
|
||||
if (!MethodUtil.methodSignaturesMatch(reference, MethodCall.GetDir.reference)) {
|
||||
return@filter null
|
||||
}
|
||||
|
||||
return@filter instructionIndex
|
||||
},
|
||||
transform = { method, index ->
|
||||
val getDirInstruction = method.getInstruction<Instruction35c>(index)
|
||||
val contextRegister = getDirInstruction.registerC
|
||||
val dataRegister = getDirInstruction.registerD
|
||||
|
||||
method.replaceInstruction(
|
||||
index,
|
||||
"invoke-virtual { v$contextRegister, v$dataRegister }, " +
|
||||
"Landroid/content/Context;->getExternalFilesDir(Ljava/lang/String;)Ljava/io/File;",
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private enum class MethodCall(
|
||||
val reference: MethodReference,
|
||||
) {
|
||||
GetDir(
|
||||
ImmutableMethodReference(
|
||||
"Landroid/content/Context;",
|
||||
"getDir",
|
||||
listOf("Ljava/lang/String;", "I"),
|
||||
"Ljava/io/File;",
|
||||
),
|
||||
),
|
||||
}
|
@ -1,23 +1,22 @@
|
||||
package app.revanced.patches.all.misc.hex
|
||||
|
||||
import app.revanced.patcher.patch.PatchException
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.registerNewPatchOption
|
||||
import app.revanced.patches.shared.misc.hex.BaseHexPatch
|
||||
import app.revanced.patcher.patch.rawResourcePatch
|
||||
import app.revanced.patcher.patch.stringsOption
|
||||
import app.revanced.patches.shared.misc.hex.Replacement
|
||||
import app.revanced.patches.shared.misc.hex.hexPatch
|
||||
import app.revanced.util.Utils.trimIndentMultiline
|
||||
import app.revanced.patcher.patch.Patch as PatchClass
|
||||
|
||||
@Patch(
|
||||
@Suppress("unused")
|
||||
val hexPatch = rawResourcePatch(
|
||||
name = "Hex",
|
||||
description = "Replaces a hexadecimal patterns of bytes of files in an APK.",
|
||||
use = false,
|
||||
)
|
||||
@Suppress("unused")
|
||||
class HexPatch : BaseHexPatch() {
|
||||
) {
|
||||
// TODO: Instead of stringArrayOption, use a custom option type to work around
|
||||
// https://github.com/ReVanced/revanced-library/issues/48.
|
||||
// Replace the custom option type with a stringArrayOption once the issue is resolved.
|
||||
private val replacementsOption by registerNewPatchOption<PatchClass<*>, List<String>>(
|
||||
val replacements by stringsOption(
|
||||
key = "replacements",
|
||||
title = "Replacements",
|
||||
description = """
|
||||
@ -34,22 +33,24 @@ class HexPatch : BaseHexPatch() {
|
||||
'aa 01 02 FF|00 00 00 00|path/to/file'
|
||||
""".trimIndentMultiline(),
|
||||
required = true,
|
||||
valueType = "StringArray",
|
||||
)
|
||||
|
||||
override val replacements
|
||||
get() = replacementsOption!!.map { from ->
|
||||
val (pattern, replacementPattern, targetFilePath) = try {
|
||||
from.split("|", limit = 3)
|
||||
} catch (e: Exception) {
|
||||
throw PatchException(
|
||||
"Invalid input: $from.\n" +
|
||||
"Every pattern must be followed by a pipe ('|'), " +
|
||||
"the replacement pattern, another pipe ('|'), " +
|
||||
"and the path to the file to make the changes in relative to the APK root. ",
|
||||
)
|
||||
}
|
||||
dependsOn(
|
||||
hexPatch {
|
||||
replacements!!.map { from ->
|
||||
val (pattern, replacementPattern, targetFilePath) = try {
|
||||
from.split("|", limit = 3)
|
||||
} catch (e: Exception) {
|
||||
throw PatchException(
|
||||
"Invalid input: $from.\n" +
|
||||
"Every pattern must be followed by a pipe ('|'), " +
|
||||
"the replacement pattern, another pipe ('|'), " +
|
||||
"and the path to the file to make the changes in relative to the APK root. ",
|
||||
)
|
||||
}
|
||||
|
||||
Replacement(pattern, replacementPattern, targetFilePath)
|
||||
}
|
||||
Replacement(pattern, replacementPattern, targetFilePath)
|
||||
}.toSet()
|
||||
},
|
||||
)
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package app.revanced.patches.all.misc.interaction.gestures
|
||||
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val predictiveBackGesturePatch = resourcePatch(
|
||||
name = "Predictive back gesture",
|
||||
description = "Enables the predictive back gesture introduced on Android 13.",
|
||||
use = false,
|
||||
) {
|
||||
execute { context ->
|
||||
val flag = "android:enableOnBackInvokedCallback"
|
||||
|
||||
context.document["AndroidManifest.xml"].use { document ->
|
||||
with(document.getElementsByTagName("application").item(0)) {
|
||||
if (attributes.getNamedItem(flag) != null) return@with
|
||||
|
||||
document.createAttribute(flag)
|
||||
.apply { value = "true" }
|
||||
.let(attributes::setNamedItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,28 +1,24 @@
|
||||
package app.revanced.patches.all.misc.network
|
||||
|
||||
import app.revanced.patcher.data.ResourceContext
|
||||
import app.revanced.patcher.patch.ResourcePatch
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patches.all.misc.debugging.EnableAndroidDebuggingPatch
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.patches.all.misc.debugging.enableAndroidDebuggingPatch
|
||||
import app.revanced.util.Utils.trimIndentMultiline
|
||||
import org.w3c.dom.Element
|
||||
import java.io.File
|
||||
|
||||
@Patch(
|
||||
@Suppress("unused")
|
||||
val overrideCertificatePinningPatch = resourcePatch(
|
||||
name = "Override certificate pinning",
|
||||
description = "Overrides certificate pinning, allowing to inspect traffic via a proxy.",
|
||||
dependencies = [EnableAndroidDebuggingPatch::class],
|
||||
use = false,
|
||||
)
|
||||
@Suppress("unused")
|
||||
object OverrideCertificatePinningPatch : ResourcePatch() {
|
||||
override fun execute(context: ResourceContext) {
|
||||
) {
|
||||
dependsOn(enableAndroidDebuggingPatch)
|
||||
|
||||
execute { context ->
|
||||
val resXmlDirectory = context.get("res/xml")
|
||||
|
||||
// Add android:networkSecurityConfig="@xml/network_security_config" and the "networkSecurityConfig" attribute if it does not exist.
|
||||
context.xmlEditor["AndroidManifest.xml"].use { editor ->
|
||||
val document = editor.file
|
||||
|
||||
context.document["AndroidManifest.xml"].use { document ->
|
||||
val applicationNode = document.getElementsByTagName("application").item(0) as Element
|
||||
|
||||
if (!applicationNode.hasAttribute("networkSecurityConfig")) {
|
@ -0,0 +1,62 @@
|
||||
package app.revanced.patches.all.misc.packagename
|
||||
|
||||
import app.revanced.patcher.patch.Option
|
||||
import app.revanced.patcher.patch.OptionException
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.patcher.patch.stringOption
|
||||
import org.w3c.dom.Element
|
||||
|
||||
lateinit var packageNameOption: Option<String>
|
||||
|
||||
/**
|
||||
* Set the package name to use.
|
||||
* If this is called multiple times, the first call will set the package name.
|
||||
*
|
||||
* @param fallbackPackageName The package name to use if the user has not already specified a package name.
|
||||
* @return The package name that was set.
|
||||
* @throws OptionException.ValueValidationException If the package name is invalid.
|
||||
*/
|
||||
fun setOrGetFallbackPackageName(fallbackPackageName: String): String {
|
||||
val packageName = packageNameOption.value!!
|
||||
|
||||
return if (packageName == packageNameOption.default) {
|
||||
fallbackPackageName.also { packageNameOption.value = it }
|
||||
} else {
|
||||
packageName
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
val changePackageNamePatch = resourcePatch(
|
||||
name = "Change package name",
|
||||
description = "Appends \".revanced\" to the package name by default. Changing the package name of the app can lead to unexpected issues.",
|
||||
use = false,
|
||||
) {
|
||||
packageNameOption = stringOption(
|
||||
key = "packageName",
|
||||
default = "Default",
|
||||
values = mapOf("Default" to "Default"),
|
||||
title = "Package name",
|
||||
description = "The name of the package to rename the app to.",
|
||||
required = true,
|
||||
) {
|
||||
it == "Default" || it!!.matches(Regex("^[a-z]\\w*(\\.[a-z]\\w*)+\$"))
|
||||
}
|
||||
|
||||
finalize { context ->
|
||||
context.document["AndroidManifest.xml"].use { document ->
|
||||
|
||||
val replacementPackageName = packageNameOption.value
|
||||
|
||||
val manifest = document.getElementsByTagName("manifest").item(0) as Element
|
||||
manifest.setAttribute(
|
||||
"package",
|
||||
if (replacementPackageName != packageNameOption.default) {
|
||||
replacementPackageName
|
||||
} else {
|
||||
"${manifest.getAttribute("package")}.revanced"
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,397 @@
|
||||
package app.revanced.patches.all.misc.resources
|
||||
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.patch.PatchException
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.patcher.util.Document
|
||||
import app.revanced.util.*
|
||||
import app.revanced.util.resource.ArrayResource
|
||||
import app.revanced.util.resource.BaseResource
|
||||
import app.revanced.util.resource.StringResource
|
||||
import org.w3c.dom.Node
|
||||
|
||||
/**
|
||||
* An identifier of an app. For example, `youtube`.
|
||||
*/
|
||||
private typealias AppId = String
|
||||
|
||||
/**
|
||||
* An identifier of a patch. For example, `ad.general.HideAdsPatch`.
|
||||
*/
|
||||
private typealias PatchId = String
|
||||
|
||||
/**
|
||||
* A set of resources of a patch.
|
||||
*/
|
||||
private typealias PatchResources = MutableSet<BaseResource>
|
||||
|
||||
/**
|
||||
* A map of resources belonging to a patch.
|
||||
*/
|
||||
private typealias AppResources = MutableMap<PatchId, PatchResources>
|
||||
|
||||
/**
|
||||
* A map of resources belonging to an app.
|
||||
*/
|
||||
private typealias Resources = MutableMap<AppId, AppResources>
|
||||
|
||||
/**
|
||||
* The value of a resource.
|
||||
* For example, `values` or `values-de`.
|
||||
*/
|
||||
private typealias Value = String
|
||||
|
||||
/**
|
||||
* A set of resources mapped by their value.
|
||||
*/
|
||||
private typealias MutableResources = MutableMap<Value, MutableSet<BaseResource>>
|
||||
|
||||
/**
|
||||
* A map of all resources associated by their value staged by [addResourcesPatch].
|
||||
*/
|
||||
private lateinit var stagedResources: Map<Value, Resources>
|
||||
|
||||
/**
|
||||
* A map of all resources added to the app by [addResourcesPatch].
|
||||
*/
|
||||
private val resources: MutableResources = mutableMapOf()
|
||||
|
||||
/**
|
||||
* Map of Crowdin locales to Android resource locale names.
|
||||
*
|
||||
* Fixme: Instead this patch should detect what locale regions are present in both patches and the target app,
|
||||
* and automatically merge into the appropriate existing target file.
|
||||
* So if a target app has only 'es', then the Crowdin file of 'es-rES' should merge into that.
|
||||
* But if a target app has specific regions (such as 'pt-rBR'),
|
||||
* then the Crowdin region specific file should merged into that.
|
||||
*/
|
||||
private val locales = mapOf(
|
||||
"af-rZA" to "af",
|
||||
"am-rET" to "am",
|
||||
"ar-rSA" to "ar",
|
||||
"as-rIN" to "as",
|
||||
"az-rAZ" to "az",
|
||||
"be-rBY" to "be",
|
||||
"bg-rBG" to "bg",
|
||||
"bn-rBD" to "bn",
|
||||
"bs-rBA" to "bs",
|
||||
"ca-rES" to "ca",
|
||||
"cs-rCZ" to "cs",
|
||||
"da-rDK" to "da",
|
||||
"de-rDE" to "de",
|
||||
"el-rGR" to "el",
|
||||
"es-rES" to "es",
|
||||
"et-rEE" to "et",
|
||||
"eu-rES" to "eu",
|
||||
"fa-rIR" to "fa",
|
||||
"fi-rFI" to "fi",
|
||||
"fil-rPH" to "tl",
|
||||
"fr-rFR" to "fr",
|
||||
"ga-rIE" to "ga",
|
||||
"gl-rES" to "gl",
|
||||
"gu-rIN" to "gu",
|
||||
"hi-rIN" to "hi",
|
||||
"hr-rHR" to "hr",
|
||||
"hu-rHU" to "hu",
|
||||
"hy-rAM" to "hy",
|
||||
"in-rID" to "in",
|
||||
"is-rIS" to "is",
|
||||
"it-rIT" to "it",
|
||||
"iw-rIL" to "iw",
|
||||
"ja-rJP" to "ja",
|
||||
"ka-rGE" to "ka",
|
||||
"kk-rKZ" to "kk",
|
||||
"km-rKH" to "km",
|
||||
"kn-rIN" to "kn",
|
||||
"ko-rKR" to "ko",
|
||||
"ky-rKG" to "ky",
|
||||
"lo-rLA" to "lo",
|
||||
"lt-rLT" to "lt",
|
||||
"lv-rLV" to "lv",
|
||||
"mk-rMK" to "mk",
|
||||
"ml-rIN" to "ml",
|
||||
"mn-rMN" to "mn",
|
||||
"mr-rIN" to "mr",
|
||||
"ms-rMY" to "ms",
|
||||
"my-rMM" to "my",
|
||||
"nb-rNO" to "nb",
|
||||
"ne-rIN" to "ne",
|
||||
"nl-rNL" to "nl",
|
||||
"or-rIN" to "or",
|
||||
"pa-rIN" to "pa",
|
||||
"pl-rPL" to "pl",
|
||||
"pt-rBR" to "pt-rBR",
|
||||
"pt-rPT" to "pt-rPT",
|
||||
"ro-rRO" to "ro",
|
||||
"ru-rRU" to "ru",
|
||||
"si-rLK" to "si",
|
||||
"sk-rSK" to "sk",
|
||||
"sl-rSI" to "sl",
|
||||
"sq-rAL" to "sq",
|
||||
"sr-rCS" to "b+sr+Latn",
|
||||
"sr-rSP" to "sr",
|
||||
"sv-rSE" to "sv",
|
||||
"sw-rKE" to "sw",
|
||||
"ta-rIN" to "ta",
|
||||
"te-rIN" to "te",
|
||||
"th-rTH" to "th",
|
||||
"tl-rPH" to "tl",
|
||||
"tr-rTR" to "tr",
|
||||
"uk-rUA" to "uk",
|
||||
"ur-rIN" to "ur",
|
||||
"uz-rUZ" to "uz",
|
||||
"vi-rVN" to "vi",
|
||||
"zh-rCN" to "zh-rCN",
|
||||
"zh-rTW" to "zh-rTW",
|
||||
"zu-rZA" to "zu",
|
||||
)
|
||||
|
||||
/**
|
||||
* Adds a [BaseResource] to the map using [MutableMap.getOrPut].
|
||||
*
|
||||
* @param value The value of the resource. For example, `values` or `values-de`.
|
||||
* @param resource The resource to add.
|
||||
*
|
||||
* @return True if the resource was added, false if it already existed.
|
||||
*/
|
||||
fun addResource(
|
||||
value: Value,
|
||||
resource: BaseResource,
|
||||
) = resources.getOrPut(value, ::mutableSetOf).add(resource)
|
||||
|
||||
/**
|
||||
* Adds a list of [BaseResource]s to the map using [MutableMap.getOrPut].
|
||||
*
|
||||
* @param value The value of the resource. For example, `values` or `values-de`.
|
||||
* @param resources The resources to add.
|
||||
*
|
||||
* @return True if the resources were added, false if they already existed.
|
||||
*/
|
||||
fun addResources(
|
||||
value: Value,
|
||||
resources: Iterable<BaseResource>,
|
||||
) = app.revanced.patches.all.misc.resources.resources.getOrPut(value, ::mutableSetOf).addAll(resources)
|
||||
|
||||
/**
|
||||
* Adds a [StringResource].
|
||||
*
|
||||
* @param name The name of the string resource.
|
||||
* @param value The value of the string resource.
|
||||
* @param formatted Whether the string resource is formatted. Defaults to `true`.
|
||||
* @param resourceValue The value of the resource. For example, `values` or `values-de`.
|
||||
*
|
||||
* @return True if the resource was added, false if it already existed.
|
||||
*/
|
||||
fun addResources(
|
||||
name: String,
|
||||
value: String,
|
||||
formatted: Boolean = true,
|
||||
resourceValue: Value = "values",
|
||||
) = addResource(resourceValue, StringResource(name, value, formatted))
|
||||
|
||||
/**
|
||||
* Adds an [ArrayResource].
|
||||
*
|
||||
* @param name The name of the array resource.
|
||||
* @param items The items of the array resource.
|
||||
*
|
||||
* @return True if the resource was added, false if it already existed.
|
||||
*/
|
||||
fun addResources(
|
||||
name: String,
|
||||
items: List<String>,
|
||||
) = addResource("values", ArrayResource(name, items))
|
||||
|
||||
/**
|
||||
* Puts all resources of any [Value] staged in [stagedResources] for the [Patch] to [addResources].
|
||||
*
|
||||
* @param patch The [Patch] of the patch to stage resources for.
|
||||
* @param parseIds A function that parses a set of [PatchId] each mapped to an [AppId] from the given [Patch].
|
||||
* This is used to access the resources in [addResources] to stage them in [stagedResources].
|
||||
* The default implementation assumes that the [Patch] has a name and declares packages it is compatible with.
|
||||
*
|
||||
* @return True if any resources were added, false if none were added.
|
||||
*
|
||||
* @see addResourcesPatch
|
||||
*/
|
||||
fun addResources(
|
||||
patch: Patch<*>,
|
||||
parseIds: (Patch<*>) -> Map<AppId, Set<PatchId>> = {
|
||||
val patchId = patch.name ?: throw PatchException("Patch has no name")
|
||||
val packages = patch.compatiblePackages ?: throw PatchException("Patch has no compatible packages")
|
||||
|
||||
buildMap<AppId, MutableSet<PatchId>> {
|
||||
packages.forEach { (appId, _) ->
|
||||
getOrPut(appId) { mutableSetOf() }.add(patchId)
|
||||
}
|
||||
}
|
||||
},
|
||||
): Boolean {
|
||||
var result = false
|
||||
|
||||
// Stage resources for the given patch to addResourcesPatch associated with their value.
|
||||
parseIds(patch).forEach { (appId, patchIds) ->
|
||||
patchIds.forEach { patchId ->
|
||||
stagedResources.forEach { (value, resources) ->
|
||||
resources[appId]?.get(patchId)?.let { patchResources ->
|
||||
if (addResources(value, patchResources)) result = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts all resources for the given [appId] and [patchId] staged in [addResources] to [addResourcesPatch].
|
||||
*
|
||||
*
|
||||
* @return True if any resources were added, false if none were added.
|
||||
*
|
||||
* @see addResourcesPatch
|
||||
*/
|
||||
fun addResources(
|
||||
appId: AppId,
|
||||
patchId: String,
|
||||
) = stagedResources.forEach { (value, resources) ->
|
||||
resources[appId]?.get(patchId)?.let { patchResources ->
|
||||
addResources(value, patchResources)
|
||||
}
|
||||
}
|
||||
|
||||
val addResourcesPatch = resourcePatch(
|
||||
description = "Add resources such as strings or arrays to the app.",
|
||||
) {
|
||||
/*
|
||||
The strategy of this patch is to stage resources present in `/resources/addresources`.
|
||||
These resources are organized by their respective value and patch.
|
||||
|
||||
On addResourcesPatch#execute, all resources are staged in a temporary map.
|
||||
After that, other patches that depend on addResourcesPatch can call
|
||||
addResourcesPatch#invoke(Patch) to stage resources belonging to that patch
|
||||
from the temporary map to addResourcesPatch.
|
||||
|
||||
After all patches that depend on addResourcesPatch have been executed,
|
||||
addResourcesPatch#finalize is finally called to add all staged resources to the app.
|
||||
*/
|
||||
execute { context ->
|
||||
stagedResources = buildMap {
|
||||
/**
|
||||
* Puts resources under `/resources/addresources/<value>/<resourceKind>.xml` into the map.
|
||||
*
|
||||
* @param sourceValue The source value of the resource. For example, `values` or `values-de-rDE`.
|
||||
* @param destValue The destination value of the resource. For example, 'values' or 'values-de'.
|
||||
* @param resourceKind The kind of the resource. For example, `strings` or `arrays`.
|
||||
* @param transform A function that transforms the [Node]s from the XML files to a [BaseResource].
|
||||
*/
|
||||
fun addResources(
|
||||
sourceValue: Value,
|
||||
destValue: Value = sourceValue,
|
||||
resourceKind: String,
|
||||
transform: (Node) -> BaseResource,
|
||||
) {
|
||||
inputStreamFromBundledResource(
|
||||
"addresources",
|
||||
"$sourceValue/$resourceKind.xml",
|
||||
)?.let { stream ->
|
||||
// Add the resources associated with the given value to the map,
|
||||
// instead of overwriting it.
|
||||
// This covers the example case such as adding strings and arrays of the same value.
|
||||
getOrPut(destValue, ::mutableMapOf).apply {
|
||||
context.document[stream].use { document ->
|
||||
document.getElementsByTagName("app").asSequence().forEach { app ->
|
||||
val appId = app.attributes.getNamedItem("id").textContent
|
||||
|
||||
getOrPut(appId, ::mutableMapOf).apply {
|
||||
app.forEachChildElement { patch ->
|
||||
val patchId = patch.attributes.getNamedItem("id").textContent
|
||||
|
||||
getOrPut(patchId, ::mutableSetOf).apply {
|
||||
patch.forEachChildElement { resourceNode ->
|
||||
val resource = transform(resourceNode)
|
||||
|
||||
add(resource)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stage all resources to a temporary map.
|
||||
// Staged resources consumed by addResourcesPatch#invoke(Patch)
|
||||
// are later used in addResourcesPatch#finalize.
|
||||
try {
|
||||
val addStringResources = { source: Value, dest: Value ->
|
||||
addResources(source, dest, "strings", StringResource::fromNode)
|
||||
}
|
||||
locales.forEach { (source, dest) -> addStringResources("values-$source", "values-$dest") }
|
||||
addStringResources("values", "values")
|
||||
|
||||
addResources("values", "values", "arrays", ArrayResource::fromNode)
|
||||
} catch (e: Exception) {
|
||||
throw PatchException("Failed to read resources", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all resources staged in [addResourcesPatch] to the app.
|
||||
* This is called after all patches that depend on [addResourcesPatch] have been executed.
|
||||
*/
|
||||
finalize { context ->
|
||||
operator fun MutableMap<String, Pair<Document, Node>>.invoke(
|
||||
value: Value,
|
||||
resource: BaseResource,
|
||||
) {
|
||||
// TODO: Fix open-closed principle violation by modifying BaseResource#serialize so that it accepts
|
||||
// a Value and the map of documents. It will then get or put the document suitable for its resource type
|
||||
// to serialize itself to it.
|
||||
val resourceFileName =
|
||||
when (resource) {
|
||||
is StringResource -> "strings"
|
||||
is ArrayResource -> "arrays"
|
||||
else -> throw NotImplementedError("Unsupported resource type")
|
||||
}
|
||||
|
||||
getOrPut(resourceFileName) {
|
||||
val targetFile =
|
||||
context["res/$value/$resourceFileName.xml"].also {
|
||||
it.parentFile?.mkdirs()
|
||||
|
||||
if (it.createNewFile()) {
|
||||
it.writeText("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n</resources>")
|
||||
}
|
||||
}
|
||||
|
||||
context.document[targetFile.path].let { document ->
|
||||
|
||||
// Save the target node here as well
|
||||
// in order to avoid having to call document.getNode("resources")
|
||||
// but also save the document so that it can be closed later.
|
||||
document to document.getNode("resources")
|
||||
}
|
||||
}.let { (_, targetNode) ->
|
||||
targetNode.addResource(resource) { invoke(value, it) }
|
||||
}
|
||||
}
|
||||
|
||||
resources.forEach { (value, resources) ->
|
||||
// A map of document associated by their kind (e.g. strings, arrays).
|
||||
// Each document is accompanied by the target node to which resources are added.
|
||||
// A map is used because Map#getOrPut allows opening a new document for the duration of a resource value.
|
||||
// This is done to prevent having to open the files for every resource that is added.
|
||||
// Instead, it is cached once and reused for resources of the same value.
|
||||
// This map is later accessed to close all documents for the current resource value.
|
||||
val documents = mutableMapOf<String, Pair<Document, Node>>()
|
||||
|
||||
resources.forEach { resource -> documents(value, resource) }
|
||||
|
||||
documents.values.forEach { (document, _) -> document.close() }
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
package app.revanced.patches.all.misc.screencapture
|
||||
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.patches.all.misc.transformation.IMethodCall
|
||||
import app.revanced.patches.all.misc.transformation.filterMapInstruction35c
|
||||
import app.revanced.patches.all.misc.transformation.transformInstructionsPatch
|
||||
import org.w3c.dom.Element
|
||||
|
||||
private val removeCaptureRestrictionResourcePatch = resourcePatch(
|
||||
description = "Sets allowAudioPlaybackCapture in manifest to true.",
|
||||
) {
|
||||
execute { context ->
|
||||
context.document["AndroidManifest.xml"].use { document ->
|
||||
// Get the application node.
|
||||
val applicationNode =
|
||||
document
|
||||
.getElementsByTagName("application")
|
||||
.item(0) as Element
|
||||
|
||||
// Set allowAudioPlaybackCapture attribute to true.
|
||||
applicationNode.setAttribute("android:allowAudioPlaybackCapture", "true")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR_PREFIX =
|
||||
"Lapp/revanced/extension/all/screencapture/removerestriction/RemoveScreencaptureRestrictionPatch"
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR = "$EXTENSION_CLASS_DESCRIPTOR_PREFIX;"
|
||||
|
||||
@Suppress("unused")
|
||||
val removeScreenCaptureRestrictionPatch = bytecodePatch(
|
||||
name = "Remove screen capture restriction",
|
||||
description = "Removes the restriction of capturing audio from apps that normally wouldn't allow it.",
|
||||
use = false,
|
||||
) {
|
||||
extendWith("extensions/all/screencapture/remove-screen-capture-restriction.rve")
|
||||
|
||||
dependsOn(
|
||||
removeCaptureRestrictionResourcePatch,
|
||||
transformInstructionsPatch(
|
||||
filterMap = { classDef, _, instruction, instructionIndex ->
|
||||
filterMapInstruction35c<MethodCall>(
|
||||
EXTENSION_CLASS_DESCRIPTOR_PREFIX,
|
||||
classDef,
|
||||
instruction,
|
||||
instructionIndex,
|
||||
)
|
||||
},
|
||||
transform = { mutableMethod, entry ->
|
||||
val (methodType, instruction, instructionIndex) = entry
|
||||
methodType.replaceInvokeVirtualWithExtension(
|
||||
EXTENSION_CLASS_DESCRIPTOR,
|
||||
mutableMethod,
|
||||
instruction,
|
||||
instructionIndex,
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Information about method calls we want to replace
|
||||
@Suppress("unused")
|
||||
private enum class MethodCall(
|
||||
override val definedClassName: String,
|
||||
override val methodName: String,
|
||||
override val methodParams: Array<String>,
|
||||
override val returnType: String,
|
||||
) : IMethodCall {
|
||||
SetAllowedCapturePolicySingle(
|
||||
"Landroid/media/AudioAttributes\$Builder;",
|
||||
"setAllowedCapturePolicy",
|
||||
arrayOf("I"),
|
||||
"Landroid/media/AudioAttributes\$Builder;",
|
||||
),
|
||||
SetAllowedCapturePolicyGlobal(
|
||||
"Landroid/media/AudioManager;",
|
||||
"setAllowedCapturePolicy",
|
||||
arrayOf("I"),
|
||||
"V",
|
||||
),
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
package app.revanced.patches.all.misc.screenshot
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.all.misc.transformation.IMethodCall
|
||||
import app.revanced.patches.all.misc.transformation.filterMapInstruction35c
|
||||
import app.revanced.patches.all.misc.transformation.transformInstructionsPatch
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction22c
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR_PREFIX =
|
||||
"Lapp/revanced/extension/all/screenshot/removerestriction/RemoveScreenshotRestrictionPatch"
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR = "$EXTENSION_CLASS_DESCRIPTOR_PREFIX;"
|
||||
|
||||
@Suppress("unused")
|
||||
val removeScreenshotRestrictionPatch = bytecodePatch(
|
||||
name = "Remove screenshot restriction",
|
||||
description = "Removes the restriction of taking screenshots in apps that normally wouldn't allow it.",
|
||||
use = false,
|
||||
) {
|
||||
extendWith("extensions/all/screenshot/remove-screenshot-restriction.rve")
|
||||
|
||||
dependsOn(
|
||||
// Remove the restriction of taking screenshots.
|
||||
transformInstructionsPatch(
|
||||
filterMap = { classDef, _, instruction, instructionIndex ->
|
||||
filterMapInstruction35c<MethodCall>(
|
||||
EXTENSION_CLASS_DESCRIPTOR_PREFIX,
|
||||
classDef,
|
||||
instruction,
|
||||
instructionIndex,
|
||||
)
|
||||
},
|
||||
transform = { mutableMethod, entry ->
|
||||
val (methodType, instruction, instructionIndex) = entry
|
||||
methodType.replaceInvokeVirtualWithExtension(
|
||||
EXTENSION_CLASS_DESCRIPTOR,
|
||||
mutableMethod,
|
||||
instruction,
|
||||
instructionIndex,
|
||||
)
|
||||
},
|
||||
),
|
||||
// Modify layout params.
|
||||
transformInstructionsPatch(
|
||||
filterMap = { _, _, instruction, instructionIndex ->
|
||||
if (instruction.opcode != Opcode.IPUT) {
|
||||
return@transformInstructionsPatch null
|
||||
}
|
||||
|
||||
val instruction22c = instruction as Instruction22c
|
||||
val fieldReference = instruction22c.reference as FieldReference
|
||||
|
||||
if (fieldReference.definingClass != "Landroid/view/WindowManager\$LayoutParams;" ||
|
||||
fieldReference.name != "flags" ||
|
||||
fieldReference.type != "I"
|
||||
) {
|
||||
return@transformInstructionsPatch null
|
||||
}
|
||||
|
||||
Pair(instruction22c, instructionIndex)
|
||||
},
|
||||
transform = { mutableMethod, entry ->
|
||||
val (instruction, index) = entry
|
||||
val register = instruction.registerA
|
||||
|
||||
mutableMethod.addInstructions(
|
||||
index,
|
||||
"and-int/lit16 v$register, v$register, -0x2001",
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Information about method calls we want to replace
|
||||
@Suppress("unused")
|
||||
private enum class MethodCall(
|
||||
override val definedClassName: String,
|
||||
override val methodName: String,
|
||||
override val methodParams: Array<String>,
|
||||
override val returnType: String,
|
||||
) : IMethodCall {
|
||||
AddFlags(
|
||||
"Landroid/view/Window;",
|
||||
"addFlags",
|
||||
arrayOf("I"),
|
||||
"V",
|
||||
),
|
||||
SetFlags(
|
||||
"Landroid/view/Window;",
|
||||
"setFlags",
|
||||
arrayOf("I", "I"),
|
||||
"V",
|
||||
),
|
||||
}
|
@ -1,26 +1,23 @@
|
||||
package app.revanced.patches.all.shortcut.sharetargets
|
||||
package app.revanced.patches.all.misc.shortcut.sharetargets
|
||||
|
||||
import app.revanced.patcher.data.ResourceContext
|
||||
import app.revanced.patcher.patch.ResourcePatch
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.util.asSequence
|
||||
import app.revanced.util.getNode
|
||||
import org.w3c.dom.Element
|
||||
import java.io.FileNotFoundException
|
||||
import java.util.logging.Logger
|
||||
|
||||
@Patch(
|
||||
@Suppress("unused")
|
||||
val removeShareTargetsPatch = resourcePatch(
|
||||
name = "Remove share targets",
|
||||
description = "Removes share targets like directly sharing to a frequent contact.",
|
||||
use = false,
|
||||
)
|
||||
@Suppress("unused")
|
||||
object RemoveShareTargetsPatch : ResourcePatch() {
|
||||
override fun execute(context: ResourceContext) {
|
||||
) {
|
||||
execute { context ->
|
||||
try {
|
||||
context.document["res/xml/shortcuts.xml"]
|
||||
} catch (_: FileNotFoundException) {
|
||||
return Logger.getLogger(this::class.java.name).warning("The app has no shortcuts")
|
||||
return@execute Logger.getLogger(this::class.java.name).warning("The app has no shortcuts")
|
||||
}.use { document ->
|
||||
val rootNode = document.getNode("shortcuts") as? Element ?: return@use
|
||||
|
@ -18,8 +18,8 @@ interface IMethodCall {
|
||||
|
||||
/**
|
||||
* Replaces an invoke-virtual instruction with an invoke-static instruction,
|
||||
* which calls a static replacement method in the respective integrations class.
|
||||
* The method definition in the integrations class is expected to be the same,
|
||||
* which calls a static replacement method in the respective extension class.
|
||||
* The method definition in the extension class is expected to be the same,
|
||||
* except that the method should be static and take as a first parameter
|
||||
* an instance of the class, in which the original method was defined in.
|
||||
*
|
||||
@ -27,56 +27,58 @@ interface IMethodCall {
|
||||
*
|
||||
* original method: Window#setFlags(int, int)
|
||||
*
|
||||
* replacement method: Integrations#setFlags(Window, int, int)
|
||||
* replacement method: Extension#setFlags(Window, int, int)
|
||||
*/
|
||||
fun replaceInvokeVirtualWithIntegrations(
|
||||
fun replaceInvokeVirtualWithExtension(
|
||||
definingClassDescriptor: String,
|
||||
method: MutableMethod,
|
||||
instruction: Instruction35c,
|
||||
instructionIndex: Int
|
||||
instructionIndex: Int,
|
||||
) {
|
||||
val registers = arrayOf(
|
||||
instruction.registerC,
|
||||
instruction.registerD,
|
||||
instruction.registerE,
|
||||
instruction.registerF,
|
||||
instruction.registerG
|
||||
instruction.registerG,
|
||||
)
|
||||
val argsNum = methodParams.size + 1 // + 1 for instance of definedClassName
|
||||
if (argsNum > registers.size) {
|
||||
// should never happen, but just to be sure (also for the future) a safety check
|
||||
throw RuntimeException(
|
||||
"Not enough registers for ${definedClassName}#${methodName}: " +
|
||||
"Required $argsNum registers, but only got ${registers.size}."
|
||||
"Not enough registers for $definedClassName#$methodName: " +
|
||||
"Required $argsNum registers, but only got ${registers.size}.",
|
||||
)
|
||||
}
|
||||
|
||||
val args = registers.take(argsNum).joinToString(separator = ", ") { reg -> "v${reg}" }
|
||||
val replacementMethodDefinition =
|
||||
"${methodName}(${definedClassName}${methodParams.joinToString(separator = "")})${returnType}"
|
||||
val args = registers.take(argsNum).joinToString(separator = ", ") { reg -> "v$reg" }
|
||||
val replacementMethod =
|
||||
"$methodName(${definedClassName}${methodParams.joinToString(separator = "")})$returnType"
|
||||
|
||||
method.replaceInstruction(
|
||||
instructionIndex,
|
||||
"invoke-static { $args }, ${definingClassDescriptor}->${replacementMethodDefinition}"
|
||||
"invoke-static { $args }, $definingClassDescriptor->$replacementMethod",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified E> fromMethodReference(methodReference: MethodReference)
|
||||
inline fun <reified E> fromMethodReference(
|
||||
methodReference: MethodReference,
|
||||
)
|
||||
where E : Enum<E>, E : IMethodCall = enumValues<E>().firstOrNull { search ->
|
||||
search.definedClassName == methodReference.definingClass
|
||||
&& search.methodName == methodReference.name
|
||||
&& methodReference.parameterTypes.toTypedArray().contentEquals(search.methodParams)
|
||||
&& search.returnType == methodReference.returnType
|
||||
search.definedClassName == methodReference.definingClass &&
|
||||
search.methodName == methodReference.name &&
|
||||
methodReference.parameterTypes.toTypedArray().contentEquals(search.methodParams) &&
|
||||
search.returnType == methodReference.returnType
|
||||
}
|
||||
|
||||
inline fun <reified E> filterMapInstruction35c(
|
||||
integrationsClassDescriptorPrefix: String,
|
||||
extensionClassDescriptorPrefix: String,
|
||||
classDef: ClassDef,
|
||||
instruction: Instruction,
|
||||
instructionIndex: Int
|
||||
instructionIndex: Int,
|
||||
): Instruction35cInfo? where E : Enum<E>, E : IMethodCall {
|
||||
if (classDef.type.startsWith(integrationsClassDescriptorPrefix)) {
|
||||
if (classDef.startsWith(extensionClassDescriptorPrefix)) {
|
||||
// avoid infinite recursion
|
||||
return null
|
||||
}
|
@ -1,24 +1,16 @@
|
||||
package app.revanced.patches.all.misc.transformation
|
||||
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.patch.BytecodePatch
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.util.findMutableMethodOf
|
||||
import com.android.tools.smali.dexlib2.iface.ClassDef
|
||||
import com.android.tools.smali.dexlib2.iface.Method
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
abstract class BaseTransformInstructionsPatch<T> : BytecodePatch(emptySet()) {
|
||||
abstract fun filterMap(
|
||||
classDef: ClassDef,
|
||||
method: Method,
|
||||
instruction: Instruction,
|
||||
instructionIndex: Int,
|
||||
): T?
|
||||
|
||||
abstract fun transform(mutableMethod: MutableMethod, entry: T)
|
||||
|
||||
fun <T> transformInstructionsPatch(
|
||||
filterMap: (ClassDef, Method, Instruction, Int) -> T?,
|
||||
transform: (MutableMethod, T) -> Unit,
|
||||
) = bytecodePatch {
|
||||
// Returns the patch indices as a Sequence, which will execute lazily.
|
||||
fun findPatchIndices(classDef: ClassDef, method: Method): Sequence<T>? {
|
||||
return method.implementation?.instructions?.asSequence()?.withIndex()?.mapNotNull { (index, instruction) ->
|
||||
@ -26,7 +18,7 @@ abstract class BaseTransformInstructionsPatch<T> : BytecodePatch(emptySet()) {
|
||||
}
|
||||
}
|
||||
|
||||
override fun execute(context: BytecodeContext) {
|
||||
execute { context ->
|
||||
// Find all methods to patch
|
||||
buildMap {
|
||||
context.classes.forEach { classDef ->
|
@ -1,22 +1,19 @@
|
||||
package app.revanced.patches.all.misc.versioncode
|
||||
|
||||
import app.revanced.patcher.data.ResourceContext
|
||||
import app.revanced.patcher.patch.ResourcePatch
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.intPatchOption
|
||||
import app.revanced.patcher.patch.intOption
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.util.getNode
|
||||
import org.w3c.dom.Element
|
||||
|
||||
@Patch(
|
||||
@Suppress("unused")
|
||||
val changeVersionCodePatch = resourcePatch(
|
||||
name = "Change version code",
|
||||
description = "Changes the version code of the app. By default the highest version code is set. " +
|
||||
"This allows older versions of an app to be installed " +
|
||||
"if their version code is set to the same or a higher value and can stop app stores to update the app.",
|
||||
use = false,
|
||||
)
|
||||
@Suppress("unused")
|
||||
object ChangeVersionCodePatch : ResourcePatch() {
|
||||
private val versionCode by intPatchOption(
|
||||
) {
|
||||
val versionCode by intOption(
|
||||
key = "versionCode",
|
||||
default = Int.MAX_VALUE,
|
||||
values = mapOf(
|
||||
@ -26,11 +23,9 @@ object ChangeVersionCodePatch : ResourcePatch() {
|
||||
title = "Version code",
|
||||
description = "The version code to use",
|
||||
required = true,
|
||||
) {
|
||||
it!! >= 1
|
||||
}
|
||||
) { versionCode -> versionCode!! >= 1 }
|
||||
|
||||
override fun execute(context: ResourceContext) {
|
||||
execute { context ->
|
||||
context.document["AndroidManifest.xml"].use { document ->
|
||||
val manifestElement = document.getNode("manifest") as Element
|
||||
manifestElement.setAttribute("android:versionCode", "$versionCode")
|
@ -0,0 +1,24 @@
|
||||
package app.revanced.patches.amazon
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val deepLinkingPatch = bytecodePatch(
|
||||
name = "Always allow deep-linking",
|
||||
description = "Open Amazon links, even if the app is not set to handle Amazon links.",
|
||||
) {
|
||||
compatibleWith("com.amazon.mShop.android.shopping")
|
||||
|
||||
val deepLinkingMatch by deepLinkingFingerprint()
|
||||
|
||||
execute {
|
||||
deepLinkingMatch.mutableMethod.addInstructions(
|
||||
0,
|
||||
"""
|
||||
const/4 v0, 0x1
|
||||
return v0
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package app.revanced.patches.amazon
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
internal val deepLinkingFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PRIVATE)
|
||||
returns("Z")
|
||||
parameters("L")
|
||||
strings("https://www.", "android.intent.action.VIEW")
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package app.revanced.patches.backdrops.misc.pro
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal val proUnlockFingerprint = fingerprint {
|
||||
opcodes(
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.INVOKE_INTERFACE,
|
||||
Opcode.MOVE_RESULT,
|
||||
Opcode.IF_EQZ,
|
||||
)
|
||||
custom { method, _ ->
|
||||
method.name == "lambda\$existPurchase\$0" &&
|
||||
method.definingClass == "Lcom/backdrops/wallpapers/data/local/DatabaseHandlerIAB;"
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package app.revanced.patches.backdrops.misc.pro
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
@Suppress("unused")
|
||||
val proUnlockPatch = bytecodePatch(
|
||||
name = "Pro unlock",
|
||||
) {
|
||||
compatibleWith("com.backdrops.wallpapers")
|
||||
|
||||
val proUnlockMatch by proUnlockFingerprint()
|
||||
|
||||
execute {
|
||||
val registerIndex = proUnlockMatch.patternMatch!!.endIndex - 1
|
||||
|
||||
proUnlockMatch.mutableMethod.apply {
|
||||
val register = getInstruction<OneRegisterInstruction>(registerIndex).registerA
|
||||
addInstruction(
|
||||
proUnlockMatch.patternMatch!!.endIndex,
|
||||
"const/4 v$register, 0x1",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package app.revanced.patches.bandcamp.limitations
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val handlePlaybackLimitsFingerprint = fingerprint {
|
||||
strings("play limits processing track", "found play_count")
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package app.revanced.patches.bandcamp.limitations
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val removePlayLimitsPatch = bytecodePatch(
|
||||
name = "Remove play limits",
|
||||
description = "Disables purchase nagging and playback limits of not purchased tracks.",
|
||||
) {
|
||||
compatibleWith("com.bandcamp.android")
|
||||
|
||||
val handlePlaybackLimitsMatch by handlePlaybackLimitsFingerprint()
|
||||
|
||||
execute {
|
||||
handlePlaybackLimitsMatch.mutableMethod.addInstructions(0, "return-void")
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package app.revanced.patches.cieid.restrictions.root
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val bypassRootChecksPatch = bytecodePatch(
|
||||
name = "Bypass root checks",
|
||||
description = "Removes the restriction to use the app with root permissions or on a custom ROM.",
|
||||
) {
|
||||
compatibleWith("it.ipzs.cieid")
|
||||
|
||||
val checkRootMatch by checkRootFingerprint()
|
||||
|
||||
execute {
|
||||
checkRootMatch.mutableMethod.addInstruction(1, "return-void")
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package app.revanced.patches.cieid.restrictions.root
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val checkRootFingerprint = fingerprint {
|
||||
custom { method, _ ->
|
||||
method.name == "onResume" && method.definingClass == "Lit/ipzs/cieid/BaseActivity;"
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package app.revanced.patches.duolingo.ad
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||
|
||||
@Suppress("unused")
|
||||
val disableAdsPatch = bytecodePatch(
|
||||
"Disable ads",
|
||||
) {
|
||||
compatibleWith("com.duolingo")
|
||||
|
||||
val initializeMonetizationDebugSettingsMatch by initializeMonetizationDebugSettingsFingerprint()
|
||||
|
||||
execute {
|
||||
// Couple approaches to remove ads exist:
|
||||
//
|
||||
// MonetizationDebugSettings has a boolean value for "disableAds".
|
||||
// OnboardingState has a getter to check if the user has any "adFreeSessions".
|
||||
// SharedPreferences has a debug boolean value with key "disable_ads", which maps to "DebugCategory.DISABLE_ADS".
|
||||
//
|
||||
// MonetizationDebugSettings seems to be the most general setting to work fine.
|
||||
initializeMonetizationDebugSettingsMatch.mutableMethod.apply {
|
||||
val insertIndex = initializeMonetizationDebugSettingsMatch.patternMatch!!.startIndex
|
||||
val register = getInstruction<TwoRegisterInstruction>(insertIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
insertIndex,
|
||||
"const/4 v$register, 0x1",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package app.revanced.patches.duolingo.ad
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal val initializeMonetizationDebugSettingsFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
|
||||
returns("V")
|
||||
parameters(
|
||||
"Z", // disableAds
|
||||
"Z", // useDebugBilling
|
||||
"Z", // showManageSubscriptions
|
||||
"Z", // alwaysShowSuperAds
|
||||
"Lcom/duolingo/debug/FamilyQuestOverride;",
|
||||
)
|
||||
opcodes(Opcode.IPUT_BOOLEAN)
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package app.revanced.patches.duolingo.debug
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||
|
||||
@Suppress("unused")
|
||||
val enableDebugMenuPatch = bytecodePatch(
|
||||
name = "Enable debug menu",
|
||||
use = false,
|
||||
) {
|
||||
compatibleWith("com.duolingo"("5.158.4"))
|
||||
|
||||
val initializeBuildConfigProviderMatch by initializeBuildConfigProviderFingerprint()
|
||||
|
||||
execute {
|
||||
initializeBuildConfigProviderMatch.mutableMethod.apply {
|
||||
val insertIndex = initializeBuildConfigProviderMatch.patternMatch!!.startIndex
|
||||
val register = getInstruction<TwoRegisterInstruction>(insertIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
insertIndex,
|
||||
"const/4 v$register, 0x1",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package app.revanced.patches.duolingo.debug
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
/**
|
||||
* The `BuildConfigProvider` class has two booleans:
|
||||
*
|
||||
* - `isChina`: (usually) compares "play" with "china"...except for builds in China
|
||||
* - `isDebug`: compares "release" with "debug" <-- we want to force this to `true`
|
||||
*/
|
||||
|
||||
internal val initializeBuildConfigProviderFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
|
||||
returns("V")
|
||||
opcodes(Opcode.IPUT_BOOLEAN)
|
||||
strings("debug", "release", "china")
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
import app.revanced.patcher.fingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal val baseModelMapperFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returns("Lcom/facebook/graphql/modelutil/BaseModelWithTree;")
|
||||
parameters("Ljava/lang/Class", "I", "I")
|
||||
opcodes(
|
||||
Opcode.SGET_OBJECT,
|
||||
Opcode.INVOKE_STATIC,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.CONST_4,
|
||||
Opcode.IF_EQ,
|
||||
)
|
||||
}
|
||||
|
||||
internal val getSponsoredDataModelTemplateFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returns("L")
|
||||
parameters()
|
||||
opcodes(
|
||||
Opcode.CONST,
|
||||
Opcode.CONST,
|
||||
Opcode.INVOKE_STATIC,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.RETURN_OBJECT,
|
||||
)
|
||||
custom { _, classDef ->
|
||||
classDef.type == "Lcom/facebook/graphql/model/GraphQLFBMultiAdsFeedUnit;"
|
||||
}
|
||||
}
|
||||
|
||||
internal val getStoryVisibilityFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
||||
returns("Ljava/lang/String;")
|
||||
opcodes(
|
||||
Opcode.INSTANCE_OF,
|
||||
Opcode.IF_NEZ,
|
||||
Opcode.INSTANCE_OF,
|
||||
Opcode.IF_NEZ,
|
||||
Opcode.INSTANCE_OF,
|
||||
Opcode.IF_NEZ,
|
||||
Opcode.CONST,
|
||||
)
|
||||
strings("This should not be called for base class object")
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
package app.revanced.patches.facebook.ads.mainfeed
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||
import baseModelMapperFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction31i
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
|
||||
import getSponsoredDataModelTemplateFingerprint
|
||||
import getStoryVisibilityFingerprint
|
||||
|
||||
@Suppress("unused")
|
||||
val hideSponsoredStoriesPatch = bytecodePatch(
|
||||
name = "Hide 'Sponsored Stories'",
|
||||
) {
|
||||
compatibleWith("com.facebook.katana")
|
||||
|
||||
val getStoryVisibilityMatch by getStoryVisibilityFingerprint()
|
||||
val getSponsoredDataModelTemplateMatch by getSponsoredDataModelTemplateFingerprint()
|
||||
val baseModelMapperMatch by baseModelMapperFingerprint()
|
||||
|
||||
execute {
|
||||
val sponsoredDataModelTemplateMethod = getSponsoredDataModelTemplateMatch.method
|
||||
val baseModelMapperMethod = baseModelMapperMatch.method
|
||||
val baseModelWithTreeType = baseModelMapperMethod.returnType
|
||||
|
||||
val graphQlStoryClassDescriptor = "Lcom/facebook/graphql/model/GraphQLStory;"
|
||||
|
||||
// The "SponsoredDataModelTemplate" methods has the ids in its body to extract sponsored data
|
||||
// from GraphQL models, but targets the wrong derived type of "BaseModelWithTree". Since those ids
|
||||
// could change in future version, we need to extract them and call the base implementation directly.
|
||||
val getSponsoredDataHelperMethod = ImmutableMethod(
|
||||
getStoryVisibilityMatch.classDef.type,
|
||||
"getSponsoredData",
|
||||
listOf(ImmutableMethodParameter(graphQlStoryClassDescriptor, null, null)),
|
||||
baseModelWithTreeType,
|
||||
AccessFlags.PRIVATE.value or AccessFlags.STATIC.value,
|
||||
null,
|
||||
null,
|
||||
MutableMethodImplementation(4),
|
||||
).toMutable().apply {
|
||||
// Extract the ids of the original method. These ids seem to correspond to model types for
|
||||
// GraphQL data structure. They are then fed to a method of BaseModelWithTree that populate
|
||||
// and cast the requested GraphQL subtype. The Ids are found in the two first "CONST" instructions.
|
||||
val constInstructions = sponsoredDataModelTemplateMethod.implementation!!.instructions
|
||||
.asSequence()
|
||||
.filterIsInstance<Instruction31i>()
|
||||
.take(2)
|
||||
.toList()
|
||||
|
||||
val storyTypeId = constInstructions[0].narrowLiteral
|
||||
val sponsoredDataTypeId = constInstructions[1].narrowLiteral
|
||||
|
||||
addInstructions(
|
||||
"""
|
||||
const-class v2, $baseModelWithTreeType
|
||||
const v1, $storyTypeId
|
||||
const v0, $sponsoredDataTypeId
|
||||
invoke-virtual {p0, v2, v1, v0}, $baseModelMapperMethod
|
||||
move-result-object v0
|
||||
check-cast v0, $baseModelWithTreeType
|
||||
return-object v0
|
||||
""",
|
||||
)
|
||||
}
|
||||
|
||||
getStoryVisibilityMatch.mutableClass.methods.add(getSponsoredDataHelperMethod)
|
||||
|
||||
// Check if the parameter type is GraphQLStory and if sponsoredDataModelGetter returns a non-null value.
|
||||
// If so, hide the story by setting the visibility to StoryVisibility.GONE.
|
||||
getStoryVisibilityMatch.mutableMethod.addInstructionsWithLabels(
|
||||
getStoryVisibilityMatch.patternMatch!!.startIndex,
|
||||
"""
|
||||
instance-of v0, p0, $graphQlStoryClassDescriptor
|
||||
if-eqz v0, :resume_normal
|
||||
invoke-static {p0}, $getSponsoredDataHelperMethod
|
||||
move-result-object v0
|
||||
if-eqz v0, :resume_normal
|
||||
const-string v0, "GONE"
|
||||
return-object v0
|
||||
:resume_normal
|
||||
nop
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package app.revanced.patches.facebook.ads.story
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import com.android.tools.smali.dexlib2.iface.value.StringEncodedValue
|
||||
|
||||
internal val adsInsertionFingerprint = fieldFingerprint(
|
||||
fieldValue = "AdBucketDataSourceUtil\$attemptAdsInsertion\$1",
|
||||
)
|
||||
|
||||
internal val fetchMoreAdsFingerprint = fieldFingerprint(
|
||||
fieldValue = "AdBucketDataSourceUtil\$attemptFetchMoreAds\$1",
|
||||
)
|
||||
|
||||
internal fun fieldFingerprint(fieldValue: String) = fingerprint {
|
||||
returns("V")
|
||||
parameters()
|
||||
custom { method, classDef ->
|
||||
method.name == "run" &&
|
||||
classDef.fields.any any@{ field ->
|
||||
if (field.name != "__redex_internal_original_name") return@any false
|
||||
(field.initialValue as? StringEncodedValue)?.value == fieldValue
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package app.revanced.patches.facebook.ads.story
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val hideStoryAdsPatch = bytecodePatch(
|
||||
name = "Hide story ads",
|
||||
description = "Hides the ads in the Facebook app stories.",
|
||||
) {
|
||||
compatibleWith("com.facebook.katana")
|
||||
|
||||
val fetchMoreAdsMatch by fetchMoreAdsFingerprint()
|
||||
val adsInsertionMatch by adsInsertionFingerprint()
|
||||
|
||||
execute {
|
||||
setOf(fetchMoreAdsMatch, adsInsertionMatch).forEach { match ->
|
||||
match.mutableMethod.replaceInstruction(0, "return-void")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package app.revanced.patches.finanzonline.detection.bootloader
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val bootloaderDetectionPatch = bytecodePatch(
|
||||
name = "Remove bootloader detection",
|
||||
description = "Removes the check for an unlocked bootloader.",
|
||||
) {
|
||||
compatibleWith("at.gv.bmf.bmf2go")
|
||||
|
||||
val createKeyMatch by createKeyFingerprint()
|
||||
val bootStateMatch by bootStateFingerprint()
|
||||
|
||||
execute {
|
||||
setOf(createKeyMatch, bootStateMatch).forEach { match ->
|
||||
match.mutableMethod.addInstructions(
|
||||
0,
|
||||
"""
|
||||
const/4 v0, 0x1
|
||||
return v0
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +1,14 @@
|
||||
package app.revanced.patches.finanzonline.detection.bootloader.fingerprints
|
||||
package app.revanced.patches.finanzonline.detection.bootloader
|
||||
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
// Located @ at.gv.bmf.bmf2go.taxequalization.tools.utils.AttestationHelper#isBootStateOk (3.0.1)
|
||||
internal object BootStateFingerprint : MethodFingerprint(
|
||||
"Z",
|
||||
accessFlags = AccessFlags.PUBLIC.value,
|
||||
opcodes = listOf(
|
||||
internal val bootStateFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC)
|
||||
returns("Z")
|
||||
opcodes(
|
||||
Opcode.INVOKE_DIRECT,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.CONST_4,
|
||||
@ -27,4 +27,11 @@ internal object BootStateFingerprint : MethodFingerprint(
|
||||
Opcode.MOVE,
|
||||
Opcode.RETURN
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Located @ at.gv.bmf.bmf2go.taxequalization.tools.utils.AttestationHelper#createKey (3.0.1)
|
||||
internal val createKeyFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC)
|
||||
returns("Z")
|
||||
strings("attestation", "SHA-256", "random", "EC", "AndroidKeyStore")
|
||||
}
|
@ -1,16 +1,15 @@
|
||||
package app.revanced.patches.finanzonline.detection.root.fingerprints
|
||||
package app.revanced.patches.finanzonline.detection.root
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
// Located @ at.gv.bmf.bmf2go.taxequalization.tools.utils.RootDetection#isRooted (3.0.1)
|
||||
internal object RootDetectionFingerprint : MethodFingerprint(
|
||||
"L",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
|
||||
parameters = listOf("L"),
|
||||
opcodes = listOf(
|
||||
internal val rootDetectionFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
||||
returns("L")
|
||||
parameters("L")
|
||||
opcodes(
|
||||
Opcode.NEW_INSTANCE,
|
||||
Opcode.INVOKE_DIRECT,
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
@ -19,4 +18,4 @@ internal object RootDetectionFingerprint : MethodFingerprint(
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.RETURN_OBJECT
|
||||
)
|
||||
)
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package app.revanced.patches.finanzonline.detection.root
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val rootDetectionPatch = bytecodePatch(
|
||||
name = "Remove root detection",
|
||||
description = "Removes the check for root permissions.",
|
||||
) {
|
||||
compatibleWith("at.gv.bmf.bmf2go")
|
||||
|
||||
val rootDetectionMatch by rootDetectionFingerprint()
|
||||
|
||||
execute {
|
||||
rootDetectionMatch.mutableMethod.addInstructions(
|
||||
0,
|
||||
"""
|
||||
sget-object v0, Ljava/lang/Boolean;->FALSE:Ljava/lang/Boolean;
|
||||
return-object v0
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package app.revanced.patches.googlenews.customtabs
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
@Suppress("unused")
|
||||
val enableCustomTabsPatch = bytecodePatch(
|
||||
name = "Enable CustomTabs",
|
||||
description = "Enables CustomTabs to open articles in your default browser.",
|
||||
) {
|
||||
compatibleWith("com.google.android.apps.magazines")
|
||||
|
||||
val launchCustomTabMatch by launchCustomTabFingerprint()
|
||||
|
||||
execute {
|
||||
launchCustomTabMatch.mutableMethod.apply {
|
||||
val checkIndex = launchCustomTabMatch.patternMatch!!.endIndex + 1
|
||||
val register = getInstruction<OneRegisterInstruction>(checkIndex).registerA
|
||||
|
||||
replaceInstruction(checkIndex, "const/4 v$register, 0x1")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package app.revanced.patches.googlenews.customtabs
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal val launchCustomTabFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
|
||||
opcodes(
|
||||
Opcode.IPUT_OBJECT,
|
||||
Opcode.CONST_4,
|
||||
Opcode.IPUT,
|
||||
Opcode.CONST_4,
|
||||
Opcode.IPUT_BOOLEAN,
|
||||
)
|
||||
custom { _, classDef -> classDef.endsWith("CustomTabsArticleLauncher;") }
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package app.revanced.patches.googlenews.misc.extension
|
||||
|
||||
import app.revanced.patches.googlenews.misc.extension.hooks.startActivityInitHook
|
||||
import app.revanced.patches.shared.misc.extension.sharedExtensionPatch
|
||||
|
||||
val extensionPatch = sharedExtensionPatch(startActivityInitHook)
|
@ -1,26 +1,15 @@
|
||||
package app.revanced.patches.googlenews.misc.integrations.fingerprints
|
||||
package app.revanced.patches.googlenews.misc.extension.hooks
|
||||
|
||||
import app.revanced.patches.googlenews.misc.integrations.fingerprints.StartActivityInitFingerprint.getApplicationContextIndex
|
||||
import app.revanced.patches.shared.misc.integrations.BaseIntegrationsPatch.IntegrationsFingerprint
|
||||
import app.revanced.patches.shared.misc.extension.extensionHook
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
internal object StartActivityInitFingerprint : IntegrationsFingerprint(
|
||||
opcodes = listOf(
|
||||
Opcode.INVOKE_STATIC,
|
||||
Opcode.MOVE_RESULT,
|
||||
Opcode.CONST_4,
|
||||
Opcode.IF_EQZ,
|
||||
Opcode.CONST,
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.IPUT_OBJECT,
|
||||
Opcode.IPUT_BOOLEAN,
|
||||
Opcode.INVOKE_VIRTUAL, // Calls startActivity.getApplicationContext().
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
),
|
||||
private var getApplicationContextIndex = -1
|
||||
|
||||
internal val startActivityInitHook = extensionHook(
|
||||
insertIndexResolver = { method ->
|
||||
getApplicationContextIndex = method.indexOfFirstInstructionOrThrow {
|
||||
getReference<MethodReference>()?.name == "getApplicationContext"
|
||||
@ -33,9 +22,20 @@ internal object StartActivityInitFingerprint : IntegrationsFingerprint(
|
||||
as OneRegisterInstruction
|
||||
moveResultInstruction.registerA
|
||||
},
|
||||
customFingerprint = { methodDef, classDef ->
|
||||
methodDef.name == "onCreate" && classDef.endsWith("/StartActivity;")
|
||||
},
|
||||
) {
|
||||
private var getApplicationContextIndex = -1
|
||||
opcodes(
|
||||
Opcode.INVOKE_STATIC,
|
||||
Opcode.MOVE_RESULT,
|
||||
Opcode.CONST_4,
|
||||
Opcode.IF_EQZ,
|
||||
Opcode.CONST,
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.IPUT_OBJECT,
|
||||
Opcode.IPUT_BOOLEAN,
|
||||
Opcode.INVOKE_VIRTUAL, // Calls startActivity.getApplicationContext().
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
)
|
||||
custom { methodDef, classDef ->
|
||||
methodDef.name == "onCreate" && classDef.endsWith("/StartActivity;")
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package app.revanced.patches.googlenews.misc.gms
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val magazinesActivityOnCreateFingerprint = fingerprint {
|
||||
custom { methodDef, classDef ->
|
||||
methodDef.name == "onCreate" && classDef.endsWith("/StartActivity;")
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package app.revanced.patches.googlenews.misc.gms
|
||||
|
||||
import app.revanced.patcher.patch.Option
|
||||
import app.revanced.patches.googlenews.misc.extension.extensionPatch
|
||||
import app.revanced.patches.googlenews.misc.gms.Constants.MAGAZINES_PACKAGE_NAME
|
||||
import app.revanced.patches.googlenews.misc.gms.Constants.REVANCED_MAGAZINES_PACKAGE_NAME
|
||||
import app.revanced.patches.shared.misc.gms.gmsCoreSupportPatch
|
||||
import app.revanced.patches.shared.misc.gms.gmsCoreSupportResourcePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val gmsCoreSupportPatch = gmsCoreSupportPatch(
|
||||
fromPackageName = MAGAZINES_PACKAGE_NAME,
|
||||
toPackageName = REVANCED_MAGAZINES_PACKAGE_NAME,
|
||||
mainActivityOnCreateFingerprint = magazinesActivityOnCreateFingerprint,
|
||||
extensionPatch = extensionPatch,
|
||||
gmsCoreSupportResourcePatchFactory = ::gmsCoreSupportResourcePatch,
|
||||
) {
|
||||
// Remove version constraint,
|
||||
// once https://github.com/ReVanced/revanced-patches/pull/3111#issuecomment-2240877277 is resolved.
|
||||
compatibleWith(MAGAZINES_PACKAGE_NAME("5.108.0.644447823"))
|
||||
}
|
||||
|
||||
private fun gmsCoreSupportResourcePatch(
|
||||
gmsCoreVendorGroupIdOption: Option<String>,
|
||||
) = gmsCoreSupportResourcePatch(
|
||||
fromPackageName = MAGAZINES_PACKAGE_NAME,
|
||||
toPackageName = REVANCED_MAGAZINES_PACKAGE_NAME,
|
||||
spoofedPackageSignature = "24bb24c05e47e0aefa68a58a766179d9b613a666",
|
||||
gmsCoreVendorGroupIdOption = gmsCoreVendorGroupIdOption,
|
||||
)
|
@ -0,0 +1,5 @@
|
||||
package app.revanced.patches.googlephotos.misc.extension
|
||||
|
||||
import app.revanced.patches.shared.misc.extension.sharedExtensionPatch
|
||||
|
||||
val extensionPatch = sharedExtensionPatch(homeActivityInitHook)
|
@ -1,22 +1,15 @@
|
||||
package app.revanced.patches.googlephotos.misc.integrations.fingerprints
|
||||
package app.revanced.patches.googlephotos.misc.extension
|
||||
|
||||
import app.revanced.patches.googlephotos.misc.integrations.fingerprints.HomeActivityInitFingerprint.getApplicationContextIndex
|
||||
import app.revanced.patches.shared.misc.integrations.BaseIntegrationsPatch.IntegrationsFingerprint
|
||||
import app.revanced.patches.shared.misc.extension.extensionHook
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
internal object HomeActivityInitFingerprint : IntegrationsFingerprint(
|
||||
opcodes = listOf(
|
||||
Opcode.CONST_STRING,
|
||||
Opcode.INVOKE_STATIC,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.IF_NEZ,
|
||||
Opcode.INVOKE_VIRTUAL, // Calls getApplicationContext().
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
),
|
||||
private var getApplicationContextIndex = -1
|
||||
|
||||
internal val homeActivityInitHook = extensionHook(
|
||||
insertIndexResolver = { method ->
|
||||
getApplicationContextIndex = method.indexOfFirstInstructionOrThrow {
|
||||
getReference<MethodReference>()?.name == "getApplicationContext"
|
||||
@ -29,9 +22,16 @@ internal object HomeActivityInitFingerprint : IntegrationsFingerprint(
|
||||
as OneRegisterInstruction
|
||||
moveResultInstruction.registerA
|
||||
},
|
||||
customFingerprint = { methodDef, classDef ->
|
||||
methodDef.name == "onCreate" && classDef.endsWith("/HomeActivity;")
|
||||
},
|
||||
) {
|
||||
private var getApplicationContextIndex = -1
|
||||
opcodes(
|
||||
Opcode.CONST_STRING,
|
||||
Opcode.INVOKE_STATIC,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.IF_NEZ,
|
||||
Opcode.INVOKE_VIRTUAL, // Calls getApplicationContext().
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
)
|
||||
custom { methodDef, classDef ->
|
||||
methodDef.name == "onCreate" && classDef.endsWith("/HomeActivity;")
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package app.revanced.patches.googlephotos.misc.features
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val initializeFeaturesEnumFingerprint = fingerprint {
|
||||
strings("com.google.android.apps.photos.NEXUS_PRELOAD")
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package app.revanced.patches.googlephotos.misc.features
|
||||
|
||||
import app.revanced.patches.all.misc.build.BuildInfo
|
||||
import app.revanced.patches.all.misc.build.baseSpoofBuildInfoPatch
|
||||
|
||||
// Spoof build info to Google Pixel XL.
|
||||
@Suppress("unused")
|
||||
val spoofBuildInfoPatch = baseSpoofBuildInfoPatch {
|
||||
BuildInfo(
|
||||
brand = "google",
|
||||
manufacturer = "Google",
|
||||
device = "marlin",
|
||||
product = "marlin",
|
||||
model = "Pixel XL",
|
||||
fingerprint = "google/marlin/marlin:10/QP1A.191005.007.A3/5972272:user/release-keys",
|
||||
)
|
||||
}
|
@ -1,30 +1,26 @@
|
||||
package app.revanced.patches.googlephotos.features
|
||||
package app.revanced.patches.googlephotos.misc.features
|
||||
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.instructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.BytecodePatch
|
||||
import app.revanced.patcher.patch.annotation.CompatiblePackage
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringArrayPatchOption
|
||||
import app.revanced.patches.googlephotos.features.fingerprints.InitializeFeaturesEnumFingerprint
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.patch.stringsOption
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.resultOrThrow
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
||||
|
||||
@Patch(
|
||||
@Suppress("unused")
|
||||
val spoofFeaturesPatch = bytecodePatch(
|
||||
name = "Spoof features",
|
||||
description = "Spoofs the device to enable Google Pixel exclusive features, including unlimited storage.",
|
||||
dependencies = [SpoofBuildInfoPatch::class],
|
||||
compatiblePackages = [CompatiblePackage("com.google.android.apps.photos")],
|
||||
)
|
||||
@Suppress("unused")
|
||||
object SpoofFeaturesPatch : BytecodePatch(setOf(InitializeFeaturesEnumFingerprint)) {
|
||||
private val featuresToEnable by stringArrayPatchOption(
|
||||
"featuresToEnable",
|
||||
arrayOf(
|
||||
) {
|
||||
compatibleWith("com.google.android.apps.photos")
|
||||
|
||||
dependsOn(spoofBuildInfoPatch)
|
||||
|
||||
val featuresToEnable by stringsOption(
|
||||
key = "featuresToEnable",
|
||||
default = listOf(
|
||||
"com.google.android.apps.photos.NEXUS_PRELOAD",
|
||||
"com.google.android.apps.photos.nexus_preload",
|
||||
),
|
||||
@ -33,9 +29,9 @@ object SpoofFeaturesPatch : BytecodePatch(setOf(InitializeFeaturesEnumFingerprin
|
||||
required = true,
|
||||
)
|
||||
|
||||
private val featuresToDisable by stringArrayPatchOption(
|
||||
"featuresToDisable",
|
||||
arrayOf(
|
||||
val featuresToDisable by stringsOption(
|
||||
key = "featuresToDisable",
|
||||
default = listOf(
|
||||
"com.google.android.apps.photos.PIXEL_2017_PRELOAD",
|
||||
"com.google.android.apps.photos.PIXEL_2018_PRELOAD",
|
||||
"com.google.android.apps.photos.PIXEL_2019_MIDYEAR_PRELOAD",
|
||||
@ -58,29 +54,32 @@ object SpoofFeaturesPatch : BytecodePatch(setOf(InitializeFeaturesEnumFingerprin
|
||||
required = true,
|
||||
)
|
||||
|
||||
override fun execute(context: BytecodeContext) {
|
||||
val initializeFeaturesEnumMatch by initializeFeaturesEnumFingerprint()
|
||||
|
||||
execute {
|
||||
@Suppress("NAME_SHADOWING")
|
||||
val featuresToEnable = featuresToEnable!!.toSet()
|
||||
|
||||
@Suppress("NAME_SHADOWING")
|
||||
val featuresToDisable = featuresToDisable!!.toSet()
|
||||
|
||||
InitializeFeaturesEnumFingerprint.resultOrThrow().let { result ->
|
||||
result.mutableMethod.apply {
|
||||
getInstructions().filter { it.opcode == Opcode.CONST_STRING }.forEach {
|
||||
val feature = it.getReference<StringReference>()!!.string
|
||||
initializeFeaturesEnumMatch.mutableMethod.apply {
|
||||
instructions.filter { it.opcode == Opcode.CONST_STRING }.forEach {
|
||||
val feature = it.getReference<StringReference>()!!.string
|
||||
|
||||
val spoofedFeature = when (feature) {
|
||||
in featuresToEnable -> "android.hardware.wifi"
|
||||
in featuresToDisable -> "dummy"
|
||||
else -> return@forEach
|
||||
}
|
||||
|
||||
val constStringIndex = it.location.index
|
||||
val constStringRegister = (it as OneRegisterInstruction).registerA
|
||||
|
||||
replaceInstruction(
|
||||
constStringIndex,
|
||||
"const-string v$constStringRegister, \"$spoofedFeature\"",
|
||||
)
|
||||
val spoofedFeature = when (feature) {
|
||||
in featuresToEnable -> "android.hardware.wifi"
|
||||
in featuresToDisable -> "dummy"
|
||||
else -> return@forEach
|
||||
}
|
||||
|
||||
val constStringIndex = it.location.index
|
||||
val constStringRegister = (it as OneRegisterInstruction).registerA
|
||||
|
||||
replaceInstruction(
|
||||
constStringIndex,
|
||||
"const-string v$constStringRegister, \"$spoofedFeature\"",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package app.revanced.patches.googlephotos.misc.gms
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val homeActivityOnCreateFingerprint = fingerprint {
|
||||
custom { methodDef, classDef ->
|
||||
methodDef.name == "onCreate" && classDef.endsWith("/HomeActivity;")
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package app.revanced.patches.googlephotos.misc.gms
|
||||
|
||||
import app.revanced.patcher.patch.Option
|
||||
import app.revanced.patches.googlephotos.misc.extension.extensionPatch
|
||||
import app.revanced.patches.googlephotos.misc.gms.Constants.PHOTOS_PACKAGE_NAME
|
||||
import app.revanced.patches.googlephotos.misc.gms.Constants.REVANCED_PHOTOS_PACKAGE_NAME
|
||||
import app.revanced.patches.shared.misc.gms.gmsCoreSupportPatch
|
||||
|
||||
@Suppress("unused")
|
||||
val gmsCoreSupportPatch = gmsCoreSupportPatch(
|
||||
fromPackageName = PHOTOS_PACKAGE_NAME,
|
||||
toPackageName = REVANCED_PHOTOS_PACKAGE_NAME,
|
||||
mainActivityOnCreateFingerprint = homeActivityOnCreateFingerprint,
|
||||
extensionPatch = extensionPatch,
|
||||
gmsCoreSupportResourcePatchFactory = ::gmsCoreSupportResourcePatch,
|
||||
) {
|
||||
compatibleWith(PHOTOS_PACKAGE_NAME)
|
||||
}
|
||||
|
||||
private fun gmsCoreSupportResourcePatch(
|
||||
gmsCoreVendorGroupIdOption: Option<String>,
|
||||
) = app.revanced.patches.shared.misc.gms.gmsCoreSupportResourcePatch(
|
||||
fromPackageName = PHOTOS_PACKAGE_NAME,
|
||||
toPackageName = REVANCED_PHOTOS_PACKAGE_NAME,
|
||||
spoofedPackageSignature = "24bb24c05e47e0aefa68a58a766179d9b613a600",
|
||||
gmsCoreVendorGroupIdOption = gmsCoreVendorGroupIdOption,
|
||||
)
|
@ -0,0 +1,8 @@
|
||||
package app.revanced.patches.googlephotos.misc.preferences
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val backupPreferencesFingerprint = fingerprint {
|
||||
returns("Lcom/google/android/apps/photos/backup/data/BackupPreferences;")
|
||||
strings("backup_prefs_had_backup_only_when_charging_enabled")
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package app.revanced.patches.googlephotos.misc.preferences
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
@Suppress("unused")
|
||||
val restoreHiddenBackUpWhileChargingTogglePatch = bytecodePatch(
|
||||
name = "Restore hidden 'Back up while charging' toggle",
|
||||
description = "Restores a hidden toggle to only run backups when the device is charging.",
|
||||
) {
|
||||
compatibleWith("com.google.android.apps.photos")
|
||||
|
||||
val backupPreferencesMatch by backupPreferencesFingerprint()
|
||||
|
||||
execute {
|
||||
// Patches 'backup_prefs_had_backup_only_when_charging_enabled' to always be true.
|
||||
val chargingPrefStringIndex = backupPreferencesMatch.stringMatches!!.first().index
|
||||
backupPreferencesMatch.mutableMethod.apply {
|
||||
// Get the register of move-result.
|
||||
val resultRegister = getInstruction<OneRegisterInstruction>(chargingPrefStringIndex + 2).registerA
|
||||
// Insert const after move-result to override register as true.
|
||||
addInstruction(chargingPrefStringIndex + 3, "const/4 v$resultRegister, 0x1")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package app.revanced.patches.googlerecorder.restrictions
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val onApplicationCreateFingerprint = fingerprint {
|
||||
strings("com.google.android.feature.PIXEL_2017_EXPERIENCE")
|
||||
custom { method, classDef ->
|
||||
if (method.name != "onCreate") return@custom false
|
||||
|
||||
classDef.endsWith("RecorderApplication;")
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package app.revanced.patches.googlerecorder.restrictions
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.removeInstructions
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
@Suppress("unused")
|
||||
val removeDeviceRestrictionsPatch = bytecodePatch(
|
||||
name = "Remove device restrictions",
|
||||
description = "Removes restrictions from using the app on any device. Requires mounting patched app over original.",
|
||||
) {
|
||||
compatibleWith("com.google.android.apps.recorder")
|
||||
|
||||
val onApplicationCreateMatch by onApplicationCreateFingerprint()
|
||||
|
||||
execute {
|
||||
val featureStringIndex = onApplicationCreateMatch.stringMatches!!.first().index
|
||||
|
||||
onApplicationCreateMatch.mutableMethod.apply {
|
||||
// Remove check for device restrictions.
|
||||
removeInstructions(featureStringIndex - 2, 5)
|
||||
|
||||
val featureAvailableRegister = getInstruction<OneRegisterInstruction>(featureStringIndex).registerA
|
||||
|
||||
// Override "isPixelDevice()" to return true.
|
||||
addInstruction(featureStringIndex, "const/4 v$featureAvailableRegister, 0x1")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package app.revanced.patches.hexeditor.ad
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstructions
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val disableAdsPatch = bytecodePatch(
|
||||
name = "Disable ads",
|
||||
) {
|
||||
compatibleWith("com.myprog.hexedit")
|
||||
|
||||
val primaryAdsMatch by primaryAdsFingerprint()
|
||||
|
||||
execute {
|
||||
primaryAdsMatch.mutableMethod.replaceInstructions(
|
||||
0,
|
||||
"""
|
||||
const/4 v0, 0x1
|
||||
return v0
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package app.revanced.patches.hexeditor.ad
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val primaryAdsFingerprint = fingerprint {
|
||||
custom { method, classDef ->
|
||||
classDef.endsWith("PreferencesHelper;") && method.name == "isAdsDisabled"
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package app.revanced.patches.iconpackstudio.misc.pro
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val checkProFingerprint = fingerprint {
|
||||
returns("Z")
|
||||
custom { _, classDef -> classDef.endsWith("IPSPurchaseRepository;") }
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package app.revanced.patches.iconpackstudio.misc.pro
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val unlockProPatch = bytecodePatch(
|
||||
name = "Unlock pro",
|
||||
) {
|
||||
compatibleWith("ginlemon.iconpackstudio"("2.2 build 016"))
|
||||
|
||||
val checkProMatch by checkProFingerprint()
|
||||
|
||||
execute {
|
||||
checkProMatch.mutableMethod.addInstructions(
|
||||
0,
|
||||
"""
|
||||
const/4 v0, 0x1
|
||||
return v0
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package app.revanced.patches.idaustria.detection.root
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
internal val attestationSupportedCheckFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC)
|
||||
returns("V")
|
||||
custom { method, classDef ->
|
||||
method.name == "attestationSupportCheck" &&
|
||||
classDef.endsWith("/DeviceIntegrityCheck;")
|
||||
}
|
||||
}
|
||||
|
||||
internal val bootloaderCheckFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC)
|
||||
returns("Z")
|
||||
custom { method, classDef ->
|
||||
method.name == "bootloaderCheck" &&
|
||||
classDef.endsWith("/DeviceIntegrityCheck;")
|
||||
}
|
||||
}
|
||||
|
||||
internal val rootCheckFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC)
|
||||
returns("V")
|
||||
custom { method, classDef ->
|
||||
method.name == "rootCheck" &&
|
||||
classDef.endsWith("/DeviceIntegrityCheck;")
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package app.revanced.patches.idaustria.detection.root
|
||||
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.util.returnEarly
|
||||
|
||||
@Suppress("unused")
|
||||
val rootDetectionPatch = bytecodePatch(
|
||||
name = "Remove root detection",
|
||||
description = "Removes the check for root permissions and unlocked bootloader.",
|
||||
) {
|
||||
compatibleWith("at.gv.oe.app")
|
||||
|
||||
attestationSupportedCheckFingerprint()
|
||||
bootloaderCheckFingerprint()
|
||||
rootCheckFingerprint()
|
||||
|
||||
execute {
|
||||
setOf(attestationSupportedCheckFingerprint, bootloaderCheckFingerprint, rootCheckFingerprint).returnEarly(true)
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package app.revanced.patches.idaustria.detection.signature
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
internal val spoofSignatureFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PRIVATE)
|
||||
returns("L")
|
||||
parameters("L")
|
||||
custom { method, classDef ->
|
||||
classDef.endsWith("/SL2Step1Task;") && method.name == "getPubKey"
|
||||
}
|
||||
}
|
@ -1,23 +1,20 @@
|
||||
package app.revanced.patches.idaustria.detection.signature
|
||||
|
||||
import app.revanced.patcher.data.BytecodeContext
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.patch.BytecodePatch
|
||||
import app.revanced.patcher.patch.annotation.CompatiblePackage
|
||||
import app.revanced.patcher.patch.annotation.Patch
|
||||
import app.revanced.patches.idaustria.detection.signature.fingerprints.SpoofSignatureFingerprint
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Patch(
|
||||
@Suppress("unused")
|
||||
val spoofSignaturePatch = bytecodePatch(
|
||||
name = "Spoof signature",
|
||||
description = "Spoofs the signature of the app.",
|
||||
compatiblePackages = [CompatiblePackage("at.gv.oe.app")]
|
||||
)
|
||||
@Suppress("unused")
|
||||
object SpoofSignaturePatch : BytecodePatch(
|
||||
setOf(SpoofSignatureFingerprint)
|
||||
) {
|
||||
private const val EXPECTED_SIGNATURE =
|
||||
"OpenSSLRSAPublicKey{modulus=ac3e6fd6050aa7e0d6010ae58190404cd89a56935b44f6fee" +
|
||||
compatibleWith("at.gv.oe.app")
|
||||
|
||||
val spoofSignatureMatch by spoofSignatureFingerprint()
|
||||
|
||||
execute {
|
||||
val expectedSignature =
|
||||
"OpenSSLRSAPublicKey{modulus=ac3e6fd6050aa7e0d6010ae58190404cd89a56935b44f6fee" +
|
||||
"067c149768320026e10b24799a1339e414605e448e3f264444a327b9ae292be2b62ad567dd1800dbed4a88f718a33dc6db6b" +
|
||||
"f5178aa41aa0efff8a3409f5ca95dbfccd92c7b4298966df806ea7a0204a00f0e745f6d9f13bdf24f3df715d7b62c1600906" +
|
||||
"15de1c8a956b9286764985a3b3c060963c435fb9481a5543aaf0671fc2dba6c5c2b17d1ef1d85137f14dc9bbdf3490288087" +
|
||||
@ -29,13 +26,12 @@ object SpoofSignaturePatch : BytecodePatch(
|
||||
"77ef1be61b2c01ebdabddcbf53cc4b6fd9a3c445606ee77b3758162c80ad8f8137b3c6864e92db904807dcb2be9d7717dd21" +
|
||||
"bf42c121d620ddfb7914f7a95c713d9e1c1b7bdb4a03d618e40cf7e9e235c0b5687e03b7ab3,publicExponent=10001}"
|
||||
|
||||
override fun execute(context: BytecodeContext) {
|
||||
SpoofSignatureFingerprint.result!!.mutableMethod.addInstructions(
|
||||
spoofSignatureMatch.mutableMethod.addInstructions(
|
||||
0,
|
||||
"""
|
||||
const-string v0, "$EXPECTED_SIGNATURE"
|
||||
const-string v0, "$expectedSignature"
|
||||
return-object v0
|
||||
"""
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package app.revanced.patches.inshorts.ad
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val inshortsAdsFingerprint = fingerprint {
|
||||
returns("V")
|
||||
strings("GoogleAdLoader", "exception in requestAd")
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package app.revanced.patches.inshorts.ad
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val hideAdsPatch = bytecodePatch(
|
||||
name = "Hide ads",
|
||||
) {
|
||||
compatibleWith("com.nis.app")
|
||||
|
||||
val inshortsAdsMatch by inshortsAdsFingerprint()
|
||||
|
||||
execute {
|
||||
inshortsAdsMatch.mutableMethod.addInstruction(
|
||||
0,
|
||||
"""
|
||||
return-void
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package app.revanced.patches.instagram.ads
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
internal val adInjectorFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PRIVATE)
|
||||
returns("Z")
|
||||
parameters("L", "L")
|
||||
strings(
|
||||
"SponsoredContentController.insertItem",
|
||||
"SponsoredContentController::Delivery",
|
||||
)
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package app.revanced.patches.instagram.ads
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val hideAdsPatch = bytecodePatch(
|
||||
name = "Hide ads",
|
||||
description = "Hides ads in stories, discover, profile, etc. " +
|
||||
"An ad can still appear once when refreshing the home feed.",
|
||||
) {
|
||||
compatibleWith("com.instagram.android")
|
||||
|
||||
val adInjectorMatch by adInjectorFingerprint()
|
||||
|
||||
execute {
|
||||
adInjectorMatch.mutableMethod.addInstructions(
|
||||
0,
|
||||
"""
|
||||
const/4 v0, 0x0
|
||||
return v0
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package app.revanced.patches.irplus.ad
|
||||
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val irplusAdsFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
|
||||
returns("V")
|
||||
parameters("L", "Z")
|
||||
strings("TAGGED")
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package app.revanced.patches.irplus.ad
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val removeAdsPatch = bytecodePatch(
|
||||
name = "Remove ads",
|
||||
) {
|
||||
compatibleWith("net.binarymode.android.irplus")
|
||||
|
||||
val irplusAdsMatch by irplusAdsFingerprint()
|
||||
|
||||
execute {
|
||||
// By overwriting the second parameter of the method,
|
||||
// the view which holds the advertisement is removed.
|
||||
irplusAdsMatch.mutableMethod.addInstruction(0, "const/4 p2, 0x0")
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package app.revanced.patches.lightroom.misc.login
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val disableMandatoryLoginPatch = bytecodePatch(
|
||||
name = "Disable mandatory login",
|
||||
) {
|
||||
compatibleWith("com.adobe.lrmobile")
|
||||
|
||||
val isLoggedInMatch by isLoggedInFingerprint()
|
||||
|
||||
execute {
|
||||
isLoggedInMatch.mutableMethod.apply {
|
||||
val index = implementation!!.instructions.lastIndex - 1
|
||||
// Set isLoggedIn = true.
|
||||
replaceInstruction(index, "const/4 v0, 0x1")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package app.revanced.patches.lightroom.misc.login
|
||||
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val isLoggedInFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC, AccessFlags.FINAL)
|
||||
returns("Z")
|
||||
opcodes(
|
||||
Opcode.INVOKE_STATIC,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.SGET_OBJECT,
|
||||
Opcode.IF_NE,
|
||||
Opcode.CONST_4,
|
||||
Opcode.GOTO
|
||||
)
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package app.revanced.patches.lightroom.misc.premium
|
||||
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val hasPurchasedFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PRIVATE, AccessFlags.FINAL)
|
||||
returns("Z")
|
||||
opcodes(
|
||||
Opcode.SGET_OBJECT,
|
||||
Opcode.CONST_4,
|
||||
Opcode.CONST_4,
|
||||
Opcode.CONST_4,
|
||||
)
|
||||
strings("isPurchaseDoneRecently = true, access platform profile present? = ")
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package app.revanced.patches.lightroom.misc.premium
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val unlockPremiumPatch = bytecodePatch(
|
||||
name = "Unlock premium",
|
||||
) {
|
||||
compatibleWith("com.adobe.lrmobile")
|
||||
|
||||
val hasPurchasedMatch by hasPurchasedFingerprint()
|
||||
|
||||
execute {
|
||||
// Set hasPremium = true.
|
||||
hasPurchasedMatch.mutableMethod.replaceInstruction(2, "const/4 v2, 0x1")
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package app.revanced.patches.memegenerator.detection.license
|
||||
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val licenseValidationFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
||||
returns("Z")
|
||||
parameters("Landroid/content/Context;")
|
||||
opcodes(
|
||||
Opcode.INVOKE_STATIC,
|
||||
Opcode.MOVE_RESULT_WIDE,
|
||||
Opcode.INVOKE_STATIC,
|
||||
Opcode.MOVE_RESULT_WIDE,
|
||||
Opcode.CMP_LONG,
|
||||
Opcode.IF_GEZ,
|
||||
Opcode.CONST_4,
|
||||
Opcode.RETURN,
|
||||
Opcode.CONST_4,
|
||||
Opcode.RETURN
|
||||
)
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package app.revanced.patches.memegenerator.detection.license
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstructions
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val licenseValidationPatch = bytecodePatch(
|
||||
description = "Disables Firebase license validation.",
|
||||
) {
|
||||
val licenseValidationMatch by licenseValidationFingerprint()
|
||||
|
||||
execute {
|
||||
licenseValidationMatch.mutableMethod.replaceInstructions(
|
||||
0,
|
||||
"""
|
||||
const/4 p0, 0x1
|
||||
return p0
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
@ -1,17 +1,14 @@
|
||||
package app.revanced.patches.memegenerator.detection.signature.fingerprints
|
||||
package app.revanced.patches.memegenerator.detection.signature
|
||||
|
||||
import app.revanced.patcher.extensions.or
|
||||
import app.revanced.patcher.fingerprint.annotation.FuzzyPatternScanMethod
|
||||
import app.revanced.patcher.fingerprint.MethodFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
@FuzzyPatternScanMethod(2)
|
||||
internal object VerifySignatureFingerprint : MethodFingerprint(
|
||||
returnType = "Z",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
|
||||
parameters = listOf("Landroid/app/Activity;"),
|
||||
opcodes = listOf(
|
||||
internal val verifySignatureFingerprint = fingerprint(fuzzyPatternScanThreshold = 2) {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
||||
returns("Z")
|
||||
parameters("Landroid/app/Activity;")
|
||||
opcodes(
|
||||
Opcode.SGET_OBJECT,
|
||||
Opcode.IF_NEZ,
|
||||
Opcode.INVOKE_STATIC,
|
||||
@ -31,5 +28,5 @@ internal object VerifySignatureFingerprint : MethodFingerprint(
|
||||
Opcode.CONST_4,
|
||||
Opcode.RETURN,
|
||||
Opcode.ADD_INT_LIT8
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package app.revanced.patches.memegenerator.detection.signature
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstructions
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val signatureVerificationPatch = bytecodePatch(
|
||||
description = "Disables detection of incorrect signature.",
|
||||
) {
|
||||
val verifySignatureMatch by verifySignatureFingerprint()
|
||||
|
||||
execute {
|
||||
verifySignatureMatch.mutableMethod.replaceInstructions(
|
||||
0,
|
||||
"""
|
||||
const/4 p0, 0x1
|
||||
return p0
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package app.revanced.patches.memegenerator.misc.pro
|
||||
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val isFreeVersionFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
||||
returns("Ljava/lang/Boolean;")
|
||||
parameters("Landroid/content/Context;")
|
||||
opcodes(
|
||||
Opcode.SGET,
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.CONST_STRING,
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.MOVE_RESULT,
|
||||
Opcode.IF_EQZ
|
||||
)
|
||||
strings("free")
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package app.revanced.patches.memegenerator.misc.pro
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstructions
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.memegenerator.detection.license.licenseValidationPatch
|
||||
import app.revanced.patches.memegenerator.detection.signature.signatureVerificationPatch
|
||||
|
||||
@Suppress("unused")
|
||||
val unlockProVersionPatch = bytecodePatch(
|
||||
name = "Unlock pro",
|
||||
) {
|
||||
dependsOn(signatureVerificationPatch, licenseValidationPatch)
|
||||
|
||||
compatibleWith("com.zombodroid.MemeGenerator"("4.6364", "4.6370", "4.6375", "4.6377"))
|
||||
|
||||
val isFreeVersionMatch by isFreeVersionFingerprint()
|
||||
|
||||
execute {
|
||||
isFreeVersionMatch.mutableMethod.replaceInstructions(
|
||||
0,
|
||||
"""
|
||||
sget-object p0, Ljava/lang/Boolean;->FALSE:Ljava/lang/Boolean;
|
||||
return-object p0
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package app.revanced.patches.messenger.inbox
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.value.StringEncodedValue
|
||||
|
||||
internal val createInboxSubTabsFingerprint = fingerprint {
|
||||
returns("V")
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
opcodes(
|
||||
Opcode.CONST_4,
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.RETURN_VOID,
|
||||
)
|
||||
custom { method, classDef ->
|
||||
method.name == "run" &&
|
||||
classDef.fields.any any@{ field ->
|
||||
if (field.name != "__redex_internal_original_name") return@any false
|
||||
(field.initialValue as? StringEncodedValue)?.value == "InboxSubtabsItemSupplierImplementation\$onSubscribe\$1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal val loadInboxAdsFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
||||
returns("V")
|
||||
strings(
|
||||
"ads_load_begin",
|
||||
"inbox_ads_fetch_start",
|
||||
)
|
||||
custom { method, _ ->
|
||||
method.definingClass == "Lcom/facebook/messaging/business/inboxads/plugins/inboxads/itemsupplier/" +
|
||||
"InboxAdsItemSupplierImplementation;"
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package app.revanced.patches.messenger.inbox
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val hideInboxAdsPatch = bytecodePatch(
|
||||
name = "Hide inbox ads",
|
||||
description = "Hides ads in inbox.",
|
||||
) {
|
||||
compatibleWith("com.facebook.orca")
|
||||
|
||||
val loadInboxAdsMatch by loadInboxAdsFingerprint()
|
||||
|
||||
execute {
|
||||
loadInboxAdsMatch.mutableMethod.replaceInstruction(0, "return-void")
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package app.revanced.patches.messenger.inbox
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val hideInboxSubtabsPatch = bytecodePatch(
|
||||
name = "Hide inbox subtabs",
|
||||
description = "Hides Home and Channels tabs between active now tray and chats.",
|
||||
) {
|
||||
compatibleWith("com.facebook.orca")
|
||||
|
||||
val createInboxSubTabsMatch by createInboxSubTabsFingerprint()
|
||||
|
||||
execute {
|
||||
createInboxSubTabsMatch.mutableMethod.replaceInstruction(2, "const/4 v0, 0x0")
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package app.revanced.patches.messenger.inputfield
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
@Suppress("unused")
|
||||
val disableSwitchingEmojiToStickerPatch = bytecodePatch(
|
||||
name = "Disable switching emoji to sticker",
|
||||
description = "Disables switching from emoji to sticker search mode in message input field.",
|
||||
) {
|
||||
compatibleWith("com.facebook.orca"("439.0.0.29.119"))
|
||||
|
||||
val switchMessangeInputEmojiButtonMatch by switchMessangeInputEmojiButtonFingerprint()
|
||||
|
||||
execute {
|
||||
val setStringIndex = switchMessangeInputEmojiButtonMatch.patternMatch!!.startIndex + 2
|
||||
|
||||
switchMessangeInputEmojiButtonMatch.mutableMethod.apply {
|
||||
val targetRegister = getInstruction<OneRegisterInstruction>(setStringIndex).registerA
|
||||
|
||||
replaceInstruction(setStringIndex, "const-string v$targetRegister, \"expression\"")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package app.revanced.patches.messenger.inputfield
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val disableTypingIndicatorPatch = bytecodePatch(
|
||||
name = "Disable typing indicator",
|
||||
description = "Disables the indicator while typing a message.",
|
||||
) {
|
||||
compatibleWith("com.facebook.orca")
|
||||
|
||||
val sendTypingIndicatorMatch by sendTypingIndicatorFingerprint()
|
||||
|
||||
execute {
|
||||
sendTypingIndicatorMatch.mutableMethod.replaceInstruction(0, "return-void")
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user