From de97562c5ddc8ec707761c1e04e74c4e18f9c158 Mon Sep 17 00:00:00 2001 From: rospino74 <34315725+rospino74@users.noreply.github.com> Date: Wed, 22 Oct 2025 08:09:34 +0200 Subject: [PATCH] feat(Samsung Radio): Add `Disable device checks` patch (#6145) --- extensions/samsung/radio/build.gradle.kts | 4 ++ .../radio/src/main/AndroidManifest.xml | 1 + .../radio/misc/fix/crash/FixCrashPatch.java | 24 ++++++++ .../device/BypassDeviceChecksPatch.java | 19 ++++++ .../samsung/radio/stub/build.gradle.kts | 17 ++++++ .../radio/stub/src/main/AndroidManifest.xml | 1 + .../java/android/os/SemSystemProperties.java | 7 +++ patches/api/patches.api | 8 +++ .../fix/crash/AddManifestPermissionsPatch.kt | 34 +++++++++++ .../radio/misc/fix/crash/Fingerprints.kt | 18 ++++++ .../radio/misc/fix/crash/FixCrashPatch.kt | 42 +++++++++++++ .../device/BypassDeviceChecksPatch.kt | 55 +++++++++++++++++ .../radio/restrictions/device/Fingerprints.kt | 61 +++++++++++++++++++ 13 files changed, 291 insertions(+) create mode 100644 extensions/samsung/radio/build.gradle.kts create mode 100644 extensions/samsung/radio/src/main/AndroidManifest.xml create mode 100644 extensions/samsung/radio/src/main/java/app/revanced/extension/samsung/radio/misc/fix/crash/FixCrashPatch.java create mode 100644 extensions/samsung/radio/src/main/java/app/revanced/extension/samsung/radio/restrictions/device/BypassDeviceChecksPatch.java create mode 100644 extensions/samsung/radio/stub/build.gradle.kts create mode 100644 extensions/samsung/radio/stub/src/main/AndroidManifest.xml create mode 100644 extensions/samsung/radio/stub/src/main/java/android/os/SemSystemProperties.java create mode 100644 patches/src/main/kotlin/app/revanced/patches/samsung/radio/misc/fix/crash/AddManifestPermissionsPatch.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/samsung/radio/misc/fix/crash/Fingerprints.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/samsung/radio/misc/fix/crash/FixCrashPatch.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/samsung/radio/restrictions/device/BypassDeviceChecksPatch.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/samsung/radio/restrictions/device/Fingerprints.kt diff --git a/extensions/samsung/radio/build.gradle.kts b/extensions/samsung/radio/build.gradle.kts new file mode 100644 index 000000000..0eadeef26 --- /dev/null +++ b/extensions/samsung/radio/build.gradle.kts @@ -0,0 +1,4 @@ +dependencies { + compileOnly(project(":extensions:shared:library")) + compileOnly(project(":extensions:samsung:radio:stub")) +} diff --git a/extensions/samsung/radio/src/main/AndroidManifest.xml b/extensions/samsung/radio/src/main/AndroidManifest.xml new file mode 100644 index 000000000..9b65eb06c --- /dev/null +++ b/extensions/samsung/radio/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/extensions/samsung/radio/src/main/java/app/revanced/extension/samsung/radio/misc/fix/crash/FixCrashPatch.java b/extensions/samsung/radio/src/main/java/app/revanced/extension/samsung/radio/misc/fix/crash/FixCrashPatch.java new file mode 100644 index 000000000..72c5addc4 --- /dev/null +++ b/extensions/samsung/radio/src/main/java/app/revanced/extension/samsung/radio/misc/fix/crash/FixCrashPatch.java @@ -0,0 +1,24 @@ +package app.revanced.extension.samsung.radio.misc.fix.crash; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@SuppressWarnings("unused") +public final class FixCrashPatch { + /** + * Injection point. + *

+ * Add the required permissions to the request list to avoid crashes on API 34+. + **/ + public static final String[] fixPermissionRequestList(String[] perms) { + List permsList = new ArrayList<>(Arrays.asList(perms)); + if (permsList.contains("android.permission.POST_NOTIFICATIONS")) { + permsList.addAll(Arrays.asList("android.permission.RECORD_AUDIO", "android.permission.READ_PHONE_STATE", "android.permission.FOREGROUND_SERVICE_MICROPHONE")); + } + if (permsList.contains("android.permission.RECORD_AUDIO")) { + permsList.add("android.permission.FOREGROUND_SERVICE_MICROPHONE"); + } + return permsList.toArray(new String[0]); + } +} diff --git a/extensions/samsung/radio/src/main/java/app/revanced/extension/samsung/radio/restrictions/device/BypassDeviceChecksPatch.java b/extensions/samsung/radio/src/main/java/app/revanced/extension/samsung/radio/restrictions/device/BypassDeviceChecksPatch.java new file mode 100644 index 000000000..19b6c3e82 --- /dev/null +++ b/extensions/samsung/radio/src/main/java/app/revanced/extension/samsung/radio/restrictions/device/BypassDeviceChecksPatch.java @@ -0,0 +1,19 @@ +package app.revanced.extension.samsung.radio.restrictions.device; + +import android.os.SemSystemProperties; + +import java.util.Arrays; + +@SuppressWarnings("unused") +public final class BypassDeviceChecksPatch { + + /** + * Injection point. + *

+ * Check if the device has the required hardware + **/ + public static final boolean checkIfDeviceIsIncompatible(String[] deviceList) { + String currentDevice = SemSystemProperties.getSalesCode(); + return Arrays.asList(deviceList).contains(currentDevice); + } +} diff --git a/extensions/samsung/radio/stub/build.gradle.kts b/extensions/samsung/radio/stub/build.gradle.kts new file mode 100644 index 000000000..b4bee8809 --- /dev/null +++ b/extensions/samsung/radio/stub/build.gradle.kts @@ -0,0 +1,17 @@ +plugins { + alias(libs.plugins.android.library) +} + +android { + namespace = "app.revanced.extension" + compileSdk = 34 + + defaultConfig { + minSdk = 24 + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } +} diff --git a/extensions/samsung/radio/stub/src/main/AndroidManifest.xml b/extensions/samsung/radio/stub/src/main/AndroidManifest.xml new file mode 100644 index 000000000..15e7c2ae6 --- /dev/null +++ b/extensions/samsung/radio/stub/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/extensions/samsung/radio/stub/src/main/java/android/os/SemSystemProperties.java b/extensions/samsung/radio/stub/src/main/java/android/os/SemSystemProperties.java new file mode 100644 index 000000000..33a4b4400 --- /dev/null +++ b/extensions/samsung/radio/stub/src/main/java/android/os/SemSystemProperties.java @@ -0,0 +1,7 @@ +package android.os; + +public class SemSystemProperties { + public static String getSalesCode() { + throw new UnsupportedOperationException("Stub"); + } +} \ No newline at end of file diff --git a/patches/api/patches.api b/patches/api/patches.api index b15bb5bd3..20f74169a 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -764,6 +764,14 @@ public final class app/revanced/patches/reddit/misc/tracking/url/SanitizeUrlQuer public static final fun getSanitizeUrlQueryPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } +public final class app/revanced/patches/samsung/radio/misc/fix/crash/FixCrashPatchKt { + public static final fun getFixCrashPatch ()Lapp/revanced/patcher/patch/BytecodePatch; +} + +public final class app/revanced/patches/samsung/radio/restrictions/device/BypassDeviceChecksPatchKt { + public static final fun getBypassDeviceChecksPatch ()Lapp/revanced/patcher/patch/BytecodePatch; +} + public final class app/revanced/patches/serviceportalbund/detection/root/RootDetectionPatchKt { public static final fun getRootDetectionPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } diff --git a/patches/src/main/kotlin/app/revanced/patches/samsung/radio/misc/fix/crash/AddManifestPermissionsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/samsung/radio/misc/fix/crash/AddManifestPermissionsPatch.kt new file mode 100644 index 000000000..35641f2e7 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/samsung/radio/misc/fix/crash/AddManifestPermissionsPatch.kt @@ -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) + } + } + } + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/samsung/radio/misc/fix/crash/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/samsung/radio/misc/fix/crash/Fingerprints.kt new file mode 100644 index 000000000..f842a45cc --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/samsung/radio/misc/fix/crash/Fingerprints.kt @@ -0,0 +1,18 @@ +@file:Suppress("unused") + +package app.revanced.patches.samsung.radio.misc.fix.crash + +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 permissionRequestListFingerprint = fingerprint { + strings( + "android.permission.POST_NOTIFICATIONS", + "android.permission.READ_MEDIA_AUDIO", + "android.permission.RECORD_AUDIO" + ) + custom { method, _ -> method.name == "" } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/samsung/radio/misc/fix/crash/FixCrashPatch.kt b/patches/src/main/kotlin/app/revanced/patches/samsung/radio/misc/fix/crash/FixCrashPatch.kt new file mode 100644 index 000000000..a076ca830 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/samsung/radio/misc/fix/crash/FixCrashPatch.kt @@ -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(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 + """ + ) + } + } + } +} \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/samsung/radio/restrictions/device/BypassDeviceChecksPatch.kt b/patches/src/main/kotlin/app/revanced/patches/samsung/radio/restrictions/device/BypassDeviceChecksPatch.kt new file mode 100644 index 000000000..68ef9a801 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/samsung/radio/restrictions/device/BypassDeviceChecksPatch.kt @@ -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()?.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 + } + + // Find an available register for our use + 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) + } + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/samsung/radio/restrictions/device/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/samsung/radio/restrictions/device/Fingerprints.kt new file mode 100644 index 000000000..476823591 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/samsung/radio/restrictions/device/Fingerprints.kt @@ -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 = 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() + + // Scan method instructions for calls to our target methods + for (instr in impl.instructions) { + val ref = instr.getReference() ?: continue + val mc = fromMethodReference(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, + override val returnType: String, +) : IMethodCall { + GetSalesCode( + "Landroid/os/SemSystemProperties;", + "getSalesCode", + arrayOf(), + "Ljava/lang/String;", + ), + GetCountryIso( + "Landroid/os/SemSystemProperties;", + "getCountryIso", + arrayOf(), + "Ljava/lang/String;", + ), +} \ No newline at end of file