Update Main.kt

This commit is contained in:
OTKUSteyler 2026-09-09 17:45:20 +02:00 • committed by GitHub
parent 750a4cd5b0
commit 2d62630e30
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,228 +1,109 @@
package GoonXposed.xposed.modules.appearance package GoonXposed.xposed
import android.content.res.AssetManager import android.app.Activity
import android.graphics.Typeface import android.content.Context
import android.graphics.Typeface.CustomFallbackBuilder import android.content.ContextWrapper
import android.graphics.fonts.Font import android.os.Bundle
import android.graphics.fonts.FontFamily import de.robv.android.xposed.IXposedHookLoadPackage
import android.os.Build import de.robv.android.xposed.IXposedHookZygoteInit
import de.robv.android.xposed.XC_MethodHook.MethodHookParam
import de.robv.android.xposed.XC_MethodReplacement
import de.robv.android.xposed.XposedHelpers
import de.robv.android.xposed.callbacks.XC_LoadPackage import de.robv.android.xposed.callbacks.XC_LoadPackage
import GoonXposed.xposed.Constants
import GoonXposed.xposed.Module
import GoonXposed.xposed.Utils.Companion.JSON
import GoonXposed.xposed.Utils.Log import GoonXposed.xposed.Utils.Log
import GoonXposed.xposed.asDir import GoonXposed.xposed.modules.*
import GoonXposed.xposed.asFile import GoonXposed.xposed.modules.appearance.FontsModule
import io.ktor.client.* import GoonXposed.xposed.modules.appearance.SysColorsModule
import io.ktor.client.call.* import GoonXposed.xposed.modules.appearance.ThemesModule
import io.ktor.client.engine.cio.* import GoonXposed.xposed.modules.bridge.AdditionalBridgeMethodsModule
import io.ktor.client.plugins.* import GoonXposed.xposed.modules.bridge.BridgeModule
import io.ktor.client.request.* import GoonXposed.xposed.modules.no_track.BlockCrashReportingModule
import io.ktor.client.statement.* import GoonXposed.xposed.modules.no_track.BlockDeepLinksTrackingModule
import io.ktor.http.* import GoonXposed.xposed.modules.LogBox.*
import kotlinx.coroutines.* import kotlinx.coroutines.CompletableDeferred
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonObjectBuilder
import kotlinx.serialization.json.put
import java.io.File
import java.io.IOException
@Serializable object HookStateHolder {
data class FontDefinition( /**
val name: String? = null, * Whether all hooks are completed, and we are ready to load the JS bundle.
val description: String? = null, */
val spec: Int? = null, val readyDeferred = CompletableDeferred<Unit>()
val main: Map<String, String>,
)
object FontsModule : Module() { /**
private val EXTENSIONS = arrayOf("", "_bold", "_italic", "_bold_italic") * Whether we have successfully received a [Context] yet.
private val FILE_EXTENSIONS = arrayOf(".ttf", ".otf") * Sometimes the app process is recreated and Xposed hooks way too late for us to get [Context] from [ContextWrapper.attachBaseContext].
private const val FONTS_ASSET_PATH = "fonts/" * But since Xposed hooks before [Activity.onCreate], we can still get it from there and still initialize properly.
*/
@Volatile
var gotContext = false
}
private lateinit var fontsDir: File class Main : Module(), IXposedHookLoadPackage, IXposedHookZygoteInit {
private lateinit var fontsDownloadsDir: File private var hooked = false
private var fontsAbsPath: String? = null private val modules = mutableListOf(
HookScriptLoaderModule,
BridgeModule,
AdditionalBridgeMethodsModule,
PluginsModule(),
UpdaterModule,
FixResourcesModule,
BlockDeepLinksTrackingModule,
BlockCrashReportingModule,
LogBoxModule,
CacheModule,
PerfPatchesModule,
FontsModule,
ThemesModule,
SysColorsModule
)
override fun buildPayload(builder: JsonObjectBuilder) { init {
builder.apply { modules += PayloadGlobalModule(modules)
put("fontPatch", 2)
}
} }
override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) = with(packageParam) { override fun initZygote(startupParam: IXposedHookZygoteInit.StartupParam) {
XposedHelpers.findAndHookMethod( for (module in modules) module.onInit(startupParam)
"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 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
}
val fontName = fontDef.name ?: return@with
fontsDownloadsDir = File(appInfo.dataDir, "${Constants.FILES_DIR}/downloads/fonts").apply { asDir() }
fontsDir = File(fontsDownloadsDir, fontName).apply { asDir() }
fontsAbsPath = fontsDir.absolutePath + "/"
fontsDir.listFiles()?.forEach { file ->
val fileName = file.name
if (!fileName.startsWith(".")) {
val fontName = fileName.split('.')[0]
if (fontDef.main.keys.none { it == fontName }) {
Log.i("Deleting font file: $fileName")
file.delete()
}
}
}
// These files should be downloaded by the JS side, but oh well
CoroutineScope(Dispatchers.IO).launch {
fontDef.main.keys.map { name ->
async {
val url = fontDef.main.getValue(name)
try {
Log.i("Downloading $name from $url")
val file =
File(fontsDir, "$name${FILE_EXTENSIONS.first { url.endsWith(it) }}").apply { asFile() }
if (file.exists()) return@async
val client = HttpClient(CIO) {
install(UserAgent) { agent = Constants.USER_AGENT }
}
val response: HttpResponse = client.get(url)
if (response.status == HttpStatusCode.OK) file.writeBytes(response.body())
return@async
} catch (e: Throwable) {
Log.e("Failed to download fonts ($name from $url)", e)
}
}
}.awaitAll()
}
} }
private fun splitFontName(fontFamilyName: String): Pair<String, String> { override fun handleLoadPackage(param: XC_LoadPackage.LoadPackageParam) = with(param) {
val parts = fontFamilyName.split(":") if (hooked) return
return if (parts.size >= 2) parts[0] to parts[1] else "" to fontFamilyName
val reactActivity = classLoader.loadClass(Constants.TARGET_ACTIVITY)
ContextWrapper::class.java.hookMethod("attachBaseContext", Context::class.java) {
after {
val ctx = args[0] as Context
HookStateHolder.gotContext = true
Log.i("Received Context")
this@Main.onContext(ctx)
}
}
reactActivity.hookMethod("onCreate", Bundle::class.java) {
after {
val act = thisObject as Activity
Log.i("Received Activity")
if (!HookStateHolder.gotContext) {
Log.w("Activity created before we got Context, process may have been recreated!")
this@Main.onContext(act.applicationContext)
}
this@Main.onActivity(act)
HookStateHolder.readyDeferred.complete(Unit)
}
}
this@Main.onLoad(param)
hooked = true
} }
private fun fontFileFor(customName: String, refName: String, fileExtension: String): File = override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) {
if (customName.isEmpty()) File(fontsDownloadsDir, "$refName.$fileExtension") for (module in modules) module.onLoad(packageParam)
else File(fontsDownloadsDir, "$customName/$refName.$fileExtension")
private fun createAssetTypefaceWithFallbacks(
fontFamilyNames: Array<String>, style: Int, assetManager: AssetManager
): Typeface? {
val fontFamilies: MutableList<FontFamily> = ArrayList()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
for (fontFamilyName in fontFamilyNames) {
try {
for (fileExtension in FILE_EXTENSIONS) {
val (customName, refName) = splitFontName(fontFamilyName)
val file = fontFileFor(customName, refName, fileExtension).apply { asFile() }
val font = Font.Builder(file).build()
val family = FontFamily.Builder(font).build()
fontFamilies.add(family)
}
} catch (_: Throwable) {
}
for (fontRootPath in arrayOf(fontsAbsPath, FONTS_ASSET_PATH).filterNotNull()) {
for (fileExtension in FILE_EXTENSIONS) {
val fileName =
StringBuilder().append(fontRootPath).append(fontFamilyName).append(fileExtension).toString()
try {
val builder = if (fileName[0] == '/') Font.Builder(File(fileName)) else Font.Builder(
assetManager, fileName
)
val font = builder.build()
val family = FontFamily.Builder(font).build()
fontFamilies.add(family)
} catch (_: RuntimeException) {
continue
} catch (_: IOException) {
continue
}
}
}
}
if (fontFamilies.isEmpty()) return createAssetTypeface(fontFamilyNames[0], style, assetManager)
val fallbackBuilder = CustomFallbackBuilder(fontFamilies[0])
for (i in 1 until fontFamilies.size) {
fallbackBuilder.addCustomFallback(fontFamilies[i])
}
return fallbackBuilder.build()
}
return null
} }
private fun createAssetTypeface( override fun onContext(context: Context) {
fontFamilyName: String, style: Int, assetManager: AssetManager for (module in modules) module.onContext(context)
): Typeface? { }
var fontFamilyName: String = fontFamilyName
val fontFamilyNames = fontFamilyName.split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
for (i in fontFamilyNames.indices) {
fontFamilyNames[i] = fontFamilyNames[i].trim()
}
if (fontFamilyNames.size > 1) { override fun onActivity(activity: Activity) {
fontFamilyName = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { for (module in modules) module.onActivity(activity)
return createAssetTypefaceWithFallbacks(fontFamilyNames, style, assetManager)
} else {
fontFamilyNames[0]
}
}
val extension = EXTENSIONS[style]
try {
for (fileExtension in FILE_EXTENSIONS) {
val (customName, refName) = splitFontName(fontFamilyName)
val file = fontFileFor(customName, refName, fileExtension).apply { asFile() }
if (!file.exists()) throw Exception()
return Typeface.createFromFile(file.absolutePath)
}
} catch (_: Throwable) {
}
for (fontRootPath in arrayOf(fontsAbsPath, FONTS_ASSET_PATH).filterNotNull()) {
for (fileExtension in FILE_EXTENSIONS) {
val fileName =
StringBuilder().append(fontRootPath).append(fontFamilyName).append(extension).append(fileExtension)
.toString()
return try {
if (fileName[0] == '/') Typeface.createFromFile(fileName)
else Typeface.createFromAsset(assetManager, fileName)
} catch (_: RuntimeException) {
continue
}
}
}
return Typeface.create(fontFamilyName, style)
} }
} }