diff --git a/api/revanced-patches.api b/api/revanced-patches.api index a32506084..769746f26 100644 --- a/api/revanced-patches.api +++ b/api/revanced-patches.api @@ -862,6 +862,12 @@ public final class app/revanced/patches/serviceportalbund/detection/root/RootDet public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V } +public abstract class app/revanced/patches/shared/misc/checks/BaseCheckEnvironmentPatch : app/revanced/patcher/patch/BytecodePatch { + public fun (Lapp/revanced/patcher/fingerprint/MethodFingerprint;Ljava/util/Set;Lapp/revanced/patches/shared/misc/integrations/BaseIntegrationsPatch;)V + public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V + public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V +} + public final class app/revanced/patches/shared/misc/fix/verticalscroll/VerticalScrollPatch : app/revanced/patcher/patch/BytecodePatch { public static final field INSTANCE Lapp/revanced/patches/shared/misc/fix/verticalscroll/VerticalScrollPatch; public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V @@ -1870,6 +1876,10 @@ public final class app/revanced/patches/youtube/misc/backgroundplayback/Backgrou public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V } +public final class app/revanced/patches/youtube/misc/check/CheckEnvironmentPatch : app/revanced/patches/shared/misc/checks/BaseCheckEnvironmentPatch { + public static final field INSTANCE Lapp/revanced/patches/youtube/misc/check/CheckEnvironmentPatch; +} + public final class app/revanced/patches/youtube/misc/debugging/DebuggingPatch : app/revanced/patcher/patch/ResourcePatch { public static final field INSTANCE Lapp/revanced/patches/youtube/misc/debugging/DebuggingPatch; public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V diff --git a/build.gradle.kts b/build.gradle.kts index a9e0830e2..9ed141678 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -31,6 +31,8 @@ dependencies { implementation(libs.guava) // Used in JsonGenerator. implementation(libs.gson) + // Android API stubs defined here. + compileOnly(project(":stub")) } kotlin { diff --git a/settings.gradle.kts b/settings.gradle.kts index 432b20014..0988585a0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -5,3 +5,5 @@ buildCache { isEnabled = "CI" !in System.getenv() } } + +include(":stub") diff --git a/src/main/kotlin/app/revanced/patches/shared/misc/checks/BaseCheckEnvironmentPatch.kt b/src/main/kotlin/app/revanced/patches/shared/misc/checks/BaseCheckEnvironmentPatch.kt new file mode 100644 index 000000000..0411a0c51 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/shared/misc/checks/BaseCheckEnvironmentPatch.kt @@ -0,0 +1,116 @@ +package app.revanced.patches.shared.misc.checks + +import android.os.Build.* +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableEncodedValue +import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableLongEncodedValue +import app.revanced.patcher.util.proxy.mutableTypes.encodedValue.MutableStringEncodedValue +import app.revanced.patches.all.misc.resources.AddResourcesPatch +import app.revanced.patches.shared.misc.checks.fingerprints.PatchInfoBuildFingerprint +import app.revanced.patches.shared.misc.checks.fingerprints.PatchInfoFingerprint +import app.revanced.patches.shared.misc.integrations.BaseIntegrationsPatch +import app.revanced.util.exception +import app.revanced.util.resultOrThrow +import com.android.tools.smali.dexlib2.immutable.value.ImmutableLongEncodedValue +import com.android.tools.smali.dexlib2.immutable.value.ImmutableStringEncodedValue +import java.nio.charset.StandardCharsets +import java.security.MessageDigest +import kotlin.io.encoding.Base64 +import kotlin.io.encoding.ExperimentalEncodingApi + +abstract class BaseCheckEnvironmentPatch( + private val mainActivityOnCreateFingerprint: MethodFingerprint, + compatiblePackages: Set, + integrationsPatch: BaseIntegrationsPatch, +) : BytecodePatch( + description = "Checks, if the application was patched by, otherwise warns the user.", + compatiblePackages = compatiblePackages, + dependencies = setOf( + AddResourcesPatch::class, + integrationsPatch::class, + ), + fingerprints = setOf( + PatchInfoFingerprint, + PatchInfoBuildFingerprint, + mainActivityOnCreateFingerprint, + ), +) { + override fun execute(context: BytecodeContext) { + AddResourcesPatch(BaseCheckEnvironmentPatch::class) + + setPatchInfo() + invokeCheck() + } + + private fun setPatchInfo() { + PatchInfoFingerprint.setClassFields( + "PATCH_TIME" to System.currentTimeMillis().encoded, + ) + + fun setBuildInfo() { + PatchInfoBuildFingerprint.setClassFields( + "PATCH_BOARD" to BOARD.encodedAndHashed, + "PATCH_BOOTLOADER" to BOOTLOADER.encodedAndHashed, + "PATCH_BRAND" to BRAND.encodedAndHashed, + "PATCH_CPU_ABI" to CPU_ABI.encodedAndHashed, + "PATCH_CPU_ABI2" to CPU_ABI2.encodedAndHashed, + "PATCH_DEVICE" to DEVICE.encodedAndHashed, + "PATCH_DISPLAY" to DISPLAY.encodedAndHashed, + "PATCH_FINGERPRINT" to FINGERPRINT.encodedAndHashed, + "PATCH_HARDWARE" to HARDWARE.encodedAndHashed, + "PATCH_HOST" to HOST.encodedAndHashed, + "PATCH_ID" to ID.encodedAndHashed, + "PATCH_MANUFACTURER" to MANUFACTURER.encodedAndHashed, + "PATCH_MODEL" to MODEL.encodedAndHashed, + "PATCH_ODM_SKU" to ODM_SKU.encodedAndHashed, + "PATCH_PRODUCT" to PRODUCT.encodedAndHashed, + "PATCH_RADIO" to RADIO.encodedAndHashed, + "PATCH_SKU" to SKU.encodedAndHashed, + "PATCH_SOC_MANUFACTURER" to SOC_MANUFACTURER.encodedAndHashed, + "PATCH_SOC_MODEL" to SOC_MODEL.encodedAndHashed, + "PATCH_TAGS" to TAGS.encodedAndHashed, + "PATCH_TYPE" to TYPE.encodedAndHashed, + "PATCH_USER" to USER.encodedAndHashed, + ) + } + + try { + Class.forName("android.os.Build") + // This only works on Android, + // because it uses Android APIs. + setBuildInfo() + } catch (_: ClassNotFoundException) { } + } + + private fun invokeCheck() = mainActivityOnCreateFingerprint.result?.mutableMethod?.addInstructions( + 0, + "invoke-static/range { p0 .. p0 },$INTEGRATIONS_CLASS_DESCRIPTOR->check(Landroid/app/Activity;)V", + ) ?: throw mainActivityOnCreateFingerprint.exception + + private companion object { + private const val INTEGRATIONS_CLASS_DESCRIPTOR = + "Lapp/revanced/integrations/shared/checks/CheckEnvironmentPatch;" + + @OptIn(ExperimentalEncodingApi::class) + private val String.encodedAndHashed + get() = MutableStringEncodedValue( + ImmutableStringEncodedValue( + Base64.encode(MessageDigest.getInstance("SHA-1") + .digest(this.toByteArray(StandardCharsets.UTF_8))), + ), + ) + + private val Long.encoded get() = MutableLongEncodedValue(ImmutableLongEncodedValue(this)) + + private fun MethodFingerprint.setClassFields(vararg fieldNameValues: Pair) { + val fieldNameValueMap = mapOf(*fieldNameValues) + + resultOrThrow().mutableClass.fields.forEach { field -> + field.initialValue = fieldNameValueMap[field.name] ?: return@forEach + } + } + } +} diff --git a/src/main/kotlin/app/revanced/patches/shared/misc/checks/fingerprints/PatchInfoBuildFingerprint.kt b/src/main/kotlin/app/revanced/patches/shared/misc/checks/fingerprints/PatchInfoBuildFingerprint.kt new file mode 100644 index 000000000..106dde705 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/shared/misc/checks/fingerprints/PatchInfoBuildFingerprint.kt @@ -0,0 +1,7 @@ +package app.revanced.patches.shared.misc.checks.fingerprints + +import app.revanced.patcher.fingerprint.MethodFingerprint + +internal object PatchInfoBuildFingerprint : MethodFingerprint( + customFingerprint = { _, classDef -> classDef.type == "Lapp/revanced/integrations/shared/checks/PatchInfo\$Build;" }, +) diff --git a/src/main/kotlin/app/revanced/patches/shared/misc/checks/fingerprints/PatchInfoFingerprint.kt b/src/main/kotlin/app/revanced/patches/shared/misc/checks/fingerprints/PatchInfoFingerprint.kt new file mode 100644 index 000000000..48d7d7dcc --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/shared/misc/checks/fingerprints/PatchInfoFingerprint.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.shared.misc.checks.fingerprints + +import app.revanced.patcher.fingerprint.MethodFingerprint + +internal object PatchInfoFingerprint : MethodFingerprint( + customFingerprint = { _, classDef -> + classDef.type == "Lapp/revanced/integrations/shared/checks/PatchInfo;" + }, +) diff --git a/src/main/kotlin/app/revanced/patches/shared/misc/gms/BaseGmsCoreSupportPatch.kt b/src/main/kotlin/app/revanced/patches/shared/misc/gms/BaseGmsCoreSupportPatch.kt index 26958a2d3..5969d2c29 100644 --- a/src/main/kotlin/app/revanced/patches/shared/misc/gms/BaseGmsCoreSupportPatch.kt +++ b/src/main/kotlin/app/revanced/patches/shared/misc/gms/BaseGmsCoreSupportPatch.kt @@ -115,8 +115,8 @@ abstract class BaseGmsCoreSupportPatch( // Verify GmsCore is installed and whitelisted for power optimizations and background usage. mainActivityOnCreateFingerprint.result?.mutableMethod?.apply { - // Temporary fix for Google photos integration. - var setContextIndex = indexOfFirstInstruction { + // Temporary fix for patches with an integrations patch that hook the onCreate method as well. + val setContextIndex = indexOfFirstInstruction { val reference = getReference() ?: return@indexOfFirstInstruction false reference.toString() == "Lapp/revanced/integrations/shared/Utils;->setContext(Landroid/content/Context;)V" diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/check/CheckEnvironmentPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/check/CheckEnvironmentPatch.kt new file mode 100644 index 000000000..01fd6814c --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/check/CheckEnvironmentPatch.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.misc.check + +import app.revanced.patches.shared.misc.checks.BaseCheckEnvironmentPatch +import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch +import app.revanced.patches.youtube.shared.fingerprints.MainActivityOnCreateFingerprint + +@Suppress("unused") +object CheckEnvironmentPatch : + BaseCheckEnvironmentPatch( + mainActivityOnCreateFingerprint = MainActivityOnCreateFingerprint, + integrationsPatch = IntegrationsPatch, + compatiblePackages = setOf(CompatiblePackage("com.google.android.youtube")), + ) diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsPatch.kt index 6f96dcffa..819b9d3b9 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsPatch.kt @@ -15,6 +15,7 @@ import app.revanced.patches.shared.misc.settings.preference.IntentPreference import app.revanced.patches.shared.misc.settings.preference.NonInteractivePreference import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen.Sorting import app.revanced.patches.shared.misc.settings.preference.TextPreference +import app.revanced.patches.youtube.misc.check.CheckEnvironmentPatch import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch import app.revanced.patches.youtube.misc.settings.fingerprints.LicenseActivityOnCreateFingerprint import app.revanced.patches.youtube.misc.settings.fingerprints.SetThemeFingerprint @@ -30,6 +31,9 @@ import java.io.Closeable IntegrationsPatch::class, SettingsResourcePatch::class, AddResourcesPatch::class, + // Currently there is no easy way to make a mandatory patch, + // so for now this is a dependent of this patch. + CheckEnvironmentPatch::class, ], ) object SettingsPatch : diff --git a/src/main/resources/addresources/values/strings.xml b/src/main/resources/addresources/values/strings.xml index db6abcaca..5f41165c1 100644 --- a/src/main/resources/addresources/values/strings.xml +++ b/src/main/resources/addresources/values/strings.xml @@ -31,6 +31,17 @@ This is because Crowdin requires temporarily flattening this file and removing t --> + + Checks failed + Open official website + Ignore + <h5>This app does not appear to be patched by you.</h5><br>This app may not function correctly, <b>could be harmful or even dangerous to use</b>.<br><br>These checks imply this app is pre-patched or obtained from someone else:<br><br><small>%1$s</small><br>It is strongly recommended to <b>uninstall this app and patch it yourself</b> to ensure you are using a validated and secure app.<p><br>If ignored, this warning will only be shown twice. + Patched on a different device + Not installed by ReVanced Manager + Patched more than 10 minutes ago + Patched %s days ago + APK build date is corrupted + ReVanced Do you wish to proceed? diff --git a/stub/build.gradle.kts b/stub/build.gradle.kts new file mode 100644 index 000000000..d80352d6e --- /dev/null +++ b/stub/build.gradle.kts @@ -0,0 +1,10 @@ +plugins { + `java-library` +} + +description = "Provide Android API stubs for ReVanced Patches." + +java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 +} diff --git a/stub/src/main/java/android/os/Build.java b/stub/src/main/java/android/os/Build.java new file mode 100644 index 000000000..45999597c --- /dev/null +++ b/stub/src/main/java/android/os/Build.java @@ -0,0 +1,26 @@ +package android.os; + +public class Build { + public static final String BOARD = null; + public static final String BOOTLOADER = null; + public static final String BRAND = null; + public static final String CPU_ABI = null; + public static final String CPU_ABI2 = null; + public static final String DEVICE = null; + public static final String DISPLAY = null; + public static final String FINGERPRINT = null; + public static final String HARDWARE = null; + public static final String HOST = null; + public static final String ID = null; + public static final String MANUFACTURER = null; + public static final String MODEL = null; + public static final String ODM_SKU = null; + public static final String PRODUCT = null; + public static final String RADIO = null; + public static final String SKU = null; + public static final String SOC_MANUFACTURER = null; + public static final String SOC_MODEL = null; + public static final String TAGS = null; + public static final String TYPE = null; + public static final String USER = null; +}