fix: prevent module load failures from hiding fonts and themes pages

This commit is contained in:
kmmiio99o 2026-09-25 16:58:08 +02:00
parent 7d15a63b4c
commit 0b39a29e8e
No known key found for this signature in database
GPG key ID: DCC0431E10FADBF1
4 changed files with 78 additions and 37 deletions

View file

@ -58,7 +58,7 @@ class Main : Module(), IXposedHookLoadPackage, IXposedHookZygoteInit {
}
override fun initZygote(startupParam: IXposedHookZygoteInit.StartupParam) {
for (module in modules) module.onInit(startupParam)
dispatch("onInit") { module -> module.onInit(startupParam) }
}
override fun handleLoadPackage(param: XC_LoadPackage.LoadPackageParam) = with(param) {
@ -96,14 +96,25 @@ class Main : Module(), IXposedHookLoadPackage, IXposedHookZygoteInit {
}
override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) {
for (module in modules) module.onLoad(packageParam)
dispatch("onLoad") { module -> module.onLoad(packageParam) }
}
override fun onContext(context: Context) {
for (module in modules) module.onContext(context)
dispatch("onContext") { module -> module.onContext(context) }
}
override fun onActivity(activity: Activity) {
for (module in modules) module.onActivity(activity)
dispatch("onActivity") { module -> module.onActivity(activity) }
}
private fun dispatch(stage: String, block: (Module) -> Unit) {
for (module in modules) {
val name = module.javaClass.simpleName.ifEmpty { module.javaClass.name }
try {
block(module)
} catch (e: Throwable) {
Log.e("Module $name failed during $stage", e)
}
}
}
}

View file

@ -8,6 +8,7 @@ import ShiggyXposed.xposed.BuildConfig
import ShiggyXposed.xposed.Constants
import ShiggyXposed.xposed.Module
import ShiggyXposed.xposed.Utils.Companion.JSON
import ShiggyXposed.xposed.Utils.Log
import ShiggyXposed.xposed.modules.HookScriptLoaderModule.PRELOADS_DIR
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
@ -23,7 +24,11 @@ class PayloadGlobalModule(private val modules: List<Module>) : Module() {
buildJsonObject {
put("loaderName", Constants.LOADER_NAME)
put("loaderVersion", BuildConfig.VERSION_NAME)
@Suppress("DEPRECATION") for (module in modules) module.buildPayload(this)
@Suppress("DEPRECATION") for (module in modules) runCatching {
module.buildPayload(this)
}.onFailure {
Log.e("Module ${module.javaClass.simpleName} failed while building payload", it)
}
})
override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) = with(packageParam) {

View file

@ -40,6 +40,11 @@ object FontsModule : Module() {
private val FILE_EXTENSIONS = arrayOf(".ttf", ".otf")
private const val FONTS_ASSET_PATH = "fonts/"
private val FONT_MANAGER_CLASSES = arrayOf(
"com.facebook.react.views.text.ReactFontManager\$Companion",
"com.facebook.react.common.assets.ReactFontManager\$Companion",
)
private lateinit var fontsDir: File
private lateinit var fontsDownloadsDir: File
private var fontsAbsPath: String? = null
@ -51,21 +56,33 @@ object FontsModule : Module() {
}
override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) = with(packageParam) {
XposedHelpers.findAndHookMethod(
"com.facebook.react.common.assets.ReactFontManager\$Companion",
classLoader,
"createAssetTypeface",
String::class.java,
Int::class.java,
"android.content.res.AssetManager",
object : XC_MethodReplacement() {
val replacement = object : XC_MethodReplacement() {
override fun replaceHookedMethod(param: MethodHookParam): Typeface? {
val fontFamilyName: String = param.args[0].toString()
val style: Int = param.args[1] as Int
val assetManager: AssetManager = param.args[2] as AssetManager
return createAssetTypeface(fontFamilyName, style, assetManager)
}
})
}
// React Native moved ReactFontManager between releases, so try every known package.
// Never let this throw: an uncaught error here aborts the whole module chain.
val hooked = FONT_MANAGER_CLASSES.any { className ->
runCatching {
XposedHelpers.findAndHookMethod(
className,
classLoader,
"createAssetTypeface",
String::class.java,
Int::class.java,
"android.content.res.AssetManager",
replacement
)
}.onFailure { Log.e("Failed to hook createAssetTypeface on $className", it) }
.isSuccess
}
if (!hooked) Log.e("Failed to hook createAssetTypeface on all known ReactFontManager classes")
val fontDefFile = File(appInfo.dataDir, "${Constants.FILES_DIR}/fonts.json").apply { asFile() }
if (!fontDefFile.exists()) return@with

View file

@ -7,6 +7,7 @@ import de.robv.android.xposed.callbacks.XC_LoadPackage
import ShiggyXposed.xposed.Constants
import ShiggyXposed.xposed.Module
import ShiggyXposed.xposed.Utils.Companion.JSON
import ShiggyXposed.xposed.Utils.Log
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonObjectBuilder
@ -81,12 +82,17 @@ object ThemesModule : Module() {
}
fun hookTheme() {
val themeManager = param.classLoader.loadClass("com.discord.theme.utils.ColorUtilsKt")
val darkTheme = param.classLoader.loadClass("com.discord.theme.DarkerTheme")
val lightTheme = param.classLoader.loadClass("com.discord.theme.LightTheme")
val theme = this.theme ?: return
val themeManager = param.classLoader.safeLoadClass("com.discord.theme.utils.ColorUtilsKt")
val darkTheme = param.classLoader.safeLoadClass("com.discord.theme.DarkerTheme")
val lightTheme = param.classLoader.safeLoadClass("com.discord.theme.LightTheme")
if (themeManager == null || darkTheme == null || lightTheme == null) {
Log.e("Failed to load Discord theme classes, skipping theme hooks")
return
}
// Apply rawColors
theme.data.rawColors?.forEach { (key, value) ->
rawColorMap[key.lowercase()] = hexStringToColorInt(value)
@ -106,17 +112,6 @@ object ThemesModule : Module() {
// If there's any rawColors value, hook the color getter
if (!theme.data.rawColors.isNullOrEmpty()) {
val getColorCompat = themeManager.getDeclaredMethod(
"getColorCompat",
Resources::class.java,
Int::class.javaPrimitiveType,
Resources.Theme::class.java,
)
val getColorCompatLegacy = themeManager.getDeclaredMethod(
"getColorCompat", Context::class.java, Int::class.javaPrimitiveType
)
val patch = MethodHookBuilder().run {
before {
val arg1 = args[0]
@ -129,8 +124,21 @@ object ThemesModule : Module() {
build()
}
getColorCompat.hook(patch)
getColorCompatLegacy.hook(patch)
listOf(
runCatching {
themeManager.getDeclaredMethod(
"getColorCompat",
Resources::class.java,
Int::class.javaPrimitiveType,
Resources.Theme::class.java,
)
}.getOrNull(),
runCatching {
themeManager.getDeclaredMethod(
"getColorCompat", Context::class.java, Int::class.javaPrimitiveType
)
}.getOrNull()
).filterNotNull().forEach { it.hook(patch) }
}
}