feat(PR): Improved Device Spoof by RSR
Improved the spoof device feature
This commit is contained in:
@@ -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<Any>, it.value as PropertyValue<Any>)
|
||||
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<Any>, it.value as PropertyValue<Any>) }
|
||||
}.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<Any>, it.value as PropertyValue<Any>) }
|
||||
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<Any>, it.value as PropertyValue<Any>) }.filter {
|
||||
configContainer.properties.map { (it.key to it.value).toPropertyPair() }.filter {
|
||||
!it.key.params.flags.contains(ConfigFlag.HIDDEN)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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<Any>, it.value as PropertyValue<Any>) }
|
||||
}.map { (it.key to it.value).toPropertyPair() }
|
||||
|
||||
PropertiesView(
|
||||
properties = properties,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -11,6 +11,10 @@ data class PropertyPair<T>(
|
||||
val name get() = key.name
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun Pair<PropertyKey<*>, PropertyValue<*>>.toPropertyPair(): PropertyPair<Any> =
|
||||
PropertyPair(first as PropertyKey<Any>, second as PropertyValue<Any>)
|
||||
|
||||
enum class FeatureNotice(
|
||||
val key: String
|
||||
) {
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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<String> = 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<String, String>()
|
||||
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<String>(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<String>(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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user