From 181d19424f76c12560e9b7b28b254708cf4cb4e5 Mon Sep 17 00:00:00 2001 From: RSR/ <52901789+RSR1337@users.noreply.github.com> Date: Wed, 11 Mar 2026 01:47:07 +0400 Subject: [PATCH] Improved the spoof device feature --- .../pages/features/FeaturesRootSection.kt | 8 +- .../themes/aphelion/AphelionFeaturesView.kt | 7 +- common/src/main/assets/lang/en_US.json | 22 +- .../common/config/ConfigObjects.kt | 4 + .../purrfectsnap/common/config/impl/Spoof.kt | 23 +- .../impl/experiments/DeviceSpoofer.kt | 235 +++++++++++++ .../impl/experiments/DeviceSpooferHook.kt | 311 +++++++----------- 7 files changed, 394 insertions(+), 216 deletions(-) create mode 100644 core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/experiments/DeviceSpoofer.kt diff --git a/app/src/main/kotlin/me/eternal/purrfectsnap/ui/manager/pages/features/FeaturesRootSection.kt b/app/src/main/kotlin/me/eternal/purrfectsnap/ui/manager/pages/features/FeaturesRootSection.kt index 482ff2e5..1db6161f 100644 --- a/app/src/main/kotlin/me/eternal/purrfectsnap/ui/manager/pages/features/FeaturesRootSection.kt +++ b/app/src/main/kotlin/me/eternal/purrfectsnap/ui/manager/pages/features/FeaturesRootSection.kt @@ -119,7 +119,7 @@ class FeaturesRootSection : Routes.Route() { it.key.dataType.type == DataProcessors.Type.CONTAINER && !it.key.params.flags.contains(ConfigFlag.HIDDEN) ) { - containers[it.key.name] = PropertyPair(it.key as PropertyKey, it.value as PropertyValue) + containers[it.key.name] = (it.key to it.value).toPropertyPair() queryContainerRecursive(it.value.get() as ConfigContainer) } } @@ -286,7 +286,7 @@ class FeaturesRootSection : Routes.Route() { context.translation[it.key.propertyName()].contains(keyword, ignoreCase = true) || context.translation[it.key.propertyDescription()].contains(keyword, ignoreCase = true) ) - }.map { PropertyPair(it.key as PropertyKey, it.value as PropertyValue) } + }.map { (it.key to it.value).toPropertyPair() } PropertiesView( properties = properties, @@ -1316,7 +1316,7 @@ class FeaturesRootSection : Routes.Route() { val isActiveSearch = isSearchResults || liveSearchQuery.isNotBlank() val globalSearchProperties = remember(enableGlobalSearch) { if (enableGlobalSearch) { - allProperties.filter { isSearchVisibleProperty(it.key) }.map { PropertyPair(it.key as PropertyKey, it.value as PropertyValue) } + allProperties.filter { isSearchVisibleProperty(it.key) }.map { (it.key to it.value).toPropertyPair() } } else { emptyList() } @@ -1537,7 +1537,7 @@ class FeaturesRootSection : Routes.Route() { ) { PropertiesView( properties = remember { - configContainer.properties.map { PropertyPair(it.key as PropertyKey, it.value as PropertyValue) }.filter { + configContainer.properties.map { (it.key to it.value).toPropertyPair() }.filter { !it.key.params.flags.contains(ConfigFlag.HIDDEN) } }, diff --git a/app/src/main/kotlin/me/eternal/purrfectsnap/ui/manager/pages/themes/aphelion/AphelionFeaturesView.kt b/app/src/main/kotlin/me/eternal/purrfectsnap/ui/manager/pages/themes/aphelion/AphelionFeaturesView.kt index df5278dc..f861df05 100644 --- a/app/src/main/kotlin/me/eternal/purrfectsnap/ui/manager/pages/themes/aphelion/AphelionFeaturesView.kt +++ b/app/src/main/kotlin/me/eternal/purrfectsnap/ui/manager/pages/themes/aphelion/AphelionFeaturesView.kt @@ -6,10 +6,9 @@ import androidx.navigation.compose.currentBackStackEntryAsState import me.eternal.purrfectsnap.ui.manager.pages.features.FeaturesRootSection import me.eternal.purrfectsnap.ui.manager.pages.features.FeaturesRootSection.Companion.FEATURE_CONTAINER_ROUTE import me.eternal.purrfectsnap.ui.manager.pages.features.FeaturesRootSection.Companion.SEARCH_FEATURE_ROUTE -import me.eternal.purrfectsnap.common.config.PropertyKey -import me.eternal.purrfectsnap.common.config.PropertyValue -import me.eternal.purrfectsnap.common.config.PropertyPair import me.eternal.purrfectsnap.common.config.ConfigContainer +import me.eternal.purrfectsnap.common.config.PropertyPair +import me.eternal.purrfectsnap.common.config.toPropertyPair @Composable fun FeaturesRootSection.AphelionFeaturesScreen(nav: NavBackStackEntry) { @@ -36,7 +35,7 @@ fun FeaturesRootSection.AphelionFeaturesScreen(nav: NavBackStackEntry) { context.translation[it.key.propertyName()].contains(keyword, ignoreCase = true) || context.translation[it.key.propertyDescription()].contains(keyword, ignoreCase = true) ) - }.map { PropertyPair(it.key as PropertyKey, it.value as PropertyValue) } + }.map { (it.key to it.value).toPropertyPair() } PropertiesView( properties = properties, diff --git a/common/src/main/assets/lang/en_US.json b/common/src/main/assets/lang/en_US.json index 7f2d7a96..acc95142 100644 --- a/common/src/main/assets/lang/en_US.json +++ b/common/src/main/assets/lang/en_US.json @@ -2296,11 +2296,23 @@ "schedule_failed": "Scheduled snap failed", "schedule_cancelled_for": "Cancelled for {name}", "device_model": { - "samsung_s25_ultra": "Samsung Galaxy S25 Ultra", - "google_pixel_10_pro": "Google Pixel 10 Pro", - "oneplus_13": "OnePlus 13", - "xiaomi_15_ultra": "Xiaomi 15 Ultra", - "null": "Device Default" + "none": "Device Default", + "random": "Random", + "Pixel 8 Pro": "Pixel 8 Pro", + "Pixel 9 Pro XL": "Pixel 9 Pro XL", + "Pixel 10": "Pixel 10", + "Pixel 10 Pro": "Pixel 10 Pro", + "Pixel 10 Pro XL": "Pixel 10 Pro XL", + "Pixel 10 Pro Fold": "Pixel 10 Pro Fold", + "Galaxy S23 Ultra": "Galaxy S23 Ultra", + "Galaxy S24 Ultra": "Galaxy S24 Ultra", + "Galaxy S25 Ultra": "Galaxy S25 Ultra", + "OnePlus 15": "OnePlus 15", + "OnePlus Open": "OnePlus Open", + "Xiaomi 15 Ultra": "Xiaomi 15 Ultra", + "OPPO Find X9 Pro": "OPPO Find X9 Pro", + "vivo X100 Pro": "vivo X100 Pro", + "realme GT 6": "realme GT 6" }, "settings_menu": { "default": "Default", diff --git a/common/src/main/kotlin/me/eternal/purrfectsnap/common/config/ConfigObjects.kt b/common/src/main/kotlin/me/eternal/purrfectsnap/common/config/ConfigObjects.kt index 5cf9a2a1..3071bd38 100644 --- a/common/src/main/kotlin/me/eternal/purrfectsnap/common/config/ConfigObjects.kt +++ b/common/src/main/kotlin/me/eternal/purrfectsnap/common/config/ConfigObjects.kt @@ -11,6 +11,10 @@ data class PropertyPair( val name get() = key.name } +@Suppress("UNCHECKED_CAST") +fun Pair, PropertyValue<*>>.toPropertyPair(): PropertyPair = + PropertyPair(first as PropertyKey, second as PropertyValue) + enum class FeatureNotice( val key: String ) { diff --git a/common/src/main/kotlin/me/eternal/purrfectsnap/common/config/impl/Spoof.kt b/common/src/main/kotlin/me/eternal/purrfectsnap/common/config/impl/Spoof.kt index 1abf5400..90b7ad25 100644 --- a/common/src/main/kotlin/me/eternal/purrfectsnap/common/config/impl/Spoof.kt +++ b/common/src/main/kotlin/me/eternal/purrfectsnap/common/config/impl/Spoof.kt @@ -19,11 +19,24 @@ class Spoof : ConfigContainer(hasGlobalState = true) { val spoofDeviceId = container("spoof_device_id", SpoofDeviceIdConfig()) { requireRestart() } val spoofDevice = boolean("spoof_device") { requireRestart() } val deviceModel = unique("device_model", - "samsung_s25_ultra", - "google_pixel_10_pro", - "oneplus_13", - "xiaomi_15_ultra" - ) { + "none", + "random", + "Pixel 8 Pro", + "Pixel 9 Pro XL", + "Pixel 10", + "Pixel 10 Pro", + "Pixel 10 Pro XL", + "Pixel 10 Pro Fold", + "Galaxy S23 Ultra", + "Galaxy S24 Ultra", + "Galaxy S25 Ultra", + "OnePlus 15", + "OnePlus Open", + "Xiaomi 15 Ultra", + "OPPO Find X9 Pro", + "vivo X100 Pro", + "realme GT 6" + ) { requireRestart() customOptionTranslationPath = "features.options.device_model" } diff --git a/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/experiments/DeviceSpoofer.kt b/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/experiments/DeviceSpoofer.kt new file mode 100644 index 00000000..60b9a546 --- /dev/null +++ b/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/experiments/DeviceSpoofer.kt @@ -0,0 +1,235 @@ +package me.eternal.purrfectsnap.core.features.impl.experiments + +import java.security.SecureRandom + +data class DeviceInfo( + val manufacturer: String, + val model: String, + val brand: String, + val device: String, + val product: String, + val hardware: String, + val board: String, + val bootloader: String, + val display: String, + val host: String +) + +object DeviceSpoofer { + private val devices = mapOf( + "Pixel 8 Pro" to DeviceInfo( + manufacturer = "Google", + model = "Pixel 8 Pro", + brand = "google", + device = "husky", + product = "husky", + hardware = "husky", + board = "husky", + bootloader = "husky-1.0-11003666", + display = "UQ1A.231205.015", + host = "abfarm-release-rbe-64-00163" + ), + "Pixel 9 Pro XL" to DeviceInfo( + manufacturer = "Google", + model = "Pixel 9 Pro XL", + brand = "google", + device = "komodo", + product = "komodo", + hardware = "komodo", + board = "komodo", + bootloader = "komodo-1.0-12110753", + display = "AP3A.241105.008", + host = "abfarm-release-rbe-64-00163" + ), + "Pixel 10" to DeviceInfo( + manufacturer = "Google", + model = "Pixel 10", + brand = "google", + device = "frankel", + product = "frankel", + hardware = "tensor_g5", + board = "frankel", + bootloader = "frankel-1.0-12345678", + display = "BP1A.250105.002", + host = "abfarm-release-rbe-65-00200" + ), + "Pixel 10 Pro" to DeviceInfo( + manufacturer = "Google", + model = "Pixel 10 Pro", + brand = "google", + device = "blazer", + product = "blazer", + hardware = "tensor_g5", + board = "blazer", + bootloader = "blazer-1.0-12345679", + display = "BP1A.250105.002", + host = "abfarm-release-rbe-65-00201" + ), + "Pixel 10 Pro XL" to DeviceInfo( + manufacturer = "Google", + model = "Pixel 10 Pro XL", + brand = "google", + device = "mustang", + product = "mustang", + hardware = "tensor_g5", + board = "mustang", + bootloader = "mustang-1.0-12345680", + display = "BP1A.250105.002", + host = "abfarm-release-rbe-65-00202" + ), + "Pixel 10 Pro Fold" to DeviceInfo( + manufacturer = "Google", + model = "Pixel 10 Pro Fold", + brand = "google", + device = "rango", + product = "rango", + hardware = "tensor_g5", + board = "rango", + bootloader = "rango-1.0-12345681", + display = "BP1A.250105.002", + host = "abfarm-release-rbe-65-00203" + ), + "Galaxy S23 Ultra" to DeviceInfo( + manufacturer = "Samsung", + model = "SM-S918B", + brand = "samsung", + device = "dm3q", + product = "dm3qxx", + hardware = "qcom", + board = "kalama", + bootloader = "S918BXXU3BWJM", + display = "UP1A.231005.007.S918BXXU3BWJM", + host = "21DH7R2P" + ), + "Galaxy S24 Ultra" to DeviceInfo( + manufacturer = "Samsung", + model = "SM-S928B", + brand = "samsung", + device = "e9q", + product = "e9qxx", + hardware = "qcom", + board = "pineapple", + bootloader = "S928BXXU1AXB5", + display = "UP1A.231005.007.S928BXXU1AXB5", + host = "21DH7R2P" + ), + "Galaxy S25 Ultra" to DeviceInfo( + manufacturer = "Samsung", + model = "SM-S938B", + brand = "samsung", + device = "e3q", + product = "e3qxx", + hardware = "qcom", + board = "s5e9945", + bootloader = "S938BXXU1AXL2", + display = "UP1A.231005.007.S938BXXU1AXL2", + host = "21DH7R2P" + ), + "OnePlus 15" to DeviceInfo( + manufacturer = "OnePlus", + model = "CPH2651", + brand = "OnePlus", + device = "OP5929L1", + product = "OP5929L1_EEA", + hardware = "qcom", + board = "taro", + bootloader = "unknown", + display = "CPH2651_15.0.0.503(EX01)", + host = "ubuntu-build" + ), + "OnePlus Open" to DeviceInfo( + manufacturer = "OnePlus", + model = "CPH2551", + brand = "OnePlus", + device = "OP594DL1", + product = "OP594DL1_EEA", + hardware = "qcom", + board = "taro", + bootloader = "unknown", + display = "CPH2551_14.0.0.600(EX01)", + host = "ubuntu-build" + ), + "Xiaomi 15 Ultra" to DeviceInfo( + manufacturer = "Xiaomi", + model = "25010PN30G", + brand = "Xiaomi", + device = "xuanyuan", + product = "xuanyuan_global", + hardware = "qcom", + board = "taro", + bootloader = "unknown", + display = "VK.15.0.3.0.VNGMIXM", + host = "c3-miui-ota-bd164.bj" + ), + "OPPO Find X9 Pro" to DeviceInfo( + manufacturer = "OPPO", + model = "PHY110", + brand = "OPPO", + device = "OP595DL1", + product = "OP595DL1_EEA", + hardware = "mt6989", + board = "k6989v1_64", + bootloader = "unknown", + display = "PHY110_15.0.0.100(EX01)", + host = "ubuntu-build-server" + ), + "vivo X100 Pro" to DeviceInfo( + manufacturer = "vivo", + model = "V2309A", + brand = "vivo", + device = "V2309A", + product = "PD2309", + hardware = "mt6989", + board = "k6989v1_64", + bootloader = "unknown", + display = "OP557L.PD2309.14.0.0.100", + host = "compiler-server" + ), + "realme GT 6" to DeviceInfo( + manufacturer = "realme", + model = "RMX3851", + brand = "realme", + device = "RMX3851", + product = "RMX3851_11_A.13", + hardware = "qcom", + board = "taro", + bootloader = "unknown", + display = "RMX3851_14.0.0.700(EX01)", + host = "ubuntu-server" + ) + ) + + fun getAvailableDevices(): List = devices.keys.toList() + + fun getDeviceInfo(modelName: String): DeviceInfo? { + return devices[modelName] + } + + fun generateFingerprint(deviceInfo: DeviceInfo, buildVersion: String): String { + val id = "AP3A.${System.currentTimeMillis().toString().take(6)}.005" + val incremental = System.nanoTime().toString().take(8) + return "${deviceInfo.brand}/${deviceInfo.product}/${deviceInfo.device}:$buildVersion/$id/$incremental:user/release-keys" + } + + fun generateAndroidId(): String { + val random = SecureRandom() + val bytes = ByteArray(8) + random.nextBytes(bytes) + return bytes.joinToString("") { "%02x".format(it) } + } + + fun androidIdToBytes(androidId: String): ByteArray { + return try { + val len = androidId.length + val data = ByteArray(len / 2) + var i = 0 + while (i < len) { + data[i / 2] = ((Character.digit(androidId[i], 16) shl 4) + Character.digit(androidId[i + 1], 16)).toByte() + i += 2 + } + data + } catch (e: Exception) { + ByteArray(8) + } + } +} diff --git a/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/experiments/DeviceSpooferHook.kt b/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/experiments/DeviceSpooferHook.kt index c7a93cc4..e8316ccb 100644 --- a/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/experiments/DeviceSpooferHook.kt +++ b/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/experiments/DeviceSpooferHook.kt @@ -16,113 +16,82 @@ import java.security.SecureRandom class DeviceSpooferHook: Feature("Device Spoofer") { private var spoofedAndroidId: String? = null - private var hasLoggedId = false - private var randomizedFingerprints = mutableMapOf() + private var spoofedDeviceInfo: DeviceInfo? = null + private var spoofedFingerprint: String? = null private fun generateAndroidId(): String { - if (spoofedAndroidId != null) return spoofedAndroidId!! + // Always check custom ID first - this ensures changes take effect immediately val customId = context.config.experimental.spoof.spoofDeviceId.customAndroidId.getNullable() if (!customId.isNullOrEmpty()) { - spoofedAndroidId = customId.lowercase() - if (!hasLoggedId) { - context.log.info("Using custom Android ID: $spoofedAndroidId") - hasLoggedId = true + val normalizedId = customId.lowercase().trim() + if (normalizedId.length == 16 && normalizedId.all { it in '0'..'9' || it in 'a'..'f' }) { + // Only log when ID actually changes + if (spoofedAndroidId != normalizedId) { + spoofedAndroidId = normalizedId + context.log.info("Using custom Android ID: $spoofedAndroidId") + } + return spoofedAndroidId!! + } else { + context.log.warn("Invalid custom Android ID format (must be 16 hex chars), generating new one") } - return spoofedAndroidId!! } + + // No custom ID set - use stored or generate new one val sharedPrefs = context.androidContext.getSharedPreferences("purrfectsnap_spoof", 0) - spoofedAndroidId = sharedPrefs.getString("android_id", null) - if (spoofedAndroidId == null) { - spoofedAndroidId = generateRandomHexString(16) + val storedId = sharedPrefs.getString("android_id", null) + if (storedId == null || storedId.length != 16) { + spoofedAndroidId = DeviceSpoofer.generateAndroidId() sharedPrefs.edit().putString("android_id", spoofedAndroidId).apply() context.log.info("Generated new Android ID: $spoofedAndroidId") - } else if (!hasLoggedId) { - context.log.info("Using stored Android ID: $spoofedAndroidId") + } else { + // Only use cached value if it matches stored value (handles regeneration) + if (spoofedAndroidId != storedId) { + spoofedAndroidId = storedId + context.log.info("Using stored Android ID: $spoofedAndroidId") + } } - hasLoggedId = true return spoofedAndroidId!! } - private fun generateRandomHexString(length: Int): String { - val random = SecureRandom() - val bytes = ByteArray(length / 2) - random.nextBytes(bytes) - return bytes.joinToString("") { "%02x".format(it) } + private fun getDeviceInfo(modelName: String): DeviceInfo? { + return DeviceSpoofer.getDeviceInfo(modelName) } - private fun randomizeFingerprintBuildNumber(fingerprint: String, deviceKey: String): String { - if (randomizedFingerprints.containsKey(deviceKey)) { - return randomizedFingerprints[deviceKey]!! - } - val sharedPrefs = context.androidContext.getSharedPreferences("purrfectsnap_spoof", 0) - val savedFingerprint = sharedPrefs.getString("fingerprint_$deviceKey", null) - if (savedFingerprint != null) { - randomizedFingerprints[deviceKey] = savedFingerprint - return savedFingerprint - } - val parts = fingerprint.split("/") - if (parts.size >= 3) { - val buildPart = parts[2] - val buildSections = buildPart.split(":") - if (buildSections.size >= 2) { - val buildDetails = buildSections[1].split("/") - if (buildDetails.size >= 2) { - val randomBuildNumber = generateRandomBuildNumber() - val newBuildDetails = buildDetails.toMutableList() - newBuildDetails[1] = randomBuildNumber - val newBuildPart = "${buildSections[0]}:${newBuildDetails.joinToString("/")}" - val newFingerprint = "${parts[0]}/${parts[1]}/$newBuildPart" - randomizedFingerprints[deviceKey] = newFingerprint - sharedPrefs.edit().putString("fingerprint_$deviceKey", newFingerprint).apply() - return newFingerprint - } + private fun getSpoofedDeviceInfo(): DeviceInfo? { + val selectedModel = context.config.experimental.spoof.deviceModel.getNullable() ?: return null + if (selectedModel == "random") { + val sharedPrefs = context.androidContext.getSharedPreferences("purrfectsnap_spoof", 0) + val randomDevice = sharedPrefs.getString("random_device", null) + if (randomDevice == null) { + val availableDevices = DeviceSpoofer.getAvailableDevices() + val newRandomDevice = availableDevices.random() + sharedPrefs.edit().putString("random_device", newRandomDevice).apply() + context.log.info("Randomly selected device: $newRandomDevice") + spoofedDeviceInfo = getDeviceInfo(newRandomDevice) + } else { + context.log.info("Using stored random device: $randomDevice") + spoofedDeviceInfo = getDeviceInfo(randomDevice) } + return spoofedDeviceInfo } - randomizedFingerprints[deviceKey] = fingerprint - return fingerprint + if (selectedModel == "none" || selectedModel == "null") return null + spoofedDeviceInfo = getDeviceInfo(selectedModel) + return spoofedDeviceInfo } - private fun generateRandomBuildNumber(): String { - val chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - val random = SecureRandom() - return (1..10).map { chars[random.nextInt(chars.length)] }.joinToString("") - } - - private fun randomizeDisplayId(display: String, deviceKey: String, buildNumber: String): String { + private fun getSpoofedFingerprint(deviceInfo: DeviceInfo): String { val sharedPrefs = context.androidContext.getSharedPreferences("purrfectsnap_spoof", 0) - val savedDisplay = sharedPrefs.getString("display_$deviceKey", null) - if (savedDisplay != null) return savedDisplay - val parts = display.split(".") - val newDisplay = if (parts.size > 1) { - "${parts[0]}.${parts[1]}.$buildNumber" + val storedFingerprint = sharedPrefs.getString("device_fingerprint", null) + if (storedFingerprint == null) { + val buildVersion = Build.VERSION.RELEASE + spoofedFingerprint = DeviceSpoofer.generateFingerprint(deviceInfo, buildVersion) + sharedPrefs.edit().putString("device_fingerprint", spoofedFingerprint).apply() + context.log.info("Generated new device fingerprint: $spoofedFingerprint") } else { - display + spoofedFingerprint = storedFingerprint + context.log.info("Using stored device fingerprint: $spoofedFingerprint") } - sharedPrefs.edit().putString("display_$deviceKey", newDisplay).apply() - return newDisplay - } - - private fun getRandomSerial(deviceKey: String): String { - val sharedPrefs = context.androidContext.getSharedPreferences("purrfectsnap_spoof", 0) - val savedSerial = sharedPrefs.getString("serial_$deviceKey", null) - if (savedSerial != null) return savedSerial - val random = SecureRandom() - val serial = (1..16).map { - "0123456789ABCDEF"[random.nextInt(16)] - }.joinToString("") - sharedPrefs.edit().putString("serial_$deviceKey", serial).apply() - return serial - } - - private fun getRandomBuildId(deviceKey: String): String { - val sharedPrefs = context.androidContext.getSharedPreferences("purrfectsnap_spoof", 0) - val savedBuildId = sharedPrefs.getString("build_id_$deviceKey", null) - if (savedBuildId != null) return savedBuildId - val chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - val random = SecureRandom() - val buildId = (1..8).map { chars[random.nextInt(chars.length)] }.joinToString("") - sharedPrefs.edit().putString("build_id_$deviceKey", buildId).apply() - return buildId + return spoofedFingerprint!! } private fun getRandomGsfId(): String { @@ -165,65 +134,6 @@ class DeviceSpooferHook: Feature("Device Spoofer") { return mac } - data class DeviceProfile( - val manufacturer: String, - val brand: String, - val model: String, - val device: String, - val product: String, - val hardware: String, - val board: String, - val fingerprint: String, - val display: String - ) - - private val deviceProfiles = mapOf( - "samsung_s25_ultra" to DeviceProfile( - manufacturer = "samsung", - brand = "samsung", - model = "SM-S938U", - device = "e3q", - product = "e3qsqw", - hardware = "qcom", - board = "taro", - fingerprint = "samsung/e3qsqw/e3q:15/AP2A.240805.005/S938USQU1AXL2:user/release-keys", - display = "AP2A.240805.005.S938USQU1AXL2" - ), - "google_pixel_10_pro" to DeviceProfile( - manufacturer = "Google", - brand = "google", - model = "Pixel 10 Pro", - device = "caiman", - product = "caiman", - hardware = "caiman", - board = "caiman", - fingerprint = "google/caiman/caiman:15/AP2A.240805.005/12345678:user/release-keys", - display = "AP2A.240805.005" - ), - "oneplus_13" to DeviceProfile( - manufacturer = "OnePlus", - brand = "OnePlus", - model = "CPH2649", - device = "OP5B41L1", - product = "CPH2649_EEA", - hardware = "qcom", - board = "kalama", - fingerprint = "OnePlus/CPH2649_EEA/OP5B41L1:15/SKQ1.240805.001/1730123456789:user/release-keys", - display = "CPH2649_15.0.0.300(EX01)" - ), - "xiaomi_15_ultra" to DeviceProfile( - manufacturer = "Xiaomi", - brand = "Xiaomi", - model = "23127PN0CC", - device = "aurora", - product = "aurora_global", - hardware = "qcom", - board = "taro", - fingerprint = "Xiaomi/aurora_global/aurora:14/UKQ1.231003.002/V816.0.7.0.UMLMIXM:user/release-keys", - display = "UKQ1.231003.002" - ) - ) - private fun hookInstallerPackageName() { context.androidContext.packageManager::class.java.hook("getInstallerPackageName", HookStage.BEFORE) { param -> param.setResult("com.android.vending") @@ -232,6 +142,15 @@ class DeviceSpooferHook: Feature("Device Spoofer") { @SuppressLint("MissingPermission") override fun init() { + val spoofDevice by context.config.experimental.spoof.spoofDevice + if (spoofDevice) { + val deviceInfo = getSpoofedDeviceInfo() + if (deviceInfo != null) { + getSpoofedFingerprint(deviceInfo) + context.log.info("Device spoofing initialized: ${deviceInfo.manufacturer} ${deviceInfo.model}") + } + } + if (LSPatchUpdater.HAS_LSPATCH) { hookInstallerPackageName() } @@ -380,66 +299,62 @@ class DeviceSpooferHook: Feature("Device Spoofer") { } } - val spoofDevice by context.config.experimental.spoof.spoofDevice if (spoofDevice) { - val selectedDevice = context.config.experimental.spoof.deviceModel.getNullable() ?: "samsung_s25_ultra" - val deviceProfile = deviceProfiles[selectedDevice] ?: deviceProfiles["samsung_s25_ultra"]!! - val randomizedFingerprint = randomizeFingerprintBuildNumber(deviceProfile.fingerprint, selectedDevice) - val buildNumber = randomizedFingerprint.split("/").getOrNull(2)?.split(":")?.getOrNull(1)?.split("/")?.getOrNull(1) ?: generateRandomBuildNumber() - val randomizedDisplay = randomizeDisplayId(deviceProfile.display, selectedDevice, buildNumber) - val randomSerial = getRandomSerial(selectedDevice) - val randomBuildId = getRandomBuildId(selectedDevice) - - context.log.info("Spoofing device as: ${deviceProfile.model}") + val deviceInfo = getSpoofedDeviceInfo() + if (deviceInfo != null) { + val fingerprint = getSpoofedFingerprint(deviceInfo) - Build::class.java.apply { - fields.forEach { field -> - if (!field.isAccessible) field.isAccessible = true - runCatching { - val modifiersField = java.lang.reflect.Field::class.java.getDeclaredField("modifiers") - modifiersField.isAccessible = true - modifiersField.setInt(field, field.modifiers and java.lang.reflect.Modifier.FINAL.inv()) - } - when (field.name) { - "MANUFACTURER" -> field.set(null, deviceProfile.manufacturer) - "BRAND" -> field.set(null, deviceProfile.brand) - "MODEL" -> field.set(null, deviceProfile.model) - "DEVICE" -> field.set(null, deviceProfile.device) - "PRODUCT" -> field.set(null, deviceProfile.product) - "HARDWARE" -> field.set(null, deviceProfile.hardware) - "BOARD" -> field.set(null, deviceProfile.board) - "FINGERPRINT" -> field.set(null, randomizedFingerprint) - "DISPLAY" -> field.set(null, randomizedDisplay) - "SERIAL" -> field.set(null, randomSerial) - "ID" -> field.set(null, randomBuildId) - "TAGS" -> field.set(null, "release-keys") - "TYPE" -> field.set(null, "user") - "USER" -> field.set(null, "android-build") - "HOST" -> field.set(null, "build-host") + context.log.info("Device spoofing active: ${deviceInfo.manufacturer} ${deviceInfo.model}") + + Build::class.java.apply { + fields.forEach { field -> + if (!field.isAccessible) field.isAccessible = true + runCatching { + val modifiersField = java.lang.reflect.Field::class.java.getDeclaredField("modifiers") + modifiersField.isAccessible = true + modifiersField.setInt(field, field.modifiers and java.lang.reflect.Modifier.FINAL.inv()) + } + when (field.name) { + "MANUFACTURER" -> field.set(null, deviceInfo.manufacturer) + "MODEL" -> field.set(null, deviceInfo.model) + "BRAND" -> field.set(null, deviceInfo.brand) + "DEVICE" -> field.set(null, deviceInfo.device) + "PRODUCT" -> field.set(null, deviceInfo.product) + "HARDWARE" -> field.set(null, deviceInfo.hardware) + "FINGERPRINT" -> field.set(null, fingerprint) + "BOARD" -> try { field.set(null, deviceInfo.board) } catch (_: Exception) {} + "BOOTLOADER" -> try { field.set(null, deviceInfo.bootloader) } catch (_: Exception) {} + "DISPLAY" -> try { field.set(null, deviceInfo.display) } catch (_: Exception) {} + "HOST" -> try { field.set(null, deviceInfo.host) } catch (_: Exception) {} + "TIME" -> try { + val currentTime = System.currentTimeMillis() + val randomDaysAgo = (30..180).random() + val buildTime = currentTime - (randomDaysAgo * 24L * 60L * 60L * 1000L) + field.setLong(null, buildTime) + } catch (_: Exception) {} + } } } - } - findClass("android.os.SystemProperties").apply { - hook("get", HookStage.BEFORE) { param -> - val key = param.arg(0) - when (key) { - "ro.product.manufacturer", "ro.product.vendor.manufacturer", "ro.product.system.manufacturer", "ro.product.odm.manufacturer" -> param.setResult(deviceProfile.manufacturer) - "ro.product.brand", "ro.product.vendor.brand", "ro.product.system.brand", "ro.product.odm.brand" -> param.setResult(deviceProfile.brand) - "ro.product.model", "ro.product.vendor.model", "ro.product.system.model", "ro.product.odm.model" -> param.setResult(deviceProfile.model) - "ro.product.device", "ro.product.vendor.device", "ro.product.system.device", "ro.product.odm.device" -> param.setResult(deviceProfile.device) - "ro.product.name", "ro.product.vendor.name", "ro.product.system.name", "ro.product.odm.name" -> param.setResult(deviceProfile.product) - "ro.hardware", "ro.hardware.chipname" -> param.setResult(deviceProfile.hardware) - "ro.product.board" -> param.setResult(deviceProfile.board) - "ro.build.fingerprint" -> param.setResult(randomizedFingerprint) - "ro.build.display.id" -> param.setResult(randomizedDisplay) - "ro.serialno", "ro.boot.serialno", "ril.serialnumber" -> param.setResult(randomSerial) - "ro.build.id" -> param.setResult(randomBuildId) - "ro.build.tags" -> param.setResult("release-keys") - "ro.build.type" -> param.setResult("user") - "ro.build.user" -> param.setResult("android-build") - "ro.build.host" -> param.setResult("build-host") + runCatching { + findClass("android.os.SystemProperties").hook("get", HookStage.BEFORE) { param -> + val key = param.argNullable(0) ?: return@hook + when (key) { + "ro.product.manufacturer" -> param.setResult(deviceInfo.manufacturer) + "ro.product.model" -> param.setResult(deviceInfo.model) + "ro.product.brand" -> param.setResult(deviceInfo.brand) + "ro.product.device" -> param.setResult(deviceInfo.device) + "ro.product.name" -> param.setResult(deviceInfo.product) + "ro.product.board" -> param.setResult(deviceInfo.board) + "ro.hardware" -> param.setResult(deviceInfo.hardware) + "ro.build.fingerprint" -> param.setResult(fingerprint) + "ro.bootloader" -> param.setResult(deviceInfo.bootloader) + "ro.build.display.id" -> param.setResult(deviceInfo.display) + } } + context.log.info("SystemProperties hooks installed successfully") + }.onFailure { + context.log.warn("Failed to hook SystemProperties: ${it.message}") } } }