From 21d693290a9085084cae288d454bf22dbc53916d Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 15 Sep 2025 22:52:14 +0700 Subject: [PATCH 1/9] feat: more native bridge methods --- .../io/github/revenge/xposed/Constants.kt | 2 + .../kotlin/io/github/revenge/xposed/Main.kt | 4 +- .../bridge/AdditionalBridgeMethodsModule.kt | 45 +++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 app/src/main/kotlin/io/github/revenge/xposed/modules/bridge/AdditionalBridgeMethodsModule.kt diff --git a/app/src/main/kotlin/io/github/revenge/xposed/Constants.kt b/app/src/main/kotlin/io/github/revenge/xposed/Constants.kt index c22a78d..a2f9a00 100644 --- a/app/src/main/kotlin/io/github/revenge/xposed/Constants.kt +++ b/app/src/main/kotlin/io/github/revenge/xposed/Constants.kt @@ -9,9 +9,11 @@ class Constants { const val CACHE_DIR = "cache/pyoncord" const val MAIN_SCRIPT_FILE = "bundle.js" + const val LOG_TAG = "Revenge" const val LOADER_NAME = "RevengeXposed" + const val USER_AGENT = "RevengeXposed" } } \ No newline at end of file diff --git a/app/src/main/kotlin/io/github/revenge/xposed/Main.kt b/app/src/main/kotlin/io/github/revenge/xposed/Main.kt index ea99f66..c5547ce 100644 --- a/app/src/main/kotlin/io/github/revenge/xposed/Main.kt +++ b/app/src/main/kotlin/io/github/revenge/xposed/Main.kt @@ -11,6 +11,7 @@ import io.github.revenge.xposed.modules.* import io.github.revenge.xposed.modules.appearance.FontsModule import io.github.revenge.xposed.modules.appearance.SysColorsModule import io.github.revenge.xposed.modules.appearance.ThemeModule +import io.github.revenge.xposed.modules.bridge.AdditionalBridgeMethodsModule import io.github.revenge.xposed.modules.bridge.BridgeModule class Main : Module(), IXposedHookLoadPackage, IXposedHookZygoteInit { @@ -18,12 +19,13 @@ class Main : Module(), IXposedHookLoadPackage, IXposedHookZygoteInit { private val modules = mutableListOf( HookScriptLoaderModule(), BridgeModule(), + AdditionalBridgeMethodsModule(), UpdaterModule(), FixResourcesModule(), LogBoxModule(), FontsModule(), ThemeModule(), - SysColorsModule(), + SysColorsModule() ) init { diff --git a/app/src/main/kotlin/io/github/revenge/xposed/modules/bridge/AdditionalBridgeMethodsModule.kt b/app/src/main/kotlin/io/github/revenge/xposed/modules/bridge/AdditionalBridgeMethodsModule.kt new file mode 100644 index 0000000..eb252f1 --- /dev/null +++ b/app/src/main/kotlin/io/github/revenge/xposed/modules/bridge/AdditionalBridgeMethodsModule.kt @@ -0,0 +1,45 @@ +package io.github.revenge.xposed.modules.bridge + +import android.app.AlertDialog +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.os.Build +import android.widget.Toast +import io.github.revenge.xposed.Module + +class AdditionalBridgeMethodsModule : Module() { + override fun onContext(context: Context) = with(context) { + BridgeModule.registerMethod("revenge.alertError") { + val (error, version) = it + val app = getAppInfo() + val errorString = "$error" + + val clipboard = applicationContext.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clip = ClipData.newPlainText("Stack Trace", errorString) + + AlertDialog.Builder(this) + .setTitle("Revenge Error") + .setMessage( + """ + Revenge: $version + ${app.name}: ${app.version} (${app.versionCode}) + Device: ${Build.MANUFACTURER} ${Build.MODEL} + + + """.trimIndent() + errorString + ) + .setPositiveButton("OK") { dialog, _ -> + dialog.dismiss() + } + .setNeutralButton("Copy") { dialog, _ -> + clipboard.setPrimaryClip(clip) + Toast.makeText(applicationContext, "Copied stack trace", Toast.LENGTH_SHORT).show() + dialog.dismiss() + } + .show() + + null + } + } +} \ No newline at end of file From 3bba1e9d6a72688b2854929b8cb30feeb5547a14 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 19 Aug 2025 22:29:05 +0700 Subject: [PATCH 2/9] feat(modules): add `CacheModule` --- .idea/.name | 1 - .../kotlin/io/github/revenge/xposed/Main.kt | 1 + .../revenge/xposed/modules/CacheModule.kt | 252 ++++++++++++++++++ 3 files changed, 253 insertions(+), 1 deletion(-) delete mode 100644 .idea/.name create mode 100644 app/src/main/kotlin/io/github/revenge/xposed/modules/CacheModule.kt diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index ca200db..0000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -RevengeXposed \ No newline at end of file diff --git a/app/src/main/kotlin/io/github/revenge/xposed/Main.kt b/app/src/main/kotlin/io/github/revenge/xposed/Main.kt index c5547ce..e6018fb 100644 --- a/app/src/main/kotlin/io/github/revenge/xposed/Main.kt +++ b/app/src/main/kotlin/io/github/revenge/xposed/Main.kt @@ -23,6 +23,7 @@ class Main : Module(), IXposedHookLoadPackage, IXposedHookZygoteInit { UpdaterModule(), FixResourcesModule(), LogBoxModule(), + CacheModule(), FontsModule(), ThemeModule(), SysColorsModule() 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 new file mode 100644 index 0000000..03efd16 --- /dev/null +++ b/app/src/main/kotlin/io/github/revenge/xposed/modules/CacheModule.kt @@ -0,0 +1,252 @@ +package io.github.revenge.xposed.modules + +import android.app.Activity +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.* + +class CacheModule : Module() { + private val CACHE_DIR = "revenge" + + private val MODULES_CACHE_PREFIX = "modules" + private val ASSETS_CACHE_PREFIX = "assets" + + private lateinit var modulesCache: ModulesCache + private lateinit var assetsCache: AssetsCache + + override fun onActivity(activity: Activity) = with(activity) { + 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() } + + ModulesCache.loadFromFileOrNull(modulesCacheFile)?.let { modulesCache = it } + AssetsCache.loadFromFileOrNull(assetsCacheFile)?.let { assetsCache = it } + + BridgeModule.registerMethod("revenge.caches.modules.read") { + if (::modulesCache.isInitialized) modulesCache.toMap() else null + } + + 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}") + } + + BridgeModule.registerMethod("revenge.caches.assets.read") { + if (::assetsCache.isInitialized) assetsCache.toMap() else null + } + + + 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}") + } + + return@with + } +} + +private class CacheVersionMismatchException(val expected: Int, val actual: Int) : + Throwable("Expected cache version: $expected, but got version: $actual") + +data class ModulesCache( + val blacklist: List, + val finds: Map?> +) { + fun saveToFile(file: File) { + val atomic = AtomicFile(file) + var fos: FileOutputStream? = null + try { + fos = atomic.startWrite() + val out = DataOutputStream(BufferedOutputStream(fos)) + + out.writeInt(VERSION) + + // Write blacklist + out.writeInt(blacklist.size) + for (num in blacklist) out.writeInt(num.toInt()) + + // Write finds + out.writeInt(finds.size) + for ((filter, matches) in finds) { + out.writeUTF(filter) // replaces writeUTF + if (matches == null) { + out.writeBoolean(false) // null marker + } else { + out.writeBoolean(true) // not null + out.writeInt(matches.size) + for ((id, flag) in matches) { + out.writeInt(id.toInt()) + out.writeInt(flag.toInt()) + } + } + } + + out.flush() + atomic.finishWrite(fos) + } catch (t: Throwable) { + if (fos != null) atomic.failWrite(fos) + throw t + } + } + + fun toMap(): Map { + val findsMap = + finds.mapValues { (_, matches) -> matches?.map { (k, v) -> k to v }?.toMap() } + + return mapOf("blacklist" to blacklist, "finds" to findsMap, "version" to VERSION) + } + + companion object { + val VERSION = 3 + + fun loadFromFileOrNull(file: File): ModulesCache? { + if (!file.exists() || file.length() <= 0L) return null + val atomic = AtomicFile(file) + try { + DataInputStream(BufferedInputStream(atomic.openRead())).use { input -> + val version = input.readInt() + if (version != VERSION) + throw CacheVersionMismatchException(VERSION, version) + + val blacklistSize = input.readInt() + val blacklist = MutableList(blacklistSize) { input.readInt().toDouble() } + + val findsSize = input.readInt() + val finds = mutableMapOf?>() + repeat(findsSize) { + val filter = input.readUTF() + val hasMatches = input.readBoolean() + if (!hasMatches) { + finds[filter] = null + } else { + val mapSize = input.readInt() + val matches = mutableMapOf() + repeat(mapSize) { + val id = input.readInt().toString() + val flag = input.readInt().toDouble() + matches[id] = flag + } + finds[filter] = matches + } + } + + return ModulesCache(blacklist, finds) + } + } catch (e: CacheVersionMismatchException) { + Log.i("Modules cache version mismatch: ${e.message}") + } catch (e: EOFException) { + Log.e("Modules cache corrupt: ${e.message}") + } catch (e: IOException) { + Log.e("Failed to read modules cache: ${e.message}") + } + runCatching { file.delete() } + return null + } + } +} + +data class AssetsCache( + val data: Map> +) { + fun saveToFile(file: File) { + val atomic = AtomicFile(file) + var fos: FileOutputStream? = null + try { + fos = atomic.startWrite() + val out = DataOutputStream(BufferedOutputStream(fos)) + + out.writeInt(VERSION) + + out.writeInt(data.size) + for ((name, mappings) in data) { + out.writeUTF(name) + out.writeInt(mappings.size) + for ((type, id) in mappings) { + out.writeUTF(type) + out.writeInt(id.toInt()) + } + } + + out.flush() + atomic.finishWrite(fos) + } catch (t: Throwable) { + if (fos != null) atomic.failWrite(fos) + throw t + } + } + + fun toMap(): Map { + return mapOf( + "data" to data.mapValues { (_, mappings) -> + mappings.map { (type, id) -> type to id }.toMap() + }, + "version" to VERSION + ) + } + + companion object { + val VERSION = 2 + + fun loadFromFileOrNull(file: File): AssetsCache? { + if (!file.exists() || file.length() <= 0L) return null + val atomic = AtomicFile(file) + try { + DataInputStream(BufferedInputStream(atomic.openRead())).use { input -> + val version = input.readInt() + if (version != VERSION) + throw CacheVersionMismatchException(VERSION, version) + + val dataSize = input.readInt() + val data = mutableMapOf>() + + repeat(dataSize) { + val name = input.readUTF() + val mappingsSize = input.readInt() + val mappings = mutableMapOf() + + repeat(mappingsSize) { + val type = input.readUTF() + val id = input.readInt().toDouble() + mappings[type] = id + } + + data[name] = mappings + } + + return AssetsCache(data) + } + } catch (e: CacheVersionMismatchException) { + Log.i("Assets cache version mismatch: ${e.message}") + } catch (e: EOFException) { + Log.e("Assets cache corrupt: ${e.message}") + } catch (e: IOException) { + Log.e("Failed to read assets cache: ${e.message}") + } + runCatching { file.delete() } + return null + } + } +} \ No newline at end of file From d54e3dd25ad44c508504a33e0bc753ac84e717d1 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Thu, 4 Sep 2025 21:59:37 +0700 Subject: [PATCH 3/9] feat(modules): add `PluginsModule` Only adding saving states for now. --- .../kotlin/io/github/revenge/xposed/Main.kt | 1 + .../revenge/xposed/modules/PluginsModule.kt | 116 ++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 app/src/main/kotlin/io/github/revenge/xposed/modules/PluginsModule.kt diff --git a/app/src/main/kotlin/io/github/revenge/xposed/Main.kt b/app/src/main/kotlin/io/github/revenge/xposed/Main.kt index e6018fb..b7afc7f 100644 --- a/app/src/main/kotlin/io/github/revenge/xposed/Main.kt +++ b/app/src/main/kotlin/io/github/revenge/xposed/Main.kt @@ -20,6 +20,7 @@ class Main : Module(), IXposedHookLoadPackage, IXposedHookZygoteInit { HookScriptLoaderModule(), BridgeModule(), AdditionalBridgeMethodsModule(), + PluginsModule(), UpdaterModule(), FixResourcesModule(), LogBoxModule(), 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 new file mode 100644 index 0000000..dd8f3f3 --- /dev/null +++ b/app/src/main/kotlin/io/github/revenge/xposed/modules/PluginsModule.kt @@ -0,0 +1,116 @@ +package io.github.revenge.xposed.modules + +import android.app.Activity +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.* + +class PluginsModule : Module() { + private val DATA_DIR = "revenge/plugins" + private val STATES_FILE = "states" + + private lateinit var states: PluginStates + + override fun onActivity(activity: Activity) = with(activity) { + val dataDir = File(filesDir, DATA_DIR) + + val statesFile = File( + dataDir, + STATES_FILE + ).apply { asFile() } + + BridgeModule.registerMethod("revenge.plugins.states.read") { + if (::states.isInitialized) states.toMap() else + PluginStates.loadFromFileOrNull(statesFile)?.let { + states = it + it.toMap() + } + } + + BridgeModule.registerMethod("revenge.plugins.states.write") { + val (flags) = it + @Suppress("UNCHECKED_CAST") + states = + PluginStates( + flags as Map + ) + .apply { saveToFile(statesFile) } + Log.i("Plugin states saved: ${statesFile.absolutePath}") + } + + return@with + } +} + +private class UnsupportedPluginStatesVersionException(version: Int) : + Throwable("Unsupported plugin states version: $version") + +data class PluginStates( + val flags: Map +) { + fun saveToFile(file: File) { + val atomic = AtomicFile(file) + var fos: FileOutputStream? = null + try { + fos = atomic.startWrite() + val out = DataOutputStream(BufferedOutputStream(fos)) + + out.writeInt(CURRENT_VERSION) + + // Write flags + out.writeInt(flags.size) + for ((id, flags) in flags) { + out.writeUTF(id) + out.writeInt(flags.toInt()) + } + + out.flush() + atomic.finishWrite(fos) + } catch (t: Throwable) { + if (fos != null) atomic.failWrite(fos) + throw t + } + } + + fun toMap(): Map = mapOf("flags" to flags) + + companion object { + const val CURRENT_VERSION = 1 + + fun loadFromFileOrNull(file: File): PluginStates? { + if (!file.exists() || file.length() <= 0L) return null + val atomic = AtomicFile(file) + + try { + DataInputStream(BufferedInputStream(atomic.openRead())).use { input -> + when (val version = input.readInt()) { + 1 -> return loadV1FromFileOrNull(input) + else -> throw UnsupportedPluginStatesVersionException(version) + } + } + } catch (e: UnsupportedPluginStatesVersionException) { + Log.i(e.message!!) + } catch (e: EOFException) { + Log.e("Plugin states corrupt: ${e.message}") + } catch (e: IOException) { + Log.e("Failed to read plugin states: ${e.message}") + } + + runCatching { file.delete() } + return null + } + + private fun loadV1FromFileOrNull(input: DataInputStream): PluginStates { + val flagsSize = input.readInt() + val flags = mutableMapOf() + + repeat(flagsSize) { + flags[input.readUTF()] = input.readInt().toDouble() + } + + return PluginStates(flags) + } + } +} \ No newline at end of file From 0edc47cf657d98e120ec92c969b87ec969c57158 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Thu, 4 Sep 2025 22:01:44 +0700 Subject: [PATCH 4/9] fix: editor warnings --- .../kotlin/io/github/revenge/xposed/modules/CacheModule.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 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 03efd16..7eeaba3 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 @@ -68,7 +68,7 @@ class CacheModule : Module() { } } -private class CacheVersionMismatchException(val expected: Int, val actual: Int) : +private class CacheVersionMismatchException(expected: Int, actual: Int) : Throwable("Expected cache version: $expected, but got version: $actual") data class ModulesCache( @@ -120,7 +120,7 @@ data class ModulesCache( } companion object { - val VERSION = 3 + const val VERSION = 3 fun loadFromFileOrNull(file: File): ModulesCache? { if (!file.exists() || file.length() <= 0L) return null @@ -208,7 +208,7 @@ data class AssetsCache( } companion object { - val VERSION = 2 + const val VERSION = 2 fun loadFromFileOrNull(file: File): AssetsCache? { if (!file.exists() || file.length() <= 0L) return null From 656cb361c8d68992c3847432179358a73b5fa0fb Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 9 Sep 2025 01:31:46 +0700 Subject: [PATCH 5/9] fix(modules): load caches and plugin states lazily --- .../main/kotlin/io/github/revenge/xposed/modules/CacheModule.kt | 2 -- 1 file changed, 2 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 7eeaba3..e8f454c 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 @@ -31,8 +31,6 @@ class CacheModule : Module() { "$ASSETS_CACHE_PREFIX.$versionCode" ).apply { asFile() } - ModulesCache.loadFromFileOrNull(modulesCacheFile)?.let { modulesCache = it } - AssetsCache.loadFromFileOrNull(assetsCacheFile)?.let { assetsCache = it } BridgeModule.registerMethod("revenge.caches.modules.read") { if (::modulesCache.isInitialized) modulesCache.toMap() else null From 85fd27fc4844b7f9518d682448f4e082afd5dfb9 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Tue, 9 Sep 2025 02:24:10 +0700 Subject: [PATCH 6/9] chore: update dependencies and sdk ints --- .idea/AndroidProjectSystem.xml | 6 ++++++ app/build.gradle.kts | 18 +++++++++--------- build.gradle.kts | 2 +- 3 files changed, 16 insertions(+), 10 deletions(-) create mode 100644 .idea/AndroidProjectSystem.xml diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml new file mode 100644 index 0000000..4a53bee --- /dev/null +++ b/.idea/AndroidProjectSystem.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ac52bda..7293f39 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -6,14 +6,14 @@ plugins { android { namespace = "io.github.revenge.xposed" - compileSdk = 35 + compileSdk = 36 defaultConfig { applicationId = "io.github.revenge.xposed" minSdk = 24 - targetSdk = 35 - versionCode = 1202 - versionName = "1.2.2" + targetSdk = 36 + versionCode = 1400 + versionName = "1.4.0" } buildTypes { @@ -41,9 +41,9 @@ android { dependencies { compileOnly("de.robv.android.xposed:api:82") - implementation("androidx.core:core-ktx:1.16.0") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.1") - implementation("io.ktor:ktor-client-core:3.1.1") - implementation("io.ktor:ktor-client-cio:3.1.1") + implementation("androidx.core:core-ktx:1.17.0") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2") + implementation("io.ktor:ktor-client-core:3.2.3") + implementation("io.ktor:ktor-client-cio:3.2.3") } diff --git a/build.gradle.kts b/build.gradle.kts index bfc2823..d421a94 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,7 +5,7 @@ buildscript { mavenCentral() } dependencies { - classpath("com.android.tools.build:gradle:8.9.2") + classpath("com.android.tools.build:gradle:8.11.0") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.10") // NOTE: Do not place your application dependencies here; they belong From f015246e9454cfdac38b1655568e6558f8b3718b Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 15 Sep 2025 22:53:57 +0700 Subject: [PATCH 7/9] chore: update project configuration --- .idea/codeStyles/Project.xml | 115 +---------------------- .idea/compiler.xml | 2 +- .idea/deploymentTargetDropDown.xml | 10 -- .idea/deploymentTargetSelector.xml | 11 +-- .idea/deviceManager.xml | 13 +++ .idea/gradle.xml | 1 - .idea/jarRepositories.xml | 30 ------ .idea/kotlinc.xml | 6 -- .idea/migrations.xml | 10 -- .idea/misc.xml | 9 +- .idea/vcs.xml | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 12 files changed, 24 insertions(+), 187 deletions(-) delete mode 100644 .idea/deploymentTargetDropDown.xml create mode 100644 .idea/deviceManager.xml delete mode 100644 .idea/jarRepositories.xml delete mode 100644 .idea/kotlinc.xml delete mode 100644 .idea/migrations.xml diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 7643783..0def1f7 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -1,121 +1,10 @@ + diff --git a/.idea/deviceManager.xml b/.idea/deviceManager.xml new file mode 100644 index 0000000..91f9558 --- /dev/null +++ b/.idea/deviceManager.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 7b3006b..639c779 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -13,7 +13,6 @@ - diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml deleted file mode 100644 index e34606c..0000000 --- a/.idea/jarRepositories.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml deleted file mode 100644 index c22b6fa..0000000 --- a/.idea/kotlinc.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/migrations.xml b/.idea/migrations.xml deleted file mode 100644 index f8051a6..0000000 --- a/.idea/migrations.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 74dd639..2ccd954 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,10 +1,7 @@ - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 35eb1dd..94a25f7 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e2847c8..37f853b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From c0045b2e1e3398048563189db30a9aeddd4a473e Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 15 Sep 2025 23:19:04 +0700 Subject: [PATCH 8/9] 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 } } From d21d130c9bb8bd29b091f4b18dc2cfadd796c8b6 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Mon, 15 Sep 2025 23:19:49 +0700 Subject: [PATCH 9/9] fix(modules/UpdaterModule): mark `job` as `Volatile` and `private set` --- .../kotlin/io/github/revenge/xposed/modules/UpdaterModule.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/kotlin/io/github/revenge/xposed/modules/UpdaterModule.kt b/app/src/main/kotlin/io/github/revenge/xposed/modules/UpdaterModule.kt index 59df27d..5356f45 100644 --- a/app/src/main/kotlin/io/github/revenge/xposed/modules/UpdaterModule.kt +++ b/app/src/main/kotlin/io/github/revenge/xposed/modules/UpdaterModule.kt @@ -47,9 +47,7 @@ class UpdaterModule : Module() { private lateinit var etag: File companion object { - @Volatile var job: Job? = null - private set private const val TIMEOUT_CACHED = 5000L private const val TIMEOUT = 10000L