feat(Module): allow modules to attach own Activity.onCreate callbacks

This commit is contained in:
PalmDevs 2025-08-19 22:23:02 +07:00
parent f8f77e88a1
commit b12b6ab996
No known key found for this signature in database
GPG key ID: 6FAE8F8677ACA8BA
6 changed files with 44 additions and 34 deletions

View file

@ -42,9 +42,13 @@ data class LoaderConfig(
class Main : Module(), IXposedHookLoadPackage {
private lateinit var preloadsDir: File
private lateinit var bundle: File
private lateinit var onActivityCreate: ((Activity) -> Unit) -> Unit
private lateinit var httpJob: Deferred<Unit>
private lateinit var activity: Activity
private val onActivityCreate: ((Activity) -> Unit) -> Unit =
{ if (::activity.isInitialized) it(activity) else onActivityCreateCallbacks.add(it) }
private val onActivityCreateCallbacks = mutableSetOf<(Activity) -> Unit>()
val PRELOADS_DIR = "preloads"
val ETAG_FILE = "etag.txt"
@ -77,35 +81,33 @@ class Main : Module(), IXposedHookLoadPackage {
val reactActivity = classLoader.safeLoadClass(TARGET_ACTIVITY)
?: return
var activity: Activity? = null
val onActivityCreateCallbacks = mutableSetOf<(Activity) -> Unit>()
reactActivity.hookMethod("onCreate", Bundle::class.java) {
before {
activity = thisObject as Activity
for (cb in onActivityCreateCallbacks) cb(activity)
onActivityCreateCallbacks.clear()
this@Main.onCreate(activity)
}
}
onActivityCreate = { callback ->
activity?.let(callback) ?: onActivityCreateCallbacks.add(callback)
}
init(param)
}
override fun onCreate(activity: Activity) {
for (module in modules) module.onCreate(activity)
for (cb in onActivityCreateCallbacks) cb(activity)
onActivityCreateCallbacks.clear()
}
private fun init(
param: XC_LoadPackage.LoadPackageParam,
) = with(param) {
val cacheDir = File(appInfo.dataDir, Constants.CACHE_DIR).apply { mkdirs() }
val filesDir = File(appInfo.dataDir, Constants.FILES_DIR).apply { mkdirs() }
val cacheDir = File(appInfo.dataDir, Constants.CACHE_DIR).apply { asDir() }
val filesDir = File(appInfo.dataDir, Constants.FILES_DIR).apply { asDir() }
preloadsDir = File(filesDir, PRELOADS_DIR).apply { mkdirs() }
bundle = File(cacheDir, Constants.BUNDLE_FILE)
preloadsDir = File(filesDir, PRELOADS_DIR).apply { asDir() }
bundle = File(cacheDir, Constants.BUNDLE_FILE).apply { asFile() }
val etag = File(cacheDir, ETAG_FILE)
val configFile = File(filesDir, CONFIG_FILE)
val etag = File(cacheDir, ETAG_FILE).apply { asFile() }
val configFile = File(filesDir, CONFIG_FILE).apply { asFile() }
val config = runCatching {
JSON.decodeFromString<LoaderConfig>(configFile.readText())
@ -163,7 +165,7 @@ class Main : Module(), IXposedHookLoadPackage {
).mapNotNull { classLoader.safeLoadClass(it) }
.forEach { hookLoadScript(it) }
for (module in modules) module.onInit(param)
for (module in modules) module.onLoad(param)
// Fix resource package name mismatch
if (packageName != TARGET_PACKAGE) {

View file

@ -1,15 +1,28 @@
package io.github.revenge.xposed
import android.app.Activity
import de.robv.android.xposed.XC_MethodHook
import de.robv.android.xposed.XposedBridge
import de.robv.android.xposed.callbacks.XC_LoadPackage
import java.lang.reflect.Method
import kotlinx.serialization.json.JsonObjectBuilder
import java.io.File
abstract class Module {
open fun buildPayload(builder: JsonObjectBuilder) {}
open fun onInit(packageParam: XC_LoadPackage.LoadPackageParam) {}
open fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) {}
open fun onCreate(activity: Activity) {}
protected fun File.asDir() {
if (!this.isDirectory()) this.delete()
this.mkdirs()
}
protected fun File.asFile() {
if (!this.isFile()) this.deleteRecursively()
}
protected fun Class<*>.method(
name: String,

View file

@ -50,7 +50,7 @@ class FontsModule: Module() {
}
}
override fun onInit(packageParam: XC_LoadPackage.LoadPackageParam) = with (packageParam) {
override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) = with (packageParam) {
XposedHelpers.findAndHookMethod(
"com.facebook.react.views.text.ReactFontManager\$Companion",
classLoader,
@ -67,15 +67,15 @@ class FontsModule: Module() {
}
})
val fontDefFile = File(appInfo.dataDir, "${Constants.FILES_DIR}/fonts.json")
val fontDefFile = File(appInfo.dataDir, "${Constants.FILES_DIR}/fonts.json").apply { asFile() }
if (!fontDefFile.exists()) return@with
val fontDef = try {
JSON.decodeFromString<FontDefinition>(fontDefFile.readText())
} catch (_: Throwable) { return@with }
fontsDownloadsDir = File(appInfo.dataDir, "${Constants.FILES_DIR}/downloads/fonts").apply { mkdirs() }
fontsDir = File(fontsDownloadsDir, fontDef.name!!).apply { mkdirs() }
fontsDownloadsDir = File(appInfo.dataDir, "${Constants.FILES_DIR}/downloads/fonts").apply { asDir() }
fontsDir = File(fontsDownloadsDir, fontDef.name!!).apply { asDir() }
fontsAbsPath = fontsDir.absolutePath + "/"
fontsDir.listFiles()?.forEach { file ->
@ -96,7 +96,7 @@ class FontsModule: Module() {
val url = fontDef.main.getValue(name)
try {
Log.i("Downloading $name from $url")
val file = File(fontsDir, "$name${FILE_EXTENSIONS.first { url.endsWith(it) }}")
val file = File(fontsDir, "$name${FILE_EXTENSIONS.first { url.endsWith(it) }}").apply { asFile() }
if (file.exists()) return@async
val client = HttpClient(CIO) {
@ -132,7 +132,7 @@ class FontsModule: Module() {
try {
for (fileExtension in FILE_EXTENSIONS) {
val (customName, refName) = fontFamilyName.split(":")
val file = File(fontsDownloadsDir, "$customName/$refName.$fileExtension")
val file = File(fontsDownloadsDir, "$customName/$refName.$fileExtension").apply { asFile() }
val font = Font.Builder(file).build()
val family = FontFamily.Builder(font).build()
fontFamilies.add(family)
@ -204,7 +204,7 @@ class FontsModule: Module() {
try {
for (fileExtension in FILE_EXTENSIONS) {
val (customName, refName) = fontFamilyName.split(":")
val file = File(fontsDownloadsDir, "$customName/$refName.$fileExtension")
val file = File(fontsDownloadsDir, "$customName/$refName.$fileExtension").apply { asFile() }
if (!file.exists()) throw Exception()
return Typeface.createFromFile(file.absolutePath)
}

View file

@ -17,7 +17,7 @@ import kotlin.system.exitProcess
class LogBoxModule: Module() {
lateinit var packageParam: XC_LoadPackage.LoadPackageParam
override fun onInit(packageParam: XC_LoadPackage.LoadPackageParam) = with (packageParam) {
override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) = with (packageParam) {
this@LogBoxModule.packageParam = packageParam
val dcdReactNativeHostClass = classLoader.loadClass("com.discord.bridge.DCDReactNativeHost")

View file

@ -15,9 +15,6 @@ typealias BridgeMethodCallback = (args: BridgeMethodArgs) -> Any?
typealias BridgeMethodArgs = ArrayList<Any>
class NativeBridgeModule : Module() {
private lateinit var packageParam: XC_LoadPackage.LoadPackageParam
private lateinit var readableMapGetStringMethod: Method
private lateinit var readableMapToHashMapMethod: Method
private lateinit var argumentsMakeNativeObject: Method
@ -55,9 +52,7 @@ class NativeBridgeModule : Module() {
else -> this
})
override fun onInit(packageParam: XC_LoadPackage.LoadPackageParam) = with(packageParam) {
this@NativeBridgeModule.packageParam = packageParam
override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) = with(packageParam) {
val argumentsClass = classLoader.loadClass("com.facebook.react.bridge.Arguments")
val readableMapClass = classLoader.loadClass("com.facebook.react.bridge.ReadableMap")
val promiseClass = classLoader.loadClass("com.facebook.react.bridge.Promise")

View file

@ -56,7 +56,7 @@ class ThemeModule : Module() {
private fun String.fromScreamingSnakeToCamelCase() = this.split("_").joinToString("") { it -> it.lowercase().replaceFirstChar { it.uppercase() } }
override fun onInit(packageParam: XC_LoadPackage.LoadPackageParam) {
override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) {
param = packageParam
theme = getTheme()
hookTheme()
@ -70,7 +70,7 @@ class ThemeModule : Module() {
}
private fun getTheme(): Theme? {
val themeFile = File(param.appInfo.dataDir, "${Constants.FILES_DIR}/${THEME_FILE}")
val themeFile = File(param.appInfo.dataDir, "${Constants.FILES_DIR}/${THEME_FILE}").apply { asFile() }
if (!themeFile.isValidish()) return null
return try {