From 8e00c29a5961903f8a0ccd644e3f9893fad57400 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=9ET=CE=9ERNAL?= Date: Thu, 2 Apr 2026 13:57:36 +0530 Subject: [PATCH] v1.6.5 --- changelogs-stable.txt | 10 + .../purrfectsnap/common/config/impl/Global.kt | 4 +- .../impl/experiments/DeviceSpooferHook.kt | 10 +- .../features/impl/tweaks/PerformanceMode.kt | 215 ++++++++++++++++-- 4 files changed, 218 insertions(+), 21 deletions(-) diff --git a/changelogs-stable.txt b/changelogs-stable.txt index 238336c7..2b42246c 100644 --- a/changelogs-stable.txt +++ b/changelogs-stable.txt @@ -1,3 +1,13 @@ +## v1.6.5 +- New: Performance mode will now be set to max by default! +- Fix: Several optimizations to the performance mode feature which will make your snapchat experience more smooth! +- Fix: Crash issues for some devices after enabling spoof +- Fix: Notification Icon for the Announcements(tq to Kaladin) +- Fix: Auto Open Engine not processing, stuck on monitor/retry/failed loop causing overheating of the devices(tq to Kaladin) +- Fix: Refactor the Notification card for the Auto Open to remove dynamic progress bar to add the native progress bar(tq to Kaladin) +- Fix: Auto Open Statistics have been refactored to now show the dynamic queue status(tq to Kaladin) +- New: Auto Open Thermal Protection/Throttle toggle, when turned on the processing will be doubled down to reduce the temps of the device to maintain a study temp of 40 C and below(tq to Kaladin) + ## v1.6.4 - New: Performance mode feature!(Smooth & Max) - Fix: Failed to init feature Device Spoofer for some devices diff --git a/common/src/main/kotlin/me/eternal/purrfectsnap/common/config/impl/Global.kt b/common/src/main/kotlin/me/eternal/purrfectsnap/common/config/impl/Global.kt index 26518d8b..d7f3f223 100644 --- a/common/src/main/kotlin/me/eternal/purrfectsnap/common/config/impl/Global.kt +++ b/common/src/main/kotlin/me/eternal/purrfectsnap/common/config/impl/Global.kt @@ -47,7 +47,9 @@ class Global : ConfigContainer() { val betterLocation = container("better_location", BetterLocationConfig()) val snapchatPlus = unique("snapchat_plus", "not_subscribed", "basic", "ad_free") { requireRestart() } val mediaUploadQualityConfig = container("media_upload_quality", MediaUploadQualityConfig()) - val performanceMode = container("performance_mode", PerformanceModeConfig()) { requireRestart() } + val performanceMode = container("performance_mode", PerformanceModeConfig()) { requireRestart() }.apply { + profile.set("max") + } val disableConfirmationDialogs = multiple("disable_confirmation_dialogs", "erase_message", "remove_friend", "block_friend", "ignore_friend", "hide_friend", "hide_conversation", "clear_conversation") { requireRestart() } val disableMetrics = boolean("disable_metrics") { requireRestart() } val disableStorySections = multiple("disable_story_sections", "friends", "suggested_stories", "following", "discover") { requireRestart(); requireCleanCache() } 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 09089465..00665201 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 @@ -357,6 +357,10 @@ class DeviceSpooferHook : Feature("Device Spoofer") { supported32BitAbis: List? = null, supported64BitAbis: List? = null ) { + val safeSupportedAbis = supportedAbis?.takeIf { it.isNotEmpty() } ?: (Build.SUPPORTED_ABIS?.toList() ?: emptyList()) + val safeSupported32BitAbis = supported32BitAbis?.takeIf { it.isNotEmpty() } ?: (Build.SUPPORTED_32_BIT_ABIS?.toList() ?: emptyList()) + val safeSupported64BitAbis = supported64BitAbis?.takeIf { it.isNotEmpty() } ?: (Build.SUPPORTED_64_BIT_ABIS?.toList() ?: emptyList()) + Build::class.java.fields.forEach { field -> if (!field.isAccessible) field.isAccessible = true runCatching { @@ -377,9 +381,9 @@ class DeviceSpooferHook : Feature("Device Spoofer") { "DISPLAY" -> if (overrideDisplay) runCatching { field.set(null, display) } "HOST" -> if (overrideHost) runCatching { field.set(null, host) } "TIME" -> if (overrideBuildTime) runCatching { field.setLong(null, buildTime) } - "SUPPORTED_ABIS" -> if (overrideAbiLists) runCatching { field.set(null, supportedAbis?.toTypedArray()) } - "SUPPORTED_32_BIT_ABIS" -> if (overrideAbiLists) runCatching { field.set(null, supported32BitAbis?.toTypedArray()) } - "SUPPORTED_64_BIT_ABIS" -> if (overrideAbiLists) runCatching { field.set(null, supported64BitAbis?.toTypedArray()) } + "SUPPORTED_ABIS" -> if (overrideAbiLists) runCatching { field.set(null, safeSupportedAbis.toTypedArray()) } + "SUPPORTED_32_BIT_ABIS" -> if (overrideAbiLists) runCatching { field.set(null, safeSupported32BitAbis.toTypedArray()) } + "SUPPORTED_64_BIT_ABIS" -> if (overrideAbiLists) runCatching { field.set(null, safeSupported64BitAbis.toTypedArray()) } } } diff --git a/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/tweaks/PerformanceMode.kt b/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/tweaks/PerformanceMode.kt index 5f798a23..8fa00135 100644 --- a/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/tweaks/PerformanceMode.kt +++ b/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/tweaks/PerformanceMode.kt @@ -2,6 +2,9 @@ package me.eternal.purrfectsnap.core.features.impl.tweaks import android.animation.ValueAnimator import android.app.Activity +import android.app.Dialog +import android.database.Cursor +import android.database.MatrixCursor import android.database.sqlite.SQLiteDatabase import android.hardware.camera2.CaptureRequest import android.media.MediaRecorder @@ -9,24 +12,41 @@ import android.os.Build import android.transition.Transition import android.os.HandlerThread import android.os.Process +import android.util.Base64 import android.util.Range import android.view.View import android.view.ViewPropertyAnimator +import android.view.WindowManager import android.view.animation.Animation import android.widget.OverScroller import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.StaggeredGridLayoutManager +import java.io.File import java.lang.Thread import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.ThreadPoolExecutor import me.eternal.purrfectsnap.core.features.Feature +import me.eternal.purrfectsnap.core.event.events.impl.NetworkApiRequestEvent import me.eternal.purrfectsnap.core.util.hook.HookStage import me.eternal.purrfectsnap.core.util.hook.hook import me.eternal.purrfectsnap.core.util.hook.hookConstructor import okhttp3.Dispatcher class PerformanceMode : Feature("Performance Mode") { + private data class SnapshotCell( + val type: Int, + val stringValue: String? = null, + val longValue: Long? = null, + val doubleValue: Double? = null, + val blobValue: String? = null, + ) + + private data class CursorSnapshot( + val columns: List, + val rows: List>, + ) + override fun init() { val profile = context.config.global.performanceMode.profile.getNullable() ?: return val isMaxProfile = profile == "max" @@ -85,14 +105,114 @@ class PerformanceMode : Feature("Performance Mode") { val sustainedModeLog = firstHitLogger("Window.setSustainedPerformanceMode") val refreshRateLog = firstHitLogger("Activity.preferredRefreshRate") val overScrollerLog = firstHitLogger("OverScroller.startScroll") + val chatFeedCacheServeLog = firstHitLogger("ChatFeed.cacheServe") + val chatFeedCacheRefreshLog = firstHitLogger("ChatFeed.cacheRefresh") + val mapDialogLog = firstHitLogger("Dialog.show") + val mapViewLog = firstHitLogger("MapView.constructor") + val mapboxNetworkBlockLog = firstHitLogger("SnapMap.telemetryBlock") + val mapCameraAnimLog = firstHitLogger("SnapMap.mapAnimatorDuration") + val mapThreadLog = firstHitLogger("SnapMap.mapThread") + val mapRendererFpsLog = firstHitLogger("SnapMap.mapRendererFps") fun isPerformanceSensitiveThread(name: String?): Boolean { val normalizedName = name?.lowercase() ?: return false - return listOf("camera", "preview", "codec", "render", "gl", "transcod", "lens", "feed", "story", "opera", "messag", "network", "db", "disk").any { + return listOf("camera", "preview", "codec", "render", "gl", "transcod", "lens", "feed", "story", "opera", "messag", "network", "db", "disk", "map", "mapbox", "snapmap", "viewport").any { normalizedName.contains(it) } } + val performanceCacheDir = File(context.androidContext.filesDir, "performance_mode_cache").apply { mkdirs() } + val chatFeedSnapshotFile = File(performanceCacheDir, "chat_feed_snapshot.json") + + fun isChatFeedQuery(sql: String): Boolean { + val normalized = sql.uppercase() + if (!normalized.startsWith("SELECT")) return false + val hitsFriendsFeedView = sql.contains("FriendsFeedView") + val hitsFeedEntry = sql.contains("feed_entry") && (sql.contains("last_updated_timestamp") || sql.contains("displayInteractionType") || sql.contains("streak_count")) + return (hitsFriendsFeedView || hitsFeedEntry) && + !normalized.contains("COUNT(") && + !normalized.contains("SELECT 0") && + !normalized.contains("WHERE KEY = ?") && + !normalized.contains("WHERE CLIENT_CONVERSATION_ID = ?") + } + + fun cursorCell(cursor: Cursor, index: Int): SnapshotCell { + return when (cursor.getType(index)) { + Cursor.FIELD_TYPE_NULL -> SnapshotCell(Cursor.FIELD_TYPE_NULL) + Cursor.FIELD_TYPE_INTEGER -> SnapshotCell(Cursor.FIELD_TYPE_INTEGER, longValue = cursor.getLong(index)) + Cursor.FIELD_TYPE_FLOAT -> SnapshotCell(Cursor.FIELD_TYPE_FLOAT, doubleValue = cursor.getDouble(index)) + Cursor.FIELD_TYPE_STRING -> SnapshotCell(Cursor.FIELD_TYPE_STRING, stringValue = cursor.getString(index)) + Cursor.FIELD_TYPE_BLOB -> SnapshotCell( + Cursor.FIELD_TYPE_BLOB, + blobValue = Base64.encodeToString(cursor.getBlob(index), Base64.NO_WRAP) + ) + else -> SnapshotCell(Cursor.FIELD_TYPE_STRING, stringValue = cursor.getString(index)) + } + } + + fun snapshotFromCursor(cursor: Cursor): CursorSnapshot { + val columns = cursor.columnNames.toList() + val rows = mutableListOf>() + if (cursor.moveToFirst()) { + do { + rows += columns.indices.map { index -> cursorCell(cursor, index) } + } while (cursor.moveToNext()) + } + return CursorSnapshot(columns, rows) + } + + fun snapshotToMatrixCursor(snapshot: CursorSnapshot): MatrixCursor { + return MatrixCursor(snapshot.columns.toTypedArray(), snapshot.rows.size).also { matrixCursor -> + snapshot.rows.forEach { row -> + matrixCursor.addRow(row.map { cell -> + when (cell.type) { + Cursor.FIELD_TYPE_NULL -> null + Cursor.FIELD_TYPE_INTEGER -> cell.longValue + Cursor.FIELD_TYPE_FLOAT -> cell.doubleValue + Cursor.FIELD_TYPE_BLOB -> cell.blobValue?.let { Base64.decode(it, Base64.NO_WRAP) } + else -> cell.stringValue + } + }) + } + } + } + + fun readSnapshot(file: File): CursorSnapshot? { + return runCatching { + if (!file.exists()) return null + context.gson.fromJson(file.readText(Charsets.UTF_8), CursorSnapshot::class.java) + }.getOrNull() + } + + fun writeSnapshot(file: File, snapshot: CursorSnapshot) { + runCatching { + file.writeText(context.gson.toJson(snapshot), Charsets.UTF_8) + }.onFailure { + context.log.error("Failed to persist friend list snapshot", it, "PerformanceMode") + } + } + + context.event.subscribe(NetworkApiRequestEvent::class) { event -> + if (!isMaxProfile) return@subscribe + val url = event.url + if (url.contains("ami/friends")) { + if (chatFeedSnapshotFile.exists()) { + chatFeedSnapshotFile.delete() + context.log.info("Invalidated chat feed snapshot after friends mutation sync", "PerformanceMode") + } + } + if (url.contains("messaging") || url.contains("conversation") || url.contains("feed")) { + if (chatFeedSnapshotFile.exists()) { + chatFeedSnapshotFile.delete() + context.log.info("Invalidated chat feed snapshot after messaging/feed network activity", "PerformanceMode") + } + } + if (url.contains("mapbox") && (url.contains("events.") || url.contains("telemetry"))) { + event.canceled = true + mapboxNetworkBlockLog("url=$url") + } + } + HandlerThread::class.java.hookConstructor(HookStage.BEFORE) { param -> if (param.args().size < 2) return@hookConstructor val threadName = param.argNullable(0) @@ -120,6 +240,9 @@ class PerformanceMode : Feature("Performance Mode") { thread.priority = Thread.MAX_PRIORITY } threadStartLog("name=${thread.name} priority=${thread.priority}") + if ((thread.name ?: "").contains("map", ignoreCase = true) || (thread.name ?: "").contains("mapbox", ignoreCase = true)) { + mapThreadLog("name=${thread.name} priority=${thread.priority}") + } } ThreadPoolExecutor::class.java.hookConstructor(HookStage.AFTER) { param -> @@ -156,6 +279,10 @@ class PerformanceMode : Feature("Performance Mode") { param.setArg(0, updated) } animatorDurationLog("requested=$original applied=${param.arg(0)}") + val thisObject = param.nullableThisObject()?.javaClass?.name ?: "" + if (thisObject.contains("map", ignoreCase = true)) { + mapCameraAnimLog("owner=$thisObject requested=$original applied=${param.arg(0)}") + } } ViewPropertyAnimator::class.java.hook("setDuration", HookStage.BEFORE) { param -> @@ -265,24 +392,17 @@ class PerformanceMode : Feature("Performance Mode") { } } + OverScroller::class.java.hook("fling", HookStage.BEFORE) { param -> + if (param.args().size >= 10) { + val overX = param.arg(8) + val overY = param.arg(9) + if (overX != 0) param.setArg(8, 0) + if (overY != 0) param.setArg(9, 0) + } + } + CaptureRequest.Builder::class.java.hook("set", HookStage.BEFORE) { param -> val key = param.arg>(0) - when (key) { - CaptureRequest.EDGE_MODE -> param.setArg(1, CaptureRequest.EDGE_MODE_FAST) - CaptureRequest.NOISE_REDUCTION_MODE -> param.setArg(1, CaptureRequest.NOISE_REDUCTION_MODE_FAST) - CaptureRequest.HOT_PIXEL_MODE -> param.setArg(1, CaptureRequest.HOT_PIXEL_MODE_FAST) - CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE -> param.setArg(1, CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE_FAST) - CaptureRequest.CONTROL_AF_MODE -> param.setArg(1, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) - CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE -> param.setArg(1, CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_OFF) - CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE -> { - val currentRange = param.argNullable(1) as? Range<*> - val lower = (currentRange?.lower as? Int) ?: minimumFrameRate - val upper = (currentRange?.upper as? Int) ?: minimumFrameRate - if (upper < minimumFrameRate) { - param.setArg(1, Range(lower.coerceAtMost(minimumFrameRate), minimumFrameRate)) - } - } - } captureRequestLog("key=${key.name} value=${param.argNullable(1)}") } @@ -308,5 +428,66 @@ class PerformanceMode : Feature("Performance Mode") { onNextActivityCreate { applyActivityPerformanceTuning(it) } + + Dialog::class.java.hook("show", HookStage.AFTER) { param -> + val dialog = param.nullableThisObject() as? Dialog ?: return@hook + val window = dialog.window ?: return@hook + runCatching { + window.setWindowAnimations(0) + window.decorView.setLayerType(View.LAYER_TYPE_HARDWARE, null) + window.attributes = window.attributes.apply { + flags = flags or WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED + } + if (dialog::class.java.name.contains("map", ignoreCase = true) || dialog::class.java.name.contains("snap", ignoreCase = true)) { + mapDialogLog("class=${dialog::class.java.name}") + } + } + } + + runCatching { + findClass("com.mapbox.mapboxsdk.maps.MapView").hookConstructor(HookStage.AFTER) { param -> + val mapView = param.nullableThisObject() as? View ?: return@hookConstructor + mapView.setLayerType(View.LAYER_TYPE_HARDWARE, null) + mapView.overScrollMode = View.OVER_SCROLL_NEVER + mapViewLog("class=${mapView::class.java.name}") + } + } + + runCatching { + findClass("com.mapbox.mapboxsdk.maps.renderer.MapRenderer").hook("setMaximumFps", HookStage.BEFORE) { param -> + val requested = param.arg(0) + val applied = requested.coerceAtLeast(120) + if (applied != requested) { + param.setArg(0, applied) + } + mapRendererFpsLog("requested=$requested applied=${param.arg(0)}") + } + } + + runCatching { + findClass("io.requery.android.database.sqlite.SQLiteDatabase").hook("rawQueryWithFactory", HookStage.BEFORE) { param -> + if (!isMaxProfile) return@hook + val sql = param.argNullable(1) ?: return@hook + if (!isChatFeedQuery(sql)) return@hook + readSnapshot(chatFeedSnapshotFile)?.let { snapshot -> + param.setResult(snapshotToMatrixCursor(snapshot)) + chatFeedCacheServeLog("rows=${snapshot.rows.size} file=${chatFeedSnapshotFile.name}") + } + } + + findClass("io.requery.android.database.sqlite.SQLiteDatabase").hook("rawQueryWithFactory", HookStage.AFTER) { param -> + if (!isMaxProfile) return@hook + val sql = param.argNullable(1) ?: return@hook + if (!isChatFeedQuery(sql)) return@hook + val cursor = param.getResult() as? Cursor ?: return@hook + val snapshot = snapshotFromCursor(cursor) + writeSnapshot(chatFeedSnapshotFile, snapshot) + param.setResult(snapshotToMatrixCursor(snapshot)) + runCatching { cursor.close() } + chatFeedCacheRefreshLog("rows=${snapshot.rows.size} file=${chatFeedSnapshotFile.name}") + } + }.onFailure { + context.log.error("Failed to install chat feed cache hooks", it, "PerformanceMode") + } } }