diff --git a/app/src/main/kotlin/ShiggyXposed/xposed/xposed/Main.kt b/app/src/main/kotlin/ShiggyXposed/xposed/xposed/Main.kt index b7ddec6..1feeeb0 100644 --- a/app/src/main/kotlin/ShiggyXposed/xposed/xposed/Main.kt +++ b/app/src/main/kotlin/ShiggyXposed/xposed/xposed/Main.kt @@ -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) + } + } } } diff --git a/app/src/main/kotlin/ShiggyXposed/xposed/xposed/modules/PayloadGlobalModule.kt b/app/src/main/kotlin/ShiggyXposed/xposed/xposed/modules/PayloadGlobalModule.kt index 956cfc2..5a3b9ae 100644 --- a/app/src/main/kotlin/ShiggyXposed/xposed/xposed/modules/PayloadGlobalModule.kt +++ b/app/src/main/kotlin/ShiggyXposed/xposed/xposed/modules/PayloadGlobalModule.kt @@ -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() { 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) { diff --git a/app/src/main/kotlin/ShiggyXposed/xposed/xposed/modules/appearance/FontsModule.kt b/app/src/main/kotlin/ShiggyXposed/xposed/xposed/modules/appearance/FontsModule.kt index 4a0fa5d..29b14ee 100644 --- a/app/src/main/kotlin/ShiggyXposed/xposed/xposed/modules/appearance/FontsModule.kt +++ b/app/src/main/kotlin/ShiggyXposed/xposed/xposed/modules/appearance/FontsModule.kt @@ -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() { - 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) - } - }) + 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 diff --git a/app/src/main/kotlin/ShiggyXposed/xposed/xposed/modules/appearance/ThemesModule.kt b/app/src/main/kotlin/ShiggyXposed/xposed/xposed/modules/appearance/ThemesModule.kt index 4c55133..a1009d3 100644 --- a/app/src/main/kotlin/ShiggyXposed/xposed/xposed/modules/appearance/ThemesModule.kt +++ b/app/src/main/kotlin/ShiggyXposed/xposed/xposed/modules/appearance/ThemesModule.kt @@ -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) } } }