Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters

# Conflicts:
#	extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/theme/SeekbarColorPatch.java
#	patches/src/main/kotlin/app/revanced/patches/youtube/layout/seekbar/SeekbarColorPatch.kt
This commit is contained in:
LisoUseInAIKyrios 2025-10-22 11:49:05 +04:00
commit f00c0e0d89
20 changed files with 303 additions and 257 deletions

View file

@ -0,0 +1,34 @@
package app.revanced.patches.samsung.radio.misc.fix.crash
import app.revanced.patcher.patch.resourcePatch
import app.revanced.util.asSequence
import org.w3c.dom.Element
@Suppress("unused")
internal val addManifestPermissionsPatch = resourcePatch {
val requiredPermissions = listOf(
"android.permission.READ_PHONE_STATE",
"android.permission.FOREGROUND_SERVICE_MICROPHONE",
"android.permission.RECORD_AUDIO",
)
execute {
document("AndroidManifest.xml").use { document ->
document.getElementsByTagName("manifest").item(0).let { manifestEl ->
// Check which permissions are missing
val existingPermissionNames = document.getElementsByTagName("uses-permission").asSequence()
.mapNotNull { (it as? Element)?.getAttribute("android:name") }.toSet()
val missingPermissions = requiredPermissions.filterNot { it in existingPermissionNames }
// Then add them
for (permission in missingPermissions) {
val element = document.createElement("uses-permission")
element.setAttribute("android:name", permission)
manifestEl.appendChild(element)
}
}
}
}
}

View file

@ -0,0 +1,12 @@
package app.revanced.patches.samsung.radio.misc.fix.crash
import app.revanced.patcher.fingerprint
internal val permissionRequestListFingerprint by fingerprint {
strings(
"android.permission.POST_NOTIFICATIONS",
"android.permission.READ_MEDIA_AUDIO",
"android.permission.RECORD_AUDIO"
)
custom { method, _ -> method.name == "<clinit>" }
}

View file

@ -0,0 +1,42 @@
@file:Suppress("unused")
package app.revanced.patches.samsung.radio.misc.fix.crash
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.samsung.radio.restrictions.device.bypassDeviceChecksPatch
import app.revanced.util.findInstructionIndicesReversedOrThrow
import app.revanced.util.indexOfFirstInstruction
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/samsung/radio/misc/fix/crash/FixCrashPatch;"
val fixCrashPatch = bytecodePatch(
name = "Fix crashes", description = "Prevents the app from crashing because of missing system permissions."
) {
dependsOn(addManifestPermissionsPatch, bypassDeviceChecksPatch)
extendWith("extensions/samsung/radio.rve")
compatibleWith("com.sec.android.app.fm"("12.4.00.7", "12.3.00.13", "12.3.00.11"))
execute {
permissionRequestListFingerprint.method.apply {
findInstructionIndicesReversedOrThrow(Opcode.FILLED_NEW_ARRAY).forEach { filledNewArrayIndex ->
val moveResultIndex = indexOfFirstInstruction(filledNewArrayIndex, Opcode.MOVE_RESULT_OBJECT)
if (moveResultIndex < 0) return@forEach // No move-result-object found after the filled-new-array
// Get the register where the array is saved
val arrayRegister = getInstruction<OneRegisterInstruction>(moveResultIndex).registerA
// Invoke the method from the extension
addInstructions(
moveResultIndex + 1, """
invoke-static { v$arrayRegister }, ${EXTENSION_CLASS_DESCRIPTOR}->fixPermissionRequestList([Ljava/lang/String;)[Ljava/lang/String;
move-result-object v$arrayRegister
"""
)
}
}
}
}

View file

@ -0,0 +1,55 @@
package app.revanced.patches.samsung.radio.restrictions.device
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.removeInstructions
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.findFreeRegister
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.reference.StringReference
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/samsung/radio/restrictions/device/BypassDeviceChecksPatch;"
@Suppress("unused")
val bypassDeviceChecksPatch = bytecodePatch(
name = "Bypass device checks",
description = "Removes firmware and region blacklisting. " +
"This patch will still not allow the app to run on devices that do not have the required hardware.",
) {
extendWith("extensions/samsung/radio.rve")
compatibleWith("com.sec.android.app.fm"("12.4.00.7", "12.3.00.13", "12.3.00.11"))
execute {
// Return false = The device is not blacklisted.
checkDeviceFingerprint.method.apply {
// Find the first string that start with "SM-", that's the list of incompatible devices.
val firstStringIndex = indexOfFirstInstructionOrThrow {
opcode == Opcode.CONST_STRING &&
getReference<StringReference>()?.string?.startsWith("SM-") == true
}
// Find the following filled-new-array (or filled-new-array/range) instruction.
val filledNewArrayIndex = indexOfFirstInstructionOrThrow(firstStringIndex + 1) {
opcode == Opcode.FILLED_NEW_ARRAY || opcode == Opcode.FILLED_NEW_ARRAY_RANGE
}
val resultRegister = findFreeRegister(filledNewArrayIndex + 1)
// Store the array there and invoke the method that we added to the class earlier.
addInstructions(
filledNewArrayIndex + 1,
"""
move-result-object v$resultRegister
invoke-static { v$resultRegister }, $EXTENSION_CLASS_DESCRIPTOR->checkIfDeviceIsIncompatible([Ljava/lang/String;)Z
move-result v$resultRegister
return v$resultRegister
"""
)
// Remove the instructions before our strings.
removeInstructions(0, firstStringIndex)
}
}
}

View file

@ -0,0 +1,61 @@
package app.revanced.patches.samsung.radio.restrictions.device
import app.revanced.patcher.fingerprint
import app.revanced.patches.all.misc.transformation.IMethodCall
import app.revanced.patches.all.misc.transformation.fromMethodReference
import app.revanced.util.getReference
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
internal val checkDeviceFingerprint by fingerprint {
returns("Z")
custom { method, _ ->
/* Check for methods call to:
- Landroid/os/SemSystemProperties;->getSalesCode()Ljava/lang/String;
- Landroid/os/SemSystemProperties;->getCountryIso()Ljava/lang/String;
*/
val impl = method.implementation ?: return@custom false
// Track which target methods we've found
val foundMethods = mutableSetOf<MethodCall>()
// Scan method instructions for calls to our target methods
for (instr in impl.instructions) {
val ref = instr.getReference<MethodReference>() ?: continue
val mc = fromMethodReference<MethodCall>(ref) ?: continue
if (mc == MethodCall.GetSalesCode || mc == MethodCall.GetCountryIso) {
foundMethods.add(mc)
// If we found both methods, return success
if (foundMethods.size == 2) {
return@custom true
}
}
}
// Only match if both methods are present
return@custom false
}
}
// Information about method calls we want to replace
private enum class MethodCall(
override val definedClassName: String,
override val methodName: String,
override val methodParams: Array<String>,
override val returnType: String,
) : IMethodCall {
GetSalesCode(
"Landroid/os/SemSystemProperties;",
"getSalesCode",
arrayOf(),
"Ljava/lang/String;",
),
GetCountryIso(
"Landroid/os/SemSystemProperties;",
"getCountryIso",
arrayOf(),
"Ljava/lang/String;",
)
}

View file

@ -1,14 +1,11 @@
package app.revanced.patches.youtube.layout.seekbar
import app.revanced.patcher.Fingerprint
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.fingerprint
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import app.revanced.patches.shared.layout.theme.lithoColorHookPatch
@ -16,21 +13,15 @@ import app.revanced.patches.shared.layout.theme.lithoColorOverrideHook
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.playservice.is_19_34_or_greater
import app.revanced.patches.youtube.misc.playservice.is_19_46_or_greater
import app.revanced.patches.youtube.misc.playservice.is_19_49_or_greater
import app.revanced.patches.youtube.misc.playservice.is_20_30_or_greater
import app.revanced.patches.youtube.misc.playservice.is_20_34_or_greater
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
import app.revanced.patches.youtube.shared.mainActivityOnCreateFingerprint
import app.revanced.util.copyXmlNode
import app.revanced.util.findElementByAttributeValueOrThrow
import app.revanced.util.findInstructionIndicesReversedOrThrow
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.inputStreamFromBundledResource
import app.revanced.util.insertLiteralOverride
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
@ -38,125 +29,6 @@ import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
import org.w3c.dom.Element
import java.io.ByteArrayInputStream
import kotlin.use
private const val splashSeekbarColorAttributeName = "splash_custom_seekbar_color"
private val seekbarColorResourcePatch = resourcePatch {
dependsOn(
resourceMappingPatch,
versionCheckPatch,
)
execute {
// Modify the resume playback drawable and replace the progress bar with a custom drawable.
document("res/drawable/resume_playback_progressbar_drawable.xml").use { document ->
val layerList = document.getElementsByTagName("layer-list").item(0) as Element
val progressNode = layerList.getElementsByTagName("item").item(1) as Element
if (!progressNode.getAttributeNode("android:id").value.endsWith("progress")) {
throw PatchException("Could not find progress bar")
}
val scaleNode = progressNode.getElementsByTagName("scale").item(0) as Element
val shapeNode = scaleNode.getElementsByTagName("shape").item(0) as Element
val replacementNode = document.createElement(
"app.revanced.extension.youtube.patches.theme.ProgressBarDrawable",
)
scaleNode.replaceChild(replacementNode, shapeNode)
}
// Add attribute and styles for splash screen custom color.
// Using a style is the only way to selectively change just the seekbar fill color.
//
// Because the style colors must be hard coded for all color possibilities,
// instead of allowing 24 bit color the style is restricted to 9-bit (3 bits per color channel)
// and the style color closest to the users custom color is used for the splash screen.
arrayOf(
inputStreamFromBundledResource("seekbar/values", "attrs.xml")!! to "res/values/attrs.xml",
ByteArrayInputStream(create9BitSeekbarColorStyles().toByteArray()) to "res/values/styles.xml"
).forEach { (source, destination) ->
"resources".copyXmlNode(
document(source),
document(destination),
).close()
}
fun setSplashDrawablePathFillColor(xmlFileNames: Iterable<String>, vararg resourceNames: String) {
xmlFileNames.forEach { xmlFileName ->
document(xmlFileName).use { document ->
val childNodes = document.childNodes
resourceNames.forEach { elementId ->
val element = childNodes.findElementByAttributeValueOrThrow(
"android:name",
elementId
)
val attribute = "android:fillColor"
if (!element.hasAttribute(attribute)) {
throw PatchException("Could not find $attribute for $elementId")
}
element.setAttribute(attribute, "?attr/$splashSeekbarColorAttributeName")
}
}
}
}
setSplashDrawablePathFillColor(
listOf(
"res/drawable/\$startup_animation_light__0.xml",
"res/drawable/\$startup_animation_dark__0.xml"
),
"_R_G_L_10_G_D_0_P_0"
)
if (!is_19_46_or_greater) {
// Resources removed in 19.46+
setSplashDrawablePathFillColor(
listOf(
"res/drawable/\$buenos_aires_animation_light__0.xml",
"res/drawable/\$buenos_aires_animation_dark__0.xml"
),
"_R_G_L_8_G_D_0_P_0"
)
}
}
}
/**
* Generate a style xml with all combinations of 9-bit colors.
*/
private fun create9BitSeekbarColorStyles(): String = StringBuilder().apply {
append("<?xml version=\"1.0\" encoding=\"utf-8\"?>")
append("<resources>\n")
for (red in 0..7) {
for (green in 0..7) {
for (blue in 0..7) {
val name = "${red}_${green}_${blue}"
fun roundTo3BitHex(channel8Bits: Int) =
(channel8Bits * 255 / 7).toString(16).padStart(2, '0')
val r = roundTo3BitHex(red)
val g = roundTo3BitHex(green)
val b = roundTo3BitHex(blue)
val color = "#ff$r$g$b"
append(
"""
<style name="splash_seekbar_color_style_$name">
<item name="$splashSeekbarColorAttributeName">$color</item>
</style>
"""
)
}
}
}
append("</resources>")
}.toString()
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/theme/SeekbarColorPatch;"
@ -166,7 +38,6 @@ val seekbarColorPatch = bytecodePatch(
dependsOn(
sharedExtensionPatch,
lithoColorHookPatch,
seekbarColorResourcePatch,
resourceMappingPatch,
versionCheckPatch
)
@ -300,21 +171,6 @@ val seekbarColorPatch = bytecodePatch(
// Hook the splash animation to set the a seekbar color.
mainActivityOnCreateFingerprint.method.apply {
val drawableIndex = indexOfFirstInstructionOrThrow {
val reference = getReference<MethodReference>()
reference?.definingClass == "Landroid/widget/ImageView;"
&& reference.name == "getDrawable"
}
val checkCastIndex = indexOfFirstInstructionOrThrow(drawableIndex, Opcode.CHECK_CAST)
val drawableRegister = getInstruction<OneRegisterInstruction>(checkCastIndex).registerA
addInstruction(
checkCastIndex + 1,
"invoke-static { v$drawableRegister }, $EXTENSION_CLASS_DESCRIPTOR->" +
"setSplashAnimationDrawableTheme(Landroid/graphics/drawable/AnimatedVectorDrawable;)V"
)
// Replace the Lottie animation view setAnimation(int) call.
val setAnimationIntMethodName =
lottieAnimationViewSetAnimationIntFingerprint.originalMethod.name
@ -328,8 +184,7 @@ val seekbarColorPatch = bytecodePatch(
replaceInstruction(
index,
"invoke-static { v${instruction.registerC}, v${instruction.registerD} }, " +
"$EXTENSION_CLASS_DESCRIPTOR->setSplashAnimationLottie" +
"(Lcom/airbnb/lottie/LottieAnimationView;I)V"
"$EXTENSION_CLASS_DESCRIPTOR->setSplashAnimationLottie(Lcom/airbnb/lottie/LottieAnimationView;I)V"
)
}
}

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr format="reference|color" name="splash_custom_seekbar_color"/>
</resources>