v1.6.3
This commit is contained in:
@@ -77,6 +77,7 @@ import com.google.gson.Gson
|
|||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import me.eternal.purrfectsnap.common.ui.TopBarActionButton
|
import me.eternal.purrfectsnap.common.ui.TopBarActionButton
|
||||||
import me.eternal.purrfectsnap.common.ui.rememberAsyncMutableStateList
|
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.components.AestheticDialog
|
||||||
import me.eternal.purrfectsnap.ui.manager.Routes
|
import me.eternal.purrfectsnap.ui.manager.Routes
|
||||||
import me.eternal.purrfectsnap.ui.manager.ManagerTheme
|
import me.eternal.purrfectsnap.ui.manager.ManagerTheme
|
||||||
@@ -188,7 +189,72 @@ class FeaturesRootSection : Routes.Route() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal fun isRandomizedProfileActionProperty(propertyName: String): Boolean {
|
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() {
|
fun navigateToMainRoot() {
|
||||||
@@ -664,6 +730,8 @@ class FeaturesRootSection : Routes.Route() {
|
|||||||
val actionLabel = when (property.name) {
|
val actionLabel = when (property.name) {
|
||||||
"generate_fresh_profile_action" -> context.translation[property.key.propertyName()] ?: "Generate Fresh Profile"
|
"generate_fresh_profile_action" -> context.translation[property.key.propertyName()] ?: "Generate Fresh Profile"
|
||||||
"view_current_profile_action" -> context.translation[property.key.propertyName()] ?: "View Current 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
|
else -> property.name
|
||||||
}
|
}
|
||||||
Button(
|
Button(
|
||||||
@@ -685,8 +753,12 @@ class FeaturesRootSection : Routes.Route() {
|
|||||||
?: "Fresh randomized profile requested. Restart Snapchat to apply it."
|
?: "Fresh randomized profile requested. Restart Snapchat to apply it."
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else if (property.name == "view_current_profile_action") {
|
||||||
showCurrentRandomProfileDialog = true
|
showCurrentRandomProfileDialog = true
|
||||||
|
} else if (property.name == "backup_profile_action") {
|
||||||
|
backupRandomizedProfile(onConfigChanged)
|
||||||
|
} else if (property.name == "restore_profile_action") {
|
||||||
|
restoreRandomizedProfile(onConfigChanged)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
colors = ButtonDefaults.buttonColors(
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ tasks.register<GetVersionTask>("getVersion") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// You can still set these for legacy use by submodules or scripts:
|
// 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("appVersionName", providers.gradleProperty("APP_VERSION_NAME").orElse("1.6.3").get())
|
||||||
rootProject.ext.set("appVersionCode", providers.gradleProperty("APP_VERSION_CODE").orElse("314").get().toInt())
|
rootProject.ext.set("appVersionCode", providers.gradleProperty("APP_VERSION_CODE").orElse("316").get().toInt())
|
||||||
rootProject.ext.set("applicationId", "me.eternal.purrfectsnap")
|
rootProject.ext.set("applicationId", "me.eternal.purrfectsnap")
|
||||||
// buildHash: when PurrfectSnap or Snapchat is updated, mappings become outdated and auto-regenerate.
|
// 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.
|
// Include version code so each release has a different hash; use random for uniqueness within same version.
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
## v1.6.3
|
||||||
|
- Fix: Blank Global Settings page
|
||||||
|
- New: Backup & Restore option for Randomized Device Profile feature!
|
||||||
|
|
||||||
## v1.6.2
|
## v1.6.2
|
||||||
- New: Randomized Device Profile Feature!
|
- New: Randomized Device Profile Feature!
|
||||||
- Show Activation Overlay
|
- Show Activation Overlay
|
||||||
|
|||||||
@@ -2057,6 +2057,14 @@
|
|||||||
"view_current_profile_action": {
|
"view_current_profile_action": {
|
||||||
"name": "View Current Profile",
|
"name": "View Current Profile",
|
||||||
"description": "Inspect the latest randomized profile snapshot"
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2410,7 +2410,7 @@
|
|||||||
"name": "Settings Details",
|
"name": "Settings Details",
|
||||||
"description": "Enable settings spoofing and fine-tune its namespaces",
|
"description": "Enable settings spoofing and fine-tune its namespaces",
|
||||||
"properties": {
|
"properties": {
|
||||||
"secure": {
|
"secure_settings": {
|
||||||
"name": "Secure Settings",
|
"name": "Secure Settings",
|
||||||
"description": "Enable secure settings spoofing and fine-tune secure values",
|
"description": "Enable secure settings spoofing and fine-tune secure values",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -2424,7 +2424,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"system": {
|
"system_settings": {
|
||||||
"name": "System Settings",
|
"name": "System Settings",
|
||||||
"description": "Enable system settings spoofing and fine-tune system values",
|
"description": "Enable system settings spoofing and fine-tune system values",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -2438,7 +2438,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"global": {
|
"global_settings": {
|
||||||
"name": "Global Settings",
|
"name": "Global Settings",
|
||||||
"description": "Enable global settings spoofing and fine-tune global values",
|
"description": "Enable global settings spoofing and fine-tune global values",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -2565,6 +2565,14 @@
|
|||||||
"view_current_profile_action": {
|
"view_current_profile_action": {
|
||||||
"name": "View Current Profile",
|
"name": "View Current Profile",
|
||||||
"description": "Inspect the latest randomized profile snapshot"
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -115,9 +115,9 @@ class Spoof : ConfigContainer(hasGlobalState = true) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inner class RandomizedSettingsConfig : ConfigContainer(hasGlobalState = true) {
|
inner class RandomizedSettingsConfig : ConfigContainer(hasGlobalState = true) {
|
||||||
val secure = container("secure", RandomizedSecureSettingsConfig().apply { globalState = true })
|
val secureSettings = container("secure_settings", RandomizedSecureSettingsConfig().apply { globalState = true })
|
||||||
val system = container("system", RandomizedSystemSettingsConfig().apply { globalState = true })
|
val systemSettings = container("system_settings", RandomizedSystemSettingsConfig().apply { globalState = true })
|
||||||
val global = container("global", RandomizedGlobalSettingsConfig().apply { globalState = true })
|
val globalSettings = container("global_settings", RandomizedGlobalSettingsConfig().apply { globalState = true })
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class RandomizedWifiConfig : ConfigContainer(hasGlobalState = true) {
|
inner class RandomizedWifiConfig : ConfigContainer(hasGlobalState = true) {
|
||||||
@@ -178,6 +178,8 @@ class Spoof : ConfigContainer(hasGlobalState = true) {
|
|||||||
}
|
}
|
||||||
val generateFreshProfileAction = string("generate_fresh_profile_action")
|
val generateFreshProfileAction = string("generate_fresh_profile_action")
|
||||||
val viewCurrentProfileAction = string("view_current_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") {
|
val profileGenerationToken = string("profile_generation_token") {
|
||||||
addFlags(ConfigFlag.HIDDEN)
|
addFlags(ConfigFlag.HIDDEN)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -296,9 +296,9 @@ class DeviceSpooferHook : Feature("Device Spoofer") {
|
|||||||
|
|
||||||
private fun getRandomizedSettingsToggleState(): RandomizedSettingsToggleState {
|
private fun getRandomizedSettingsToggleState(): RandomizedSettingsToggleState {
|
||||||
val config = context.config.experimental.spoof.randomizeDeviceProfile.settingsOptions
|
val config = context.config.experimental.spoof.randomizeDeviceProfile.settingsOptions
|
||||||
val secure = config.secure
|
val secure = config.secureSettings
|
||||||
val system = config.system
|
val system = config.systemSettings
|
||||||
val global = config.global
|
val global = config.globalSettings
|
||||||
return RandomizedSettingsToggleState(
|
return RandomizedSettingsToggleState(
|
||||||
secure = secure.globalState == true && (secure.base.get() || secure.tts.get()),
|
secure = secure.globalState == true && (secure.base.get() || secure.tts.get()),
|
||||||
system = system.globalState == true && (system.base.get() || system.bluetooth.get()),
|
system = system.globalState == true && (system.base.get() || system.bluetooth.get()),
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ org.gradle.configuration-cache=true
|
|||||||
org.gradle.configuration-cache.problems=warn
|
org.gradle.configuration-cache.problems=warn
|
||||||
nativeAbis=arm64-v8a
|
nativeAbis=arm64-v8a
|
||||||
|
|
||||||
APP_VERSION_NAME=1.6.2
|
APP_VERSION_NAME=1.6.3
|
||||||
APP_VERSION_CODE=314
|
APP_VERSION_CODE=316
|
||||||
debug_build_hash=18fe2a814d0e2eb5
|
debug_build_hash=18fe2a814d0e2eb5
|
||||||
psIntegrityPinnedSha256=
|
psIntegrityPinnedSha256=
|
||||||
EXPECTED_CERT_SHA256=0f188cb17d8ea4902ee15ce98f4928ba4226a4790fa3c52f62d776b08e46ca1c
|
EXPECTED_CERT_SHA256=0f188cb17d8ea4902ee15ce98f4928ba4226a4790fa3c52f62d776b08e46ca1c
|
||||||
|
|||||||
Reference in New Issue
Block a user