This commit is contained in:
ΞTΞRNAL
2026-04-01 16:36:35 +05:30
parent a82db6a863
commit 6e6d311c23
8 changed files with 109 additions and 15 deletions

View File

@@ -77,6 +77,7 @@ import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import me.eternal.purrfectsnap.common.ui.TopBarActionButton
import me.eternal.purrfectsnap.common.ui.rememberAsyncMutableStateList
import me.eternal.purrfectsnap.core.features.impl.experiments.RandomizedDeviceProfile
import me.eternal.purrfectsnap.ui.manager.components.AestheticDialog
import me.eternal.purrfectsnap.ui.manager.Routes
import me.eternal.purrfectsnap.ui.manager.ManagerTheme
@@ -188,7 +189,72 @@ class FeaturesRootSection : Routes.Route() {
}
internal fun isRandomizedProfileActionProperty(propertyName: String): Boolean {
return propertyName == "generate_fresh_profile_action" || propertyName == "view_current_profile_action"
return propertyName == "generate_fresh_profile_action" ||
propertyName == "view_current_profile_action" ||
propertyName == "backup_profile_action" ||
propertyName == "restore_profile_action"
}
internal fun backupRandomizedProfile(onConfigChanged: () -> Unit) {
val profileSnapshot = getRandomizedProfileSnapshot()
if (profileSnapshot.startsWith("No generated profile")) {
context.shortToast(
context.translation["manager.dialogs.randomize_device_profile.empty"]
?: "No generated profile is available yet. Enable the feature in Snapchat first."
)
return
}
activityLauncher {
saveFile("randomized-device-profile.json", "application/json") { uri ->
runCatching {
context.androidContext.contentResolver.openOutputStream(uri.toUri())?.bufferedWriter()?.use {
it.write(profileSnapshot)
} ?: error("Failed to open backup destination")
onConfigChanged()
context.shortToast("Randomized profile backup saved")
}.onFailure {
context.log.error("Failed to back up randomized profile", it)
context.shortToast("Failed to back up randomized profile")
}
}
}
}
internal fun restoreRandomizedProfile(onConfigChanged: () -> Unit) {
activityLauncher {
openFile("application/json") { uri ->
runCatching {
val importedJson = context.androidContext.contentResolver.openInputStream(uri.toUri())
?.bufferedReader()
?.use { it.readText() }
?.trim()
?: error("Failed to read randomized profile backup")
val profile = RandomizedDeviceProfile.fromJson(importedJson)
val generationToken = UUID.randomUUID().toString()
context.androidContext.getSharedPreferences("purrfectsnap_spoof", 0)
.edit()
.putString("randomized_device_profile", profile.toJson().toString())
.putString("randomized_device_profile_token", generationToken)
.putString("android_id", profile.androidId)
.putString("advertising_id", profile.advertisingId)
.putString("bluetooth_address", profile.bluetoothMacAddress)
.putString("gsf_id", profile.gsfId)
.putString("random_device", profile.deviceInfo.model)
.putString("device_fingerprint", profile.buildFingerprint)
.apply()
val randomizeConfig = context.config.root.experimental.spoof.randomizeDeviceProfile
randomizeConfig.profileGenerationToken.set(generationToken)
randomizeConfig.currentProfileSnapshot.set(profile.toJson().toString(2))
context.config.writeConfig()
onConfigChanged()
context.shortToast("Randomized profile restored. Restart Snapchat to apply it.")
}.onFailure {
context.log.error("Failed to restore randomized profile", it)
context.shortToast("Failed to restore randomized profile")
}
}
}
}
fun navigateToMainRoot() {
@@ -664,6 +730,8 @@ class FeaturesRootSection : Routes.Route() {
val actionLabel = when (property.name) {
"generate_fresh_profile_action" -> context.translation[property.key.propertyName()] ?: "Generate Fresh Profile"
"view_current_profile_action" -> context.translation[property.key.propertyName()] ?: "View Current Profile"
"backup_profile_action" -> context.translation[property.key.propertyName()] ?: "Backup Profile"
"restore_profile_action" -> context.translation[property.key.propertyName()] ?: "Restore Profile"
else -> property.name
}
Button(
@@ -685,8 +753,12 @@ class FeaturesRootSection : Routes.Route() {
?: "Fresh randomized profile requested. Restart Snapchat to apply it."
)
}
} else {
} else if (property.name == "view_current_profile_action") {
showCurrentRandomProfileDialog = true
} else if (property.name == "backup_profile_action") {
backupRandomizedProfile(onConfigChanged)
} else if (property.name == "restore_profile_action") {
restoreRandomizedProfile(onConfigChanged)
}
},
colors = ButtonDefaults.buttonColors(

View File

@@ -33,8 +33,8 @@ tasks.register<GetVersionTask>("getVersion") {
}
// You can still set these for legacy use by submodules or scripts:
rootProject.ext.set("appVersionName", providers.gradleProperty("APP_VERSION_NAME").orElse("1.6.2").get())
rootProject.ext.set("appVersionCode", providers.gradleProperty("APP_VERSION_CODE").orElse("314").get().toInt())
rootProject.ext.set("appVersionName", providers.gradleProperty("APP_VERSION_NAME").orElse("1.6.3").get())
rootProject.ext.set("appVersionCode", providers.gradleProperty("APP_VERSION_CODE").orElse("316").get().toInt())
rootProject.ext.set("applicationId", "me.eternal.purrfectsnap")
// buildHash: when PurrfectSnap or Snapchat is updated, mappings become outdated and auto-regenerate.
// Include version code so each release has a different hash; use random for uniqueness within same version.

View File

@@ -1,3 +1,7 @@
## v1.6.3
- Fix: Blank Global Settings page
- New: Backup & Restore option for Randomized Device Profile feature!
## v1.6.2
- New: Randomized Device Profile Feature!
- Show Activation Overlay

View File

@@ -2057,6 +2057,14 @@
"view_current_profile_action": {
"name": "View Current Profile",
"description": "Inspect the latest randomized profile snapshot"
},
"backup_profile_action": {
"name": "Backup Profile",
"description": "Export the full randomized profile as a backup file"
},
"restore_profile_action": {
"name": "Restore Profile",
"description": "Import and restore a previously backed up randomized profile"
}
}
},

View File

@@ -2410,7 +2410,7 @@
"name": "Settings Details",
"description": "Enable settings spoofing and fine-tune its namespaces",
"properties": {
"secure": {
"secure_settings": {
"name": "Secure Settings",
"description": "Enable secure settings spoofing and fine-tune secure values",
"properties": {
@@ -2424,7 +2424,7 @@
}
}
},
"system": {
"system_settings": {
"name": "System Settings",
"description": "Enable system settings spoofing and fine-tune system values",
"properties": {
@@ -2438,7 +2438,7 @@
}
}
},
"global": {
"global_settings": {
"name": "Global Settings",
"description": "Enable global settings spoofing and fine-tune global values",
"properties": {
@@ -2565,6 +2565,14 @@
"view_current_profile_action": {
"name": "View Current Profile",
"description": "Inspect the latest randomized profile snapshot"
},
"backup_profile_action": {
"name": "Backup Profile",
"description": "Export the full randomized profile as a backup file"
},
"restore_profile_action": {
"name": "Restore Profile",
"description": "Import and restore a previously backed up randomized profile"
}
}
},

View File

@@ -115,9 +115,9 @@ class Spoof : ConfigContainer(hasGlobalState = true) {
}
inner class RandomizedSettingsConfig : ConfigContainer(hasGlobalState = true) {
val secure = container("secure", RandomizedSecureSettingsConfig().apply { globalState = true })
val system = container("system", RandomizedSystemSettingsConfig().apply { globalState = true })
val global = container("global", RandomizedGlobalSettingsConfig().apply { globalState = true })
val secureSettings = container("secure_settings", RandomizedSecureSettingsConfig().apply { globalState = true })
val systemSettings = container("system_settings", RandomizedSystemSettingsConfig().apply { globalState = true })
val globalSettings = container("global_settings", RandomizedGlobalSettingsConfig().apply { globalState = true })
}
inner class RandomizedWifiConfig : ConfigContainer(hasGlobalState = true) {
@@ -178,6 +178,8 @@ class Spoof : ConfigContainer(hasGlobalState = true) {
}
val generateFreshProfileAction = string("generate_fresh_profile_action")
val viewCurrentProfileAction = string("view_current_profile_action")
val backupProfileAction = string("backup_profile_action")
val restoreProfileAction = string("restore_profile_action")
val profileGenerationToken = string("profile_generation_token") {
addFlags(ConfigFlag.HIDDEN)
}

View File

@@ -296,9 +296,9 @@ class DeviceSpooferHook : Feature("Device Spoofer") {
private fun getRandomizedSettingsToggleState(): RandomizedSettingsToggleState {
val config = context.config.experimental.spoof.randomizeDeviceProfile.settingsOptions
val secure = config.secure
val system = config.system
val global = config.global
val secure = config.secureSettings
val system = config.systemSettings
val global = config.globalSettings
return RandomizedSettingsToggleState(
secure = secure.globalState == true && (secure.base.get() || secure.tts.get()),
system = system.globalState == true && (system.base.get() || system.bluetooth.get()),

View File

@@ -7,8 +7,8 @@ org.gradle.configuration-cache=true
org.gradle.configuration-cache.problems=warn
nativeAbis=arm64-v8a
APP_VERSION_NAME=1.6.2
APP_VERSION_CODE=314
APP_VERSION_NAME=1.6.3
APP_VERSION_CODE=316
debug_build_hash=18fe2a814d0e2eb5
psIntegrityPinnedSha256=
EXPECTED_CERT_SHA256=0f188cb17d8ea4902ee15ce98f4928ba4226a4790fa3c52f62d776b08e46ca1c