diff --git a/app/src/main/kotlin/cocobo1/pupu/xposed/Main.kt b/app/src/main/kotlin/cocobo1/pupu/xposed/Main.kt index da2e785..70d0220 100644 --- a/app/src/main/kotlin/cocobo1/pupu/xposed/Main.kt +++ b/app/src/main/kotlin/cocobo1/pupu/xposed/Main.kt @@ -36,23 +36,23 @@ object HookStateHolder { class Main : Module(), IXposedHookLoadPackage, IXposedHookZygoteInit { private var hooked = false private val modules = mutableListOf( - HookScriptLoaderModule(), - BridgeModule(), - AdditionalBridgeMethodsModule(), + HookScriptLoaderModule, + BridgeModule, + AdditionalBridgeMethodsModule, PluginsModule(), UpdaterModule, - FixResourcesModule(), - BlockDeepLinksTrackingModule(), - BlockCrashReportingModule(), - LogBoxModule(), - CacheModule(), - FontsModule(), - ThemesModule(), - SysColorsModule() + FixResourcesModule, + BlockDeepLinksTrackingModule, + BlockCrashReportingModule, + LogBoxModule, + CacheModule, + FontsModule, + ThemesModule, + SysColorsModule ) init { - modules.add(PayloadGlobalModule(modules)) + modules += PayloadGlobalModule(modules) } override fun initZygote(startupParam: IXposedHookZygoteInit.StartupParam) { diff --git a/app/src/main/kotlin/cocobo1/pupu/xposed/modules/CacheModule.kt b/app/src/main/kotlin/cocobo1/pupu/xposed/modules/CacheModule.kt index ffe6f59..61a3410 100644 --- a/app/src/main/kotlin/cocobo1/pupu/xposed/modules/CacheModule.kt +++ b/app/src/main/kotlin/cocobo1/pupu/xposed/modules/CacheModule.kt @@ -33,13 +33,13 @@ import java.io.* * 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 companion object { - const val CACHE_DIR = "kettu" +object CacheModule : Module() { + + private const val CACHE_DIR = "kettu" + + private const val MODULES_CACHE_PREFIX = "modules" + private const 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 diff --git a/app/src/main/kotlin/cocobo1/pupu/xposed/modules/FixResourcesModule.kt b/app/src/main/kotlin/cocobo1/pupu/xposed/modules/FixResourcesModule.kt index 09e12d8..cbefe02 100644 --- a/app/src/main/kotlin/cocobo1/pupu/xposed/modules/FixResourcesModule.kt +++ b/app/src/main/kotlin/cocobo1/pupu/xposed/modules/FixResourcesModule.kt @@ -8,7 +8,7 @@ import cocobo1.pupu.xposed.Module /** * Hooks [Resources.getIdentifier] to fix resource package name mismatch. */ -class FixResourcesModule : Module() { +object FixResourcesModule : Module() { override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) = with(packageParam) { if (packageName != TARGET_PACKAGE) { Resources::class.java.hookMethod( diff --git a/app/src/main/kotlin/cocobo1/pupu/xposed/modules/HookScriptLoaderModule.kt b/app/src/main/kotlin/cocobo1/pupu/xposed/modules/HookScriptLoaderModule.kt index 8d780f3..0eb237f 100644 --- a/app/src/main/kotlin/cocobo1/pupu/xposed/modules/HookScriptLoaderModule.kt +++ b/app/src/main/kotlin/cocobo1/pupu/xposed/modules/HookScriptLoaderModule.kt @@ -24,16 +24,14 @@ import java.lang.reflect.Method * The main bundle should be placed in the [Constants.CACHE_DIR] directory named [Constants.MAIN_SCRIPT_FILE]. * If the bundle file does not exist, it will attempt to load `assets://kettu.bundle` from the module's assets. */ -class HookScriptLoaderModule : Module() { +object HookScriptLoaderModule : Module() { private lateinit var preloadsDir: File private lateinit var mainScript: File private lateinit var modulePath: String private lateinit var resources: XModuleResources - companion object { - const val PRELOADS_DIR = "preloads" - } + const val PRELOADS_DIR = "preloads" override fun onInit(startupParam: IXposedHookZygoteInit.StartupParam) { this@HookScriptLoaderModule.modulePath = startupParam.modulePath @@ -51,23 +49,16 @@ class HookScriptLoaderModule : Module() { "com.facebook.react.runtime.ReactInstance$1", // TODO: Remove once Discord fully switches to Bridgeless "com.facebook.react.bridge.CatalystInstanceImpl" - ).mapNotNull { classLoader.safeLoadClass(it) } - .forEach { hook(it) } + ).mapNotNull { classLoader.safeLoadClass(it) }.forEach { hook(it) } } private fun hook(instance: Class<*>) = runCatching { val loadScriptFromAssets = instance.method( - "loadScriptFromAssets", - AssetManager::class.java, - String::class.java, - Boolean::class.javaPrimitiveType + "loadScriptFromAssets", AssetManager::class.java, String::class.java, Boolean::class.javaPrimitiveType ) val loadScriptFromFile = instance.method( - "loadScriptFromFile", - String::class.java, - String::class.java, - Boolean::class.javaPrimitiveType + "loadScriptFromFile", String::class.java, String::class.java, Boolean::class.javaPrimitiveType ) loadScriptFromAssets.hook { @@ -102,25 +93,20 @@ class HookScriptLoaderModule : Module() { Log.i("Loading script: ${file.absolutePath}") XposedBridge.invokeOriginalMethod( - loadScriptFromFile, - thisObject, - arrayOf(file.absolutePath, file.absolutePath, loadSynchronously) + loadScriptFromFile, thisObject, arrayOf(file.absolutePath, file.absolutePath, loadSynchronously) ) Unit } try { - preloadsDir.walk() - .filter { it.isFile } - .forEach(runScriptFile) + preloadsDir.walk().filter { it.isFile }.forEach(runScriptFile) if (mainScript.exists()) runScriptFile(mainScript) else { Log.i("Main script does not exist, falling back") - if (!::resources.isInitialized) resources = - XModuleResources.createInstance(modulePath, null) + if (!::resources.isInitialized) resources = XModuleResources.createInstance(modulePath, null) XposedBridge.invokeOriginalMethod( loadScriptFromAssets, diff --git a/app/src/main/kotlin/cocobo1/pupu/xposed/modules/appearance/FontsModule.kt b/app/src/main/kotlin/cocobo1/pupu/xposed/modules/appearance/FontsModule.kt index 9df7124..45212b6 100644 --- a/app/src/main/kotlin/cocobo1/pupu/xposed/modules/appearance/FontsModule.kt +++ b/app/src/main/kotlin/cocobo1/pupu/xposed/modules/appearance/FontsModule.kt @@ -35,12 +35,10 @@ data class FontDefinition( val main: Map, ) -class FontsModule : Module() { - private companion object { - val EXTENSIONS = arrayOf("", "_bold", "_italic", "_bold_italic") - val FILE_EXTENSIONS = arrayOf(".ttf", ".otf") - const val FONTS_ASSET_PATH = "fonts/" - } +object FontsModule : Module() { + private val EXTENSIONS = arrayOf("", "_bold", "_italic", "_bold_italic") + private val FILE_EXTENSIONS = arrayOf(".ttf", ".otf") + private const val FONTS_ASSET_PATH = "fonts/" private lateinit var fontsDir: File private lateinit var fontsDownloadsDir: File diff --git a/app/src/main/kotlin/cocobo1/pupu/xposed/modules/appearance/SysColorsModule.kt b/app/src/main/kotlin/cocobo1/pupu/xposed/modules/appearance/SysColorsModule.kt index 95c550a..4b58185 100644 --- a/app/src/main/kotlin/cocobo1/pupu/xposed/modules/appearance/SysColorsModule.kt +++ b/app/src/main/kotlin/cocobo1/pupu/xposed/modules/appearance/SysColorsModule.kt @@ -7,13 +7,14 @@ import android.os.Build import androidx.core.content.ContextCompat import cocobo1.pupu.xposed.Module import kotlinx.serialization.json.* +import java.lang.ref.WeakReference -class SysColorsModule : Module() { - private lateinit var context: Context +object SysColorsModule : Module() { + private lateinit var context: WeakReference private fun isSupported() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S override fun buildPayload(builder: JsonObjectBuilder) { - context = AndroidAppHelper.currentApplication() + context = WeakReference(AndroidAppHelper.currentApplication()) val accents = arrayOf("accent1", "accent2", "accent3", "neutral1", "neutral2") val shades = arrayOf(0, 10, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000) @@ -36,7 +37,7 @@ class SysColorsModule : Module() { } private fun convertToColor(id: Int): String { - val clr = if (isSupported()) ContextCompat.getColor(context, id) else 0 + val clr = if (isSupported()) ContextCompat.getColor(context.get()!!, id) else 0 return String.format("#%06X", 0xFFFFFF and clr) } } \ No newline at end of file diff --git a/app/src/main/kotlin/cocobo1/pupu/xposed/modules/appearance/ThemesModule.kt b/app/src/main/kotlin/cocobo1/pupu/xposed/modules/appearance/ThemesModule.kt index 09beca1..80e7090 100644 --- a/app/src/main/kotlin/cocobo1/pupu/xposed/modules/appearance/ThemesModule.kt +++ b/app/src/main/kotlin/cocobo1/pupu/xposed/modules/appearance/ThemesModule.kt @@ -34,15 +34,14 @@ data class Theme( val id: String, val selected: Boolean, val data: ThemeData ) -class ThemesModule : Module() { +object ThemesModule : Module() { private lateinit var param: XC_LoadPackage.LoadPackageParam private var theme: Theme? = null private val rawColorMap = mutableMapOf() - private companion object { - const val THEME_FILE = "current-theme.json" - } + private const val THEME_FILE = "current-theme.json" + @ExperimentalSerializationApi override fun buildPayload(builder: JsonObjectBuilder) { @@ -62,7 +61,7 @@ class ThemesModule : Module() { hookTheme() } - private fun File.isValidish(): Boolean { + private fun File.isValidThemeFile(): Boolean { if (!this.exists()) return false val text = this.readText() @@ -71,7 +70,7 @@ class ThemesModule : Module() { private fun getTheme(): Theme? { val themeFile = File(param.appInfo.dataDir, "${Constants.FILES_DIR}/${THEME_FILE}").apply { asFile() } - if (!themeFile.isValidish()) return null + if (!themeFile.isValidThemeFile()) return null return try { val themeText = themeFile.readText() diff --git a/app/src/main/kotlin/cocobo1/pupu/xposed/modules/bridge/AdditionalBridgeMethodsModule.kt b/app/src/main/kotlin/cocobo1/pupu/xposed/modules/bridge/AdditionalBridgeMethodsModule.kt index 632fa3c..17e686c 100644 --- a/app/src/main/kotlin/cocobo1/pupu/xposed/modules/bridge/AdditionalBridgeMethodsModule.kt +++ b/app/src/main/kotlin/cocobo1/pupu/xposed/modules/bridge/AdditionalBridgeMethodsModule.kt @@ -11,7 +11,7 @@ import cocobo1.pupu.xposed.Module import cocobo1.pupu.xposed.Utils import java.io.File -class AdditionalBridgeMethodsModule : Module() { +object AdditionalBridgeMethodsModule : Module() { override fun onContext(context: Context) = with(context) { BridgeModule.registerMethod("kettu.fs.getConstants") { mapOf( diff --git a/app/src/main/kotlin/cocobo1/pupu/xposed/modules/bridge/BridgeModule.kt b/app/src/main/kotlin/cocobo1/pupu/xposed/modules/bridge/BridgeModule.kt index f93a8b7..eeb298c 100644 --- a/app/src/main/kotlin/cocobo1/pupu/xposed/modules/bridge/BridgeModule.kt +++ b/app/src/main/kotlin/cocobo1/pupu/xposed/modules/bridge/BridgeModule.kt @@ -45,43 +45,28 @@ typealias BridgeMethodArgs = ArrayList * } * ``` */ -class BridgeModule : Module() { +object BridgeModule : Module() { private lateinit var readableMapGetString: Method private lateinit var readableMapToHashMap: Method private lateinit var argumentsMakeNative: Method - companion object { - private const val CALL_DATA_KEY = "kettu" - private const val METHOD_NAME_KEY = "method" - private const val METHOD_ARGS_KEY = "args" + private const val CALL_DATA_KEY = "kettu" + private const val METHOD_NAME_KEY = "method" + private const val METHOD_ARGS_KEY = "args" - private val methods: MutableMap = mutableMapOf("kettu.info" to { - mapOf( - "name" to Constants.LOADER_NAME, "version" to BuildConfig.VERSION_CODE - ) - }, "kettu.test" to { - mapOf( - "string" to "string", - "number" to 7256, - "array" to listOf("testing", 527737, listOf(true)), - "object" to mapOf("nested" to true), - "boolean" to false, - "args" to it, - ) - }) + private val methods: MutableMap = mutableMapOf() - /** - * Registers a bridge method that can be called from JavaScript. - * - * If the method is already registered, it will be overwritten. - * - * @param name The name of the method to register. - * @param callback The callback to invoke when the method is called. - */ - fun registerMethod(name: String, callback: BridgeMethodCallback) { - if (methods.containsKey(name)) Log.w("Bridge method already exists and will be overridden: $name") - methods[name] = callback - } + /** + * Registers a bridge method that can be called from JavaScript. + * + * If the method is already registered, it will be overwritten. + * + * @param name The name of the method to register. + * @param callback The callback to invoke when the method is called. + */ + fun registerMethod(name: String, callback: BridgeMethodCallback) { + if (methods.containsKey(name)) Log.w("Bridge method already exists and will be overridden: $name") + methods[name] = callback } override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) = with(packageParam) { @@ -112,9 +97,32 @@ class BridgeModule : Module() { } } + // Clear methods every package load. Plugins should re-register on each load. + methods.clear() + registerDefaultMethods() + return@with } + private fun registerDefaultMethods() { + methods["revenge.info"] = { + mapOf( + "name" to Constants.LOADER_NAME, "version" to BuildConfig.VERSION_CODE + ) + } + + methods["revenge.test"] = { + mapOf( + "string" to "string", + "number" to 7256, + "array" to listOf("testing", 527737, listOf(true)), + "object" to mapOf("nested" to true), + "boolean" to false, + "args" to it, + ) + } + } + private fun Any?.toNativeObject(): Any? = argumentsMakeNative.invoke( null, when (this) { Unit -> null diff --git a/app/src/main/kotlin/cocobo1/pupu/xposed/modules/no_track/BlockCrashReportingModule.kt b/app/src/main/kotlin/cocobo1/pupu/xposed/modules/no_track/BlockCrashReportingModule.kt index da59312..f84ecb7 100644 --- a/app/src/main/kotlin/cocobo1/pupu/xposed/modules/no_track/BlockCrashReportingModule.kt +++ b/app/src/main/kotlin/cocobo1/pupu/xposed/modules/no_track/BlockCrashReportingModule.kt @@ -9,7 +9,7 @@ import cocobo1.pupu.xposed.Utils.Log * Hooks Discord's crash reporting to disable Sentry initialization. * While Discord doesn't have Sentry auto-initialization on by default, we still hook the content provider just to be safe. */ -class BlockCrashReportingModule : Module() { +object BlockCrashReportingModule : Module() { override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) = with(packageParam) { val crashReportingClass = classLoader.safeLoadClass("com.discord.crash_reporting.CrashReporting") crashReportingClass?.hookMethod( diff --git a/app/src/main/kotlin/cocobo1/pupu/xposed/modules/no_track/BlockDeepLinksTrackingModule.kt b/app/src/main/kotlin/cocobo1/pupu/xposed/modules/no_track/BlockDeepLinksTrackingModule.kt index 07eec4f..dd3f1fa 100644 --- a/app/src/main/kotlin/cocobo1/pupu/xposed/modules/no_track/BlockDeepLinksTrackingModule.kt +++ b/app/src/main/kotlin/cocobo1/pupu/xposed/modules/no_track/BlockDeepLinksTrackingModule.kt @@ -8,7 +8,7 @@ import cocobo1.pupu.xposed.Utils.Log /** * Hooks Discord's deep links tracking to disable AppsFlyer initialization. */ -class BlockDeepLinksTrackingModule : Module() { +object BlockDeepLinksTrackingModule : Module() { override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) = with(packageParam) { val deepLinksClass = classLoader.safeLoadClass("com.discord.deep_link.DeepLinks") deepLinksClass?.hookMethod(