mirror of
https://github.com/revanced/revanced-patches.git
synced 2025-06-12 05:07:45 +02:00
build: Bump ReVanced Patcher
BREAKING CHANGE: Various APIs have been changed or removed.
This commit is contained in:
1556
patches/api/patches.api
Normal file
1556
patches/api/patches.api
Normal file
File diff suppressed because it is too large
Load Diff
35
patches/build.gradle.kts
Normal file
35
patches/build.gradle.kts
Normal file
@ -0,0 +1,35 @@
|
||||
group = "app.revanced"
|
||||
|
||||
patches {
|
||||
about {
|
||||
name = "ReVanced Patches"
|
||||
description = "Patches for ReVanced"
|
||||
source = "git@github.com:revanced/revanced-patches.git"
|
||||
author = "ReVanced"
|
||||
contact = "contact@revanced.app"
|
||||
website = "https://revanced.app"
|
||||
license = "GNU General Public License v3.0"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Used by JsonGenerator.
|
||||
implementation(libs.gson)
|
||||
// Required due to smali, or build fails. Can be removed once smali is bumped.
|
||||
implementation(libs.guava)
|
||||
// Android API stubs defined here.
|
||||
compileOnly(project(":patches:stub"))
|
||||
}
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
maven {
|
||||
name = "GitHubPackages"
|
||||
url = uri("https://maven.pkg.github.com/revanced/revanced-patches")
|
||||
credentials {
|
||||
username = System.getenv("GITHUB_ACTOR")
|
||||
password = System.getenv("GITHUB_TOKEN")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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")
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
@ -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,
|
||||
)
|
||||
},
|
||||
|
||||
)
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
@file:Suppress("unused")
|
||||
|
||||
package app.revanced.patches.all.misc.connectivity.location.hide
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.all.misc.transformation.IMethodCall
|
||||
import app.revanced.patches.all.misc.transformation.fromMethodReference
|
||||
import app.revanced.patches.all.misc.transformation.transformInstructionsPatch
|
||||
import app.revanced.util.getReference
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
@Suppress("unused")
|
||||
val hideMockLocationPatch = bytecodePatch(
|
||||
name = "Hide mock location",
|
||||
description = "Prevents the app from knowing the device location is being mocked by a third party app.",
|
||||
use = false,
|
||||
) {
|
||||
dependsOn(
|
||||
transformInstructionsPatch(
|
||||
filterMap = filter@{ _, _, instruction, instructionIndex ->
|
||||
val reference = instruction.getReference<MethodReference>() ?: return@filter null
|
||||
if (fromMethodReference<MethodCall>(reference) == null) return@filter null
|
||||
|
||||
instruction to instructionIndex
|
||||
},
|
||||
transform = { method, entry ->
|
||||
val (instruction, index) = entry
|
||||
instruction as FiveRegisterInstruction
|
||||
|
||||
// Replace return value with a constant `false` boolean.
|
||||
method.replaceInstruction(
|
||||
index + 1,
|
||||
"const/4 v${instruction.registerC}, 0x0",
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private enum class MethodCall(
|
||||
override val definedClassName: String,
|
||||
override val methodName: String,
|
||||
override val methodParams: Array<String>,
|
||||
override val returnType: String,
|
||||
) : IMethodCall {
|
||||
IsMock("Landroid/location/Location;", "isMock", emptyArray(), "Z"),
|
||||
IsFromMockProvider("Landroid/location/Location;", "isFromMockProvider", emptyArray(), "Z"),
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
package app.revanced.patches.all.misc.connectivity.telephony.sim.spoof
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.patch.stringOption
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patches.all.misc.transformation.transformInstructionsPatch
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableMethodReference
|
||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||
import java.util.*
|
||||
|
||||
@Suppress("unused")
|
||||
val spoofSimCountryPatch = bytecodePatch(
|
||||
name = "Spoof SIM country",
|
||||
description = "Spoofs country information returned by the SIM card provider.",
|
||||
use = false,
|
||||
) {
|
||||
val countries = Locale.getISOCountries().associateBy { Locale("", it).displayCountry }
|
||||
|
||||
fun isoCountryPatchOption(
|
||||
key: String,
|
||||
title: String,
|
||||
) = stringOption(
|
||||
key,
|
||||
null,
|
||||
countries,
|
||||
title,
|
||||
"ISO-3166-1 alpha-2 country code equivalent for the SIM provider's country code.",
|
||||
false,
|
||||
validator = { it: String? -> it == null || it.uppercase() in countries.values },
|
||||
)
|
||||
|
||||
val networkCountryIso by isoCountryPatchOption(
|
||||
"networkCountryIso",
|
||||
"Network ISO Country Code",
|
||||
)
|
||||
|
||||
val simCountryIso by isoCountryPatchOption(
|
||||
"simCountryIso",
|
||||
"Sim ISO Country Code",
|
||||
)
|
||||
|
||||
dependsOn(
|
||||
transformInstructionsPatch(
|
||||
filterMap = { _, _, instruction, instructionIndex ->
|
||||
if (instruction !is ReferenceInstruction) return@transformInstructionsPatch null
|
||||
|
||||
val reference = instruction.reference as? MethodReference ?: return@transformInstructionsPatch null
|
||||
|
||||
val match = MethodCall.entries.firstOrNull { search ->
|
||||
MethodUtil.methodSignaturesMatch(reference, search.reference)
|
||||
} ?: return@transformInstructionsPatch null
|
||||
|
||||
val iso = when (match) {
|
||||
MethodCall.NetworkCountryIso -> networkCountryIso
|
||||
MethodCall.SimCountryIso -> simCountryIso
|
||||
}?.lowercase()
|
||||
|
||||
iso?.let { instructionIndex to it }
|
||||
},
|
||||
transform = { mutableMethod, entry: Pair<Int, String> ->
|
||||
transformMethodCall(entry, mutableMethod)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun transformMethodCall(
|
||||
entry: Pair<Int, String>,
|
||||
mutableMethod: MutableMethod,
|
||||
) {
|
||||
val (instructionIndex, methodCallValue) = entry
|
||||
|
||||
val register = mutableMethod.getInstruction<OneRegisterInstruction>(instructionIndex + 1).registerA
|
||||
|
||||
mutableMethod.replaceInstruction(
|
||||
instructionIndex + 1,
|
||||
"const-string v$register, \"$methodCallValue\"",
|
||||
)
|
||||
}
|
||||
|
||||
private enum class MethodCall(
|
||||
val reference: MethodReference,
|
||||
) {
|
||||
NetworkCountryIso(
|
||||
ImmutableMethodReference(
|
||||
"Landroid/telephony/TelephonyManager;",
|
||||
"getNetworkCountryIso",
|
||||
emptyList(),
|
||||
"Ljava/lang/String;",
|
||||
),
|
||||
),
|
||||
SimCountryIso(
|
||||
ImmutableMethodReference(
|
||||
"Landroid/telephony/TelephonyManager;",
|
||||
"getSimCountryIso",
|
||||
emptyList(),
|
||||
"Ljava/lang/String;",
|
||||
),
|
||||
),
|
||||
}
|
@ -0,0 +1,224 @@
|
||||
package app.revanced.patches.all.misc.connectivity.wifi.spoof
|
||||
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.all.misc.transformation.IMethodCall
|
||||
import app.revanced.patches.all.misc.transformation.filterMapInstruction35c
|
||||
import app.revanced.patches.all.misc.transformation.transformInstructionsPatch
|
||||
|
||||
internal const val EXTENSION_CLASS_DESCRIPTOR_PREFIX = "Lapp/revanced/extension/all/connectivity/wifi/spoof/SpoofWifiPatch"
|
||||
|
||||
internal const val EXTENSION_CLASS_DESCRIPTOR = "$EXTENSION_CLASS_DESCRIPTOR_PREFIX;"
|
||||
|
||||
@Suppress("unused")
|
||||
val spoofWifiPatch = bytecodePatch(
|
||||
name = "Spoof Wi-Fi connection",
|
||||
description = "Spoofs an existing Wi-Fi connection.",
|
||||
use = false,
|
||||
) {
|
||||
extendWith("extensions/all/connectivity/wifi/spoof/spoof-wifi.rve")
|
||||
|
||||
dependsOn(
|
||||
transformInstructionsPatch(
|
||||
filterMap = { classDef, _, instruction, instructionIndex ->
|
||||
filterMapInstruction35c<MethodCall>(
|
||||
EXTENSION_CLASS_DESCRIPTOR_PREFIX,
|
||||
classDef,
|
||||
instruction,
|
||||
instructionIndex,
|
||||
)
|
||||
},
|
||||
transform = { method, entry ->
|
||||
val (methodType, instruction, instructionIndex) = entry
|
||||
methodType.replaceInvokeVirtualWithExtension(
|
||||
EXTENSION_CLASS_DESCRIPTOR,
|
||||
method,
|
||||
instruction,
|
||||
instructionIndex,
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Information about method calls we want to replace
|
||||
@Suppress("unused")
|
||||
private enum class MethodCall(
|
||||
override val definedClassName: String,
|
||||
override val methodName: String,
|
||||
override val methodParams: Array<String>,
|
||||
override val returnType: String,
|
||||
) : IMethodCall {
|
||||
GetSystemService1(
|
||||
"Landroid/content/Context;",
|
||||
"getSystemService",
|
||||
arrayOf("Ljava/lang/String;"),
|
||||
"Ljava/lang/Object;",
|
||||
),
|
||||
GetSystemService2(
|
||||
"Landroid/content/Context;",
|
||||
"getSystemService",
|
||||
arrayOf("Ljava/lang/Class;"),
|
||||
"Ljava/lang/Object;",
|
||||
),
|
||||
GetActiveNetworkInfo(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"getActiveNetworkInfo",
|
||||
arrayOf(),
|
||||
"Landroid/net/NetworkInfo;",
|
||||
),
|
||||
IsConnected(
|
||||
"Landroid/net/NetworkInfo;",
|
||||
"isConnected",
|
||||
arrayOf(),
|
||||
"Z",
|
||||
),
|
||||
IsConnectedOrConnecting(
|
||||
"Landroid/net/NetworkInfo;",
|
||||
"isConnectedOrConnecting",
|
||||
arrayOf(),
|
||||
"Z",
|
||||
),
|
||||
IsAvailable(
|
||||
"Landroid/net/NetworkInfo;",
|
||||
"isAvailable",
|
||||
arrayOf(),
|
||||
"Z",
|
||||
),
|
||||
GetState(
|
||||
"Landroid/net/NetworkInfo;",
|
||||
"getState",
|
||||
arrayOf(),
|
||||
"Landroid/net/NetworkInfo\$State;",
|
||||
),
|
||||
GetDetailedState(
|
||||
"Landroid/net/NetworkInfo;",
|
||||
"getDetailedState",
|
||||
arrayOf(),
|
||||
"Landroid/net/NetworkInfo\$DetailedState;",
|
||||
),
|
||||
IsActiveNetworkMetered(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"isActiveNetworkMetered",
|
||||
arrayOf(),
|
||||
"Z",
|
||||
),
|
||||
GetActiveNetwork(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"getActiveNetwork",
|
||||
arrayOf(),
|
||||
"Landroid/net/Network;",
|
||||
),
|
||||
GetNetworkInfo(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"getNetworkInfo",
|
||||
arrayOf("Landroid/net/Network;"),
|
||||
"Landroid/net/NetworkInfo;",
|
||||
),
|
||||
HasTransport(
|
||||
"Landroid/net/NetworkCapabilities;",
|
||||
"hasTransport",
|
||||
arrayOf("I"),
|
||||
"Z",
|
||||
),
|
||||
HasCapability(
|
||||
"Landroid/net/NetworkCapabilities;",
|
||||
"hasCapability",
|
||||
arrayOf("I"),
|
||||
"Z",
|
||||
),
|
||||
RegisterBestMatchingNetworkCallback(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"registerBestMatchingNetworkCallback",
|
||||
arrayOf(
|
||||
"Landroid/net/NetworkRequest;",
|
||||
"Landroid/net/ConnectivityManager\$NetworkCallback;",
|
||||
"Landroid/os/Handler;",
|
||||
),
|
||||
"V",
|
||||
),
|
||||
RegisterDefaultNetworkCallback1(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"registerDefaultNetworkCallback",
|
||||
arrayOf("Landroid/net/ConnectivityManager\$NetworkCallback;"),
|
||||
"V",
|
||||
),
|
||||
RegisterDefaultNetworkCallback2(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"registerDefaultNetworkCallback",
|
||||
arrayOf("Landroid/net/ConnectivityManager\$NetworkCallback;", "Landroid/os/Handler;"),
|
||||
"V",
|
||||
),
|
||||
RegisterNetworkCallback1(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"registerNetworkCallback",
|
||||
arrayOf("Landroid/net/NetworkRequest;", "Landroid/net/ConnectivityManager\$NetworkCallback;"),
|
||||
"V",
|
||||
),
|
||||
RegisterNetworkCallback2(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"registerNetworkCallback",
|
||||
arrayOf("Landroid/net/NetworkRequest;", "Landroid/app/PendingIntent;"),
|
||||
"V",
|
||||
),
|
||||
RegisterNetworkCallback3(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"registerNetworkCallback",
|
||||
arrayOf(
|
||||
"Landroid/net/NetworkRequest;",
|
||||
"Landroid/net/ConnectivityManager\$NetworkCallback;",
|
||||
"Landroid/os/Handler;",
|
||||
),
|
||||
"V",
|
||||
),
|
||||
RequestNetwork1(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"requestNetwork",
|
||||
arrayOf("Landroid/net/NetworkRequest;", "Landroid/net/ConnectivityManager\$NetworkCallback;"),
|
||||
"V",
|
||||
),
|
||||
RequestNetwork2(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"requestNetwork",
|
||||
arrayOf("Landroid/net/NetworkRequest;", "Landroid/net/ConnectivityManager\$NetworkCallback;", "I"),
|
||||
"V",
|
||||
),
|
||||
RequestNetwork3(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"requestNetwork",
|
||||
arrayOf(
|
||||
"Landroid/net/NetworkRequest;",
|
||||
"Landroid/net/ConnectivityManager\$NetworkCallback;",
|
||||
"Landroid/os/Handler;",
|
||||
),
|
||||
"V",
|
||||
),
|
||||
RequestNetwork4(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"requestNetwork",
|
||||
arrayOf("Landroid/net/NetworkRequest;", "Landroid/app/PendingIntent;"),
|
||||
"V",
|
||||
),
|
||||
RequestNetwork5(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"requestNetwork",
|
||||
arrayOf(
|
||||
"Landroid/net/NetworkRequest;",
|
||||
"Landroid/net/ConnectivityManager\$NetworkCallback;",
|
||||
"Landroid/os/Handler;",
|
||||
"I",
|
||||
),
|
||||
"V",
|
||||
),
|
||||
UnregisterNetworkCallback1(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"unregisterNetworkCallback",
|
||||
arrayOf("Landroid/net/ConnectivityManager\$NetworkCallback;"),
|
||||
"V",
|
||||
),
|
||||
UnregisterNetworkCallback2(
|
||||
"Landroid/net/ConnectivityManager;",
|
||||
"unregisterNetworkCallback",
|
||||
arrayOf("Landroid/app/PendingIntent;"),
|
||||
"V",
|
||||
),
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
@ -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;",
|
||||
),
|
||||
),
|
||||
}
|
@ -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()
|
||||
},
|
||||
)
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package app.revanced.patches.all.misc.packagename
|
||||
|
||||
import app.revanced.patcher.patch.Option
|
||||
import app.revanced.patcher.patch.OptionException
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.patcher.patch.stringOption
|
||||
import org.w3c.dom.Element
|
||||
|
||||
lateinit var packageNameOption: Option<String>
|
||||
|
||||
/**
|
||||
* Set the package name to use.
|
||||
* If this is called multiple times, the first call will set the package name.
|
||||
*
|
||||
* @param fallbackPackageName The package name to use if the user has not already specified a package name.
|
||||
* @return The package name that was set.
|
||||
* @throws OptionException.ValueValidationException If the package name is invalid.
|
||||
*/
|
||||
fun setOrGetFallbackPackageName(fallbackPackageName: String): String {
|
||||
val packageName = packageNameOption.value!!
|
||||
|
||||
return if (packageName == packageNameOption.default) {
|
||||
fallbackPackageName.also { packageNameOption.value = it }
|
||||
} else {
|
||||
packageName
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
val changePackageNamePatch = resourcePatch(
|
||||
name = "Change package name",
|
||||
description = "Appends \".revanced\" to the package name by default. Changing the package name of the app can lead to unexpected issues.",
|
||||
use = false,
|
||||
) {
|
||||
packageNameOption = stringOption(
|
||||
key = "packageName",
|
||||
default = "Default",
|
||||
values = mapOf("Default" to "Default"),
|
||||
title = "Package name",
|
||||
description = "The name of the package to rename the app to.",
|
||||
required = true,
|
||||
) {
|
||||
it == "Default" || it!!.matches(Regex("^[a-z]\\w*(\\.[a-z]\\w*)+\$"))
|
||||
}
|
||||
|
||||
finalize { context ->
|
||||
context.document["AndroidManifest.xml"].use { document ->
|
||||
|
||||
val replacementPackageName = packageNameOption.value
|
||||
|
||||
val manifest = document.getElementsByTagName("manifest").item(0) as Element
|
||||
manifest.setAttribute(
|
||||
"package",
|
||||
if (replacementPackageName != packageNameOption.default) {
|
||||
replacementPackageName
|
||||
} else {
|
||||
"${manifest.getAttribute("package")}.revanced"
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,397 @@
|
||||
package app.revanced.patches.all.misc.resources
|
||||
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.patch.PatchException
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.patcher.util.Document
|
||||
import app.revanced.util.*
|
||||
import app.revanced.util.resource.ArrayResource
|
||||
import app.revanced.util.resource.BaseResource
|
||||
import app.revanced.util.resource.StringResource
|
||||
import org.w3c.dom.Node
|
||||
|
||||
/**
|
||||
* An identifier of an app. For example, `youtube`.
|
||||
*/
|
||||
private typealias AppId = String
|
||||
|
||||
/**
|
||||
* An identifier of a patch. For example, `ad.general.HideAdsPatch`.
|
||||
*/
|
||||
private typealias PatchId = String
|
||||
|
||||
/**
|
||||
* A set of resources of a patch.
|
||||
*/
|
||||
private typealias PatchResources = MutableSet<BaseResource>
|
||||
|
||||
/**
|
||||
* A map of resources belonging to a patch.
|
||||
*/
|
||||
private typealias AppResources = MutableMap<PatchId, PatchResources>
|
||||
|
||||
/**
|
||||
* A map of resources belonging to an app.
|
||||
*/
|
||||
private typealias Resources = MutableMap<AppId, AppResources>
|
||||
|
||||
/**
|
||||
* The value of a resource.
|
||||
* For example, `values` or `values-de`.
|
||||
*/
|
||||
private typealias Value = String
|
||||
|
||||
/**
|
||||
* A set of resources mapped by their value.
|
||||
*/
|
||||
private typealias MutableResources = MutableMap<Value, MutableSet<BaseResource>>
|
||||
|
||||
/**
|
||||
* A map of all resources associated by their value staged by [addResourcesPatch].
|
||||
*/
|
||||
private lateinit var stagedResources: Map<Value, Resources>
|
||||
|
||||
/**
|
||||
* A map of all resources added to the app by [addResourcesPatch].
|
||||
*/
|
||||
private val resources: MutableResources = mutableMapOf()
|
||||
|
||||
/**
|
||||
* Map of Crowdin locales to Android resource locale names.
|
||||
*
|
||||
* Fixme: Instead this patch should detect what locale regions are present in both patches and the target app,
|
||||
* and automatically merge into the appropriate existing target file.
|
||||
* So if a target app has only 'es', then the Crowdin file of 'es-rES' should merge into that.
|
||||
* But if a target app has specific regions (such as 'pt-rBR'),
|
||||
* then the Crowdin region specific file should merged into that.
|
||||
*/
|
||||
private val locales = mapOf(
|
||||
"af-rZA" to "af",
|
||||
"am-rET" to "am",
|
||||
"ar-rSA" to "ar",
|
||||
"as-rIN" to "as",
|
||||
"az-rAZ" to "az",
|
||||
"be-rBY" to "be",
|
||||
"bg-rBG" to "bg",
|
||||
"bn-rBD" to "bn",
|
||||
"bs-rBA" to "bs",
|
||||
"ca-rES" to "ca",
|
||||
"cs-rCZ" to "cs",
|
||||
"da-rDK" to "da",
|
||||
"de-rDE" to "de",
|
||||
"el-rGR" to "el",
|
||||
"es-rES" to "es",
|
||||
"et-rEE" to "et",
|
||||
"eu-rES" to "eu",
|
||||
"fa-rIR" to "fa",
|
||||
"fi-rFI" to "fi",
|
||||
"fil-rPH" to "tl",
|
||||
"fr-rFR" to "fr",
|
||||
"ga-rIE" to "ga",
|
||||
"gl-rES" to "gl",
|
||||
"gu-rIN" to "gu",
|
||||
"hi-rIN" to "hi",
|
||||
"hr-rHR" to "hr",
|
||||
"hu-rHU" to "hu",
|
||||
"hy-rAM" to "hy",
|
||||
"in-rID" to "in",
|
||||
"is-rIS" to "is",
|
||||
"it-rIT" to "it",
|
||||
"iw-rIL" to "iw",
|
||||
"ja-rJP" to "ja",
|
||||
"ka-rGE" to "ka",
|
||||
"kk-rKZ" to "kk",
|
||||
"km-rKH" to "km",
|
||||
"kn-rIN" to "kn",
|
||||
"ko-rKR" to "ko",
|
||||
"ky-rKG" to "ky",
|
||||
"lo-rLA" to "lo",
|
||||
"lt-rLT" to "lt",
|
||||
"lv-rLV" to "lv",
|
||||
"mk-rMK" to "mk",
|
||||
"ml-rIN" to "ml",
|
||||
"mn-rMN" to "mn",
|
||||
"mr-rIN" to "mr",
|
||||
"ms-rMY" to "ms",
|
||||
"my-rMM" to "my",
|
||||
"nb-rNO" to "nb",
|
||||
"ne-rIN" to "ne",
|
||||
"nl-rNL" to "nl",
|
||||
"or-rIN" to "or",
|
||||
"pa-rIN" to "pa",
|
||||
"pl-rPL" to "pl",
|
||||
"pt-rBR" to "pt-rBR",
|
||||
"pt-rPT" to "pt-rPT",
|
||||
"ro-rRO" to "ro",
|
||||
"ru-rRU" to "ru",
|
||||
"si-rLK" to "si",
|
||||
"sk-rSK" to "sk",
|
||||
"sl-rSI" to "sl",
|
||||
"sq-rAL" to "sq",
|
||||
"sr-rCS" to "b+sr+Latn",
|
||||
"sr-rSP" to "sr",
|
||||
"sv-rSE" to "sv",
|
||||
"sw-rKE" to "sw",
|
||||
"ta-rIN" to "ta",
|
||||
"te-rIN" to "te",
|
||||
"th-rTH" to "th",
|
||||
"tl-rPH" to "tl",
|
||||
"tr-rTR" to "tr",
|
||||
"uk-rUA" to "uk",
|
||||
"ur-rIN" to "ur",
|
||||
"uz-rUZ" to "uz",
|
||||
"vi-rVN" to "vi",
|
||||
"zh-rCN" to "zh-rCN",
|
||||
"zh-rTW" to "zh-rTW",
|
||||
"zu-rZA" to "zu",
|
||||
)
|
||||
|
||||
/**
|
||||
* Adds a [BaseResource] to the map using [MutableMap.getOrPut].
|
||||
*
|
||||
* @param value The value of the resource. For example, `values` or `values-de`.
|
||||
* @param resource The resource to add.
|
||||
*
|
||||
* @return True if the resource was added, false if it already existed.
|
||||
*/
|
||||
fun addResource(
|
||||
value: Value,
|
||||
resource: BaseResource,
|
||||
) = resources.getOrPut(value, ::mutableSetOf).add(resource)
|
||||
|
||||
/**
|
||||
* Adds a list of [BaseResource]s to the map using [MutableMap.getOrPut].
|
||||
*
|
||||
* @param value The value of the resource. For example, `values` or `values-de`.
|
||||
* @param resources The resources to add.
|
||||
*
|
||||
* @return True if the resources were added, false if they already existed.
|
||||
*/
|
||||
fun addResources(
|
||||
value: Value,
|
||||
resources: Iterable<BaseResource>,
|
||||
) = app.revanced.patches.all.misc.resources.resources.getOrPut(value, ::mutableSetOf).addAll(resources)
|
||||
|
||||
/**
|
||||
* Adds a [StringResource].
|
||||
*
|
||||
* @param name The name of the string resource.
|
||||
* @param value The value of the string resource.
|
||||
* @param formatted Whether the string resource is formatted. Defaults to `true`.
|
||||
* @param resourceValue The value of the resource. For example, `values` or `values-de`.
|
||||
*
|
||||
* @return True if the resource was added, false if it already existed.
|
||||
*/
|
||||
fun addResources(
|
||||
name: String,
|
||||
value: String,
|
||||
formatted: Boolean = true,
|
||||
resourceValue: Value = "values",
|
||||
) = addResource(resourceValue, StringResource(name, value, formatted))
|
||||
|
||||
/**
|
||||
* Adds an [ArrayResource].
|
||||
*
|
||||
* @param name The name of the array resource.
|
||||
* @param items The items of the array resource.
|
||||
*
|
||||
* @return True if the resource was added, false if it already existed.
|
||||
*/
|
||||
fun addResources(
|
||||
name: String,
|
||||
items: List<String>,
|
||||
) = addResource("values", ArrayResource(name, items))
|
||||
|
||||
/**
|
||||
* Puts all resources of any [Value] staged in [stagedResources] for the [Patch] to [addResources].
|
||||
*
|
||||
* @param patch The [Patch] of the patch to stage resources for.
|
||||
* @param parseIds A function that parses a set of [PatchId] each mapped to an [AppId] from the given [Patch].
|
||||
* This is used to access the resources in [addResources] to stage them in [stagedResources].
|
||||
* The default implementation assumes that the [Patch] has a name and declares packages it is compatible with.
|
||||
*
|
||||
* @return True if any resources were added, false if none were added.
|
||||
*
|
||||
* @see addResourcesPatch
|
||||
*/
|
||||
fun addResources(
|
||||
patch: Patch<*>,
|
||||
parseIds: (Patch<*>) -> Map<AppId, Set<PatchId>> = {
|
||||
val patchId = patch.name ?: throw PatchException("Patch has no name")
|
||||
val packages = patch.compatiblePackages ?: throw PatchException("Patch has no compatible packages")
|
||||
|
||||
buildMap<AppId, MutableSet<PatchId>> {
|
||||
packages.forEach { (appId, _) ->
|
||||
getOrPut(appId) { mutableSetOf() }.add(patchId)
|
||||
}
|
||||
}
|
||||
},
|
||||
): Boolean {
|
||||
var result = false
|
||||
|
||||
// Stage resources for the given patch to addResourcesPatch associated with their value.
|
||||
parseIds(patch).forEach { (appId, patchIds) ->
|
||||
patchIds.forEach { patchId ->
|
||||
stagedResources.forEach { (value, resources) ->
|
||||
resources[appId]?.get(patchId)?.let { patchResources ->
|
||||
if (addResources(value, patchResources)) result = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts all resources for the given [appId] and [patchId] staged in [addResources] to [addResourcesPatch].
|
||||
*
|
||||
*
|
||||
* @return True if any resources were added, false if none were added.
|
||||
*
|
||||
* @see addResourcesPatch
|
||||
*/
|
||||
fun addResources(
|
||||
appId: AppId,
|
||||
patchId: String,
|
||||
) = stagedResources.forEach { (value, resources) ->
|
||||
resources[appId]?.get(patchId)?.let { patchResources ->
|
||||
addResources(value, patchResources)
|
||||
}
|
||||
}
|
||||
|
||||
val addResourcesPatch = resourcePatch(
|
||||
description = "Add resources such as strings or arrays to the app.",
|
||||
) {
|
||||
/*
|
||||
The strategy of this patch is to stage resources present in `/resources/addresources`.
|
||||
These resources are organized by their respective value and patch.
|
||||
|
||||
On addResourcesPatch#execute, all resources are staged in a temporary map.
|
||||
After that, other patches that depend on addResourcesPatch can call
|
||||
addResourcesPatch#invoke(Patch) to stage resources belonging to that patch
|
||||
from the temporary map to addResourcesPatch.
|
||||
|
||||
After all patches that depend on addResourcesPatch have been executed,
|
||||
addResourcesPatch#finalize is finally called to add all staged resources to the app.
|
||||
*/
|
||||
execute { context ->
|
||||
stagedResources = buildMap {
|
||||
/**
|
||||
* Puts resources under `/resources/addresources/<value>/<resourceKind>.xml` into the map.
|
||||
*
|
||||
* @param sourceValue The source value of the resource. For example, `values` or `values-de-rDE`.
|
||||
* @param destValue The destination value of the resource. For example, 'values' or 'values-de'.
|
||||
* @param resourceKind The kind of the resource. For example, `strings` or `arrays`.
|
||||
* @param transform A function that transforms the [Node]s from the XML files to a [BaseResource].
|
||||
*/
|
||||
fun addResources(
|
||||
sourceValue: Value,
|
||||
destValue: Value = sourceValue,
|
||||
resourceKind: String,
|
||||
transform: (Node) -> BaseResource,
|
||||
) {
|
||||
inputStreamFromBundledResource(
|
||||
"addresources",
|
||||
"$sourceValue/$resourceKind.xml",
|
||||
)?.let { stream ->
|
||||
// Add the resources associated with the given value to the map,
|
||||
// instead of overwriting it.
|
||||
// This covers the example case such as adding strings and arrays of the same value.
|
||||
getOrPut(destValue, ::mutableMapOf).apply {
|
||||
context.document[stream].use { document ->
|
||||
document.getElementsByTagName("app").asSequence().forEach { app ->
|
||||
val appId = app.attributes.getNamedItem("id").textContent
|
||||
|
||||
getOrPut(appId, ::mutableMapOf).apply {
|
||||
app.forEachChildElement { patch ->
|
||||
val patchId = patch.attributes.getNamedItem("id").textContent
|
||||
|
||||
getOrPut(patchId, ::mutableSetOf).apply {
|
||||
patch.forEachChildElement { resourceNode ->
|
||||
val resource = transform(resourceNode)
|
||||
|
||||
add(resource)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stage all resources to a temporary map.
|
||||
// Staged resources consumed by addResourcesPatch#invoke(Patch)
|
||||
// are later used in addResourcesPatch#finalize.
|
||||
try {
|
||||
val addStringResources = { source: Value, dest: Value ->
|
||||
addResources(source, dest, "strings", StringResource::fromNode)
|
||||
}
|
||||
locales.forEach { (source, dest) -> addStringResources("values-$source", "values-$dest") }
|
||||
addStringResources("values", "values")
|
||||
|
||||
addResources("values", "values", "arrays", ArrayResource::fromNode)
|
||||
} catch (e: Exception) {
|
||||
throw PatchException("Failed to read resources", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all resources staged in [addResourcesPatch] to the app.
|
||||
* This is called after all patches that depend on [addResourcesPatch] have been executed.
|
||||
*/
|
||||
finalize { context ->
|
||||
operator fun MutableMap<String, Pair<Document, Node>>.invoke(
|
||||
value: Value,
|
||||
resource: BaseResource,
|
||||
) {
|
||||
// TODO: Fix open-closed principle violation by modifying BaseResource#serialize so that it accepts
|
||||
// a Value and the map of documents. It will then get or put the document suitable for its resource type
|
||||
// to serialize itself to it.
|
||||
val resourceFileName =
|
||||
when (resource) {
|
||||
is StringResource -> "strings"
|
||||
is ArrayResource -> "arrays"
|
||||
else -> throw NotImplementedError("Unsupported resource type")
|
||||
}
|
||||
|
||||
getOrPut(resourceFileName) {
|
||||
val targetFile =
|
||||
context["res/$value/$resourceFileName.xml"].also {
|
||||
it.parentFile?.mkdirs()
|
||||
|
||||
if (it.createNewFile()) {
|
||||
it.writeText("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n</resources>")
|
||||
}
|
||||
}
|
||||
|
||||
context.document[targetFile.path].let { document ->
|
||||
|
||||
// Save the target node here as well
|
||||
// in order to avoid having to call document.getNode("resources")
|
||||
// but also save the document so that it can be closed later.
|
||||
document to document.getNode("resources")
|
||||
}
|
||||
}.let { (_, targetNode) ->
|
||||
targetNode.addResource(resource) { invoke(value, it) }
|
||||
}
|
||||
}
|
||||
|
||||
resources.forEach { (value, resources) ->
|
||||
// A map of document associated by their kind (e.g. strings, arrays).
|
||||
// Each document is accompanied by the target node to which resources are added.
|
||||
// A map is used because Map#getOrPut allows opening a new document for the duration of a resource value.
|
||||
// This is done to prevent having to open the files for every resource that is added.
|
||||
// Instead, it is cached once and reused for resources of the same value.
|
||||
// This map is later accessed to close all documents for the current resource value.
|
||||
val documents = mutableMapOf<String, Pair<Document, Node>>()
|
||||
|
||||
resources.forEach { resource -> documents(value, resource) }
|
||||
|
||||
documents.values.forEach { (document, _) -> document.close() }
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
package app.revanced.patches.all.misc.screencapture
|
||||
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.patches.all.misc.transformation.IMethodCall
|
||||
import app.revanced.patches.all.misc.transformation.filterMapInstruction35c
|
||||
import app.revanced.patches.all.misc.transformation.transformInstructionsPatch
|
||||
import org.w3c.dom.Element
|
||||
|
||||
private val removeCaptureRestrictionResourcePatch = resourcePatch(
|
||||
description = "Sets allowAudioPlaybackCapture in manifest to true.",
|
||||
) {
|
||||
execute { context ->
|
||||
context.document["AndroidManifest.xml"].use { document ->
|
||||
// Get the application node.
|
||||
val applicationNode =
|
||||
document
|
||||
.getElementsByTagName("application")
|
||||
.item(0) as Element
|
||||
|
||||
// Set allowAudioPlaybackCapture attribute to true.
|
||||
applicationNode.setAttribute("android:allowAudioPlaybackCapture", "true")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR_PREFIX =
|
||||
"Lapp/revanced/extension/all/screencapture/removerestriction/RemoveScreencaptureRestrictionPatch"
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR = "$EXTENSION_CLASS_DESCRIPTOR_PREFIX;"
|
||||
|
||||
@Suppress("unused")
|
||||
val removeScreenCaptureRestrictionPatch = bytecodePatch(
|
||||
name = "Remove screen capture restriction",
|
||||
description = "Removes the restriction of capturing audio from apps that normally wouldn't allow it.",
|
||||
use = false,
|
||||
) {
|
||||
extendWith("extensions/all/screencapture/remove-screen-capture-restriction.rve")
|
||||
|
||||
dependsOn(
|
||||
removeCaptureRestrictionResourcePatch,
|
||||
transformInstructionsPatch(
|
||||
filterMap = { classDef, _, instruction, instructionIndex ->
|
||||
filterMapInstruction35c<MethodCall>(
|
||||
EXTENSION_CLASS_DESCRIPTOR_PREFIX,
|
||||
classDef,
|
||||
instruction,
|
||||
instructionIndex,
|
||||
)
|
||||
},
|
||||
transform = { mutableMethod, entry ->
|
||||
val (methodType, instruction, instructionIndex) = entry
|
||||
methodType.replaceInvokeVirtualWithExtension(
|
||||
EXTENSION_CLASS_DESCRIPTOR,
|
||||
mutableMethod,
|
||||
instruction,
|
||||
instructionIndex,
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Information about method calls we want to replace
|
||||
@Suppress("unused")
|
||||
private enum class MethodCall(
|
||||
override val definedClassName: String,
|
||||
override val methodName: String,
|
||||
override val methodParams: Array<String>,
|
||||
override val returnType: String,
|
||||
) : IMethodCall {
|
||||
SetAllowedCapturePolicySingle(
|
||||
"Landroid/media/AudioAttributes\$Builder;",
|
||||
"setAllowedCapturePolicy",
|
||||
arrayOf("I"),
|
||||
"Landroid/media/AudioAttributes\$Builder;",
|
||||
),
|
||||
SetAllowedCapturePolicyGlobal(
|
||||
"Landroid/media/AudioManager;",
|
||||
"setAllowedCapturePolicy",
|
||||
arrayOf("I"),
|
||||
"V",
|
||||
),
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
package app.revanced.patches.all.misc.screenshot
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.all.misc.transformation.IMethodCall
|
||||
import app.revanced.patches.all.misc.transformation.filterMapInstruction35c
|
||||
import app.revanced.patches.all.misc.transformation.transformInstructionsPatch
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction22c
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR_PREFIX =
|
||||
"Lapp/revanced/extension/all/screenshot/removerestriction/RemoveScreenshotRestrictionPatch"
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR = "$EXTENSION_CLASS_DESCRIPTOR_PREFIX;"
|
||||
|
||||
@Suppress("unused")
|
||||
val removeScreenshotRestrictionPatch = bytecodePatch(
|
||||
name = "Remove screenshot restriction",
|
||||
description = "Removes the restriction of taking screenshots in apps that normally wouldn't allow it.",
|
||||
use = false,
|
||||
) {
|
||||
extendWith("extensions/all/screenshot/remove-screenshot-restriction.rve")
|
||||
|
||||
dependsOn(
|
||||
// Remove the restriction of taking screenshots.
|
||||
transformInstructionsPatch(
|
||||
filterMap = { classDef, _, instruction, instructionIndex ->
|
||||
filterMapInstruction35c<MethodCall>(
|
||||
EXTENSION_CLASS_DESCRIPTOR_PREFIX,
|
||||
classDef,
|
||||
instruction,
|
||||
instructionIndex,
|
||||
)
|
||||
},
|
||||
transform = { mutableMethod, entry ->
|
||||
val (methodType, instruction, instructionIndex) = entry
|
||||
methodType.replaceInvokeVirtualWithExtension(
|
||||
EXTENSION_CLASS_DESCRIPTOR,
|
||||
mutableMethod,
|
||||
instruction,
|
||||
instructionIndex,
|
||||
)
|
||||
},
|
||||
),
|
||||
// Modify layout params.
|
||||
transformInstructionsPatch(
|
||||
filterMap = { _, _, instruction, instructionIndex ->
|
||||
if (instruction.opcode != Opcode.IPUT) {
|
||||
return@transformInstructionsPatch null
|
||||
}
|
||||
|
||||
val instruction22c = instruction as Instruction22c
|
||||
val fieldReference = instruction22c.reference as FieldReference
|
||||
|
||||
if (fieldReference.definingClass != "Landroid/view/WindowManager\$LayoutParams;" ||
|
||||
fieldReference.name != "flags" ||
|
||||
fieldReference.type != "I"
|
||||
) {
|
||||
return@transformInstructionsPatch null
|
||||
}
|
||||
|
||||
Pair(instruction22c, instructionIndex)
|
||||
},
|
||||
transform = { mutableMethod, entry ->
|
||||
val (instruction, index) = entry
|
||||
val register = instruction.registerA
|
||||
|
||||
mutableMethod.addInstructions(
|
||||
index,
|
||||
"and-int/lit16 v$register, v$register, -0x2001",
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Information about method calls we want to replace
|
||||
@Suppress("unused")
|
||||
private enum class MethodCall(
|
||||
override val definedClassName: String,
|
||||
override val methodName: String,
|
||||
override val methodParams: Array<String>,
|
||||
override val returnType: String,
|
||||
) : IMethodCall {
|
||||
AddFlags(
|
||||
"Landroid/view/Window;",
|
||||
"addFlags",
|
||||
arrayOf("I"),
|
||||
"V",
|
||||
),
|
||||
SetFlags(
|
||||
"Landroid/view/Window;",
|
||||
"setFlags",
|
||||
arrayOf("I", "I"),
|
||||
"V",
|
||||
),
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package app.revanced.patches.amazon
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val deepLinkingPatch = bytecodePatch(
|
||||
name = "Always allow deep-linking",
|
||||
description = "Open Amazon links, even if the app is not set to handle Amazon links.",
|
||||
) {
|
||||
compatibleWith("com.amazon.mShop.android.shopping")
|
||||
|
||||
val deepLinkingMatch by deepLinkingFingerprint()
|
||||
|
||||
execute {
|
||||
deepLinkingMatch.mutableMethod.addInstructions(
|
||||
0,
|
||||
"""
|
||||
const/4 v0, 0x1
|
||||
return v0
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package app.revanced.patches.amazon
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
internal val deepLinkingFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PRIVATE)
|
||||
returns("Z")
|
||||
parameters("L")
|
||||
strings("https://www.", "android.intent.action.VIEW")
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package app.revanced.patches.backdrops.misc.pro
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal val proUnlockFingerprint = fingerprint {
|
||||
opcodes(
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.INVOKE_INTERFACE,
|
||||
Opcode.MOVE_RESULT,
|
||||
Opcode.IF_EQZ,
|
||||
)
|
||||
custom { method, _ ->
|
||||
method.name == "lambda\$existPurchase\$0" &&
|
||||
method.definingClass == "Lcom/backdrops/wallpapers/data/local/DatabaseHandlerIAB;"
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package app.revanced.patches.backdrops.misc.pro
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
@Suppress("unused")
|
||||
val proUnlockPatch = bytecodePatch(
|
||||
name = "Pro unlock",
|
||||
) {
|
||||
compatibleWith("com.backdrops.wallpapers")
|
||||
|
||||
val proUnlockMatch by proUnlockFingerprint()
|
||||
|
||||
execute {
|
||||
val registerIndex = proUnlockMatch.patternMatch!!.endIndex - 1
|
||||
|
||||
proUnlockMatch.mutableMethod.apply {
|
||||
val register = getInstruction<OneRegisterInstruction>(registerIndex).registerA
|
||||
addInstruction(
|
||||
proUnlockMatch.patternMatch!!.endIndex,
|
||||
"const/4 v$register, 0x1",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package app.revanced.patches.bandcamp.limitations
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val handlePlaybackLimitsFingerprint = fingerprint {
|
||||
strings("play limits processing track", "found play_count")
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package app.revanced.patches.bandcamp.limitations
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val removePlayLimitsPatch = bytecodePatch(
|
||||
name = "Remove play limits",
|
||||
description = "Disables purchase nagging and playback limits of not purchased tracks.",
|
||||
) {
|
||||
compatibleWith("com.bandcamp.android")
|
||||
|
||||
val handlePlaybackLimitsMatch by handlePlaybackLimitsFingerprint()
|
||||
|
||||
execute {
|
||||
handlePlaybackLimitsMatch.mutableMethod.addInstructions(0, "return-void")
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package app.revanced.patches.cieid.restrictions.root
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val bypassRootChecksPatch = bytecodePatch(
|
||||
name = "Bypass root checks",
|
||||
description = "Removes the restriction to use the app with root permissions or on a custom ROM.",
|
||||
) {
|
||||
compatibleWith("it.ipzs.cieid")
|
||||
|
||||
val checkRootMatch by checkRootFingerprint()
|
||||
|
||||
execute {
|
||||
checkRootMatch.mutableMethod.addInstruction(1, "return-void")
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package app.revanced.patches.cieid.restrictions.root
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val checkRootFingerprint = fingerprint {
|
||||
custom { method, _ ->
|
||||
method.name == "onResume" && method.definingClass == "Lit/ipzs/cieid/BaseActivity;"
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package app.revanced.patches.duolingo.ad
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||
|
||||
@Suppress("unused")
|
||||
val disableAdsPatch = bytecodePatch(
|
||||
"Disable ads",
|
||||
) {
|
||||
compatibleWith("com.duolingo")
|
||||
|
||||
val initializeMonetizationDebugSettingsMatch by initializeMonetizationDebugSettingsFingerprint()
|
||||
|
||||
execute {
|
||||
// Couple approaches to remove ads exist:
|
||||
//
|
||||
// MonetizationDebugSettings has a boolean value for "disableAds".
|
||||
// OnboardingState has a getter to check if the user has any "adFreeSessions".
|
||||
// SharedPreferences has a debug boolean value with key "disable_ads", which maps to "DebugCategory.DISABLE_ADS".
|
||||
//
|
||||
// MonetizationDebugSettings seems to be the most general setting to work fine.
|
||||
initializeMonetizationDebugSettingsMatch.mutableMethod.apply {
|
||||
val insertIndex = initializeMonetizationDebugSettingsMatch.patternMatch!!.startIndex
|
||||
val register = getInstruction<TwoRegisterInstruction>(insertIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
insertIndex,
|
||||
"const/4 v$register, 0x1",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package app.revanced.patches.duolingo.ad
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal val initializeMonetizationDebugSettingsFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
|
||||
returns("V")
|
||||
parameters(
|
||||
"Z", // disableAds
|
||||
"Z", // useDebugBilling
|
||||
"Z", // showManageSubscriptions
|
||||
"Z", // alwaysShowSuperAds
|
||||
"Lcom/duolingo/debug/FamilyQuestOverride;",
|
||||
)
|
||||
opcodes(Opcode.IPUT_BOOLEAN)
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package app.revanced.patches.duolingo.debug
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||
|
||||
@Suppress("unused")
|
||||
val enableDebugMenuPatch = bytecodePatch(
|
||||
name = "Enable debug menu",
|
||||
use = false,
|
||||
) {
|
||||
compatibleWith("com.duolingo"("5.158.4"))
|
||||
|
||||
val initializeBuildConfigProviderMatch by initializeBuildConfigProviderFingerprint()
|
||||
|
||||
execute {
|
||||
initializeBuildConfigProviderMatch.mutableMethod.apply {
|
||||
val insertIndex = initializeBuildConfigProviderMatch.patternMatch!!.startIndex
|
||||
val register = getInstruction<TwoRegisterInstruction>(insertIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
insertIndex,
|
||||
"const/4 v$register, 0x1",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package app.revanced.patches.duolingo.debug
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
/**
|
||||
* The `BuildConfigProvider` class has two booleans:
|
||||
*
|
||||
* - `isChina`: (usually) compares "play" with "china"...except for builds in China
|
||||
* - `isDebug`: compares "release" with "debug" <-- we want to force this to `true`
|
||||
*/
|
||||
|
||||
internal val initializeBuildConfigProviderFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
|
||||
returns("V")
|
||||
opcodes(Opcode.IPUT_BOOLEAN)
|
||||
strings("debug", "release", "china")
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
import app.revanced.patcher.fingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal val baseModelMapperFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returns("Lcom/facebook/graphql/modelutil/BaseModelWithTree;")
|
||||
parameters("Ljava/lang/Class", "I", "I")
|
||||
opcodes(
|
||||
Opcode.SGET_OBJECT,
|
||||
Opcode.INVOKE_STATIC,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.CONST_4,
|
||||
Opcode.IF_EQ,
|
||||
)
|
||||
}
|
||||
|
||||
internal val getSponsoredDataModelTemplateFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returns("L")
|
||||
parameters()
|
||||
opcodes(
|
||||
Opcode.CONST,
|
||||
Opcode.CONST,
|
||||
Opcode.INVOKE_STATIC,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.RETURN_OBJECT,
|
||||
)
|
||||
custom { _, classDef ->
|
||||
classDef.type == "Lcom/facebook/graphql/model/GraphQLFBMultiAdsFeedUnit;"
|
||||
}
|
||||
}
|
||||
|
||||
internal val getStoryVisibilityFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
||||
returns("Ljava/lang/String;")
|
||||
opcodes(
|
||||
Opcode.INSTANCE_OF,
|
||||
Opcode.IF_NEZ,
|
||||
Opcode.INSTANCE_OF,
|
||||
Opcode.IF_NEZ,
|
||||
Opcode.INSTANCE_OF,
|
||||
Opcode.IF_NEZ,
|
||||
Opcode.CONST,
|
||||
)
|
||||
strings("This should not be called for base class object")
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
package app.revanced.patches.facebook.ads.mainfeed
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||
import baseModelMapperFingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction31i
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
|
||||
import getSponsoredDataModelTemplateFingerprint
|
||||
import getStoryVisibilityFingerprint
|
||||
|
||||
@Suppress("unused")
|
||||
val hideSponsoredStoriesPatch = bytecodePatch(
|
||||
name = "Hide 'Sponsored Stories'",
|
||||
) {
|
||||
compatibleWith("com.facebook.katana")
|
||||
|
||||
val getStoryVisibilityMatch by getStoryVisibilityFingerprint()
|
||||
val getSponsoredDataModelTemplateMatch by getSponsoredDataModelTemplateFingerprint()
|
||||
val baseModelMapperMatch by baseModelMapperFingerprint()
|
||||
|
||||
execute {
|
||||
val sponsoredDataModelTemplateMethod = getSponsoredDataModelTemplateMatch.method
|
||||
val baseModelMapperMethod = baseModelMapperMatch.method
|
||||
val baseModelWithTreeType = baseModelMapperMethod.returnType
|
||||
|
||||
val graphQlStoryClassDescriptor = "Lcom/facebook/graphql/model/GraphQLStory;"
|
||||
|
||||
// The "SponsoredDataModelTemplate" methods has the ids in its body to extract sponsored data
|
||||
// from GraphQL models, but targets the wrong derived type of "BaseModelWithTree". Since those ids
|
||||
// could change in future version, we need to extract them and call the base implementation directly.
|
||||
val getSponsoredDataHelperMethod = ImmutableMethod(
|
||||
getStoryVisibilityMatch.classDef.type,
|
||||
"getSponsoredData",
|
||||
listOf(ImmutableMethodParameter(graphQlStoryClassDescriptor, null, null)),
|
||||
baseModelWithTreeType,
|
||||
AccessFlags.PRIVATE.value or AccessFlags.STATIC.value,
|
||||
null,
|
||||
null,
|
||||
MutableMethodImplementation(4),
|
||||
).toMutable().apply {
|
||||
// Extract the ids of the original method. These ids seem to correspond to model types for
|
||||
// GraphQL data structure. They are then fed to a method of BaseModelWithTree that populate
|
||||
// and cast the requested GraphQL subtype. The Ids are found in the two first "CONST" instructions.
|
||||
val constInstructions = sponsoredDataModelTemplateMethod.implementation!!.instructions
|
||||
.asSequence()
|
||||
.filterIsInstance<Instruction31i>()
|
||||
.take(2)
|
||||
.toList()
|
||||
|
||||
val storyTypeId = constInstructions[0].narrowLiteral
|
||||
val sponsoredDataTypeId = constInstructions[1].narrowLiteral
|
||||
|
||||
addInstructions(
|
||||
"""
|
||||
const-class v2, $baseModelWithTreeType
|
||||
const v1, $storyTypeId
|
||||
const v0, $sponsoredDataTypeId
|
||||
invoke-virtual {p0, v2, v1, v0}, $baseModelMapperMethod
|
||||
move-result-object v0
|
||||
check-cast v0, $baseModelWithTreeType
|
||||
return-object v0
|
||||
""",
|
||||
)
|
||||
}
|
||||
|
||||
getStoryVisibilityMatch.mutableClass.methods.add(getSponsoredDataHelperMethod)
|
||||
|
||||
// Check if the parameter type is GraphQLStory and if sponsoredDataModelGetter returns a non-null value.
|
||||
// If so, hide the story by setting the visibility to StoryVisibility.GONE.
|
||||
getStoryVisibilityMatch.mutableMethod.addInstructionsWithLabels(
|
||||
getStoryVisibilityMatch.patternMatch!!.startIndex,
|
||||
"""
|
||||
instance-of v0, p0, $graphQlStoryClassDescriptor
|
||||
if-eqz v0, :resume_normal
|
||||
invoke-static {p0}, $getSponsoredDataHelperMethod
|
||||
move-result-object v0
|
||||
if-eqz v0, :resume_normal
|
||||
const-string v0, "GONE"
|
||||
return-object v0
|
||||
:resume_normal
|
||||
nop
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package app.revanced.patches.facebook.ads.story
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import com.android.tools.smali.dexlib2.iface.value.StringEncodedValue
|
||||
|
||||
internal val adsInsertionFingerprint = fieldFingerprint(
|
||||
fieldValue = "AdBucketDataSourceUtil\$attemptAdsInsertion\$1",
|
||||
)
|
||||
|
||||
internal val fetchMoreAdsFingerprint = fieldFingerprint(
|
||||
fieldValue = "AdBucketDataSourceUtil\$attemptFetchMoreAds\$1",
|
||||
)
|
||||
|
||||
internal fun fieldFingerprint(fieldValue: String) = fingerprint {
|
||||
returns("V")
|
||||
parameters()
|
||||
custom { method, classDef ->
|
||||
method.name == "run" &&
|
||||
classDef.fields.any any@{ field ->
|
||||
if (field.name != "__redex_internal_original_name") return@any false
|
||||
(field.initialValue as? StringEncodedValue)?.value == fieldValue
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package app.revanced.patches.facebook.ads.story
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val hideStoryAdsPatch = bytecodePatch(
|
||||
name = "Hide story ads",
|
||||
description = "Hides the ads in the Facebook app stories.",
|
||||
) {
|
||||
compatibleWith("com.facebook.katana")
|
||||
|
||||
val fetchMoreAdsMatch by fetchMoreAdsFingerprint()
|
||||
val adsInsertionMatch by adsInsertionFingerprint()
|
||||
|
||||
execute {
|
||||
setOf(fetchMoreAdsMatch, adsInsertionMatch).forEach { match ->
|
||||
match.mutableMethod.replaceInstruction(0, "return-void")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package app.revanced.patches.finanzonline.detection.bootloader
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val bootloaderDetectionPatch = bytecodePatch(
|
||||
name = "Remove bootloader detection",
|
||||
description = "Removes the check for an unlocked bootloader.",
|
||||
) {
|
||||
compatibleWith("at.gv.bmf.bmf2go")
|
||||
|
||||
val createKeyMatch by createKeyFingerprint()
|
||||
val bootStateMatch by bootStateFingerprint()
|
||||
|
||||
execute {
|
||||
setOf(createKeyMatch, bootStateMatch).forEach { match ->
|
||||
match.mutableMethod.addInstructions(
|
||||
0,
|
||||
"""
|
||||
const/4 v0, 0x1
|
||||
return v0
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -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")
|
||||
}
|
@ -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
|
||||
)
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package app.revanced.patches.finanzonline.detection.root
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val rootDetectionPatch = bytecodePatch(
|
||||
name = "Remove root detection",
|
||||
description = "Removes the check for root permissions.",
|
||||
) {
|
||||
compatibleWith("at.gv.bmf.bmf2go")
|
||||
|
||||
val rootDetectionMatch by rootDetectionFingerprint()
|
||||
|
||||
execute {
|
||||
rootDetectionMatch.mutableMethod.addInstructions(
|
||||
0,
|
||||
"""
|
||||
sget-object v0, Ljava/lang/Boolean;->FALSE:Ljava/lang/Boolean;
|
||||
return-object v0
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package app.revanced.patches.googlenews.customtabs
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
@Suppress("unused")
|
||||
val enableCustomTabsPatch = bytecodePatch(
|
||||
name = "Enable CustomTabs",
|
||||
description = "Enables CustomTabs to open articles in your default browser.",
|
||||
) {
|
||||
compatibleWith("com.google.android.apps.magazines")
|
||||
|
||||
val launchCustomTabMatch by launchCustomTabFingerprint()
|
||||
|
||||
execute {
|
||||
launchCustomTabMatch.mutableMethod.apply {
|
||||
val checkIndex = launchCustomTabMatch.patternMatch!!.endIndex + 1
|
||||
val register = getInstruction<OneRegisterInstruction>(checkIndex).registerA
|
||||
|
||||
replaceInstruction(checkIndex, "const/4 v$register, 0x1")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package app.revanced.patches.googlenews.customtabs
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal val launchCustomTabFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
|
||||
opcodes(
|
||||
Opcode.IPUT_OBJECT,
|
||||
Opcode.CONST_4,
|
||||
Opcode.IPUT,
|
||||
Opcode.CONST_4,
|
||||
Opcode.IPUT_BOOLEAN,
|
||||
)
|
||||
custom { _, classDef -> classDef.endsWith("CustomTabsArticleLauncher;") }
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package app.revanced.patches.googlenews.misc.extension
|
||||
|
||||
import app.revanced.patches.googlenews.misc.extension.hooks.startActivityInitHook
|
||||
import app.revanced.patches.shared.misc.extension.sharedExtensionPatch
|
||||
|
||||
val extensionPatch = sharedExtensionPatch(startActivityInitHook)
|
@ -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;")
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package app.revanced.patches.googlenews.misc.gms
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val magazinesActivityOnCreateFingerprint = fingerprint {
|
||||
custom { methodDef, classDef ->
|
||||
methodDef.name == "onCreate" && classDef.endsWith("/StartActivity;")
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package app.revanced.patches.googlenews.misc.gms
|
||||
|
||||
import app.revanced.patcher.patch.Option
|
||||
import app.revanced.patches.googlenews.misc.extension.extensionPatch
|
||||
import app.revanced.patches.googlenews.misc.gms.Constants.MAGAZINES_PACKAGE_NAME
|
||||
import app.revanced.patches.googlenews.misc.gms.Constants.REVANCED_MAGAZINES_PACKAGE_NAME
|
||||
import app.revanced.patches.shared.misc.gms.gmsCoreSupportPatch
|
||||
import app.revanced.patches.shared.misc.gms.gmsCoreSupportResourcePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val gmsCoreSupportPatch = gmsCoreSupportPatch(
|
||||
fromPackageName = MAGAZINES_PACKAGE_NAME,
|
||||
toPackageName = REVANCED_MAGAZINES_PACKAGE_NAME,
|
||||
mainActivityOnCreateFingerprint = magazinesActivityOnCreateFingerprint,
|
||||
extensionPatch = extensionPatch,
|
||||
gmsCoreSupportResourcePatchFactory = ::gmsCoreSupportResourcePatch,
|
||||
) {
|
||||
// Remove version constraint,
|
||||
// once https://github.com/ReVanced/revanced-patches/pull/3111#issuecomment-2240877277 is resolved.
|
||||
compatibleWith(MAGAZINES_PACKAGE_NAME("5.108.0.644447823"))
|
||||
}
|
||||
|
||||
private fun gmsCoreSupportResourcePatch(
|
||||
gmsCoreVendorGroupIdOption: Option<String>,
|
||||
) = gmsCoreSupportResourcePatch(
|
||||
fromPackageName = MAGAZINES_PACKAGE_NAME,
|
||||
toPackageName = REVANCED_MAGAZINES_PACKAGE_NAME,
|
||||
spoofedPackageSignature = "24bb24c05e47e0aefa68a58a766179d9b613a666",
|
||||
gmsCoreVendorGroupIdOption = gmsCoreVendorGroupIdOption,
|
||||
)
|
@ -0,0 +1,5 @@
|
||||
package app.revanced.patches.googlephotos.misc.extension
|
||||
|
||||
import app.revanced.patches.shared.misc.extension.sharedExtensionPatch
|
||||
|
||||
val extensionPatch = sharedExtensionPatch(homeActivityInitHook)
|
@ -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;")
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package app.revanced.patches.googlephotos.misc.features
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val initializeFeaturesEnumFingerprint = fingerprint {
|
||||
strings("com.google.android.apps.photos.NEXUS_PRELOAD")
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package app.revanced.patches.googlephotos.misc.features
|
||||
|
||||
import app.revanced.patches.all.misc.build.BuildInfo
|
||||
import app.revanced.patches.all.misc.build.baseSpoofBuildInfoPatch
|
||||
|
||||
// Spoof build info to Google Pixel XL.
|
||||
@Suppress("unused")
|
||||
val spoofBuildInfoPatch = baseSpoofBuildInfoPatch {
|
||||
BuildInfo(
|
||||
brand = "google",
|
||||
manufacturer = "Google",
|
||||
device = "marlin",
|
||||
product = "marlin",
|
||||
model = "Pixel XL",
|
||||
fingerprint = "google/marlin/marlin:10/QP1A.191005.007.A3/5972272:user/release-keys",
|
||||
)
|
||||
}
|
@ -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\"",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package app.revanced.patches.googlephotos.misc.gms
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val homeActivityOnCreateFingerprint = fingerprint {
|
||||
custom { methodDef, classDef ->
|
||||
methodDef.name == "onCreate" && classDef.endsWith("/HomeActivity;")
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package app.revanced.patches.googlephotos.misc.gms
|
||||
|
||||
import app.revanced.patcher.patch.Option
|
||||
import app.revanced.patches.googlephotos.misc.extension.extensionPatch
|
||||
import app.revanced.patches.googlephotos.misc.gms.Constants.PHOTOS_PACKAGE_NAME
|
||||
import app.revanced.patches.googlephotos.misc.gms.Constants.REVANCED_PHOTOS_PACKAGE_NAME
|
||||
import app.revanced.patches.shared.misc.gms.gmsCoreSupportPatch
|
||||
|
||||
@Suppress("unused")
|
||||
val gmsCoreSupportPatch = gmsCoreSupportPatch(
|
||||
fromPackageName = PHOTOS_PACKAGE_NAME,
|
||||
toPackageName = REVANCED_PHOTOS_PACKAGE_NAME,
|
||||
mainActivityOnCreateFingerprint = homeActivityOnCreateFingerprint,
|
||||
extensionPatch = extensionPatch,
|
||||
gmsCoreSupportResourcePatchFactory = ::gmsCoreSupportResourcePatch,
|
||||
) {
|
||||
compatibleWith(PHOTOS_PACKAGE_NAME)
|
||||
}
|
||||
|
||||
private fun gmsCoreSupportResourcePatch(
|
||||
gmsCoreVendorGroupIdOption: Option<String>,
|
||||
) = app.revanced.patches.shared.misc.gms.gmsCoreSupportResourcePatch(
|
||||
fromPackageName = PHOTOS_PACKAGE_NAME,
|
||||
toPackageName = REVANCED_PHOTOS_PACKAGE_NAME,
|
||||
spoofedPackageSignature = "24bb24c05e47e0aefa68a58a766179d9b613a600",
|
||||
gmsCoreVendorGroupIdOption = gmsCoreVendorGroupIdOption,
|
||||
)
|
@ -0,0 +1,8 @@
|
||||
package app.revanced.patches.googlephotos.misc.preferences
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val backupPreferencesFingerprint = fingerprint {
|
||||
returns("Lcom/google/android/apps/photos/backup/data/BackupPreferences;")
|
||||
strings("backup_prefs_had_backup_only_when_charging_enabled")
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package app.revanced.patches.googlephotos.misc.preferences
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
@Suppress("unused")
|
||||
val restoreHiddenBackUpWhileChargingTogglePatch = bytecodePatch(
|
||||
name = "Restore hidden 'Back up while charging' toggle",
|
||||
description = "Restores a hidden toggle to only run backups when the device is charging.",
|
||||
) {
|
||||
compatibleWith("com.google.android.apps.photos")
|
||||
|
||||
val backupPreferencesMatch by backupPreferencesFingerprint()
|
||||
|
||||
execute {
|
||||
// Patches 'backup_prefs_had_backup_only_when_charging_enabled' to always be true.
|
||||
val chargingPrefStringIndex = backupPreferencesMatch.stringMatches!!.first().index
|
||||
backupPreferencesMatch.mutableMethod.apply {
|
||||
// Get the register of move-result.
|
||||
val resultRegister = getInstruction<OneRegisterInstruction>(chargingPrefStringIndex + 2).registerA
|
||||
// Insert const after move-result to override register as true.
|
||||
addInstruction(chargingPrefStringIndex + 3, "const/4 v$resultRegister, 0x1")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package app.revanced.patches.googlerecorder.restrictions
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val onApplicationCreateFingerprint = fingerprint {
|
||||
strings("com.google.android.feature.PIXEL_2017_EXPERIENCE")
|
||||
custom { method, classDef ->
|
||||
if (method.name != "onCreate") return@custom false
|
||||
|
||||
classDef.endsWith("RecorderApplication;")
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package app.revanced.patches.googlerecorder.restrictions
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.removeInstructions
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
@Suppress("unused")
|
||||
val removeDeviceRestrictionsPatch = bytecodePatch(
|
||||
name = "Remove device restrictions",
|
||||
description = "Removes restrictions from using the app on any device. Requires mounting patched app over original.",
|
||||
) {
|
||||
compatibleWith("com.google.android.apps.recorder")
|
||||
|
||||
val onApplicationCreateMatch by onApplicationCreateFingerprint()
|
||||
|
||||
execute {
|
||||
val featureStringIndex = onApplicationCreateMatch.stringMatches!!.first().index
|
||||
|
||||
onApplicationCreateMatch.mutableMethod.apply {
|
||||
// Remove check for device restrictions.
|
||||
removeInstructions(featureStringIndex - 2, 5)
|
||||
|
||||
val featureAvailableRegister = getInstruction<OneRegisterInstruction>(featureStringIndex).registerA
|
||||
|
||||
// Override "isPixelDevice()" to return true.
|
||||
addInstruction(featureStringIndex, "const/4 v$featureAvailableRegister, 0x1")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package app.revanced.patches.hexeditor.ad
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstructions
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val disableAdsPatch = bytecodePatch(
|
||||
name = "Disable ads",
|
||||
) {
|
||||
compatibleWith("com.myprog.hexedit")
|
||||
|
||||
val primaryAdsMatch by primaryAdsFingerprint()
|
||||
|
||||
execute {
|
||||
primaryAdsMatch.mutableMethod.replaceInstructions(
|
||||
0,
|
||||
"""
|
||||
const/4 v0, 0x1
|
||||
return v0
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package app.revanced.patches.hexeditor.ad
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val primaryAdsFingerprint = fingerprint {
|
||||
custom { method, classDef ->
|
||||
classDef.endsWith("PreferencesHelper;") && method.name == "isAdsDisabled"
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package app.revanced.patches.iconpackstudio.misc.pro
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val checkProFingerprint = fingerprint {
|
||||
returns("Z")
|
||||
custom { _, classDef -> classDef.endsWith("IPSPurchaseRepository;") }
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package app.revanced.patches.iconpackstudio.misc.pro
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val unlockProPatch = bytecodePatch(
|
||||
name = "Unlock pro",
|
||||
) {
|
||||
compatibleWith("ginlemon.iconpackstudio"("2.2 build 016"))
|
||||
|
||||
val checkProMatch by checkProFingerprint()
|
||||
|
||||
execute {
|
||||
checkProMatch.mutableMethod.addInstructions(
|
||||
0,
|
||||
"""
|
||||
const/4 v0, 0x1
|
||||
return v0
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package app.revanced.patches.idaustria.detection.root
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
internal val attestationSupportedCheckFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC)
|
||||
returns("V")
|
||||
custom { method, classDef ->
|
||||
method.name == "attestationSupportCheck" &&
|
||||
classDef.endsWith("/DeviceIntegrityCheck;")
|
||||
}
|
||||
}
|
||||
|
||||
internal val bootloaderCheckFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC)
|
||||
returns("Z")
|
||||
custom { method, classDef ->
|
||||
method.name == "bootloaderCheck" &&
|
||||
classDef.endsWith("/DeviceIntegrityCheck;")
|
||||
}
|
||||
}
|
||||
|
||||
internal val rootCheckFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC)
|
||||
returns("V")
|
||||
custom { method, classDef ->
|
||||
method.name == "rootCheck" &&
|
||||
classDef.endsWith("/DeviceIntegrityCheck;")
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package app.revanced.patches.idaustria.detection.root
|
||||
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.util.returnEarly
|
||||
|
||||
@Suppress("unused")
|
||||
val rootDetectionPatch = bytecodePatch(
|
||||
name = "Remove root detection",
|
||||
description = "Removes the check for root permissions and unlocked bootloader.",
|
||||
) {
|
||||
compatibleWith("at.gv.oe.app")
|
||||
|
||||
attestationSupportedCheckFingerprint()
|
||||
bootloaderCheckFingerprint()
|
||||
rootCheckFingerprint()
|
||||
|
||||
execute {
|
||||
setOf(attestationSupportedCheckFingerprint, bootloaderCheckFingerprint, rootCheckFingerprint).returnEarly(true)
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package app.revanced.patches.idaustria.detection.signature
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
internal val spoofSignatureFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PRIVATE)
|
||||
returns("L")
|
||||
parameters("L")
|
||||
custom { method, classDef ->
|
||||
classDef.endsWith("/SL2Step1Task;") && method.name == "getPubKey"
|
||||
}
|
||||
}
|
@ -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
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package app.revanced.patches.inshorts.ad
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val inshortsAdsFingerprint = fingerprint {
|
||||
returns("V")
|
||||
strings("GoogleAdLoader", "exception in requestAd")
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package app.revanced.patches.inshorts.ad
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val hideAdsPatch = bytecodePatch(
|
||||
name = "Hide ads",
|
||||
) {
|
||||
compatibleWith("com.nis.app")
|
||||
|
||||
val inshortsAdsMatch by inshortsAdsFingerprint()
|
||||
|
||||
execute {
|
||||
inshortsAdsMatch.mutableMethod.addInstruction(
|
||||
0,
|
||||
"""
|
||||
return-void
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package app.revanced.patches.instagram.ads
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
internal val adInjectorFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PRIVATE)
|
||||
returns("Z")
|
||||
parameters("L", "L")
|
||||
strings(
|
||||
"SponsoredContentController.insertItem",
|
||||
"SponsoredContentController::Delivery",
|
||||
)
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package app.revanced.patches.instagram.ads
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val hideAdsPatch = bytecodePatch(
|
||||
name = "Hide ads",
|
||||
description = "Hides ads in stories, discover, profile, etc. " +
|
||||
"An ad can still appear once when refreshing the home feed.",
|
||||
) {
|
||||
compatibleWith("com.instagram.android")
|
||||
|
||||
val adInjectorMatch by adInjectorFingerprint()
|
||||
|
||||
execute {
|
||||
adInjectorMatch.mutableMethod.addInstructions(
|
||||
0,
|
||||
"""
|
||||
const/4 v0, 0x0
|
||||
return v0
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package app.revanced.patches.irplus.ad
|
||||
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val irplusAdsFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
|
||||
returns("V")
|
||||
parameters("L", "Z")
|
||||
strings("TAGGED")
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package app.revanced.patches.irplus.ad
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val removeAdsPatch = bytecodePatch(
|
||||
name = "Remove ads",
|
||||
) {
|
||||
compatibleWith("net.binarymode.android.irplus")
|
||||
|
||||
val irplusAdsMatch by irplusAdsFingerprint()
|
||||
|
||||
execute {
|
||||
// By overwriting the second parameter of the method,
|
||||
// the view which holds the advertisement is removed.
|
||||
irplusAdsMatch.mutableMethod.addInstruction(0, "const/4 p2, 0x0")
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package app.revanced.patches.lightroom.misc.login
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val disableMandatoryLoginPatch = bytecodePatch(
|
||||
name = "Disable mandatory login",
|
||||
) {
|
||||
compatibleWith("com.adobe.lrmobile")
|
||||
|
||||
val isLoggedInMatch by isLoggedInFingerprint()
|
||||
|
||||
execute {
|
||||
isLoggedInMatch.mutableMethod.apply {
|
||||
val index = implementation!!.instructions.lastIndex - 1
|
||||
// Set isLoggedIn = true.
|
||||
replaceInstruction(index, "const/4 v0, 0x1")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package app.revanced.patches.lightroom.misc.login
|
||||
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val isLoggedInFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC, AccessFlags.FINAL)
|
||||
returns("Z")
|
||||
opcodes(
|
||||
Opcode.INVOKE_STATIC,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.SGET_OBJECT,
|
||||
Opcode.IF_NE,
|
||||
Opcode.CONST_4,
|
||||
Opcode.GOTO
|
||||
)
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package app.revanced.patches.lightroom.misc.premium
|
||||
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val hasPurchasedFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PRIVATE, AccessFlags.FINAL)
|
||||
returns("Z")
|
||||
opcodes(
|
||||
Opcode.SGET_OBJECT,
|
||||
Opcode.CONST_4,
|
||||
Opcode.CONST_4,
|
||||
Opcode.CONST_4,
|
||||
)
|
||||
strings("isPurchaseDoneRecently = true, access platform profile present? = ")
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package app.revanced.patches.lightroom.misc.premium
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val unlockPremiumPatch = bytecodePatch(
|
||||
name = "Unlock premium",
|
||||
) {
|
||||
compatibleWith("com.adobe.lrmobile")
|
||||
|
||||
val hasPurchasedMatch by hasPurchasedFingerprint()
|
||||
|
||||
execute {
|
||||
// Set hasPremium = true.
|
||||
hasPurchasedMatch.mutableMethod.replaceInstruction(2, "const/4 v2, 0x1")
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package app.revanced.patches.memegenerator.detection.license
|
||||
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val licenseValidationFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
||||
returns("Z")
|
||||
parameters("Landroid/content/Context;")
|
||||
opcodes(
|
||||
Opcode.INVOKE_STATIC,
|
||||
Opcode.MOVE_RESULT_WIDE,
|
||||
Opcode.INVOKE_STATIC,
|
||||
Opcode.MOVE_RESULT_WIDE,
|
||||
Opcode.CMP_LONG,
|
||||
Opcode.IF_GEZ,
|
||||
Opcode.CONST_4,
|
||||
Opcode.RETURN,
|
||||
Opcode.CONST_4,
|
||||
Opcode.RETURN
|
||||
)
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package app.revanced.patches.memegenerator.detection.license
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstructions
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val licenseValidationPatch = bytecodePatch(
|
||||
description = "Disables Firebase license validation.",
|
||||
) {
|
||||
val licenseValidationMatch by licenseValidationFingerprint()
|
||||
|
||||
execute {
|
||||
licenseValidationMatch.mutableMethod.replaceInstructions(
|
||||
0,
|
||||
"""
|
||||
const/4 p0, 0x1
|
||||
return p0
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
@ -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
|
||||
)
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package app.revanced.patches.memegenerator.detection.signature
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstructions
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val signatureVerificationPatch = bytecodePatch(
|
||||
description = "Disables detection of incorrect signature.",
|
||||
) {
|
||||
val verifySignatureMatch by verifySignatureFingerprint()
|
||||
|
||||
execute {
|
||||
verifySignatureMatch.mutableMethod.replaceInstructions(
|
||||
0,
|
||||
"""
|
||||
const/4 p0, 0x1
|
||||
return p0
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package app.revanced.patches.memegenerator.misc.pro
|
||||
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val isFreeVersionFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
||||
returns("Ljava/lang/Boolean;")
|
||||
parameters("Landroid/content/Context;")
|
||||
opcodes(
|
||||
Opcode.SGET,
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.CONST_STRING,
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.MOVE_RESULT,
|
||||
Opcode.IF_EQZ
|
||||
)
|
||||
strings("free")
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package app.revanced.patches.memegenerator.misc.pro
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstructions
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.memegenerator.detection.license.licenseValidationPatch
|
||||
import app.revanced.patches.memegenerator.detection.signature.signatureVerificationPatch
|
||||
|
||||
@Suppress("unused")
|
||||
val unlockProVersionPatch = bytecodePatch(
|
||||
name = "Unlock pro",
|
||||
) {
|
||||
dependsOn(signatureVerificationPatch, licenseValidationPatch)
|
||||
|
||||
compatibleWith("com.zombodroid.MemeGenerator"("4.6364", "4.6370", "4.6375", "4.6377"))
|
||||
|
||||
val isFreeVersionMatch by isFreeVersionFingerprint()
|
||||
|
||||
execute {
|
||||
isFreeVersionMatch.mutableMethod.replaceInstructions(
|
||||
0,
|
||||
"""
|
||||
sget-object p0, Ljava/lang/Boolean;->FALSE:Ljava/lang/Boolean;
|
||||
return-object p0
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package app.revanced.patches.messenger.inbox
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.value.StringEncodedValue
|
||||
|
||||
internal val createInboxSubTabsFingerprint = fingerprint {
|
||||
returns("V")
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
opcodes(
|
||||
Opcode.CONST_4,
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.RETURN_VOID,
|
||||
)
|
||||
custom { method, classDef ->
|
||||
method.name == "run" &&
|
||||
classDef.fields.any any@{ field ->
|
||||
if (field.name != "__redex_internal_original_name") return@any false
|
||||
(field.initialValue as? StringEncodedValue)?.value == "InboxSubtabsItemSupplierImplementation\$onSubscribe\$1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal val loadInboxAdsFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
||||
returns("V")
|
||||
strings(
|
||||
"ads_load_begin",
|
||||
"inbox_ads_fetch_start",
|
||||
)
|
||||
custom { method, _ ->
|
||||
method.definingClass == "Lcom/facebook/messaging/business/inboxads/plugins/inboxads/itemsupplier/" +
|
||||
"InboxAdsItemSupplierImplementation;"
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package app.revanced.patches.messenger.inbox
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val hideInboxAdsPatch = bytecodePatch(
|
||||
name = "Hide inbox ads",
|
||||
description = "Hides ads in inbox.",
|
||||
) {
|
||||
compatibleWith("com.facebook.orca")
|
||||
|
||||
val loadInboxAdsMatch by loadInboxAdsFingerprint()
|
||||
|
||||
execute {
|
||||
loadInboxAdsMatch.mutableMethod.replaceInstruction(0, "return-void")
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package app.revanced.patches.messenger.inbox
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val hideInboxSubtabsPatch = bytecodePatch(
|
||||
name = "Hide inbox subtabs",
|
||||
description = "Hides Home and Channels tabs between active now tray and chats.",
|
||||
) {
|
||||
compatibleWith("com.facebook.orca")
|
||||
|
||||
val createInboxSubTabsMatch by createInboxSubTabsFingerprint()
|
||||
|
||||
execute {
|
||||
createInboxSubTabsMatch.mutableMethod.replaceInstruction(2, "const/4 v0, 0x0")
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package app.revanced.patches.messenger.inputfield
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
@Suppress("unused")
|
||||
val disableSwitchingEmojiToStickerPatch = bytecodePatch(
|
||||
name = "Disable switching emoji to sticker",
|
||||
description = "Disables switching from emoji to sticker search mode in message input field.",
|
||||
) {
|
||||
compatibleWith("com.facebook.orca"("439.0.0.29.119"))
|
||||
|
||||
val switchMessangeInputEmojiButtonMatch by switchMessangeInputEmojiButtonFingerprint()
|
||||
|
||||
execute {
|
||||
val setStringIndex = switchMessangeInputEmojiButtonMatch.patternMatch!!.startIndex + 2
|
||||
|
||||
switchMessangeInputEmojiButtonMatch.mutableMethod.apply {
|
||||
val targetRegister = getInstruction<OneRegisterInstruction>(setStringIndex).registerA
|
||||
|
||||
replaceInstruction(setStringIndex, "const-string v$targetRegister, \"expression\"")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package app.revanced.patches.messenger.inputfield
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
|
||||
@Suppress("unused")
|
||||
val disableTypingIndicatorPatch = bytecodePatch(
|
||||
name = "Disable typing indicator",
|
||||
description = "Disables the indicator while typing a message.",
|
||||
) {
|
||||
compatibleWith("com.facebook.orca")
|
||||
|
||||
val sendTypingIndicatorMatch by sendTypingIndicatorFingerprint()
|
||||
|
||||
execute {
|
||||
sendTypingIndicatorMatch.mutableMethod.replaceInstruction(0, "return-void")
|
||||
}
|
||||
}
|
@ -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")
|
||||
}
|
@ -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;"
|
||||
}
|
||||
}
|
@ -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\"",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -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;"
|
||||
}
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
@ -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.")
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
@ -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
|
||||
""",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
)
|
||||
}
|
@ -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")
|
||||
}
|
@ -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
Reference in New Issue
Block a user