From c0045b2e1e3398048563189db30a9aeddd4a473e Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 15 Sep 2025 23:19:04 +0700 Subject: [PATCH] fix(modules): register all native methods in `onContext` --- .../revenge/xposed/modules/CacheModule.kt | 102 +++++++++++------- .../revenge/xposed/modules/PluginsModule.kt | 34 +++--- 2 files changed, 82 insertions(+), 54 deletions(-) diff --git a/app/src/main/kotlin/io/github/revenge/xposed/modules/CacheModule.kt b/app/src/main/kotlin/io/github/revenge/xposed/modules/CacheModule.kt index e8f454c..54db659 100644 --- a/app/src/main/kotlin/io/github/revenge/xposed/modules/CacheModule.kt +++ b/app/src/main/kotlin/io/github/revenge/xposed/modules/CacheModule.kt @@ -1,68 +1,93 @@ package io.github.revenge.xposed.modules -import android.app.Activity +import android.content.Context import android.util.AtomicFile import io.github.revenge.xposed.Module import io.github.revenge.xposed.Utils.Log import io.github.revenge.xposed.modules.bridge.BridgeModule import java.io.* +/** + * A module for caching versioned modules finds and blacklist, and assets mappings. + * + * See [ModulesCache] and [AssetsCache] for the cache structure. + * + * ## Methods + * + * ### Modules cache + * + * - `revenge.caches.modules.read() : { blacklist: number[], finds: { [filter: string]: { [id: string]: number } | null }, version: number } | null` + * - Reads the modules cache. Returns `null` if the cache file does not exist or is invalid. + * + * - `revenge.caches.modules.write(blacklist: number[], finds: { [filter: string]: { [id: string]: number } | null }) : void` + * - Writes the modules cache. + * + * ### Assets cache + * + * - `revenge.caches.assets.read() : { data: { [name: string]: { [type: string]: number } }, version: number } | null` + * - Reads the assets cache. Returns `null` if the cache file does not exist or is invalid. + * + * - `revenge.caches.assets.write(data: { [name: string]: { [type: string]: number } }) : void` + * - Writes the assets cache. + * + * The caches are versioned and tied to the app version code. When the app is updated, + * the caches are invalidated and new cache files are created. + */ class CacheModule : Module() { - private val CACHE_DIR = "revenge" + private companion object { + const val CACHE_DIR = "revenge" - private val MODULES_CACHE_PREFIX = "modules" - private val ASSETS_CACHE_PREFIX = "assets" + const val MODULES_CACHE_PREFIX = "modules" + const val ASSETS_CACHE_PREFIX = "assets" + } private lateinit var modulesCache: ModulesCache private lateinit var assetsCache: AssetsCache - override fun onActivity(activity: Activity) = with(activity) { + override fun onContext(context: Context) = with(context) { val revengeCacheDir = File(cacheDir, CACHE_DIR).apply { asDir() } val (_, _, _, versionCode) = getAppInfo() - val modulesCacheFile = - File( - revengeCacheDir, - "$MODULES_CACHE_PREFIX.$versionCode" - ).apply { asFile() } - val assetsCacheFile = - File( - revengeCacheDir, - "$ASSETS_CACHE_PREFIX.$versionCode" - ).apply { asFile() } + val modulesCacheFile = File( + revengeCacheDir, "$MODULES_CACHE_PREFIX.$versionCode" + ).apply { asFile() } + val assetsCacheFile = File( + revengeCacheDir, "$ASSETS_CACHE_PREFIX.$versionCode" + ).apply { asFile() } BridgeModule.registerMethod("revenge.caches.modules.read") { - if (::modulesCache.isInitialized) modulesCache.toMap() else null + if (::modulesCache.isInitialized) modulesCache.toMap() else ModulesCache.loadFromFileOrNull(modulesCacheFile) + ?.let { + modulesCache = it + it.toMap() + } } BridgeModule.registerMethod("revenge.caches.modules.write") { val (blacklist, finds) = it @Suppress("UNCHECKED_CAST") - modulesCache = - ModulesCache( - blacklist as ArrayList, - finds as HashMap?> - ) - .apply { saveToFile(modulesCacheFile) } - Log.i("Modules cache saved: ${modulesCacheFile.absolutePath}") + modulesCache = ModulesCache( + blacklist as ArrayList, finds as HashMap?> + ).apply { saveToFile(modulesCacheFile) } + Log.i("Modules cache saved: ${modulesCacheFile.absolutePath} (blacklisted: ${blacklist.size}, finds: ${finds.size})") } BridgeModule.registerMethod("revenge.caches.assets.read") { - if (::assetsCache.isInitialized) assetsCache.toMap() else null + if (::assetsCache.isInitialized) assetsCache.toMap() else AssetsCache.loadFromFileOrNull(assetsCacheFile) + ?.let { + assetsCache = it + it.toMap() + } } - BridgeModule.registerMethod("revenge.caches.assets.write") { val (data) = it @Suppress("UNCHECKED_CAST") assetsCache = - AssetsCache(data as HashMap>) - .apply { saveToFile(assetsCacheFile) } - Log.i("Assets cache saved: ${assetsCacheFile.absolutePath}") + AssetsCache(data as HashMap>).apply { saveToFile(assetsCacheFile) } + Log.i("Assets cache saved: ${assetsCacheFile.absolutePath} (count: ${data.size})") } - - return@with } } @@ -70,8 +95,7 @@ private class CacheVersionMismatchException(expected: Int, actual: Int) : Throwable("Expected cache version: $expected, but got version: $actual") data class ModulesCache( - val blacklist: List, - val finds: Map?> + val blacklist: List, val finds: Map?> ) { fun saveToFile(file: File) { val atomic = AtomicFile(file) @@ -89,7 +113,7 @@ data class ModulesCache( // Write finds out.writeInt(finds.size) for ((filter, matches) in finds) { - out.writeUTF(filter) // replaces writeUTF + out.writeUTF(filter) if (matches == null) { out.writeBoolean(false) // null marker } else { @@ -111,8 +135,7 @@ data class ModulesCache( } fun toMap(): Map { - val findsMap = - finds.mapValues { (_, matches) -> matches?.map { (k, v) -> k to v }?.toMap() } + val findsMap = finds.mapValues { (_, matches) -> matches?.map { (k, v) -> k to v }?.toMap() } return mapOf("blacklist" to blacklist, "finds" to findsMap, "version" to VERSION) } @@ -126,8 +149,7 @@ data class ModulesCache( try { DataInputStream(BufferedInputStream(atomic.openRead())).use { input -> val version = input.readInt() - if (version != VERSION) - throw CacheVersionMismatchException(VERSION, version) + if (version != VERSION) throw CacheVersionMismatchException(VERSION, version) val blacklistSize = input.readInt() val blacklist = MutableList(blacklistSize) { input.readInt().toDouble() } @@ -200,8 +222,7 @@ data class AssetsCache( return mapOf( "data" to data.mapValues { (_, mappings) -> mappings.map { (type, id) -> type to id }.toMap() - }, - "version" to VERSION + }, "version" to VERSION ) } @@ -214,8 +235,7 @@ data class AssetsCache( try { DataInputStream(BufferedInputStream(atomic.openRead())).use { input -> val version = input.readInt() - if (version != VERSION) - throw CacheVersionMismatchException(VERSION, version) + if (version != VERSION) throw CacheVersionMismatchException(VERSION, version) val dataSize = input.readInt() val data = mutableMapOf>() diff --git a/app/src/main/kotlin/io/github/revenge/xposed/modules/PluginsModule.kt b/app/src/main/kotlin/io/github/revenge/xposed/modules/PluginsModule.kt index dd8f3f3..5948b43 100644 --- a/app/src/main/kotlin/io/github/revenge/xposed/modules/PluginsModule.kt +++ b/app/src/main/kotlin/io/github/revenge/xposed/modules/PluginsModule.kt @@ -1,24 +1,36 @@ package io.github.revenge.xposed.modules -import android.app.Activity +import android.content.Context import android.util.AtomicFile import io.github.revenge.xposed.Module import io.github.revenge.xposed.Utils.Log import io.github.revenge.xposed.modules.bridge.BridgeModule import java.io.* +/** + * Manages the states of plugins, allowing reading and writing of plugin states to a file. + * + * ## Methods + * + * - `revenge.plugins.states.read(): { flags: { [pluginId: string]: number } }` + * - Reads the current plugin states from the file and returns them as a map. + * + * - `revenge.plugins.states.write(flags: { [pluginId: string]: number }): void` + * - Writes the provided plugin states to the file. + */ class PluginsModule : Module() { - private val DATA_DIR = "revenge/plugins" - private val STATES_FILE = "states" + private companion object { + const val DATA_DIR = "revenge/plugins" + const val STATES_FILE = "states" + } private lateinit var states: PluginStates - override fun onActivity(activity: Activity) = with(activity) { + override fun onContext(context: Context) = with(context) { val dataDir = File(filesDir, DATA_DIR) val statesFile = File( - dataDir, - STATES_FILE + dataDir, STATES_FILE ).apply { asFile() } BridgeModule.registerMethod("revenge.plugins.states.read") { @@ -32,15 +44,11 @@ class PluginsModule : Module() { BridgeModule.registerMethod("revenge.plugins.states.write") { val (flags) = it @Suppress("UNCHECKED_CAST") - states = - PluginStates( - flags as Map - ) - .apply { saveToFile(statesFile) } + states = PluginStates( + flags as Map + ).apply { saveToFile(statesFile) } Log.i("Plugin states saved: ${statesFile.absolutePath}") } - - return@with } }