Merge remote-tracking branch 'upstream'
This commit is contained in:
commit
c2ecba402b
11 changed files with 83 additions and 91 deletions
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -35,12 +35,10 @@ data class FontDefinition(
|
|||
val main: Map<String, String>,
|
||||
)
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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<Context>
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String, Int>()
|
||||
|
||||
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()
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -45,43 +45,28 @@ typealias BridgeMethodArgs = ArrayList<Any>
|
|||
* }
|
||||
* ```
|
||||
*/
|
||||
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<String, BridgeMethodCallback> = 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<String, BridgeMethodCallback> = 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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue