Compare commits
1 commit
fix/modern
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a707e6fd05 |
9 changed files with 1136 additions and 267 deletions
File diff suppressed because one or more lines are too long
|
|
@ -16,7 +16,6 @@ import GoonXposed.xposed.modules.bridge.AdditionalBridgeMethodsModule
|
|||
import GoonXposed.xposed.modules.bridge.BridgeModule
|
||||
import GoonXposed.xposed.modules.no_track.BlockCrashReportingModule
|
||||
import GoonXposed.xposed.modules.no_track.BlockDeepLinksTrackingModule
|
||||
import GoonXposed.xposed.modules.LogBox.LogBoxModule
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
|
||||
object HookStateHolder {
|
||||
|
|
@ -60,23 +59,11 @@ class Main : Module(), IXposedHookLoadPackage, IXposedHookZygoteInit {
|
|||
override fun initZygote(startupParam: IXposedHookZygoteInit.StartupParam) {
|
||||
for (module in modules) module.onInit(startupParam)
|
||||
}
|
||||
override fun handleLoadPackage(param: XC_LoadPackage.LoadPackageParam) = with(param) {
|
||||
val isTargetApp = packageName == Constants.TARGET_PACKAGE
|
||||
|| packageName.startsWith("com.discord")
|
||||
|| packageName == "dev.shiggy.cord"
|
||||
|| packageName.contains("cord")
|
||||
|| packageName.contains("discord")
|
||||
|
||||
if (!isTargetApp) return
|
||||
if (processName != packageName) return
|
||||
override fun handleLoadPackage(param: XC_LoadPackage.LoadPackageParam) = with(param) {
|
||||
if (hooked) return
|
||||
|
||||
val reactActivity = try {
|
||||
classLoader.loadClass(Constants.TARGET_ACTIVITY)
|
||||
} catch (e: Throwable) {
|
||||
Log.e("Failed to load target activity ${Constants.TARGET_ACTIVITY}", e)
|
||||
null
|
||||
}
|
||||
val reactActivity = classLoader.loadClass(Constants.TARGET_ACTIVITY)
|
||||
|
||||
ContextWrapper::class.java.hookMethod("attachBaseContext", Context::class.java) {
|
||||
after {
|
||||
|
|
@ -87,7 +74,7 @@ class Main : Module(), IXposedHookLoadPackage, IXposedHookZygoteInit {
|
|||
}
|
||||
}
|
||||
|
||||
reactActivity?.hookMethod("onCreate", Bundle::class.java) {
|
||||
reactActivity.hookMethod("onCreate", Bundle::class.java) {
|
||||
after {
|
||||
val act = thisObject as Activity
|
||||
Log.i("Received Activity")
|
||||
|
|
@ -102,20 +89,6 @@ class Main : Module(), IXposedHookLoadPackage, IXposedHookZygoteInit {
|
|||
}
|
||||
}
|
||||
|
||||
reactActivity?.hookMethod("onResume") {
|
||||
after {
|
||||
val act = thisObject as Activity
|
||||
this@Main.onResume(act)
|
||||
}
|
||||
}
|
||||
|
||||
reactActivity?.hookMethod("onPause") {
|
||||
after {
|
||||
val act = thisObject as Activity
|
||||
this@Main.onPause(act)
|
||||
}
|
||||
}
|
||||
|
||||
this@Main.onLoad(param)
|
||||
|
||||
hooked = true
|
||||
|
|
@ -132,12 +105,4 @@ class Main : Module(), IXposedHookLoadPackage, IXposedHookZygoteInit {
|
|||
override fun onActivity(activity: Activity) {
|
||||
for (module in modules) module.onActivity(activity)
|
||||
}
|
||||
|
||||
override fun onResume(activity: Activity) {
|
||||
for (module in modules) module.onResume(activity)
|
||||
}
|
||||
|
||||
override fun onPause(activity: Activity) {
|
||||
for (module in modules) module.onPause(activity)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,10 +27,6 @@ open class Module {
|
|||
|
||||
open fun onActivity(activity: Activity) {}
|
||||
|
||||
open fun onResume(activity: Activity) {}
|
||||
|
||||
open fun onPause(activity: Activity) {}
|
||||
|
||||
@Deprecated("This method is deprecated in the parent class")
|
||||
open fun buildPayload(builder: JsonObjectBuilder) {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ import GoonXposed.xposed.modules.HookScriptLoaderModule.PRELOADS_DIR
|
|||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import java.io.File
|
||||
import java.lang.reflect.Method
|
||||
|
||||
|
|
@ -89,18 +88,16 @@ object HookScriptLoaderModule : Module() {
|
|||
Log.i("Running custom scripts...")
|
||||
|
||||
runBlocking {
|
||||
val ready = async { HookStateHolder.readyDeferred.join() }
|
||||
|
||||
if (!mainScript.exists()) {
|
||||
Log.i("Main script does not exist, downloading before load...")
|
||||
try {
|
||||
withTimeout(10000L) {
|
||||
UpdaterModule.downloadScript().join()
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
Log.w("Bundle download did not complete in time", e)
|
||||
}
|
||||
val download = async { UpdaterModule.downloadScript().join() }
|
||||
awaitAll(ready, download)
|
||||
} else {
|
||||
Log.i("Main script exists, updating in background...")
|
||||
UpdaterModule.downloadScript()
|
||||
ready.await()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -125,25 +122,15 @@ object HookScriptLoaderModule : Module() {
|
|||
if (mainScript.exists()) {
|
||||
runScriptFile(mainScript)
|
||||
} else {
|
||||
Log.i("Main script does not exist, checking for fallback asset")
|
||||
Log.i("Main script does not exist, falling back")
|
||||
|
||||
if (!::resources.isInitialized) resources = XModuleResources.createInstance(modulePath, null)
|
||||
|
||||
var hasFallback = false
|
||||
try {
|
||||
resources.assets.open("Shiggy.bundle").use { hasFallback = true }
|
||||
} catch (_: Throwable) {}
|
||||
|
||||
if (hasFallback) {
|
||||
Log.i("Loading fallback assets://Shiggy.bundle")
|
||||
XposedBridge.invokeOriginalMethod(
|
||||
loadScriptFromAssets,
|
||||
thisObject,
|
||||
arrayOf(resources.assets, "assets://Shiggy.bundle", loadSynchronously)
|
||||
)
|
||||
} else {
|
||||
Log.w("No fallback bundle asset found; letting host app load default JS")
|
||||
}
|
||||
XposedBridge.invokeOriginalMethod(
|
||||
loadScriptFromAssets,
|
||||
thisObject,
|
||||
arrayOf(resources.assets, "assets://Shiggy.bundle", loadSynchronously)
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package GoonXposed.xposed.modules.LogBox
|
|||
import GoonXposed.xposed.Module
|
||||
import GoonXposed.xposed.Utils.Log
|
||||
import GoonXposed.xposed.Utils.Companion.reloadApp
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import de.robv.android.xposed.XC_MethodHook
|
||||
import de.robv.android.xposed.XC_MethodReplacement
|
||||
|
|
@ -11,57 +10,33 @@ import de.robv.android.xposed.XposedBridge
|
|||
import de.robv.android.xposed.XposedHelpers
|
||||
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
||||
import kotlinx.coroutines.*
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
object LogBoxModule : Module() {
|
||||
lateinit var packageParam: XC_LoadPackage.LoadPackageParam
|
||||
private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||
var contextForMenu: Context? = null
|
||||
private var currentActivity: WeakReference<Activity>? = null
|
||||
|
||||
private val shakeDetector = ShakeDetector {
|
||||
val act = currentActivity?.get() ?: return@ShakeDetector
|
||||
act.runOnUiThread {
|
||||
try {
|
||||
Log.i("Shake detected! Opening Recovery Menu...")
|
||||
LogBoxNavigation.showRecoveryMenu(act)
|
||||
} catch (e: Throwable) {
|
||||
Log.e("Failed to show recovery menu on shake: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivity(activity: Activity) {
|
||||
currentActivity = WeakReference(activity)
|
||||
contextForMenu = activity
|
||||
}
|
||||
|
||||
override fun onResume(activity: Activity) {
|
||||
currentActivity = WeakReference(activity)
|
||||
contextForMenu = activity
|
||||
try {
|
||||
shakeDetector.start(activity)
|
||||
Log.i("ShakeDetector started for activity: ${activity.javaClass.simpleName}")
|
||||
} catch (e: Throwable) {
|
||||
Log.e("Failed to start ShakeDetector: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause(activity: Activity) {
|
||||
try {
|
||||
shakeDetector.stop()
|
||||
Log.i("ShakeDetector stopped")
|
||||
} catch (e: Throwable) {
|
||||
Log.e("Failed to stop ShakeDetector: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) = with(packageParam) {
|
||||
this@LogBoxModule.packageParam = packageParam
|
||||
|
||||
// Do not force getUseDeveloperSupport = true, as it causes React Native to loop
|
||||
// attempting to connect to a Metro bundler on localhost:8081 and hang the app loading screen.
|
||||
// Shake detector and recovery menu work independently without it.
|
||||
try {
|
||||
val dcdReactNativeHostClass = classLoader.loadClass("com.discord.bridge.DCDReactNativeHost")
|
||||
val getUseDeveloperSupportMethod =
|
||||
dcdReactNativeHostClass.methods.first { it.name == "getUseDeveloperSupport" }
|
||||
|
||||
// NOTE: This used to call a custom `.hook { before { result = true } }` DSL that
|
||||
// relied on a shared extensions file which broke (unresolved references).
|
||||
// Rewritten using the standard XposedBridge/XC_MethodHook API, which is already
|
||||
// used elsewhere in this file, to force this method to always return true.
|
||||
XposedBridge.hookMethod(getUseDeveloperSupportMethod, object : XC_MethodHook() {
|
||||
override fun beforeHookedMethod(param: MethodHookParam) {
|
||||
param.result = true
|
||||
}
|
||||
})
|
||||
Log.e("Successfully hooked DCDReactNativeHost")
|
||||
} catch (e: Exception) {
|
||||
Log.e("Failed to hook DCDReactNativeHost: ${e.message}")
|
||||
}
|
||||
|
||||
return@with
|
||||
}
|
||||
|
|
@ -151,7 +126,7 @@ object LogBoxModule : Module() {
|
|||
Log.e("Failed to get context from DevSupport (non-fatal): ${e.message}")
|
||||
}
|
||||
|
||||
val finalContext = currentActivity?.get() ?: activityContext ?: contextForMenu ?: context
|
||||
val finalContext = activityContext ?: contextForMenu ?: context
|
||||
Log.e("Using context: $finalContext (type: ${finalContext.javaClass.name})")
|
||||
|
||||
LogBoxNavigation.showRecoveryMenu(finalContext)
|
||||
|
|
|
|||
|
|
@ -1,56 +0,0 @@
|
|||
package GoonXposed.xposed.modules.LogBox
|
||||
|
||||
import android.content.Context
|
||||
import android.hardware.Sensor
|
||||
import android.hardware.SensorEvent
|
||||
import android.hardware.SensorEventListener
|
||||
import android.hardware.SensorManager
|
||||
import kotlin.math.sqrt
|
||||
|
||||
class ShakeDetector(private val onShake: () -> Unit) : SensorEventListener {
|
||||
private var sensorManager: SensorManager? = null
|
||||
private var accelerometer: Sensor? = null
|
||||
private var lastShakeTimestamp: Long = 0
|
||||
|
||||
fun start(context: Context): Boolean {
|
||||
if (sensorManager != null) return true
|
||||
sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as? SensorManager
|
||||
accelerometer = sensorManager?.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
|
||||
return accelerometer?.let {
|
||||
sensorManager?.registerListener(this, it, SensorManager.SENSOR_DELAY_UI)
|
||||
} ?: false
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
sensorManager?.unregisterListener(this)
|
||||
sensorManager = null
|
||||
accelerometer = null
|
||||
}
|
||||
|
||||
override fun onSensorChanged(event: SensorEvent) {
|
||||
if (event.sensor.type != Sensor.TYPE_ACCELEROMETER) return
|
||||
|
||||
val x = event.values[0] / SensorManager.GRAVITY_EARTH
|
||||
val y = event.values[1] / SensorManager.GRAVITY_EARTH
|
||||
val z = event.values[2] / SensorManager.GRAVITY_EARTH
|
||||
|
||||
// gForce will be close to 1 when there is no movement.
|
||||
val gForce = sqrt((x * x + y * y + z * z).toDouble()).toFloat()
|
||||
|
||||
if (gForce > SHAKE_THRESHOLD_GRAVITY) {
|
||||
val now = System.currentTimeMillis()
|
||||
// Ignore shake events too close to each other (1 second debounce)
|
||||
if (now - lastShakeTimestamp > SHAKE_SLOP_TIME_MS) {
|
||||
lastShakeTimestamp = now
|
||||
onShake()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
|
||||
|
||||
companion object {
|
||||
private const val SHAKE_THRESHOLD_GRAVITY = 2.7f
|
||||
private const val SHAKE_SLOP_TIME_MS = 1000L
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -61,30 +61,21 @@ object FontsModule : Module() {
|
|||
}
|
||||
|
||||
override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) = with(packageParam) {
|
||||
val fontManagerClass = runCatching {
|
||||
classLoader.loadClass("com.facebook.react.common.assets.ReactFontManager\$Companion")
|
||||
}.getOrNull() ?: runCatching {
|
||||
classLoader.loadClass("com.facebook.react.views.text.ReactFontManager\$Companion")
|
||||
}.getOrNull()
|
||||
|
||||
if (fontManagerClass != null) {
|
||||
XposedHelpers.findAndHookMethod(
|
||||
fontManagerClass,
|
||||
"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)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Log.w("FontsModule: ReactFontManager\$Companion class not found")
|
||||
}
|
||||
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 fontDefFile = File(appInfo.dataDir, "${Constants.FILES_DIR}/fonts.json").apply { asFile() }
|
||||
if (!fontDefFile.exists()) return@with
|
||||
|
|
@ -158,16 +149,12 @@ object FontsModule : Module() {
|
|||
// for use in the CustomFallbackBuilder below.
|
||||
for (fontFamilyName in fontFamilyNames) {
|
||||
try {
|
||||
if (fontFamilyName.contains(":")) {
|
||||
for (fileExtension in FILE_EXTENSIONS) {
|
||||
val (customName, refName) = fontFamilyName.split(":")
|
||||
val file = File(fontsDownloadsDir, "$customName/$refName.$fileExtension").apply { asFile() }
|
||||
if (file.exists()) {
|
||||
val font = Font.Builder(file).build()
|
||||
val family = FontFamily.Builder(font).build()
|
||||
fontFamilies.add(family)
|
||||
}
|
||||
}
|
||||
for (fileExtension in FILE_EXTENSIONS) {
|
||||
val (customName, refName) = fontFamilyName.split(":")
|
||||
val file = File(fontsDownloadsDir, "$customName/$refName.$fileExtension").apply { asFile() }
|
||||
val font = Font.Builder(file).build()
|
||||
val family = FontFamily.Builder(font).build()
|
||||
fontFamilies.add(family)
|
||||
}
|
||||
} catch (_: Throwable) {
|
||||
}
|
||||
|
|
@ -231,14 +218,11 @@ object FontsModule : Module() {
|
|||
val extension = EXTENSIONS[style]
|
||||
|
||||
try {
|
||||
if (fontFamilyName.contains(":")) {
|
||||
for (fileExtension in FILE_EXTENSIONS) {
|
||||
val (customName, refName) = fontFamilyName.split(":")
|
||||
val file = File(fontsDownloadsDir, "$customName/$refName.$fileExtension").apply { asFile() }
|
||||
if (file.exists()) {
|
||||
return Typeface.createFromFile(file.absolutePath)
|
||||
}
|
||||
}
|
||||
for (fileExtension in FILE_EXTENSIONS) {
|
||||
val (customName, refName) = fontFamilyName.split(":")
|
||||
val file = File(fontsDownloadsDir, "$customName/$refName.$fileExtension").apply { asFile() }
|
||||
if (!file.exists()) throw Exception()
|
||||
return Typeface.createFromFile(file.absolutePath)
|
||||
}
|
||||
} catch (_: Throwable) {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,20 +86,18 @@ object ThemesModule : Module() {
|
|||
|
||||
fun hookTheme() {
|
||||
try {
|
||||
val themeManager = runCatching { param.classLoader.loadClass("com.discord.theme.utils.ColorUtilsKt") }.getOrNull()
|
||||
val darkTheme = runCatching { param.classLoader.loadClass("com.discord.theme.DarkTheme") }.getOrNull()
|
||||
?: 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()
|
||||
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")
|
||||
|
||||
hookThemeInternal(themeManager, darkTheme, onyxTheme, lightTheme)
|
||||
hookThemeInternal(themeManager, darkTheme, lightTheme)
|
||||
} catch (e: Throwable) {
|
||||
GoonXposed.xposed.Utils.Log.e("Failed to hook theme: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun hookThemeInternal(
|
||||
themeManager: Class<*>?, darkTheme: Class<*>?, onyxTheme: Class<*>?, lightTheme: Class<*>?
|
||||
themeManager: Class<*>, darkTheme: Class<*>, lightTheme: Class<*>
|
||||
) {
|
||||
val theme = this.theme ?: return
|
||||
|
||||
|
|
@ -113,36 +111,26 @@ object ThemesModule : Module() {
|
|||
// TEXT_NORMAL -> getTextNormal
|
||||
val methodName = "get${key.fromScreamingSnakeToCamelCase()}"
|
||||
value.forEachIndexed { index, v ->
|
||||
val colorInt = hexStringToColorInt(v)
|
||||
when (index) {
|
||||
0 -> {
|
||||
if (darkTheme != null) hookThemeMethod(darkTheme, methodName, colorInt)
|
||||
if (onyxTheme != null) hookThemeMethod(onyxTheme, methodName, colorInt)
|
||||
}
|
||||
1 -> {
|
||||
if (lightTheme != null) hookThemeMethod(lightTheme, methodName, colorInt)
|
||||
}
|
||||
0 -> hookThemeMethod(darkTheme, methodName, hexStringToColorInt(v))
|
||||
1 -> hookThemeMethod(lightTheme, methodName, hexStringToColorInt(v))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there's any rawColors value, hook the color getter
|
||||
if (!theme.data.rawColors.isNullOrEmpty() && themeManager != null) {
|
||||
if (!theme.data.rawColors.isNullOrEmpty()) {
|
||||
try {
|
||||
val getColorCompat = runCatching {
|
||||
themeManager.getDeclaredMethod(
|
||||
"getColorCompat",
|
||||
Resources::class.java,
|
||||
Int::class.javaPrimitiveType,
|
||||
Resources.Theme::class.java,
|
||||
)
|
||||
}.getOrNull()
|
||||
val getColorCompat = themeManager.getDeclaredMethod(
|
||||
"getColorCompat",
|
||||
Resources::class.java,
|
||||
Int::class.javaPrimitiveType,
|
||||
Resources.Theme::class.java,
|
||||
)
|
||||
|
||||
val getColorCompatLegacy = runCatching {
|
||||
themeManager.getDeclaredMethod(
|
||||
"getColorCompat", Context::class.java, Int::class.javaPrimitiveType
|
||||
)
|
||||
}.getOrNull()
|
||||
val getColorCompatLegacy = themeManager.getDeclaredMethod(
|
||||
"getColorCompat", Context::class.java, Int::class.javaPrimitiveType
|
||||
)
|
||||
|
||||
val patch = MethodHookBuilder().run {
|
||||
before {
|
||||
|
|
@ -159,8 +147,8 @@ object ThemesModule : Module() {
|
|||
build()
|
||||
}
|
||||
|
||||
getColorCompat?.hook(patch)
|
||||
getColorCompatLegacy?.hook(patch)
|
||||
getColorCompat.hook(patch)
|
||||
getColorCompatLegacy.hook(patch)
|
||||
} catch (e: Throwable) {
|
||||
GoonXposed.xposed.Utils.Log.e("Failed to hook getColorCompat: ${e.message}")
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue