diff --git a/CHANGELOG.md b/CHANGELOG.md index cbd1883bdf..bfebce7e69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,33 @@ +# [6.1.0](https://github.com/ReVanced/revanced-patches/compare/v6.0.1...v6.1.0) (2026-03-18) + + +### Bug Fixes + +* **Export internal data documents provider:** Correct S_IFLNK constant and symlink detection mask ([#6819](https://github.com/ReVanced/revanced-patches/issues/6819)) ([252617b](https://github.com/ReVanced/revanced-patches/commit/252617b8dd3f24e1ff9a04ba1d91b43dc29bd757)) +* **YouTube - Custom branding:** Fix double icons and change default branding to ReVanced ([#6806](https://github.com/ReVanced/revanced-patches/issues/6806)) ([e51c529](https://github.com/ReVanced/revanced-patches/commit/e51c5292c171325e7cfa0f5ee85474d9b3961a34)) + + +### Features + +* Add `Spoof root of trust` and `Spoof keystore security level` patch ([#6751](https://github.com/ReVanced/revanced-patches/issues/6751)) ([4bc8c7c](https://github.com/ReVanced/revanced-patches/commit/4bc8c7c0f60a095533f07dc281f0320f8eb22f3c)) +* **Announcements:** Support ReVanced API v5 announcements ([a05386e](https://github.com/ReVanced/revanced-patches/commit/a05386e8bc24c085b5c74f3674c402c5dd5ad468)) +* Change contact email in patches about ([df1c3a4](https://github.com/ReVanced/revanced-patches/commit/df1c3a4a70fd2595d77b539299f1f7301bc60d24)) +* **Instagram:** Add `Enable location sticker redesign` patch ([#6808](https://github.com/ReVanced/revanced-patches/issues/6808)) ([4b699da](https://github.com/ReVanced/revanced-patches/commit/4b699da220e5d1527c390792b6228e2d9cffedb7)) +* **Spoof video streams:** Add Android Reel client to fix playback issues ([#6830](https://github.com/ReVanced/revanced-patches/issues/6830)) ([4b6c3e3](https://github.com/ReVanced/revanced-patches/commit/4b6c3e312328fbf6a1c7065e27d8ff04573e58be)) + +# [6.1.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v6.1.0-dev.3...v6.1.0-dev.4) (2026-03-18) + + +### Bug Fixes + +* **YouTube - Custom branding:** Fix double icons and change default branding to ReVanced ([#6806](https://github.com/ReVanced/revanced-patches/issues/6806)) ([e51c529](https://github.com/ReVanced/revanced-patches/commit/e51c5292c171325e7cfa0f5ee85474d9b3961a34)) + + +### Features + +* Add `Spoof root of trust` and `Spoof keystore security level` patch ([#6751](https://github.com/ReVanced/revanced-patches/issues/6751)) ([4bc8c7c](https://github.com/ReVanced/revanced-patches/commit/4bc8c7c0f60a095533f07dc281f0320f8eb22f3c)) +* **Instagram:** Add `Enable location sticker redesign` patch ([#6808](https://github.com/ReVanced/revanced-patches/issues/6808)) ([4b699da](https://github.com/ReVanced/revanced-patches/commit/4b699da220e5d1527c390792b6228e2d9cffedb7)) + # [6.1.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v6.1.0-dev.2...v6.1.0-dev.3) (2026-03-18) diff --git a/extensions/all/misc/disable-play-integrity/src/main/java/app/revanced/extension/playintegrity/DisablePlayIntegrityPatch.java b/extensions/all/misc/disable-play-integrity/src/main/java/app/revanced/extension/play/DisablePlayIntegrityPatch.java similarity index 92% rename from extensions/all/misc/disable-play-integrity/src/main/java/app/revanced/extension/playintegrity/DisablePlayIntegrityPatch.java rename to extensions/all/misc/disable-play-integrity/src/main/java/app/revanced/extension/play/DisablePlayIntegrityPatch.java index a27e56be95..4dd09f693f 100644 --- a/extensions/all/misc/disable-play-integrity/src/main/java/app/revanced/extension/playintegrity/DisablePlayIntegrityPatch.java +++ b/extensions/all/misc/disable-play-integrity/src/main/java/app/revanced/extension/play/DisablePlayIntegrityPatch.java @@ -1,4 +1,4 @@ -package app.revanced.extension.playintegrity; +package app.revanced.extension.play; import android.content.Context; import android.content.Intent; diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/CustomBrandingPatch.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/CustomBrandingPatch.java index 0dc411f8b0..d13513e2df 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/CustomBrandingPatch.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/CustomBrandingPatch.java @@ -114,7 +114,7 @@ public class CustomBrandingPatch { /** * Injection point. - * + *

* The total number of app name aliases, including dummy aliases. */ private static int numberOfPresetAppNames() { @@ -146,13 +146,13 @@ public class CustomBrandingPatch { public static int getDefaultAppNameIndex() { return userProvidedCustomName() ? numberOfPresetAppNames() - : 1; + : 2; } public static BrandingTheme getDefaultIconStyle() { return userProvidedCustomIcon() ? BrandingTheme.CUSTOM - : BrandingTheme.ORIGINAL; + : BrandingTheme.ROUNDED; } /** diff --git a/gradle.properties b/gradle.properties index 1ec45a3285..d65f6857e0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,4 +4,4 @@ org.gradle.parallel = true android.useAndroidX = true android.uniquePackageNames = false kotlin.code.style = official -version = 6.1.0-dev.3 +version = 6.1.0 diff --git a/patches/api/patches.api b/patches/api/patches.api index be251e84a5..4a8ed81323 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -89,10 +89,14 @@ public final class app/revanced/patches/all/misc/packagename/ChangePackageNamePa public static final fun setOrGetFallbackPackageName (Ljava/lang/String;)Ljava/lang/String; } -public final class app/revanced/patches/all/misc/playintegrity/DisablePlayIntegrityKt { +public final class app/revanced/patches/all/misc/play/DisablePlayIntegrityKt { public static final fun getDisablePlayIntegrityPatch ()Lapp/revanced/patcher/patch/Patch; } +public final class app/revanced/patches/all/misc/play/SpoofPlayAgeSignalsKt { + public static final fun getSpoofPlayAgeSignalsPatch ()Lapp/revanced/patcher/patch/Patch; +} + public final class app/revanced/patches/all/misc/resources/AddResourcesPatchKt { public static final fun addResource (Ljava/lang/String;Lapp/revanced/util/resource/BaseResource;)Z public static final fun addResources (Lapp/revanced/patcher/patch/Patch;Lkotlin/jvm/functions/Function1;)Z @@ -125,6 +129,14 @@ public final class app/revanced/patches/all/misc/spoof/EnableRomSignatureSpoofin public static final fun getEnableROMSignatureSpoofingPatch ()Lapp/revanced/patcher/patch/Patch; } +public final class app/revanced/patches/all/misc/spoof/SpoofKeystoreSecurityLevelPatchKt { + public static final fun getSpoofKeystoreSecurityLevelPatch ()Lapp/revanced/patcher/patch/Patch; +} + +public final class app/revanced/patches/all/misc/spoof/SpoofRootOfTrustPatchKt { + public static final fun getSpoofRootOfTrustPatch ()Lapp/revanced/patcher/patch/Patch; +} + public final class app/revanced/patches/all/misc/targetSdk/SetTargetSdkVersion34Kt { public static final fun getSetTargetSDKVersion34Patch ()Lapp/revanced/patcher/patch/Patch; } @@ -365,6 +377,10 @@ public final class app/revanced/patches/instagram/story/flipping/DisableStoryAut public static final fun getDisableStoryAutoFlippingPatch ()Lapp/revanced/patcher/patch/Patch; } +public final class app/revanced/patches/instagram/story/locationsticker/EnableLocationStickerRedesignPatchKt { + public static final fun getEnableLocationStickerRedesignPatch ()Lapp/revanced/patcher/patch/Patch; +} + public final class app/revanced/patches/irplus/ad/RemoveAdsPatchKt { public static final fun getRemoveAdsPatch ()Lapp/revanced/patcher/patch/Patch; } diff --git a/patches/src/main/kotlin/app/revanced/patches/all/misc/connectivity/telephony/sim/spoof/SpoofSimProviderPatch.kt b/patches/src/main/kotlin/app/revanced/patches/all/misc/connectivity/telephony/sim/spoof/SpoofSimProviderPatch.kt index a7dfdf7bf4..cb5723de76 100644 --- a/patches/src/main/kotlin/app/revanced/patches/all/misc/connectivity/telephony/sim/spoof/SpoofSimProviderPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/all/misc/connectivity/telephony/sim/spoof/SpoofSimProviderPatch.kt @@ -6,7 +6,8 @@ import app.revanced.patcher.extensions.replaceInstruction import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.intOption import app.revanced.patcher.patch.stringOption -import app.revanced.patches.all.misc.transformation.transformInstructionsPatch +import app.revanced.util.forEachInstructionAsSequence +import com.android.tools.smali.dexlib2.iface.instruction.Instruction 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 @@ -33,7 +34,8 @@ val spoofSIMProviderPatch = bytecodePatch( validator = { it: String? -> it == null || it.uppercase() in countries.values }, ) - fun isMccMncValid(it: Int?): Boolean = it == null || (it >= 10000 && it <= 999999) + fun isMccMncValid(it: Int?) = it == null || (it in 10000..999999) + fun isNumericValid(it: String?, length: Int) = it.isNullOrBlank() || it.equals("random", true) || it.length == length val networkCountryIso by isoCountryPatchOption("Network ISO country code") @@ -61,46 +63,119 @@ val spoofSIMProviderPatch = bytecodePatch( description = "The full name of the SIM operator.", ) + val imei by stringOption( + name = "IMEI value", + description = "15-digit IMEI to spoof, blank to skip, or 'random'.", + validator = { isNumericValid(it, 15) }, + ) + + val meid by stringOption( + name = "MEID value", + description = "14-char hex MEID to spoof, blank to skip, or 'random'.", + validator = { isNumericValid(it, 14) }, + ) + + val imsi by stringOption( + name = "IMSI (Subscriber ID)", + description = "15-digit IMSI to spoof, blank to skip, or 'random'.", + validator = { isNumericValid(it, 15) }, + ) + + val iccid by stringOption( + name = "ICCID (SIM Serial)", + description = "19-digit ICCID to spoof, blank to skip, or 'random'.", + validator = { isNumericValid(it, 19) }, + ) + + val phone by stringOption( + name = "Phone number", + description = "Phone number to spoof, blank to skip, or 'random'.", + validator = { it.isNullOrBlank() || it.equals("random", ignoreCase = true) || it.startsWith("+") }, + ) + dependsOn( - transformInstructionsPatch( - filterMap = { _, _, instruction, instructionIndex -> - if (instruction !is ReferenceInstruction) return@transformInstructionsPatch null + bytecodePatch { + apply { + fun generateRandomNumeric(length: Int) = (1..length).map { ('0'..'9').random() }.joinToString("") - val reference = instruction.reference as? MethodReference ?: return@transformInstructionsPatch null - - val match = MethodCall.entries.firstOrNull { search -> - MethodUtil.methodSignaturesMatch(reference, search.reference) - } ?: return@transformInstructionsPatch null - - val replacement = when (match) { - MethodCall.NetworkCountryIso -> networkCountryIso?.lowercase() - MethodCall.NetworkOperator -> networkOperator?.toString() - MethodCall.NetworkOperatorName -> networkOperatorName - MethodCall.SimCountryIso -> simCountryIso?.lowercase() - MethodCall.SimOperator -> simOperator?.toString() - MethodCall.SimOperatorName -> simOperatorName + fun String?.computeSpoof(randomizer: () -> String): String? { + if (this.isNullOrBlank()) return null + if (this.equals("random", ignoreCase = true)) return randomizer() + return this } - replacement?.let { instructionIndex to it } - }, - transform = ::transformMethodCall, - ), + + // Calculate the Luhn checksum (mod 10) to generate a valid 15th digit, standard for IMEI numbers. + // Structure of an IMEI is as follows: + // TAC (Type Allocation Code): First 8 digits (e.g., "86" + 6 digits) + // SNR (Serial Number): Next 6 digits + // CD (Check Digit): The 15th digit + val computedImei = imei.computeSpoof { + val prefix = "86" + generateRandomNumeric(12) + + val sum = prefix.mapIndexed { i, c -> + var d = c.digitToInt() + // Double every second digit (index 1, 3, 5...). + if (i % 2 != 0) { + d *= 2 + // If result is two digits (e.g. 14), sum them (1+4=5). + // This is mathematically equivalent to d - 9. + if (d > 9) d -= 9 + } + d + }.sum() + // Append the calculated check digit to the 14-digit prefix. + prefix + ((10 - (sum % 10)) % 10) + } + + val computedMeid = meid.computeSpoof { (1..14).map { "0123456789ABCDEF".random() }.joinToString("") }?.uppercase() + val computedImsi = imsi.computeSpoof { generateRandomNumeric(15) } + val computedIccid = iccid.computeSpoof { "89" + generateRandomNumeric(17) } + val computedPhone = phone.computeSpoof { "+" + generateRandomNumeric(11) } + + forEachInstructionAsSequence( + match = { _, _, instruction, instructionIndex -> + if (instruction !is ReferenceInstruction) return@forEachInstructionAsSequence null + + val reference = instruction.reference as? MethodReference ?: return@forEachInstructionAsSequence null + + val match = MethodCall.entries.firstOrNull { search -> + MethodUtil.methodSignaturesMatch(reference, search.reference) + } ?: return@forEachInstructionAsSequence null + + val replacement = when (match) { + MethodCall.NetworkCountryIso -> networkCountryIso?.lowercase() + MethodCall.NetworkOperator -> networkOperator?.toString() + MethodCall.NetworkOperatorName -> networkOperatorName + MethodCall.SimCountryIso -> simCountryIso?.lowercase() + MethodCall.SimOperator -> simOperator?.toString() + MethodCall.SimOperatorName -> simOperatorName + MethodCall.Imei, MethodCall.ImeiWithSlot, MethodCall.DeviceId, MethodCall.DeviceIdWithSlot -> computedImei + MethodCall.Meid, MethodCall.MeidWithSlot -> computedMeid + MethodCall.SubscriberId, MethodCall.SubscriberIdWithSlot -> computedImsi + MethodCall.SimSerialNumber, MethodCall.SimSerialNumberWithSlot -> computedIccid + MethodCall.Line1Number, MethodCall.Line1NumberWithSlot -> computedPhone + } + replacement?.let { instructionIndex to it } + }, + transform = ::transformMethodCall + ) + } + }, ) } -private fun transformMethodCall( - mutableMethod: MutableMethod, - entry: Pair, -) { - val (instructionIndex, methodCallValue) = entry +private fun transformMethodCall(mutableMethod: MutableMethod, entry: Pair) { + val (index, value) = entry + val nextInstr = mutableMethod.getInstruction(index + 1) - // Get the register which would have contained the return value - val register = mutableMethod.getInstruction(instructionIndex + 1).registerA + if (nextInstr.opcode.name != "move-result-object") { + mutableMethod.replaceInstruction(index, "nop") + return + } - // Replace the move-result instruction with our fake value - mutableMethod.replaceInstruction( - instructionIndex + 1, - "const-string v$register, \"$methodCallValue\"", - ) + val register = (nextInstr as OneRegisterInstruction).registerA + mutableMethod.replaceInstruction(index, "const-string v$register, \"$value\"") + mutableMethod.replaceInstruction(index + 1, "nop") } private enum class MethodCall( @@ -154,4 +229,100 @@ private enum class MethodCall( "Ljava/lang/String;", ), ), + Imei( + ImmutableMethodReference( + "Landroid/telephony/TelephonyManager;", + "getImei", + emptyList(), + "Ljava/lang/String;" + ), + ), + ImeiWithSlot( + ImmutableMethodReference( + "Landroid/telephony/TelephonyManager;", + "getImei", + listOf("I"), + "Ljava/lang/String;" + ), + ), + DeviceId( + ImmutableMethodReference( + "Landroid/telephony/TelephonyManager;", + "getDeviceId", + emptyList(), + "Ljava/lang/String;" + ), + ), + DeviceIdWithSlot( + ImmutableMethodReference( + "Landroid/telephony/TelephonyManager;", + "getDeviceId", + listOf("I"), + "Ljava/lang/String;" + ), + ), + Meid( + ImmutableMethodReference( + "Landroid/telephony/TelephonyManager;", + "getMeid", + emptyList(), + "Ljava/lang/String;" + ), + ), + MeidWithSlot( + ImmutableMethodReference( + "Landroid/telephony/TelephonyManager;", + "getMeid", + listOf("I"), + "Ljava/lang/String;" + ), + ), + SubscriberId( + ImmutableMethodReference( + "Landroid/telephony/TelephonyManager;", + "getSubscriberId", + emptyList(), + "Ljava/lang/String;" + ) + ), + SubscriberIdWithSlot( + ImmutableMethodReference( + "Landroid/telephony/TelephonyManager;", + "getSubscriberId", + listOf("I"), + "Ljava/lang/String;" + ) + ), + SimSerialNumber( + ImmutableMethodReference( + "Landroid/telephony/TelephonyManager;", + "getSimSerialNumber", + emptyList(), + "Ljava/lang/String;" + ) + ), + SimSerialNumberWithSlot( + ImmutableMethodReference( + "Landroid/telephony/TelephonyManager;", + "getSimSerialNumber", + listOf("I"), + "Ljava/lang/String;" + ) + ), + Line1Number( + ImmutableMethodReference( + "Landroid/telephony/TelephonyManager;", + "getLine1Number", + emptyList(), + "Ljava/lang/String;" + ) + ), + Line1NumberWithSlot( + ImmutableMethodReference( + "Landroid/telephony/TelephonyManager;", + "getLine1Number", + listOf("I"), + "Ljava/lang/String;" + ) + ) } diff --git a/patches/src/main/kotlin/app/revanced/patches/all/misc/connectivity/wifi/spoof/SpoofWifiPatch.kt b/patches/src/main/kotlin/app/revanced/patches/all/misc/connectivity/wifi/spoof/SpoofWifiPatch.kt index 1666a27a8d..e00f37feaf 100644 --- a/patches/src/main/kotlin/app/revanced/patches/all/misc/connectivity/wifi/spoof/SpoofWifiPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/all/misc/connectivity/wifi/spoof/SpoofWifiPatch.kt @@ -3,7 +3,7 @@ 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 +import app.revanced.util.forEachInstructionAsSequence private const val EXTENSION_CLASS_DESCRIPTOR_PREFIX = "Lapp/revanced/extension/all/misc/connectivity/wifi/spoof/SpoofWifiPatch" @@ -19,29 +19,32 @@ val spoofWiFiConnectionPatch = bytecodePatch( extendWith("extensions/all/misc/connectivity/wifi/spoof/spoof-wifi.rve") dependsOn( - transformInstructionsPatch( - filterMap = { classDef, _, instruction, instructionIndex -> - filterMapInstruction35c( - EXTENSION_CLASS_DESCRIPTOR_PREFIX, - classDef, - instruction, - instructionIndex, - ) - }, - transform = { method, entry -> - val (methodType, instruction, instructionIndex) = entry - methodType.replaceInvokeVirtualWithExtension( - EXTENSION_CLASS_DESCRIPTOR, - method, - instruction, - instructionIndex, - ) - }, - ), + bytecodePatch { + apply { + forEachInstructionAsSequence( + match = { classDef, _, instruction, instructionIndex -> + filterMapInstruction35c( + 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 +// Information about method calls we want to replace. @Suppress("unused") private enum class MethodCall( override val definedClassName: String, @@ -89,13 +92,13 @@ private enum class MethodCall( "Landroid/net/NetworkInfo;", "getState", arrayOf(), - "Landroid/net/NetworkInfo\$State;", + $$"Landroid/net/NetworkInfo$State;", ), GetDetailedState( "Landroid/net/NetworkInfo;", "getDetailedState", arrayOf(), - "Landroid/net/NetworkInfo\$DetailedState;", + $$"Landroid/net/NetworkInfo$DetailedState;", ), IsActiveNetworkMetered( "Landroid/net/ConnectivityManager;", @@ -132,7 +135,7 @@ private enum class MethodCall( "registerBestMatchingNetworkCallback", arrayOf( "Landroid/net/NetworkRequest;", - "Landroid/net/ConnectivityManager\$NetworkCallback;", + $$"Landroid/net/ConnectivityManager$NetworkCallback;", "Landroid/os/Handler;", ), "V", @@ -140,19 +143,19 @@ private enum class MethodCall( RegisterDefaultNetworkCallback1( "Landroid/net/ConnectivityManager;", "registerDefaultNetworkCallback", - arrayOf("Landroid/net/ConnectivityManager\$NetworkCallback;"), + arrayOf($$"Landroid/net/ConnectivityManager$NetworkCallback;"), "V", ), RegisterDefaultNetworkCallback2( "Landroid/net/ConnectivityManager;", "registerDefaultNetworkCallback", - arrayOf("Landroid/net/ConnectivityManager\$NetworkCallback;", "Landroid/os/Handler;"), + arrayOf($$"Landroid/net/ConnectivityManager$NetworkCallback;", "Landroid/os/Handler;"), "V", ), RegisterNetworkCallback1( "Landroid/net/ConnectivityManager;", "registerNetworkCallback", - arrayOf("Landroid/net/NetworkRequest;", "Landroid/net/ConnectivityManager\$NetworkCallback;"), + arrayOf("Landroid/net/NetworkRequest;", $$"Landroid/net/ConnectivityManager$NetworkCallback;"), "V", ), RegisterNetworkCallback2( @@ -166,7 +169,7 @@ private enum class MethodCall( "registerNetworkCallback", arrayOf( "Landroid/net/NetworkRequest;", - "Landroid/net/ConnectivityManager\$NetworkCallback;", + $$"Landroid/net/ConnectivityManager$NetworkCallback;", "Landroid/os/Handler;", ), "V", @@ -174,13 +177,13 @@ private enum class MethodCall( RequestNetwork1( "Landroid/net/ConnectivityManager;", "requestNetwork", - arrayOf("Landroid/net/NetworkRequest;", "Landroid/net/ConnectivityManager\$NetworkCallback;"), + arrayOf("Landroid/net/NetworkRequest;", $$"Landroid/net/ConnectivityManager$NetworkCallback;"), "V", ), RequestNetwork2( "Landroid/net/ConnectivityManager;", "requestNetwork", - arrayOf("Landroid/net/NetworkRequest;", "Landroid/net/ConnectivityManager\$NetworkCallback;", "I"), + arrayOf("Landroid/net/NetworkRequest;", $$"Landroid/net/ConnectivityManager$NetworkCallback;", "I"), "V", ), RequestNetwork3( @@ -188,7 +191,7 @@ private enum class MethodCall( "requestNetwork", arrayOf( "Landroid/net/NetworkRequest;", - "Landroid/net/ConnectivityManager\$NetworkCallback;", + $$"Landroid/net/ConnectivityManager$NetworkCallback;", "Landroid/os/Handler;", ), "V", @@ -204,7 +207,7 @@ private enum class MethodCall( "requestNetwork", arrayOf( "Landroid/net/NetworkRequest;", - "Landroid/net/ConnectivityManager\$NetworkCallback;", + $$"Landroid/net/ConnectivityManager$NetworkCallback;", "Landroid/os/Handler;", "I", ), @@ -213,7 +216,7 @@ private enum class MethodCall( UnregisterNetworkCallback1( "Landroid/net/ConnectivityManager;", "unregisterNetworkCallback", - arrayOf("Landroid/net/ConnectivityManager\$NetworkCallback;"), + arrayOf($$"Landroid/net/ConnectivityManager$NetworkCallback;"), "V", ), UnregisterNetworkCallback2( diff --git a/patches/src/main/kotlin/app/revanced/patches/all/misc/playintegrity/DisablePlayIntegrity.kt b/patches/src/main/kotlin/app/revanced/patches/all/misc/play/DisablePlayIntegrity.kt similarity index 95% rename from patches/src/main/kotlin/app/revanced/patches/all/misc/playintegrity/DisablePlayIntegrity.kt rename to patches/src/main/kotlin/app/revanced/patches/all/misc/play/DisablePlayIntegrity.kt index 12461fc40a..dd5dad79e4 100644 --- a/patches/src/main/kotlin/app/revanced/patches/all/misc/playintegrity/DisablePlayIntegrity.kt +++ b/patches/src/main/kotlin/app/revanced/patches/all/misc/play/DisablePlayIntegrity.kt @@ -1,4 +1,4 @@ -package app.revanced.patches.all.misc.playintegrity +package app.revanced.patches.all.misc.play import app.revanced.patcher.extensions.replaceInstruction import app.revanced.patcher.patch.bytecodePatch @@ -9,7 +9,7 @@ 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 -private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/playintegrity/DisablePlayIntegrityPatch;" +private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/play/DisablePlayIntegrityPatch;" private val CONTEXT_BIND_SERVICE_METHOD_REFERENCE = ImmutableMethodReference( "Landroid/content/Context;", diff --git a/patches/src/main/kotlin/app/revanced/patches/all/misc/play/SpoofPlayAgeSignals.kt b/patches/src/main/kotlin/app/revanced/patches/all/misc/play/SpoofPlayAgeSignals.kt new file mode 100644 index 0000000000..ccb41b81b4 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/all/misc/play/SpoofPlayAgeSignals.kt @@ -0,0 +1,138 @@ +package app.revanced.patches.all.misc.play + +import app.revanced.patcher.extensions.addInstructions +import app.revanced.patcher.extensions.getInstruction +import app.revanced.patcher.extensions.removeInstructions +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patcher.patch.intOption +import app.revanced.patcher.patch.option +import app.revanced.util.forEachInstructionAsSequence +import app.revanced.util.getReference +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 + +@Suppress("unused") +val spoofPlayAgeSignalsPatch = bytecodePatch( + name = "Spoof Play Age Signals", + description = "Spoofs Google Play data about the user's age and verification status.", + use = false, +) { + val lowerAgeBound by intOption( + name = "Lower age bound", + description = "A positive integer.", + default = 18, + validator = { it == null || it > 0 }, + ) + + val upperAgeBound by intOption( + name = "Upper age bound", + description = "A positive integer. Must be greater than the lower age bound.", + default = Int.MAX_VALUE, + validator = { it == null || it > lowerAgeBound!! }, + ) + + val userStatus by intOption( + name = "User status", + description = "An integer representing the user status.", + default = UserStatus.VERIFIED.value, + values = UserStatus.entries.associate { it.name to it.value }, + ) + + apply { + forEachInstructionAsSequence(match = { classDef, _, instruction, instructionIndex -> + // Avoid patching the library itself. + if (classDef.type.startsWith("Lcom/google/android/play/agesignals/")) return@forEachInstructionAsSequence null + + // Keep method calls only. + val reference = instruction.getReference() + ?: return@forEachInstructionAsSequence null + + val match = MethodCall.entries.firstOrNull { + reference == it.reference + } ?: return@forEachInstructionAsSequence null + + val replacement = when (match) { + MethodCall.AgeLower -> lowerAgeBound!! + MethodCall.AgeUpper -> upperAgeBound!! + MethodCall.UserStatus -> userStatus!! + } + + replacement.let { instructionIndex to it } + }, transform = { method, entry -> + val (instructionIndex, replacement) = entry + + // Get the register which would have contained the return value. + val register = method.getInstruction(instructionIndex + 1).registerA + + // Replace the call instructions with the spoofed value. + method.removeInstructions(instructionIndex, 2) + method.addInstructions( + instructionIndex, + """ + const v$register, $replacement + invoke-static { v$register }, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer; + move-result-object v$register + """.trimIndent(), + ) + }) + } +} + +/** + * See [AgeSignalsResult](https://developer.android.com/google/play/age-signals/reference/com/google/android/play/agesignals/AgeSignalsResult). + */ +private enum class MethodCall( + val reference: MethodReference, +) { + AgeLower( + ImmutableMethodReference( + "Lcom/google/android/play/agesignals/AgeSignalsResult;", + "ageLower", + emptyList(), + "Ljava/lang/Integer;", + ), + ), + AgeUpper( + ImmutableMethodReference( + "Lcom/google/android/play/agesignals/AgeSignalsResult;", + "ageUpper", + emptyList(), + "Ljava/lang/Integer;", + ), + ), + UserStatus( + ImmutableMethodReference( + "Lcom/google/android/play/agesignals/AgeSignalsResult;", + "userStatus", + emptyList(), + "Ljava/lang/Integer;", + ), + ), +} + +/** + * All possible user verification statuses. + * + * See [AgeSignalsVerificationStatus](https://developer.android.com/google/play/age-signals/reference/com/google/android/play/agesignals/model/AgeSignalsVerificationStatus). + */ +private enum class UserStatus(val value: Int) { + /** The user provided their age, but it hasn't been verified yet. */ + DECLARED(5), + + /** The user is 18+. */ + VERIFIED(0), + + /** The user's guardian has set the age for him. */ + SUPERVISED(1), + + /** The user's guardian hasn't approved the significant changes yet. */ + SUPERVISED_APPROVAL_PENDING(2), + + /** The user's guardian has denied approval for one or more pending significant changes. */ + SUPERVISED_APPROVAL_DENIED(3), + + /** The user is not verified or supervised. */ + UNKNOWN(4), +} diff --git a/patches/src/main/kotlin/app/revanced/patches/all/misc/spoof/SpoofKeystoreSecurityLevelPatch.kt b/patches/src/main/kotlin/app/revanced/patches/all/misc/spoof/SpoofKeystoreSecurityLevelPatch.kt new file mode 100644 index 0000000000..b45cfb4e0d --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/all/misc/spoof/SpoofKeystoreSecurityLevelPatch.kt @@ -0,0 +1,28 @@ +package app.revanced.patches.all.misc.spoof + +import app.revanced.patcher.extensions.replaceInstructions +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.util.forEachInstructionAsSequence + +@Suppress("unused") +val spoofKeystoreSecurityLevelPatch = bytecodePatch( + name = "Spoof keystore security level", + description = "Forces apps to see Keymaster and Attestation security levels as 'StrongBox' (Level 2).", + use = false +) { + apply { + forEachInstructionAsSequence( + match = { _, method, _, _ -> + // Match methods by comparing the current method to a reference criteria. + val name = method.name.lowercase() + if (name.contains("securitylevel") && method.returnType == "I") method else null + }, + transform = { mutableMethod, _ -> + // Ensure the method has an implementation before replacing. + if (mutableMethod.implementation?.instructions?.iterator()?.hasNext() == true) { + mutableMethod.replaceInstructions(0, "const/4 v0, 0x2\nreturn v0") + } + } + ) + } +} \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/all/misc/spoof/SpoofRootOfTrustPatch.kt b/patches/src/main/kotlin/app/revanced/patches/all/misc/spoof/SpoofRootOfTrustPatch.kt new file mode 100644 index 0000000000..6177b7e317 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/all/misc/spoof/SpoofRootOfTrustPatch.kt @@ -0,0 +1,70 @@ +package app.revanced.patches.all.misc.spoof + +import app.revanced.patcher.extensions.replaceInstructions +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.util.forEachInstructionAsSequence +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 spoofRootOfTrustPatch = bytecodePatch( + name = "Spoof root of trust", + description = "Spoofs device integrity states (Locked Bootloader, Verified OS) for apps that perform local certificate attestation.", + use = false +) { + apply { + forEachInstructionAsSequence( + match = { _, method, _, _ -> + MethodCall.entries.firstOrNull { MethodUtil.methodSignaturesMatch(method, it.reference) } + }, + transform = { mutableMethod, methodCall -> + if (mutableMethod.implementation?.instructions?.iterator()?.hasNext() == true) { + mutableMethod.replaceInstructions(0, methodCall.replacementInstructions) + } + } + ) + } +} + +private enum class MethodCall( + val reference: MethodReference, + val replacementInstructions: String, +) { + IsDeviceLockedRootOfTrust( + ImmutableMethodReference( + "LRootOfTrust;", + "isDeviceLocked", + emptyList(), + "Z" + ), + "const/4 v0, 0x1\nreturn v0", + ), + GetVerifiedBootStateRootOfTrust( + ImmutableMethodReference( + "LRootOfTrust;", + "getVerifiedBootState", + emptyList(), + "I" + ), + "const/4 v0, 0x0\nreturn v0", + ), + IsDeviceLockedAttestation( + ImmutableMethodReference( + "LAttestation;", + "isDeviceLocked", + emptyList(), + "Z" + ), + "const/4 v0, 0x1\nreturn v0", + ), + GetVerifiedBootStateAttestation( + ImmutableMethodReference( + "LAttestation;", + "getVerifiedBootState", + emptyList(), + "I" + ), + "const/4 v0, 0x0\nreturn v0", + ), +} \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/instagram/story/locationsticker/EnableLocationStickerRedesignPatch.kt b/patches/src/main/kotlin/app/revanced/patches/instagram/story/locationsticker/EnableLocationStickerRedesignPatch.kt new file mode 100644 index 0000000000..443d60d155 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/instagram/story/locationsticker/EnableLocationStickerRedesignPatch.kt @@ -0,0 +1,20 @@ +package app.revanced.patches.instagram.story.locationsticker + +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.util.returnEarly + +@Suppress("unused") +val enableLocationStickerRedesignPatch = bytecodePatch( + name = "Enable location sticker redesign", + description = "Unlocks the redesigned location sticker with additional style options.", + use = false, +) { + compatibleWith("com.instagram.android") + + apply { + // The gate method reads a MobileConfig boolean flag and returns it directly. + // Returning early with true bypasses the flag check entirely, + // enabling the redesigned sticker styles regardless of server configuration. + locationStickerRedesignGateMethodMatch.method.returnEarly(true) + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/instagram/story/locationsticker/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/instagram/story/locationsticker/Fingerprints.kt new file mode 100644 index 0000000000..0972d692c9 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/instagram/story/locationsticker/Fingerprints.kt @@ -0,0 +1,16 @@ +package app.revanced.patches.instagram.story.locationsticker + +import app.revanced.patcher.composingFirstMethod +import app.revanced.patcher.instructions +import app.revanced.patcher.invoke +import app.revanced.patcher.patch.BytecodePatchContext + +// MobileConfig boolean key that gates the redesigned location sticker styles. +// The method containing this constant reads the flag and returns it directly, +// making it the sole control point for the feature. The key is stable across +// app updates as MobileConfig keys are server-assigned constants. +private const val LOCATION_STICKER_REDESIGN_CONFIG_KEY = 0x8105a100041e0dL + +internal val BytecodePatchContext.locationStickerRedesignGateMethodMatch by composingFirstMethod { + instructions(LOCATION_STICKER_REDESIGN_CONFIG_KEY()) +} diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/layout/branding/BaseCustomBrandingPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/layout/branding/BaseCustomBrandingPatch.kt index 35baad31cf..9a583e2c7a 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/layout/branding/BaseCustomBrandingPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/layout/branding/BaseCustomBrandingPatch.kt @@ -3,7 +3,6 @@ package app.revanced.patches.shared.layout.branding import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableMethod import app.revanced.patcher.extensions.addInstruction import app.revanced.patcher.extensions.getInstruction -import app.revanced.patcher.firstImmutableClassDef import app.revanced.patcher.patch.* import app.revanced.patches.all.misc.packagename.setOrGetFallbackPackageName import app.revanced.patches.all.misc.resources.addResources @@ -126,14 +125,14 @@ internal fun baseCustomBrandingPatch( val getBuilderIndex = if (isYouTubeMusic) { // YT Music the field is not a plain object type. indexOfFirstInstructionOrThrow { - getReference()?.type == "Landroid/app/Notification\$Builder;" + getReference()?.type == $$"Landroid/app/Notification$Builder;" } } else { // Find the field name of the notification builder. Field is an Object type. val builderCastIndex = indexOfFirstInstructionOrThrow { val reference = getReference() opcode == Opcode.CHECK_CAST && - reference?.type == "Landroid/app/Notification\$Builder;" + reference?.type == $$"Landroid/app/Notification$Builder;" } indexOfFirstInstructionReversedOrThrow(builderCastIndex) { getReference()?.type == "Ljava/lang/Object;" @@ -148,11 +147,11 @@ internal fun baseCustomBrandingPatch( ).forEach { index -> addInstructionsAtControlFlowLabel( index, - """ + $$""" move-object/from16 v0, p0 - iget-object v0, v0, $builderFieldName - check-cast v0, Landroid/app/Notification${'$'}Builder; - invoke-static { v0 }, $EXTENSION_CLASS_DESCRIPTOR->setNotificationIcon(Landroid/app/Notification${'$'}Builder;)V + iget-object v0, v0, $$builderFieldName + check-cast v0, Landroid/app/Notification$Builder; + invoke-static { v0 }, $$EXTENSION_CLASS_DESCRIPTOR->setNotificationIcon(Landroid/app/Notification$Builder;)V """, ) } @@ -162,16 +161,37 @@ internal fun baseCustomBrandingPatch( ) afterDependents { + val useCustomName = customName != null + val useCustomIcon = customIcon != null + val isRootInstall = setOrGetFallbackPackageName(originalAppPackageName) == originalAppPackageName + // Can only check if app is root installation by checking if change package name patch is in use. // and can only do that in the afterDependents block here. // The UI preferences cannot be selectively added here, because the settings afterDependents block // may have already run and the settings are already wrote to file. // Instead, show a warning if any patch option was used (A rooted device launcher ignores the manifest changes), // and the non-functional in-app settings are removed on app startup by extension code. - if (customName != null || customIcon != null) { - if (setOrGetFallbackPackageName(originalAppPackageName) == originalAppPackageName) { - Logger.getLogger(this::class.java.name).warning( - "Custom branding does not work with root installation. No changes applied.", + if (isRootInstall && (useCustomName || useCustomIcon)) { + Logger.getLogger(this::class.java.name).warning( + "Custom branding does not work with root installation. No changes applied." + ) + } + + if (!isRootInstall || useCustomName) { + document("AndroidManifest.xml").use { document -> + val application = document.getElementsByTagName("application").item(0) as Element + application.setAttribute( + "android:label", + if (useCustomName) { + // Use custom name everywhere. + customName + } else { + // The YT application name can appear in some places alongside the system + // YouTube app, such as the settings app list and in the "open with" file picker. + // Because the YouTube app cannot be completely uninstalled and only disabled, + // use a custom name for this situation to disambiguate which app is which. + "@string/revanced_custom_branding_name_entry_2" + } ) } } @@ -312,16 +332,19 @@ internal fun baseCustomBrandingPatch( activityAliasNameWithIntents, ).childNodes - // The YT application name can appear in some places alongside the system - // YouTube app, such as the settings app list and in the "open with" file picker. - // Because the YouTube app cannot be completely uninstalled and only disabled, - // use a custom name for this situation to disambiguate which app is which. - application.setAttribute( - "android:label", - "@string/revanced_custom_branding_name_entry_2", - ) + // If user provides a custom icon, then change the application icon ('static' icon) + // which shows as the push notification for some devices, in the app settings, + // and as the icon for the apk before installing. + // This icon cannot be dynamically selected and this change must only be done if the + // user provides an icon otherwise there is no way to restore the original YouTube icon. + if (useCustomIcon) { + application.setAttribute( + "android:icon", + "@mipmap/revanced_launcher_custom" + ) + } - val enabledNameIndex = if (useCustomName) numberOfPresetAppNames else 1 // 1 indexing. + val enabledNameIndex = if (useCustomName) numberOfPresetAppNames else 2 // 1 indexing. val enabledIconIndex = if (useCustomIcon) iconStyleNames.size else 0 // 0 indexing. for (appNameIndex in 1..numberOfPresetAppNames) { @@ -336,7 +359,7 @@ internal fun baseCustomBrandingPatch( iconMipmapName = originalLauncherIconName, appNameIndex = appNameIndex, useCustomName = useCustomNameLabel, - enabled = (appNameIndex == 1), + enabled = false, intentFilters, ), )