1
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:
oSumAtrIX 2024-10-10 19:43:01 +02:00
parent 5848269c2e
commit eee1692277
No known key found for this signature in database
GPG Key ID: A9B3094ACDB604B4
1359 changed files with 22895 additions and 29623 deletions

View File

@ -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

View File

@ -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

View File

@ -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
View File

@ -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/

View File

@ -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>

View File

@ -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

View File

@ -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"])
}

View File

@ -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

View File

@ -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

View File

@ -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

File diff suppressed because it is too large Load Diff

35
patches/build.gradle.kts Normal file
View 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")
}
}
}
}

View File

@ -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)
}

View File

@ -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")
},
),
)
}

View File

@ -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,
)
},
)
}

View File

@ -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"),
}

View File

@ -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;",
),
),
}

View File

@ -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",
),
}

View File

@ -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")

View File

@ -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;",
),
),
}

View 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()
},
)
}

View File

@ -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)
}
}
}
}

View File

@ -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")) {
@ -58,4 +54,4 @@ object OverrideCertificatePinningPatch : ResourcePatch() {
)
}
}
}
}

View File

@ -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"
},
)
}
}
}

View File

@ -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() }
}
}
}

View File

@ -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",
),
}

View File

@ -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",
),
}

View File

@ -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

View File

@ -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
}

View File

@ -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 ->

View File

@ -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")

View File

@ -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
""",
)
}
}

View File

@ -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")
}

View File

@ -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;"
}
}

View File

@ -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",
)
}
}
}

View File

@ -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")
}

View File

@ -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")
}
}

View File

@ -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")
}
}

View File

@ -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;"
}
}

View File

@ -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",
)
}
}
}

View File

@ -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)
}

View File

@ -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",
)
}
}
}

View File

@ -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")
}

View File

@ -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")
}

View File

@ -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
""",
)
}
}

View File

@ -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
}
}
}

View File

@ -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")
}
}
}

View File

@ -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
""",
)
}
}
}

View File

@ -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")
}

View File

@ -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
)
)
}

View File

@ -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
""",
)
}
}

View File

@ -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")
}
}
}

View File

@ -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;") }
}

View File

@ -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)

View File

@ -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;")
}
}

View File

@ -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;")
}
}

View File

@ -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,
)

View File

@ -0,0 +1,5 @@
package app.revanced.patches.googlephotos.misc.extension
import app.revanced.patches.shared.misc.extension.sharedExtensionPatch
val extensionPatch = sharedExtensionPatch(homeActivityInitHook)

View File

@ -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;")
}
}

View File

@ -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")
}

View File

@ -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",
)
}

View File

@ -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\"",
)
}
}
}

View File

@ -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;")
}
}

View File

@ -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,
)

View File

@ -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")
}

View File

@ -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")
}
}
}

View File

@ -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;")
}
}

View File

@ -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")
}
}
}

View File

@ -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
""",
)
}
}

View File

@ -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"
}
}

View File

@ -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;") }
}

View File

@ -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
""",
)
}
}

View File

@ -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;")
}
}

View File

@ -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)
}
}

View File

@ -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"
}
}

View File

@ -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
"""
""",
)
}
}

View File

@ -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")
}

View File

@ -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
""",
)
}
}

View File

@ -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",
)
}

View File

@ -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
""",
)
}
}

View File

@ -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")
}

View File

@ -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")
}
}

View File

@ -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")
}
}
}

View File

@ -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
)
}

View File

@ -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? = ")
}

View File

@ -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")
}
}

View File

@ -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
)
}

View File

@ -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
""",
)
}
}

View File

@ -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
),
)
)
}

View File

@ -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
""",
)
}
}

View File

@ -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")
}

View File

@ -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
""",
)
}
}

View File

@ -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;"
}
}

View File

@ -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")
}
}

View File

@ -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")
}
}

View File

@ -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\"")
}
}
}

View File

@ -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