fix: modern Discord themes, fonts, package hook filter, and bundle update
Some checks failed
Build APK / Build and push (push) Failing after 4m10s
Some checks failed
Build APK / Build and push (push) Failing after 4m10s
This commit is contained in:
parent
b9980f5cb9
commit
8055dbf3ff
5 changed files with 111 additions and 95 deletions
File diff suppressed because one or more lines are too long
|
|
@ -60,10 +60,15 @@ class Main : Module(), IXposedHookLoadPackage, IXposedHookZygoteInit {
|
||||||
override fun initZygote(startupParam: IXposedHookZygoteInit.StartupParam) {
|
override fun initZygote(startupParam: IXposedHookZygoteInit.StartupParam) {
|
||||||
for (module in modules) module.onInit(startupParam)
|
for (module in modules) module.onInit(startupParam)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handleLoadPackage(param: XC_LoadPackage.LoadPackageParam) = with(param) {
|
override fun handleLoadPackage(param: XC_LoadPackage.LoadPackageParam) = with(param) {
|
||||||
if (packageName != Constants.TARGET_PACKAGE) return
|
val isTargetApp = packageName == Constants.TARGET_PACKAGE
|
||||||
if (processName != Constants.TARGET_PACKAGE) return
|
|| packageName.startsWith("com.discord")
|
||||||
|
|| packageName == "dev.shiggy.cord"
|
||||||
|
|| packageName.contains("cord")
|
||||||
|
|| packageName.contains("discord")
|
||||||
|
|
||||||
|
if (!isTargetApp) return
|
||||||
|
if (processName != packageName) return
|
||||||
if (hooked) return
|
if (hooked) return
|
||||||
|
|
||||||
val reactActivity = try {
|
val reactActivity = try {
|
||||||
|
|
|
||||||
|
|
@ -59,24 +59,9 @@ object LogBoxModule : Module() {
|
||||||
override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) = with(packageParam) {
|
override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) = with(packageParam) {
|
||||||
this@LogBoxModule.packageParam = packageParam
|
this@LogBoxModule.packageParam = packageParam
|
||||||
|
|
||||||
runCatching {
|
// Do not force getUseDeveloperSupport = true, as it causes React Native to loop
|
||||||
val dcdReactNativeHostClass = classLoader.loadClass("com.discord.bridge.DCDReactNativeHost")
|
// attempting to connect to a Metro bundler on localhost:8081 and hang the app loading screen.
|
||||||
val getUseDeveloperSupportMethod =
|
// Shake detector and recovery menu work independently without it.
|
||||||
dcdReactNativeHostClass.declaredMethods.firstOrNull { it.name == "getUseDeveloperSupport" }
|
|
||||||
?: dcdReactNativeHostClass.methods.firstOrNull { it.name == "getUseDeveloperSupport" }
|
|
||||||
|
|
||||||
if (getUseDeveloperSupportMethod != null) {
|
|
||||||
getUseDeveloperSupportMethod.isAccessible = true
|
|
||||||
XposedBridge.hookMethod(getUseDeveloperSupportMethod, object : XC_MethodHook() {
|
|
||||||
override fun beforeHookedMethod(param: MethodHookParam) {
|
|
||||||
param.result = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
Log.i("Successfully hooked DCDReactNativeHost")
|
|
||||||
}
|
|
||||||
}.onFailure { e ->
|
|
||||||
Log.w("Could not hook DCDReactNativeHost: ${e.message}")
|
|
||||||
}
|
|
||||||
|
|
||||||
return@with
|
return@with
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,21 +61,30 @@ object FontsModule : Module() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) = with(packageParam) {
|
override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) = with(packageParam) {
|
||||||
XposedHelpers.findAndHookMethod(
|
val fontManagerClass = runCatching {
|
||||||
"com.facebook.react.common.assets.ReactFontManager\$Companion",
|
classLoader.loadClass("com.facebook.react.common.assets.ReactFontManager\$Companion")
|
||||||
classLoader,
|
}.getOrNull() ?: runCatching {
|
||||||
"createAssetTypeface",
|
classLoader.loadClass("com.facebook.react.views.text.ReactFontManager\$Companion")
|
||||||
String::class.java,
|
}.getOrNull()
|
||||||
Int::class.java,
|
|
||||||
"android.content.res.AssetManager",
|
if (fontManagerClass != null) {
|
||||||
object : XC_MethodReplacement() {
|
XposedHelpers.findAndHookMethod(
|
||||||
override fun replaceHookedMethod(param: MethodHookParam): Typeface? {
|
fontManagerClass,
|
||||||
val fontFamilyName: String = param.args[0].toString()
|
"createAssetTypeface",
|
||||||
val style: Int = param.args[1] as Int
|
String::class.java,
|
||||||
val assetManager: AssetManager = param.args[2] as AssetManager
|
Int::class.java,
|
||||||
return createAssetTypeface(fontFamilyName, style, assetManager)
|
"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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Log.w("FontsModule: ReactFontManager\$Companion class not found")
|
||||||
|
}
|
||||||
|
|
||||||
val fontDefFile = File(appInfo.dataDir, "${Constants.FILES_DIR}/fonts.json").apply { asFile() }
|
val fontDefFile = File(appInfo.dataDir, "${Constants.FILES_DIR}/fonts.json").apply { asFile() }
|
||||||
if (!fontDefFile.exists()) return@with
|
if (!fontDefFile.exists()) return@with
|
||||||
|
|
@ -149,12 +158,16 @@ object FontsModule : Module() {
|
||||||
// for use in the CustomFallbackBuilder below.
|
// for use in the CustomFallbackBuilder below.
|
||||||
for (fontFamilyName in fontFamilyNames) {
|
for (fontFamilyName in fontFamilyNames) {
|
||||||
try {
|
try {
|
||||||
for (fileExtension in FILE_EXTENSIONS) {
|
if (fontFamilyName.contains(":")) {
|
||||||
val (customName, refName) = fontFamilyName.split(":")
|
for (fileExtension in FILE_EXTENSIONS) {
|
||||||
val file = File(fontsDownloadsDir, "$customName/$refName.$fileExtension").apply { asFile() }
|
val (customName, refName) = fontFamilyName.split(":")
|
||||||
val font = Font.Builder(file).build()
|
val file = File(fontsDownloadsDir, "$customName/$refName.$fileExtension").apply { asFile() }
|
||||||
val family = FontFamily.Builder(font).build()
|
if (file.exists()) {
|
||||||
fontFamilies.add(family)
|
val font = Font.Builder(file).build()
|
||||||
|
val family = FontFamily.Builder(font).build()
|
||||||
|
fontFamilies.add(family)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (_: Throwable) {
|
} catch (_: Throwable) {
|
||||||
}
|
}
|
||||||
|
|
@ -218,11 +231,14 @@ object FontsModule : Module() {
|
||||||
val extension = EXTENSIONS[style]
|
val extension = EXTENSIONS[style]
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (fileExtension in FILE_EXTENSIONS) {
|
if (fontFamilyName.contains(":")) {
|
||||||
val (customName, refName) = fontFamilyName.split(":")
|
for (fileExtension in FILE_EXTENSIONS) {
|
||||||
val file = File(fontsDownloadsDir, "$customName/$refName.$fileExtension").apply { asFile() }
|
val (customName, refName) = fontFamilyName.split(":")
|
||||||
if (!file.exists()) throw Exception()
|
val file = File(fontsDownloadsDir, "$customName/$refName.$fileExtension").apply { asFile() }
|
||||||
return Typeface.createFromFile(file.absolutePath)
|
if (file.exists()) {
|
||||||
|
return Typeface.createFromFile(file.absolutePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (_: Throwable) {
|
} catch (_: Throwable) {
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -86,18 +86,20 @@ object ThemesModule : Module() {
|
||||||
|
|
||||||
fun hookTheme() {
|
fun hookTheme() {
|
||||||
try {
|
try {
|
||||||
val themeManager = param.classLoader.loadClass("com.discord.theme.utils.ColorUtilsKt")
|
val themeManager = runCatching { param.classLoader.loadClass("com.discord.theme.utils.ColorUtilsKt") }.getOrNull()
|
||||||
val darkTheme = param.classLoader.loadClass("com.discord.theme.DarkerTheme")
|
val darkTheme = runCatching { param.classLoader.loadClass("com.discord.theme.DarkTheme") }.getOrNull()
|
||||||
val lightTheme = param.classLoader.loadClass("com.discord.theme.LightTheme")
|
?: runCatching { param.classLoader.loadClass("com.discord.theme.DarkerTheme") }.getOrNull()
|
||||||
|
val onyxTheme = runCatching { param.classLoader.loadClass("com.discord.theme.OnyxTheme") }.getOrNull()
|
||||||
|
val lightTheme = runCatching { param.classLoader.loadClass("com.discord.theme.LightTheme") }.getOrNull()
|
||||||
|
|
||||||
hookThemeInternal(themeManager, darkTheme, lightTheme)
|
hookThemeInternal(themeManager, darkTheme, onyxTheme, lightTheme)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
GoonXposed.xposed.Utils.Log.e("Failed to hook theme: ${e.message}")
|
GoonXposed.xposed.Utils.Log.e("Failed to hook theme: ${e.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun hookThemeInternal(
|
private fun hookThemeInternal(
|
||||||
themeManager: Class<*>, darkTheme: Class<*>, lightTheme: Class<*>
|
themeManager: Class<*>?, darkTheme: Class<*>?, onyxTheme: Class<*>?, lightTheme: Class<*>?
|
||||||
) {
|
) {
|
||||||
val theme = this.theme ?: return
|
val theme = this.theme ?: return
|
||||||
|
|
||||||
|
|
@ -111,26 +113,36 @@ object ThemesModule : Module() {
|
||||||
// TEXT_NORMAL -> getTextNormal
|
// TEXT_NORMAL -> getTextNormal
|
||||||
val methodName = "get${key.fromScreamingSnakeToCamelCase()}"
|
val methodName = "get${key.fromScreamingSnakeToCamelCase()}"
|
||||||
value.forEachIndexed { index, v ->
|
value.forEachIndexed { index, v ->
|
||||||
|
val colorInt = hexStringToColorInt(v)
|
||||||
when (index) {
|
when (index) {
|
||||||
0 -> hookThemeMethod(darkTheme, methodName, hexStringToColorInt(v))
|
0 -> {
|
||||||
1 -> hookThemeMethod(lightTheme, methodName, hexStringToColorInt(v))
|
if (darkTheme != null) hookThemeMethod(darkTheme, methodName, colorInt)
|
||||||
|
if (onyxTheme != null) hookThemeMethod(onyxTheme, methodName, colorInt)
|
||||||
|
}
|
||||||
|
1 -> {
|
||||||
|
if (lightTheme != null) hookThemeMethod(lightTheme, methodName, colorInt)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there's any rawColors value, hook the color getter
|
// If there's any rawColors value, hook the color getter
|
||||||
if (!theme.data.rawColors.isNullOrEmpty()) {
|
if (!theme.data.rawColors.isNullOrEmpty() && themeManager != null) {
|
||||||
try {
|
try {
|
||||||
val getColorCompat = themeManager.getDeclaredMethod(
|
val getColorCompat = runCatching {
|
||||||
"getColorCompat",
|
themeManager.getDeclaredMethod(
|
||||||
Resources::class.java,
|
"getColorCompat",
|
||||||
Int::class.javaPrimitiveType,
|
Resources::class.java,
|
||||||
Resources.Theme::class.java,
|
Int::class.javaPrimitiveType,
|
||||||
)
|
Resources.Theme::class.java,
|
||||||
|
)
|
||||||
|
}.getOrNull()
|
||||||
|
|
||||||
val getColorCompatLegacy = themeManager.getDeclaredMethod(
|
val getColorCompatLegacy = runCatching {
|
||||||
"getColorCompat", Context::class.java, Int::class.javaPrimitiveType
|
themeManager.getDeclaredMethod(
|
||||||
)
|
"getColorCompat", Context::class.java, Int::class.javaPrimitiveType
|
||||||
|
)
|
||||||
|
}.getOrNull()
|
||||||
|
|
||||||
val patch = MethodHookBuilder().run {
|
val patch = MethodHookBuilder().run {
|
||||||
before {
|
before {
|
||||||
|
|
@ -147,8 +159,8 @@ object ThemesModule : Module() {
|
||||||
build()
|
build()
|
||||||
}
|
}
|
||||||
|
|
||||||
getColorCompat.hook(patch)
|
getColorCompat?.hook(patch)
|
||||||
getColorCompatLegacy.hook(patch)
|
getColorCompatLegacy?.hook(patch)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
GoonXposed.xposed.Utils.Log.e("Failed to hook getColorCompat: ${e.message}")
|
GoonXposed.xposed.Utils.Log.e("Failed to hook getColorCompat: ${e.message}")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue