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
1359 changed files with 22895 additions and 29623 deletions

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

@ -0,0 +1,37 @@
package app.revanced.patches.all.misc.activity.exportall
import app.revanced.patcher.patch.resourcePatch
@Suppress("unused")
val exportAllActivitiesPatch = resourcePatch(
name = "Export all activities",
description = "Makes all app activities exportable.",
use = false,
) {
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(exportedFlag)
if (exportedAttribute != null) {
if (exportedAttribute.nodeValue != "true") {
exportedAttribute.nodeValue = "true"
}
}
// 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(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

@ -0,0 +1,214 @@
package app.revanced.patches.all.misc.build
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.longOption
import app.revanced.patcher.patch.stringOption
@Suppress("unused")
val spoofBuildInfoPatch = bytecodePatch(
name = "Spoof build info",
description = "Spoof the information about the current build.",
use = false,
) {
val board by stringOption(
key = "board",
default = null,
title = "Board",
description = "The name of the underlying board, like \"goldfish\".",
)
val bootloader by stringOption(
key = "bootloader",
default = null,
title = "Bootloader",
description = "The system bootloader version number.",
)
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.",
)
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.",
)
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.",
)
val device by stringOption(
key = "device",
default = null,
title = "Device",
description = "The name of the industrial design.",
)
val display by stringOption(
key = "display",
default = null,
title = "Display",
description = "A build ID string meant for displaying to the user.",
)
val fingerprint by stringOption(
key = "fingerprint",
default = null,
title = "Fingerprint",
description = "A string that uniquely identifies this build.",
)
val hardware by stringOption(
key = "hardware",
default = null,
title = "Hardware",
description = "The name of the hardware (from the kernel command line or /proc).",
)
val host by stringOption(
key = "host",
default = null,
title = "Host",
description = "The host.",
)
val id by stringOption(
key = "id",
default = null,
title = "ID",
description = "Either a changelist number, or a label like \"M4-rc20\".",
)
val manufacturer by stringOption(
key = "manufacturer",
default = null,
title = "Manufacturer",
description = "The manufacturer of the product/hardware.",
)
val model by stringOption(
key = "model",
default = null,
title = "Model",
description = "The end-user-visible name for the end product.",
)
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).",
)
val product by stringOption(
key = "product",
default = null,
title = "Product",
description = "The name of the overall product.",
)
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.",
)
val serial by stringOption(
key = "serial",
default = null,
title = "Serial",
description = "This field was deprecated in API level 26. Use getSerial() instead.",
)
val sku by stringOption(
key = "sku",
default = null,
title = "SKU",
description = "The SKU of the hardware (from the kernel command line).",
)
val socManufacturer by stringOption(
key = "soc-manufacturer",
default = null,
title = "SOC Manufacturer",
description = "The manufacturer of the device's primary system-on-chip.",
)
val socModel by stringOption(
key = "soc-model",
default = null,
title = "SOC Model",
description = "The model name of the device's primary system-on-chip.",
)
val tags by stringOption(
key = "tags",
default = null,
title = "Tags",
description = "Comma-separated tags describing the build, like \"unsigned,debug\".",
)
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.",
)
val type by stringOption(
key = "type",
default = null,
title = "Type",
description = "The type of build, like \"user\" or \"eng\".",
)
val user by stringOption(
key = "user",
default = null,
title = "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

@ -0,0 +1,23 @@
package app.revanced.patches.all.misc.debugging
import app.revanced.patcher.patch.resourcePatch
import org.w3c.dom.Element
@Suppress("unused")
val enableAndroidDebuggingPatch = resourcePatch(
name = "Enable Android debugging",
description = "Enables Android debugging capabilities. This can slow down the app.",
use = false,
) {
execute { context ->
context.document["AndroidManifest.xml"].use { document ->
val applicationNode =
document
.getElementsByTagName("application")
.item(0) as Element
// set application as debuggable
applicationNode.setAttribute("android:debuggable", "true")
}
}
}

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

@ -0,0 +1,56 @@
package app.revanced.patches.all.misc.hex
import app.revanced.patcher.patch.PatchException
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
@Suppress("unused")
val hexPatch = rawResourcePatch(
name = "Hex",
description = "Replaces a hexadecimal patterns of bytes of files in an APK.",
use = false,
) {
// 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.
val replacements by stringsOption(
key = "replacements",
title = "Replacements",
description = """
Hexadecimal patterns to search for and replace with another in a target file.
A pattern is a sequence of case insensitive strings, each representing hexadecimal bytes, separated by spaces.
An example pattern is 'aa 01 02 FF'.
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.
The replacement pattern must have the same length as the original pattern.
Full example of a valid input:
'aa 01 02 FF|00 00 00 00|path/to/file'
""".trimIndentMultiline(),
required = true,
)
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)
}.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

@ -0,0 +1,57 @@
package app.revanced.patches.all.misc.network
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
@Suppress("unused")
val overrideCertificatePinningPatch = resourcePatch(
name = "Override certificate pinning",
description = "Overrides certificate pinning, allowing to inspect traffic via a proxy.",
use = false,
) {
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.document["AndroidManifest.xml"].use { document ->
val applicationNode = document.getElementsByTagName("application").item(0) as Element
if (!applicationNode.hasAttribute("networkSecurityConfig")) {
document.createAttribute("android:networkSecurityConfig")
.apply { value = "@xml/network_security_config" }.let(applicationNode.attributes::setNamedItem)
}
}
// In case the file does not exist create the "network_security_config.xml" file.
File(resXmlDirectory, "network_security_config.xml").apply {
writeText(
"""
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<certificates
src="user"
overridePins="true" />
</trust-anchors>
</base-config>
<debug-overrides>
<trust-anchors>
<certificates src="system" />
<certificates
src="user"
overridePins="true" />
</trust-anchors>
</debug-overrides>
</network-security-config>
""".trimIndentMultiline(),
)
}
}
}

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

@ -0,0 +1,29 @@
package app.revanced.patches.all.misc.shortcut.sharetargets
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
@Suppress("unused")
val removeShareTargetsPatch = resourcePatch(
name = "Remove share targets",
description = "Removes share targets like directly sharing to a frequent contact.",
use = false,
) {
execute { context ->
try {
context.document["res/xml/shortcuts.xml"]
} catch (_: FileNotFoundException) {
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
document.getElementsByTagName("share-target").asSequence().forEach {
rootNode.removeChild(it)
}
}
}
}

View File

@ -0,0 +1,95 @@
package app.revanced.patches.all.misc.transformation
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
typealias Instruction35cInfo = Triple<IMethodCall, Instruction35c, Int>
interface IMethodCall {
val definedClassName: String
val methodName: String
val methodParams: Array<String>
val returnType: String
/**
* Replaces an invoke-virtual instruction with an invoke-static instruction,
* 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.
*
* Example:
*
* original method: Window#setFlags(int, int)
*
* replacement method: Extension#setFlags(Window, int, int)
*/
fun replaceInvokeVirtualWithExtension(
definingClassDescriptor: String,
method: MutableMethod,
instruction: Instruction35c,
instructionIndex: Int,
) {
val registers = arrayOf(
instruction.registerC,
instruction.registerD,
instruction.registerE,
instruction.registerF,
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}.",
)
}
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->$replacementMethod",
)
}
}
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
}
inline fun <reified E> filterMapInstruction35c(
extensionClassDescriptorPrefix: String,
classDef: ClassDef,
instruction: Instruction,
instructionIndex: Int,
): Instruction35cInfo? where E : Enum<E>, E : IMethodCall {
if (classDef.startsWith(extensionClassDescriptorPrefix)) {
// avoid infinite recursion
return null
}
if (instruction.opcode != Opcode.INVOKE_VIRTUAL) {
return null
}
val invokeInstruction = instruction as Instruction35c
val methodRef = invokeInstruction.reference as MethodReference
val methodCall = fromMethodReference<E>(methodRef) ?: return null
return Instruction35cInfo(methodCall, invokeInstruction, instructionIndex)
}

View File

@ -0,0 +1,50 @@
package app.revanced.patches.all.misc.transformation
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
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) ->
filterMap(classDef, method, instruction, index)
}
}
execute { context ->
// Find all methods to patch
buildMap {
context.classes.forEach { classDef ->
val methods = buildList {
classDef.methods.forEach { method ->
// Since the Sequence executes lazily,
// using any() results in only calling
// filterMap until the first index has been found.
if (findPatchIndices(classDef, method)?.any() == true) add(method)
}
}
if (methods.isNotEmpty()) {
put(classDef, methods)
}
}
}.forEach { (classDef, methods) ->
// And finally transform the methods...
val mutableClass = context.proxy(classDef).mutableClass
methods.map(mutableClass::findMutableMethodOf).forEach methods@{ mutableMethod ->
val patchIndices = findPatchIndices(mutableClass, mutableMethod)?.toCollection(ArrayDeque())
?: return@methods
while (!patchIndices.isEmpty()) transform(mutableMethod, patchIndices.removeLast())
}
}
}
}

View File

@ -0,0 +1,34 @@
package app.revanced.patches.all.misc.versioncode
import app.revanced.patcher.patch.intOption
import app.revanced.patcher.patch.resourcePatch
import app.revanced.util.getNode
import org.w3c.dom.Element
@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,
) {
val versionCode by intOption(
key = "versionCode",
default = Int.MAX_VALUE,
values = mapOf(
"Lowest" to 1,
"Highest" to Int.MAX_VALUE,
),
title = "Version code",
description = "The version code to use",
required = true,
) { versionCode -> versionCode!! >= 1 }
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

@ -0,0 +1,37 @@
package app.revanced.patches.finanzonline.detection.bootloader
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 val bootStateFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC)
returns("Z")
opcodes(
Opcode.INVOKE_DIRECT,
Opcode.MOVE_RESULT_OBJECT,
Opcode.CONST_4,
Opcode.NEW_ARRAY,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.CONST_4,
Opcode.APUT_OBJECT,
Opcode.CONST_STRING,
Opcode.INVOKE_STATIC,
Opcode.SGET_OBJECT,
Opcode.IF_EQ,
Opcode.SGET_OBJECT,
Opcode.IF_NE,
Opcode.GOTO,
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

@ -0,0 +1,21 @@
package app.revanced.patches.finanzonline.detection.root
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 val rootDetectionFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
returns("L")
parameters("L")
opcodes(
Opcode.NEW_INSTANCE,
Opcode.INVOKE_DIRECT,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT,
Opcode.INVOKE_STATIC,
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

@ -0,0 +1,41 @@
package app.revanced.patches.googlenews.misc.extension.hooks
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
private var getApplicationContextIndex = -1
internal val startActivityInitHook = extensionHook(
insertIndexResolver = { method ->
getApplicationContextIndex = method.indexOfFirstInstructionOrThrow {
getReference<MethodReference>()?.name == "getApplicationContext"
}
getApplicationContextIndex + 2 // Below the move-result-object instruction.
},
contextRegisterResolver = { method ->
val moveResultInstruction = method.implementation!!.instructions.elementAt(getApplicationContextIndex + 1)
as OneRegisterInstruction
moveResultInstruction.registerA
},
) {
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,6 @@
package app.revanced.patches.googlenews.misc.gms
internal object Constants {
const val MAGAZINES_PACKAGE_NAME = "com.google.android.apps.magazines"
const val REVANCED_MAGAZINES_PACKAGE_NAME = "app.revanced.android.magazines"
}

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

@ -0,0 +1,37 @@
package app.revanced.patches.googlephotos.misc.extension
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
private var getApplicationContextIndex = -1
internal val homeActivityInitHook = extensionHook(
insertIndexResolver = { method ->
getApplicationContextIndex = method.indexOfFirstInstructionOrThrow {
getReference<MethodReference>()?.name == "getApplicationContext"
}
getApplicationContextIndex + 2 // Below the move-result-object instruction.
},
contextRegisterResolver = { method ->
val moveResultInstruction = method.implementation!!.instructions.elementAt(getApplicationContextIndex + 1)
as OneRegisterInstruction
moveResultInstruction.registerA
},
) {
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

@ -0,0 +1,86 @@
package app.revanced.patches.googlephotos.misc.features
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.stringsOption
import app.revanced.util.getReference
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
@Suppress("unused")
val spoofFeaturesPatch = bytecodePatch(
name = "Spoof features",
description = "Spoofs the device to enable Google Pixel exclusive features, including unlimited storage.",
) {
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",
),
title = "Features to enable",
description = "Google Pixel exclusive features to enable. Features up to Pixel XL enable the unlimited storage feature.",
required = true,
)
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",
"com.google.android.apps.photos.PIXEL_2019_PRELOAD",
"com.google.android.feature.PIXEL_2020_MIDYEAR_EXPERIENCE",
"com.google.android.feature.PIXEL_2020_EXPERIENCE",
"com.google.android.feature.PIXEL_2021_MIDYEAR_EXPERIENCE",
"com.google.android.feature.PIXEL_2021_EXPERIENCE",
"com.google.android.feature.PIXEL_2022_MIDYEAR_EXPERIENCE",
"com.google.android.feature.PIXEL_2022_EXPERIENCE",
"com.google.android.feature.PIXEL_2023_MIDYEAR_EXPERIENCE",
"com.google.android.feature.PIXEL_2023_EXPERIENCE",
"com.google.android.feature.PIXEL_2024_MIDYEAR_EXPERIENCE",
"com.google.android.feature.PIXEL_2024_EXPERIENCE",
"com.google.android.feature.PIXEL_2025_MIDYEAR_EXPERIENCE",
),
title = "Features to disable",
description = "Google Pixel exclusive features to disable." +
"Features after Pixel XL may have to be disabled for unlimited storage depending on the device.",
required = true,
)
val initializeFeaturesEnumMatch by initializeFeaturesEnumFingerprint()
execute {
@Suppress("NAME_SHADOWING")
val featuresToEnable = featuresToEnable!!.toSet()
@Suppress("NAME_SHADOWING")
val featuresToDisable = featuresToDisable!!.toSet()
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\"",
)
}
}
}
}

View File

@ -0,0 +1,6 @@
package app.revanced.patches.googlephotos.misc.gms
internal object Constants {
const val PHOTOS_PACKAGE_NAME = "com.google.android.apps.photos"
const val REVANCED_PHOTOS_PACKAGE_NAME = "app.revanced.android.photos"
}

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

@ -0,0 +1,37 @@
package app.revanced.patches.idaustria.detection.signature
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.bytecodePatch
@Suppress("unused")
val spoofSignaturePatch = bytecodePatch(
name = "Spoof signature",
description = "Spoofs the signature of the app.",
) {
compatibleWith("at.gv.oe.app")
val spoofSignatureMatch by spoofSignatureFingerprint()
execute {
val expectedSignature =
"OpenSSLRSAPublicKey{modulus=ac3e6fd6050aa7e0d6010ae58190404cd89a56935b44f6fee" +
"067c149768320026e10b24799a1339e414605e448e3f264444a327b9ae292be2b62ad567dd1800dbed4a88f718a33dc6db6b" +
"f5178aa41aa0efff8a3409f5ca95dbfccd92c7b4298966df806ea7a0204a00f0e745f6d9f13bdf24f3df715d7b62c1600906" +
"15de1c8a956b9286764985a3b3c060963c435fb9481a5543aaf0671fc2dba6c5c2b17d1ef1d85137f14dc9bbdf3490288087" +
"324cd48341cce64fabf6a9b55d1a7bf23b2fcdff451fd85bf0c7feb0a5e884d7c5c078e413149566a12a686e6efa70ae5161" +
"a0201307692834cda336c55157fef125e67c01c1359886f94742105596b42a790404bbcda5dad6a65f115aaff5e45ef3c28b" +
"2316ff6cef07aa49a45aa58c07bf258051b13ef449ccb37a3679afd5cfb9132f70bb9d931a937897544f90c3bcc80ed012e9" +
"f6ba020b8cdc23f8c29ac092b88f0e370ff9434e4f0f359e614ae0868dc526fa41e4b7596533e8d10279b66e923ecd9f0b20" +
"0def55be2c1f6f9c72c92fb45d7e0a9ac571cb38f0a9a37bb33ea06f223fde8c7a92e8c47769e386f9799776e8f110c21df2" +
"77ef1be61b2c01ebdabddcbf53cc4b6fd9a3c445606ee77b3758162c80ad8f8137b3c6864e92db904807dcb2be9d7717dd21" +
"bf42c121d620ddfb7914f7a95c713d9e1c1b7bdb4a03d618e40cf7e9e235c0b5687e03b7ab3,publicExponent=10001}"
spoofSignatureMatch.mutableMethod.addInstructions(
0,
"""
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

@ -0,0 +1,32 @@
package app.revanced.patches.memegenerator.detection.signature
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.AccessFlags
import app.revanced.patcher.fingerprint
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,
Opcode.CONST_4,
Opcode.CONST_4,
Opcode.SGET_OBJECT,
Opcode.ARRAY_LENGTH,
Opcode.IF_GE,
Opcode.AGET_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.SGET_OBJECT,
Opcode.IF_EQZ,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT,
Opcode.IF_EQZ,
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")
}
}

View File

@ -0,0 +1,31 @@
package app.revanced.patches.messenger.inputfield
import app.revanced.patcher.fingerprint
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.dexbacked.value.DexBackedStringEncodedValue
internal val sendTypingIndicatorFingerprint = fingerprint {
returns("V")
parameters()
custom { method, classDef ->
method.name == "run" &&
classDef.fields.any {
it.name == "__redex_internal_original_name" &&
(it.initialValue as? DexBackedStringEncodedValue)?.value == "ConversationTypingContext\$sendActiveStateRunnable\$1"
}
}
}
internal val switchMessangeInputEmojiButtonFingerprint = fingerprint {
returns("V")
parameters("L", "Z")
opcodes(
Opcode.IGET_OBJECT,
Opcode.IF_EQZ,
Opcode.CONST_STRING,
Opcode.GOTO,
Opcode.CONST_STRING,
Opcode.GOTO,
)
strings("afterTextChanged", "expression_search")
}

View File

@ -0,0 +1,12 @@
package app.revanced.patches.mifitness.misc.locale
import app.revanced.patcher.fingerprint
import com.android.tools.smali.dexlib2.Opcode
internal val syncBluetoothLanguageFingerprint = fingerprint {
opcodes(Opcode.MOVE_RESULT_OBJECT)
custom { method, _ ->
method.name == "syncBluetoothLanguage" &&
method.definingClass == "Lcom/xiaomi/fitness/devicesettings/DeviceSettingsSyncer;"
}
}

View File

@ -0,0 +1,33 @@
package app.revanced.patches.mifitness.misc.locale
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.mifitness.misc.login.fixLoginPatch
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
@Suppress("unused")
val forceEnglishLocalePatch = bytecodePatch(
name = "Force English locale",
description = "Forces wearable devices to use the English locale.",
) {
compatibleWith("com.xiaomi.wearable")
dependsOn(fixLoginPatch)
val syncBluetoothLanguageMatch by syncBluetoothLanguageFingerprint()
execute {
val resolvePhoneLocaleInstruction = syncBluetoothLanguageMatch.patternMatch!!.startIndex
syncBluetoothLanguageMatch.mutableMethod.apply {
val registerIndexToUpdate =
getInstruction<OneRegisterInstruction>(resolvePhoneLocaleInstruction).registerA
replaceInstruction(
resolvePhoneLocaleInstruction,
"const-string v$registerIndexToUpdate, \"en_gb\"",
)
}
}
}

View File

@ -0,0 +1,12 @@
package app.revanced.patches.mifitness.misc.login
import app.revanced.patcher.fingerprint
import com.android.tools.smali.dexlib2.AccessFlags
internal val xiaomiAccountManagerConstructorFingerprint = fingerprint {
accessFlags(AccessFlags.PRIVATE, AccessFlags.CONSTRUCTOR)
parameters("Landroid/content/Context;", "Z")
custom { method, _ ->
method.definingClass == "Lcom/xiaomi/passport/accountmanager/XiaomiAccountManager;"
}
}

View File

@ -0,0 +1,18 @@
package app.revanced.patches.mifitness.misc.login
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.patch.bytecodePatch
@Suppress("unused")
val fixLoginPatch = bytecodePatch(
name = "Fix login",
description = "Fixes login for uncertified Mi Fitness app",
) {
compatibleWith("com.xiaomi.wearable")
val xiaomiAccountManagerConstructorMatch by xiaomiAccountManagerConstructorFingerprint()
execute {
xiaomiAccountManagerConstructorMatch.mutableMethod.addInstruction(0, "const/16 p2, 0x0")
}
}

View File

@ -0,0 +1,13 @@
package app.revanced.patches.music.ad.video
import app.revanced.patcher.fingerprint
import com.android.tools.smali.dexlib2.Opcode
internal val showVideoAdsParentFingerprint = fingerprint {
opcodes(
Opcode.MOVE_RESULT_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.IGET_OBJECT,
)
strings("maybeRegenerateCpnAndStatsClient called unexpectedly, but no error.")
}

View File

@ -0,0 +1,22 @@
package app.revanced.patches.music.ad.video
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.patch.bytecodePatch
@Suppress("unused")
val hideVideoAdsPatch = bytecodePatch(
name = "Hide music video ads",
description = "Hides ads that appear while listening to or streaming music videos, podcasts, or songs.",
) {
compatibleWith("com.google.android.apps.youtube.music")
val showVideoAdsParentMatch by showVideoAdsParentFingerprint()
execute { context ->
val showVideoAdsMethod = context
.navigate(showVideoAdsParentMatch.mutableMethod)
.at(showVideoAdsParentMatch.patternMatch!!.startIndex + 1).mutable()
showVideoAdsMethod.addInstruction(0, "const/4 p1, 0x0")
}
}

View File

@ -0,0 +1,26 @@
package app.revanced.patches.music.audio.exclusiveaudio
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.bytecodePatch
@Suppress("unused")
val enableExclusiveAudioPlaybackPatch = bytecodePatch(
name = "Enable exclusive audio playback",
description = "Enables the option to play audio without video.",
) {
compatibleWith("com.google.android.apps.youtube.music")
val allowExclusiveAudioPlaybackMatch by allowExclusiveAudioPlaybackFingerprint()
execute {
allowExclusiveAudioPlaybackMatch.mutableMethod.apply {
addInstructions(
0,
"""
const/4 v0, 0x1
return v0
""",
)
}
}
}

View File

@ -0,0 +1,24 @@
package app.revanced.patches.music.audio.exclusiveaudio
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.AccessFlags
import app.revanced.patcher.fingerprint
internal val allowExclusiveAudioPlaybackFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("Z")
parameters()
opcodes(
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.CHECK_CAST,
Opcode.IF_NEZ,
Opcode.IGET_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT,
Opcode.GOTO,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT,
Opcode.RETURN
)
}

View File

@ -0,0 +1,20 @@
package app.revanced.patches.music.interaction.permanentrepeat
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.AccessFlags
import app.revanced.patcher.fingerprint
internal val repeatTrackFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("V")
parameters("L", "L")
opcodes(
Opcode.IGET_OBJECT,
Opcode.IGET_OBJECT,
Opcode.SGET_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT,
Opcode.IF_NEZ
)
strings("w_st")
}

View File

@ -0,0 +1,31 @@
package app.revanced.patches.music.interaction.permanentrepeat
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.instructions
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.smali.ExternalLabel
import org.stringtemplate.v4.compiler.Bytecode.instructions
@Suppress("unused")
val permanentRepeatPatch = bytecodePatch(
name = "Permanent repeat",
description = "Permanently remember your repeating preference even if the playlist ends or another track is played.",
use = false,
) {
compatibleWith("com.google.android.apps.youtube.music")
val repeatTrackMatch by repeatTrackFingerprint()
execute {
val startIndex = repeatTrackMatch.patternMatch!!.endIndex
val repeatIndex = startIndex + 1
repeatTrackMatch.mutableMethod.apply {
addInstructionsWithLabels(
startIndex,
"goto :repeat",
ExternalLabel("repeat", instructions[repeatIndex]),
)
}
}
}

Some files were not shown because too many files have changed in this diff Show More