Compare commits

...

64 commits

Author SHA1 Message Date
OTKUSteyler
a707e6fd05
Update UpdaterModule.kt
Some checks failed
Build APK / Build and push (push) Failing after 6m51s
2026-09-12 15:55:10 +02:00
OTKUSteyler
d55ed5221c
Update LogBoxModule.kt 2026-09-12 12:52:36 +02:00
OTKUSteyler
a5af0bddb8
Update FontsModule.kt 2026-09-12 12:41:28 +02:00
OTKUSteyler
9611038048
Update FontsModule.kt 2026-09-12 12:33:34 +02:00
OTKUSteyler
06dde39cfd
Update LogBoxModule.kt 2026-09-12 12:25:15 +02:00
OTKUSteyler
69496c1f3c Revert "Fix: Add missing LogBoxModule and onActivity hook - fixes blank plugin/theme tabs"
This reverts commit 1a9fd61ec3.
2026-09-12 11:27:48 +02:00
OTKUSteyler
ce6f201fcd
Update FontsModule.kt 2026-09-12 10:13:49 +02:00
OTKUSteyler
73a93a29bb
Update Main.kt 2026-09-12 10:10:19 +02:00
OTKUSteyler
ee7e30e67c
Update FontsModule.kt 2026-09-12 10:08:55 +02:00
OTKUSteyler
57ebd3daed
Update LogBoxModule.kt 2026-09-11 22:12:32 +02:00
OTKUSteyler
82214b6ed0
Update FontsModule.kt 2026-09-11 21:48:46 +02:00
OTKUSteyler
2b9956d97b
Update LogBoxModule.kt 2026-09-10 17:47:17 +02:00
OTKUSteyler
b2c7e23a57
Update FontsModule.kt 2026-09-10 17:15:27 +02:00
OTKUSteyler
468311bbba
Update LogBoxModule.kt 2026-09-10 17:15:05 +02:00
OTKUSteyler
7f90a61ca5
Delete app/src/main/kotlin/GoonXposed/xposed/xposed/a 2026-09-10 16:58:53 +02:00
OTKUSteyler
17e1b9220f
Create a 2026-09-10 16:57:51 +02:00
OTKUSteyler
34b4a9f30c
Delete app/src/main/kotlin/GoonXposed/xposed/xposed/a 2026-09-10 16:56:10 +02:00
OTKUSteyler
abb1c6ddd8
Create a 2026-09-10 16:55:55 +02:00
OTKUSteyler
32421df002
Update Main.kt 2026-09-10 15:20:01 +02:00
OTKUSteyler
f16af054be
Update Utils.kt 2026-09-10 15:11:45 +02:00
OTKUSteyler
3a6abc9a79
Update FontsModule.kt 2026-09-10 13:54:51 +02:00
OTKUSteyler
a836fd1e73
Update FontsModule.kt 2026-09-10 12:49:20 +02:00
OTKUSteyler
0190e2d468
Update Main.kt 2026-09-10 12:18:30 +02:00
OTKUSteyler
ea21c20d18
Update Main.kt 2026-09-10 12:09:38 +02:00
OTKUSteyler
ad10c8a2a1
Update build.gradle.kts 2026-09-10 08:05:34 +02:00
OTKUSteyler
65d6ae239a
Update build.gradle.kts 2026-09-09 21:16:48 +02:00
OTKUSteyler
415f0066d7
Update build.gradle.kts 2026-09-09 21:13:44 +02:00
OTKUSteyler
4f3c599c3b
Update build.gradle.kts 2026-09-09 20:25:56 +02:00
OTKUSteyler
5e8e4538f8
Update build.gradle.kts 2026-09-09 20:17:37 +02:00
OTKUSteyler
64f83dcd5c
Update FontsModule.kt 2026-09-09 19:52:56 +02:00
OTKUSteyler
cd418c2d79
Update LogBoxActions.kt 2026-09-09 19:51:20 +02:00
OTKUSteyler
0267d095e2
Update FontsModule.kt 2026-09-09 19:42:01 +02:00
OTKUSteyler
2d62630e30
Update Main.kt 2026-09-09 17:45:20 +02:00
OTKUSteyler
750a4cd5b0
Update Main.kt 2026-09-05 20:37:19 +02:00
OTKUSteyler
baacefef54
Update FontsModule.kt 2026-09-05 19:46:48 +02:00
OTKUSteyler
f96d53db9a
Update Main.kt 2026-09-05 13:43:27 +02:00
OTKUSteyler
720e84487e
Update FontsModule.kt 2026-09-05 13:26:30 +02:00
OTKUSteyler
8616f04995
Update ThemesModule.kt 2026-09-05 13:25:51 +02:00
OTKUSteyler
7152b06265
Update Main.kt 2026-09-05 13:25:17 +02:00
OTKUSteyler
2b3510f55d
Update Utils.kt 2026-09-05 12:53:22 +02:00
OTKUSteyler
d1334d0ec0
Update FixResourcesModule.kt 2026-09-05 12:49:09 +02:00
OTKUSteyler
853564ebe9
Update FontsModule.kt 2026-09-05 12:48:11 +02:00
OTKUSteyler
3bf2f76c56
Update RecoveryMenu.kt 2026-09-05 12:47:19 +02:00
OTKUSteyler
6f707af677
Update OptionsMenu.kt 2026-09-05 12:45:27 +02:00
OTKUSteyler
1abaaff7d1
Update PayloadGlobalModule.kt 2026-09-05 12:44:49 +02:00
OTKUSteyler
3aa888bc26
Update HookScriptLoaderModule.kt 2026-09-05 12:44:22 +02:00
OTKUSteyler
d57081364b
Update SysColorsModule.kt 2026-09-05 12:43:35 +02:00
OTKUSteyler
4599c2d96f
Update Utils.kt 2026-09-05 12:43:04 +02:00
OTKUSteyler
14a8067aea
Update Module.kt 2026-09-05 12:42:17 +02:00
OTKUSteyler
d719e15cf8
Add files via upload 2026-09-05 12:25:16 +02:00
OTKUSteyler
7814cd0e82
Add files via upload 2026-09-05 12:24:51 +02:00
OTKUSteyler
469cdfb95e
Add files via upload 2026-09-05 12:24:24 +02:00
OTKUSteyler
e90c20bfc5
Add files via upload 2026-09-05 12:23:41 +02:00
OTKUSteyler
fc9dd1ee9b
Add files via upload 2026-09-05 12:22:25 +02:00
OTKUSteyler
c85761f92e
Add files via upload 2026-09-05 12:21:42 +02:00
OTKUSteyler
c342b478c1
Update gradle.properties 2026-09-05 12:16:21 +02:00
OTKUSteyler
fc6772a124
Update gradle.properties 2026-09-05 12:13:46 +02:00
OTKUSteyler
743b6283c0
Update build.gradle.kts 2026-09-04 06:25:39 +02:00
OTKUSteyler
3562b0e730
Update FontsModule.kt 2026-09-03 21:06:47 +02:00
OTKUSteyler
26a356a158
Update xposed_init 2026-09-03 20:15:40 +02:00
OTKUSteyler
a3ebf0259e
Update xposed_init 2026-09-03 20:12:29 +02:00
OTKUSteyler
df097bfc81
Update build.gradle.kts 2026-09-03 19:35:10 +02:00
OTKUSteyler
765c546c20
Update Module.kt 2026-09-03 18:29:43 +02:00
OTKUSteyler
13bdcdad03
Update FontsModule.kt 2026-09-03 18:27:03 +02:00
22 changed files with 304 additions and 257 deletions

View file

@ -68,5 +68,6 @@ dependencies {
implementation("io.ktor:ktor-client-core:3.3.1") implementation("io.ktor:ktor-client-core:3.3.1")
implementation("io.ktor:ktor-client-cio:3.3.1") implementation("io.ktor:ktor-client-cio:3.3.1")
implementation("com.google.android.material:material:1.12.0") implementation("com.google.android.material:material:1.12.0")
implementation("dev.rushii:libunbound:1.0.0") implementation("dev.rikka.shizuku:api:13.1.5")
implementation("dev.rikka.shizuku:provider:13.1.5")
} }

View file

@ -16,7 +16,6 @@ import GoonXposed.xposed.modules.bridge.AdditionalBridgeMethodsModule
import GoonXposed.xposed.modules.bridge.BridgeModule import GoonXposed.xposed.modules.bridge.BridgeModule
import GoonXposed.xposed.modules.no_track.BlockCrashReportingModule import GoonXposed.xposed.modules.no_track.BlockCrashReportingModule
import GoonXposed.xposed.modules.no_track.BlockDeepLinksTrackingModule import GoonXposed.xposed.modules.no_track.BlockDeepLinksTrackingModule
import GoonXposed.xposed.modules.LogBox.LogBoxModule
import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CompletableDeferred
object HookStateHolder { object HookStateHolder {

View file

@ -2,182 +2,160 @@ package GoonXposed.xposed
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.os.Build import android.content.pm.PackageManager
import de.robv.android.xposed.IXposedHookZygoteInit import de.robv.android.xposed.IXposedHookZygoteInit
import de.robv.android.xposed.XC_MethodHook import de.robv.android.xposed.XC_MethodHook
import de.robv.android.xposed.XposedBridge import de.robv.android.xposed.XposedBridge
import de.robv.android.xposed.XposedHelpers
import de.robv.android.xposed.callbacks.XC_LoadPackage import de.robv.android.xposed.callbacks.XC_LoadPackage
import kotlinx.serialization.json.JsonObjectBuilder import kotlinx.serialization.json.JsonObjectBuilder
import java.io.File import java.io.File
import java.lang.reflect.Method import java.lang.reflect.Method
/**
* Base class for every Xposed side module.
*
* The [Main] loader forwards the various [XC_LoadPackage] lifecycle callbacks to every
* registered [Module] instance. Modules may override any subset of the hooks below.
*/
open class Module {
open fun onInit(startupParam: IXposedHookZygoteInit.StartupParam) {}
open fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) {}
open fun onContext(context: Context) {}
open fun onActivity(activity: Activity) {}
@Deprecated("This method is deprecated in the parent class")
open fun buildPayload(builder: JsonObjectBuilder) {}
}
/**
* Runtime wrapper around an [XC_MethodHook.MethodHookParam] used as the receiver of every
* `before`/`after` hook block, exposing [thisObject], [args], [result] and [param] directly.
*/
class HookScope(val param: XC_MethodHook.MethodHookParam) {
val thisObject: Any?
get() = param.thisObject
val args: Array<Any?>
get() = param.args
var result: Any?
get() = param.result
set(value) {
param.result = value
}
var throwable: Throwable?
get() = param.throwable
set(value) {
param.throwable = value
}
}
/**
* DSL builder for lazily constructed [XC_MethodHook] instances.
*
* ```kotlin
* val hook = MethodHookBuilder().run {
* before { result = null }
* after { Log.i("done") }
* build()
* }
* ```
*/
class MethodHookBuilder {
private val beforeCallbacks = mutableListOf<HookScope.() -> Unit>()
private val afterCallbacks = mutableListOf<HookScope.() -> Unit>()
fun before(callback: HookScope.() -> Unit) {
beforeCallbacks += callback
}
fun after(callback: HookScope.() -> Unit) {
afterCallbacks += callback
}
fun build(): XC_MethodHook = object : XC_MethodHook() {
override fun beforeHookedMethod(param: MethodHookParam) {
val scope = HookScope(param)
beforeCallbacks.forEach { scope.run(it) }
}
override fun afterHookedMethod(param: MethodHookParam) {
val scope = HookScope(param)
afterCallbacks.forEach { scope.run(it) }
}
}
companion object {
fun from(block: MethodHookBuilder.() -> Unit): XC_MethodHook = MethodHookBuilder().run {
block()
build()
}
}
}
fun ClassLoader.safeLoadClass(name: String): Class<*>? = try {
loadClass(name)
} catch (e: Throwable) {
null
}
fun Class<*>.method(name: String, vararg parameterTypes: Class<*>): Method =
XposedHelpers.findMethodExact(this, name, *parameterTypes)
fun Method.hook(block: MethodHookBuilder.() -> Unit) {
val builder = MethodHookBuilder()
builder.block()
XposedBridge.hookMethod(this, builder.build())
}
fun Method.hook(hook: XC_MethodHook) {
XposedBridge.hookMethod(this, hook)
}
fun Class<*>.hookMethod(
name: String,
vararg parameterTypes: Class<*>,
block: MethodHookBuilder.() -> Unit
): Method {
val method = method(name, *parameterTypes)
method.hook(block)
return method
}
fun File.asDir(): File {
if (!exists() || !isDirectory) mkdirs()
return this
}
fun File.asFile(): File {
parentFile?.mkdirs()
return this
}
data class AppInfo( data class AppInfo(
val name: String, val name: String,
val packageName: String, val packageName: String,
val version: String, val version: String,
val versionCode: Long, val versionCode: Int
) )
abstract class Module { fun Context.getAppInfo(): AppInfo {
/** val label = applicationInfo.loadLabel(packageManager).toString()
* Builds a JSON payload to be injected into the JavaScript context. val packageInfo = try {
*/ packageManager.getPackageInfo(packageName, 0)
@Deprecated("This will be removed in future versions. Payloads can be replaced via synchronous bridge methods.") } catch (e: PackageManager.NameNotFoundException) {
open fun buildPayload(builder: JsonObjectBuilder) { null
} }
/** return AppInfo(
* Called during Zygote initialization. name = label,
*/ packageName = packageName,
open fun onInit(startupParam: IXposedHookZygoteInit.StartupParam) {} version = packageInfo?.versionName ?: "",
versionCode = packageInfo?.versionCode ?: 0
/** )
* Called when a package is loaded.
*
* Hooks should typically be set up here.
*
* Bridge methods that do not require [Context] can also be registered here.
*/
open fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) {}
/**
* Called after [Context] has been attached to a [android.content.ContextWrapper]
* via [android.content.ContextWrapper.attachBaseContext].
*
* This may be called multiple times for different [Context]s, e.g. when the app is killed by the system.
*
* For bridge methods that require access to [Context], register them here.
*/
open fun onContext(context: Context) {}
/**
* Called after an [Activity.onCreate] method is executed.
*
* UI code that requires an [Activity] context can be placed here.
*
* Bridge methods must not be registered here, as it can be too late and cause crashes.
*
* If you need to register bridge methods that require an [Activity], do it in [onContext] and make it async.
*
* Once an [Activity] is available, execute the queued calls. Then from JS, call the method asynchronously.
*/
open fun onActivity(activity: Activity) {}
protected fun Context.getAppInfo(): AppInfo {
val pInfo = packageManager.getPackageInfo(packageName, 0)
val versionCode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) pInfo.longVersionCode
else @Suppress("DEPRECATION") pInfo.versionCode.toLong()
return AppInfo(
packageManager.getApplicationLabel(applicationInfo).toString(),
packageName,
pInfo.versionName ?: versionCode.toString(),
versionCode,
)
}
protected fun File.asDir() {
if (!this.isDirectory) this.delete()
this.mkdirs()
}
protected fun File.asFile() {
if (!this.isFile) this.deleteRecursively()
}
protected fun Class<*>.method(
name: String, vararg params: Class<*>?
): Method = getDeclaredMethod(name, *params).apply {
isAccessible = true
}
protected fun ClassLoader.safeLoadClass(name: String): Class<*>? = runCatching { loadClass(name) }.getOrNull()
protected fun Method.hook(hook: XC_MethodHook): XC_MethodHook.Unhook = XposedBridge.hookMethod(this, hook)
protected fun Method.hook(block: MethodHookBuilder.() -> Unit): XC_MethodHook.Unhook =
hook(MethodHookBuilder().apply(block).build())
protected fun Class<*>.hookMethod(
name: String, vararg params: Class<*>?, block: MethodHookBuilder.() -> Unit
): XC_MethodHook.Unhook = method(name, *params).hook(MethodHookBuilder().apply(block).build())
protected class MethodHookBuilder {
private var beforeBlock: (HookScope.() -> Unit)? = null
private var afterBlock: (HookScope.() -> Unit)? = null
fun before(block: HookScope.() -> Unit) {
beforeBlock = block
}
fun after(block: HookScope.() -> Unit) {
afterBlock = block
}
fun build(): XC_MethodHook = object : XC_MethodHook() {
override fun beforeHookedMethod(param: MethodHookParam) {
val b = beforeBlock
if (b != null) {
val scope = HookScope(
param = param, proceed = { p ->
super.beforeHookedMethod(p)
})
scope.b()
} else {
super.beforeHookedMethod(param)
}
}
override fun afterHookedMethod(param: MethodHookParam) {
val a = afterBlock
if (a != null) {
val scope = HookScope(
param = param, proceed = { p ->
super.afterHookedMethod(p)
})
scope.a()
} else {
super.afterHookedMethod(param)
}
}
}
}
/**
* Scope object passed to before/after hook blocks.
*
* Provides:
* - Access to the [param] object
* - [proceed] to call the original XC_MethodHook super method
* - Accessors for `thisObject`, `args`, `result`, and `throwable`
*
* @property param The [XC_MethodHook.MethodHookParam] for the current hook.
* @property proceed Function that calls the super method.
*/
protected class HookScope internal constructor(
val param: XC_MethodHook.MethodHookParam, private val proceed: (XC_MethodHook.MethodHookParam) -> Unit
) {
/**
* Continues with the default XC_MethodHook super behavior.
* Equivalent to calling `super.beforeHookedMethod(param)` or
* `super.afterHookedMethod(param)` depending on the phase.
*/
fun proceed() = proceed(param)
val thisObject: Any? get() = param.thisObject
val args: Array<Any?> get() = param.args
var result: Any?
get() = param.result
set(value) {
param.result = value
}
var throwable: Throwable?
get() = param.throwable
set(value) {
param.throwable = value
}
}
} }

View file

@ -1,9 +1,10 @@
package GoonXposed.xposed package GoonXposed.xposed
import android.app.AlertDialog import android.app.AlertDialog
import android.app.AndroidAppHelper import android.app.Application
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import de.robv.android.xposed.XposedHelpers
import GoonXposed.xposed.modules.UpdaterModule import GoonXposed.xposed.modules.UpdaterModule
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.io.File import java.io.File
@ -14,7 +15,9 @@ class Utils {
val JSON = Json { ignoreUnknownKeys = true } val JSON = Json { ignoreUnknownKeys = true }
fun reloadApp() { fun reloadApp() {
val application = AndroidAppHelper.currentApplication() val activityThread = XposedHelpers.findClass("android.app.ActivityThread", null)
val application =
XposedHelpers.callStaticMethod(activityThread, "currentApplication") as? Application ?: return
val intent = application.packageManager.getLaunchIntentForPackage(application.packageName) val intent = application.packageManager.getLaunchIntentForPackage(application.packageName)
application.startActivity(Intent.makeRestartActivityTask(intent!!.component)) application.startActivity(Intent.makeRestartActivityTask(intent!!.component))
exitProcess(0) exitProcess(0)

View file

@ -4,6 +4,9 @@ import android.content.Context
import android.util.AtomicFile import android.util.AtomicFile
import GoonXposed.xposed.Module import GoonXposed.xposed.Module
import GoonXposed.xposed.Utils.Log import GoonXposed.xposed.Utils.Log
import GoonXposed.xposed.asDir
import GoonXposed.xposed.asFile
import GoonXposed.xposed.getAppInfo
import GoonXposed.xposed.modules.bridge.BridgeModule import GoonXposed.xposed.modules.bridge.BridgeModule
import java.io.* import java.io.*

View file

@ -1,9 +1,11 @@
package GoonXposed.xposed.modules package GoonXposed.xposed.modules
import GoonXposed.xposed.hookMethod
import android.content.res.Resources import android.content.res.Resources
import de.robv.android.xposed.callbacks.XC_LoadPackage import de.robv.android.xposed.callbacks.XC_LoadPackage
import GoonXposed.xposed.Constants.Companion.TARGET_PACKAGE import GoonXposed.xposed.Constants.Companion.TARGET_PACKAGE
import GoonXposed.xposed.Module import GoonXposed.xposed.Module
import GoonXposed.xposed.hookMethod
/** /**
* Hooks [Resources.getIdentifier] to fix resource package name mismatch. * Hooks [Resources.getIdentifier] to fix resource package name mismatch.

View file

@ -6,9 +6,15 @@ import de.robv.android.xposed.IXposedHookZygoteInit
import de.robv.android.xposed.XposedBridge import de.robv.android.xposed.XposedBridge
import de.robv.android.xposed.callbacks.XC_LoadPackage import de.robv.android.xposed.callbacks.XC_LoadPackage
import GoonXposed.xposed.Constants import GoonXposed.xposed.Constants
import GoonXposed.xposed.HookScope
import GoonXposed.xposed.HookStateHolder import GoonXposed.xposed.HookStateHolder
import GoonXposed.xposed.Module import GoonXposed.xposed.Module
import GoonXposed.xposed.Utils.Log import GoonXposed.xposed.Utils.Log
import GoonXposed.xposed.asDir
import GoonXposed.xposed.asFile
import GoonXposed.xposed.hook
import GoonXposed.xposed.method
import GoonXposed.xposed.safeLoadClass
import GoonXposed.xposed.modules.HookScriptLoaderModule.PRELOADS_DIR import GoonXposed.xposed.modules.HookScriptLoaderModule.PRELOADS_DIR
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
@ -54,11 +60,11 @@ object HookScriptLoaderModule : Module() {
private fun hook(instance: Class<*>) = runCatching { private fun hook(instance: Class<*>) = runCatching {
val loadScriptFromAssets = instance.method( val loadScriptFromAssets = instance.method(
"loadScriptFromAssets", AssetManager::class.java, String::class.java, Boolean::class.javaPrimitiveType "loadScriptFromAssets", AssetManager::class.java, String::class.java, Boolean::class.javaPrimitiveType!!
) )
val loadScriptFromFile = instance.method( val loadScriptFromFile = instance.method(
"loadScriptFromFile", String::class.java, String::class.java, Boolean::class.javaPrimitiveType "loadScriptFromFile", String::class.java, String::class.java, Boolean::class.javaPrimitiveType!!
) )
loadScriptFromAssets.hook { loadScriptFromAssets.hook {
@ -107,14 +113,10 @@ object HookScriptLoaderModule : Module() {
} }
try { try {
// If a disabled marker exists next to the cached bundle, treat this as a global
// disable for the entire script-loading step. In that case, we skip preloads,
// cached main script and the assets fallback so nothing is injected.
val disabledMarker = File(mainScript.parentFile, "${Constants.MAIN_SCRIPT_FILE}.disabled") val disabledMarker = File(mainScript.parentFile, "${Constants.MAIN_SCRIPT_FILE}.disabled")
if (disabledMarker.exists()) { if (disabledMarker.exists()) {
Log.i("Script loading disabled by marker; skipping preloads, cached bundle and fallback") Log.i("Script loading disabled by marker; skipping preloads, cached bundle and fallback")
} else { } else {
// Normal behaviour: run preloads then cached main script or fallback to bundled asset
preloadsDir.walk().filter { it.isFile }.forEach(runScriptFile) preloadsDir.walk().filter { it.isFile }.forEach(runScriptFile)
if (mainScript.exists()) { if (mainScript.exists()) {

View file

@ -134,7 +134,7 @@ object LogBoxActions {
val config = JSONObject(configFile.readText()) val config = JSONObject(configFile.readText())
val customLoadUrl = config.optJSONObject("customLoadUrl") ?: JSONObject() val customLoadUrl = config.optJSONObject("customLoadUrl") ?: JSONObject()
customLoadUrl.put("enabled", false) customLoadUrl.put("enabled", false)
customLoadUrl.put("url", "http://localhost:4040/shiggycord.js") customLoadUrl.put("url", "http://localhost:4040/gooncord.js")
config.put("customLoadUrl", customLoadUrl) config.put("customLoadUrl", customLoadUrl)
configFile.writeText(config.toString()) configFile.writeText(config.toString())
} }

View file

@ -4,6 +4,7 @@ import GoonXposed.xposed.Module
import GoonXposed.xposed.Utils.Log import GoonXposed.xposed.Utils.Log
import GoonXposed.xposed.Utils.Companion.reloadApp import GoonXposed.xposed.Utils.Companion.reloadApp
import android.content.Context import android.content.Context
import de.robv.android.xposed.XC_MethodHook
import de.robv.android.xposed.XC_MethodReplacement import de.robv.android.xposed.XC_MethodReplacement
import de.robv.android.xposed.XposedBridge import de.robv.android.xposed.XposedBridge
import de.robv.android.xposed.XposedHelpers import de.robv.android.xposed.XposedHelpers
@ -23,11 +24,15 @@ object LogBoxModule : Module() {
val getUseDeveloperSupportMethod = val getUseDeveloperSupportMethod =
dcdReactNativeHostClass.methods.first { it.name == "getUseDeveloperSupport" } dcdReactNativeHostClass.methods.first { it.name == "getUseDeveloperSupport" }
getUseDeveloperSupportMethod.hook { // NOTE: This used to call a custom `.hook { before { result = true } }` DSL that
before { // relied on a shared extensions file which broke (unresolved references).
result = true // 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") Log.e("Successfully hooked DCDReactNativeHost")
} catch (e: Exception) { } catch (e: Exception) {
Log.e("Failed to hook DCDReactNativeHost: ${e.message}") Log.e("Failed to hook DCDReactNativeHost: ${e.message}")

View file

@ -19,40 +19,29 @@ object OptionsMenu {
container.addView(LogBoxComponents.createM3Button(context, injectionText, colors) { container.addView(LogBoxComponents.createM3Button(context, injectionText, colors) {
dialog.dismiss() dialog.dismiss()
LogBoxActions.toggleBundleInjection(context) LogBoxActions.toggleBundleInjection(context)
GoonXposed.xposed.Utils.Companion.reloadApp()
}) })
container.addView(LogBoxComponents.createM3Button(context, "Refetch Bundle", colors) { container.addView(LogBoxComponents.createM3Button(context, "Refetch Bundle", colors) {
dialog.dismiss() dialog.dismiss()
RecoveryMenu::class.java.getDeclaredMethod( RecoveryMenu.showConfirmAction(
"showConfirmAction",
Context::class.java,
String::class.java,
String::class.java,
Runnable::class.java
).apply { isAccessible = true }.invoke(
null,
context, context,
"Refetch Bundle", "Refetch Bundle",
"This will download the latest bundle from Github.", "This will download the latest bundle from Github."
Runnable { LogBoxActions.refetchBundle(context) } ) {
) LogBoxActions.refetchBundle(context)
}
}) })
container.addView(LogBoxComponents.createM3Button(context, "Clear Cache & Reset", colors) { container.addView(LogBoxComponents.createM3Button(context, "Clear Cache & Reset", colors) {
dialog.dismiss() dialog.dismiss()
RecoveryMenu::class.java.getDeclaredMethod( RecoveryMenu.showConfirmAction(
"showConfirmAction",
Context::class.java,
String::class.java,
String::class.java,
Runnable::class.java
).apply { isAccessible = true }.invoke(
null,
context, context,
"Clear Cache & Reset", "Clear Cache & Reset",
"This will clear all cached bundles and reset to default settings.", "This will clear all cached bundles and reset to default settings."
Runnable { LogBoxActions.clearCacheAndReset(context) } ) {
) LogBoxActions.clearCacheAndReset(context)
}
}) })
container.addView(LogBoxComponents.createM3Button(context, "Back", colors) { container.addView(LogBoxComponents.createM3Button(context, "Back", colors) {

View file

@ -76,7 +76,7 @@ object RecoveryMenu {
} }
} }
private fun showConfirmAction(context: Context, title: String, message: String, action: () -> Unit) { fun showConfirmAction(context: Context, title: String, message: String, action: () -> Unit) {
val colors = LogBoxTheme.getM3Colors(context) val colors = LogBoxTheme.getM3Colors(context)
val container = LogBoxComponents.createMenuContainer(context, colors) val container = LogBoxComponents.createMenuContainer(context, colors)

View file

@ -19,6 +19,7 @@ import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.core.view.setPadding import androidx.core.view.setPadding
import de.robv.android.xposed.XC_MethodHook.MethodHookParam
import de.robv.android.xposed.XC_MethodReplacement import de.robv.android.xposed.XC_MethodReplacement
import de.robv.android.xposed.XposedBridge import de.robv.android.xposed.XposedBridge
import de.robv.android.xposed.XposedHelpers import de.robv.android.xposed.XposedHelpers
@ -26,6 +27,7 @@ import de.robv.android.xposed.callbacks.XC_LoadPackage
import GoonXposed.xposed.Module import GoonXposed.xposed.Module
import GoonXposed.xposed.Utils.Companion.reloadApp import GoonXposed.xposed.Utils.Companion.reloadApp
import GoonXposed.xposed.Utils.Log import GoonXposed.xposed.Utils.Log
import GoonXposed.xposed.hook
import kotlinx.coroutines.* import kotlinx.coroutines.*
import org.json.JSONObject import org.json.JSONObject
import java.io.File import java.io.File

View file

@ -6,8 +6,12 @@ import de.robv.android.xposed.XposedBridge
import de.robv.android.xposed.callbacks.XC_LoadPackage import de.robv.android.xposed.callbacks.XC_LoadPackage
import GoonXposed.xposed.BuildConfig import GoonXposed.xposed.BuildConfig
import GoonXposed.xposed.Constants import GoonXposed.xposed.Constants
import GoonXposed.xposed.MethodHookBuilder
import GoonXposed.xposed.Module import GoonXposed.xposed.Module
import GoonXposed.xposed.Utils.Companion.JSON import GoonXposed.xposed.Utils.Companion.JSON
import GoonXposed.xposed.hook
import GoonXposed.xposed.method
import GoonXposed.xposed.safeLoadClass
import GoonXposed.xposed.modules.HookScriptLoaderModule.PRELOADS_DIR import GoonXposed.xposed.modules.HookScriptLoaderModule.PRELOADS_DIR
import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put import kotlinx.serialization.json.put
@ -48,7 +52,7 @@ class PayloadGlobalModule(private val modules: List<Module>) : Module() {
"loadScriptFromFile", "loadScriptFromFile",
String::class.java, String::class.java,
String::class.java, String::class.java,
Boolean::class.javaPrimitiveType Boolean::class.javaPrimitiveType!!
), param.thisObject, arrayOf(absolutePath, absolutePath, param.args[2]) ), param.thisObject, arrayOf(absolutePath, absolutePath, param.args[2])
) )
@ -70,11 +74,11 @@ class PayloadGlobalModule(private val modules: List<Module>) : Module() {
private fun hook(instance: Class<*>, hook: XC_MethodHook) { private fun hook(instance: Class<*>, hook: XC_MethodHook) {
instance.method( instance.method(
"loadScriptFromAssets", AssetManager::class.java, String::class.java, Boolean::class.javaPrimitiveType, "loadScriptFromAssets", AssetManager::class.java, String::class.java, Boolean::class.javaPrimitiveType!!,
).hook(hook) ).hook(hook)
instance.method( instance.method(
"loadScriptFromFile", String::class.java, String::class.java, Boolean::class.javaPrimitiveType "loadScriptFromFile", String::class.java, String::class.java, Boolean::class.javaPrimitiveType!!
).hook(hook) ).hook(hook)
} }
} }

View file

@ -4,6 +4,7 @@ import android.content.Context
import android.util.AtomicFile import android.util.AtomicFile
import GoonXposed.xposed.Module import GoonXposed.xposed.Module
import GoonXposed.xposed.Utils.Log import GoonXposed.xposed.Utils.Log
import GoonXposed.xposed.asFile
import GoonXposed.xposed.modules.bridge.BridgeModule import GoonXposed.xposed.modules.bridge.BridgeModule
import java.io.* import java.io.*

View file

@ -57,7 +57,7 @@ object UpdaterModule : Module() {
private const val CONFIG_FILE = "loader.json" private const val CONFIG_FILE = "loader.json"
private const val DEFAULT_BUNDLE_URL = private const val DEFAULT_BUNDLE_URL =
"https://github.com/OTKUSteyler/GoonCord/releases/download/latest/gooncord.js" "https://github.com/OTKUSteyler/GoonCord/releases/latest/download/gooncord.js"
override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) = with(packageParam) { override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) = with(packageParam) {
// store app data dir for later checks (LogBox settings live under files/logbox) // store app data dir for later checks (LogBox settings live under files/logbox)

View file

@ -1,3 +1,5 @@
@file:Suppress("DEPRECATION")
package GoonXposed.xposed.modules.appearance package GoonXposed.xposed.modules.appearance
import android.content.res.AssetManager import android.content.res.AssetManager
@ -27,6 +29,14 @@ import kotlinx.serialization.json.put
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
// NOTE: These extensions used to be resolved from a shared Utils/Extensions file,
// but that reference broke (renamed/removed/relocated), causing "Unresolved reference"
// build failures. Defining them locally here removes the dependency on that file.
// asFile(): ensures the parent directory of a file exists.
// asDir(): ensures the directory itself exists.
private fun File.asFile(): File = apply { parentFile?.mkdirs() }
private fun File.asDir(): File = apply { mkdirs() }
@Serializable @Serializable
data class FontDefinition( data class FontDefinition(
val name: String? = null, val name: String? = null,
@ -93,28 +103,40 @@ object FontsModule : Module() {
// These files should be downloaded by the JS side, but oh well // These files should be downloaded by the JS side, but oh well
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
fontDef.main.keys.map { name -> val client = HttpClient(CIO) {
async { install(UserAgent) { agent = Constants.USER_AGENT }
val url = fontDef.main.getValue(name) install(HttpTimeout) {
try { requestTimeoutMillis = 15_000
Log.i("Downloading $name from $url") connectTimeoutMillis = 10_000
val file = socketTimeoutMillis = 15_000
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() }
try {
fontDef.main.keys.map { name ->
async {
val url = fontDef.main.getValue(name)
try {
val extension = FILE_EXTENSIONS.firstOrNull { url.substringBefore('?').endsWith(it) }
?: FILE_EXTENSIONS[0]
val file = File(fontsDir, "$name$extension").apply { asFile() }
if (file.exists()) return@async
Log.i("Downloading $name from $url")
val response: HttpResponse = client.get(url)
if (response.status == HttpStatusCode.OK) {
file.writeBytes(response.body())
} else {
Log.e("Failed to download fonts ($name from $url): HTTP ${response.status}")
}
} catch (e: Throwable) {
Log.e("Failed to download fonts ($name from $url)", e)
}
}
}.awaitAll()
} finally {
client.close()
}
} }
} }

View file

@ -1,10 +1,10 @@
package GoonXposed.xposed.modules.appearance package GoonXposed.xposed.modules.appearance
import android.R.color import android.R.color
import android.app.AndroidAppHelper
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import de.robv.android.xposed.XposedHelpers
import GoonXposed.xposed.Module import GoonXposed.xposed.Module
import kotlinx.serialization.json.* import kotlinx.serialization.json.*
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
@ -15,7 +15,11 @@ object SysColorsModule : Module() {
@Deprecated("This method is deprecated in the parent class") @Deprecated("This method is deprecated in the parent class")
override fun buildPayload(builder: JsonObjectBuilder) { override fun buildPayload(builder: JsonObjectBuilder) {
context = WeakReference(AndroidAppHelper.currentApplication()) context = WeakReference(runCatching {
XposedHelpers.callStaticMethod(
XposedHelpers.findClass("android.app.ActivityThread", null), "currentApplication"
) as Context
}.getOrNull())
val accents = arrayOf("accent1", "accent2", "accent3", "neutral1", "neutral2") val accents = arrayOf("accent1", "accent2", "accent3", "neutral1", "neutral2")
val shades = arrayOf(0, 10, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000) val shades = arrayOf(0, 10, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000)
@ -38,7 +42,8 @@ object SysColorsModule : Module() {
} }
private fun convertToColor(id: Int): String { private fun convertToColor(id: Int): String {
val clr = if (isSupported()) ContextCompat.getColor(context.get()!!, id) else 0 val ctx = context.get()
val clr = if (isSupported() && ctx != null) ContextCompat.getColor(ctx, id) else 0
return String.format("#%06X", 0xFFFFFF and clr) return String.format("#%06X", 0xFFFFFF and clr)
} }
} }

View file

@ -5,8 +5,12 @@ import android.content.res.Resources
import androidx.core.graphics.toColorInt import androidx.core.graphics.toColorInt
import de.robv.android.xposed.callbacks.XC_LoadPackage import de.robv.android.xposed.callbacks.XC_LoadPackage
import GoonXposed.xposed.Constants import GoonXposed.xposed.Constants
import GoonXposed.xposed.MethodHookBuilder
import GoonXposed.xposed.Module import GoonXposed.xposed.Module
import GoonXposed.xposed.Utils.Companion.JSON import GoonXposed.xposed.Utils.Companion.JSON
import GoonXposed.xposed.asFile
import GoonXposed.xposed.hook
import GoonXposed.xposed.method
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonObjectBuilder import kotlinx.serialization.json.JsonObjectBuilder
@ -42,7 +46,6 @@ object ThemesModule : Module() {
private const val THEME_FILE = "current-theme.json" private const val THEME_FILE = "current-theme.json"
@Deprecated("This method is deprecated in the parent class") @Deprecated("This method is deprecated in the parent class")
@ExperimentalSerializationApi @ExperimentalSerializationApi
override fun buildPayload(builder: JsonObjectBuilder) { override fun buildPayload(builder: JsonObjectBuilder) {
@ -82,10 +85,20 @@ object ThemesModule : Module() {
} }
fun hookTheme() { fun hookTheme() {
val themeManager = param.classLoader.loadClass("com.discord.theme.utils.ColorUtilsKt") try {
val darkTheme = param.classLoader.loadClass("com.discord.theme.DarkerTheme") val themeManager = param.classLoader.loadClass("com.discord.theme.utils.ColorUtilsKt")
val lightTheme = param.classLoader.loadClass("com.discord.theme.LightTheme") val darkTheme = param.classLoader.loadClass("com.discord.theme.DarkerTheme")
val lightTheme = param.classLoader.loadClass("com.discord.theme.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<*>, lightTheme: Class<*>
) {
val theme = this.theme ?: return val theme = this.theme ?: return
// Apply rawColors // Apply rawColors
@ -107,42 +120,53 @@ object ThemesModule : Module() {
// 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()) {
val getColorCompat = themeManager.getDeclaredMethod( try {
"getColorCompat", val getColorCompat = themeManager.getDeclaredMethod(
Resources::class.java, "getColorCompat",
Int::class.javaPrimitiveType, Resources::class.java,
Resources.Theme::class.java, Int::class.javaPrimitiveType,
) Resources.Theme::class.java,
)
val getColorCompatLegacy = themeManager.getDeclaredMethod( val getColorCompatLegacy = themeManager.getDeclaredMethod(
"getColorCompat", Context::class.java, Int::class.javaPrimitiveType "getColorCompat", Context::class.java, Int::class.javaPrimitiveType
) )
val patch = MethodHookBuilder().run { val patch = MethodHookBuilder().run {
before { before {
val arg1 = args[0] try {
val resources = if (arg1 is Context) arg1.resources else (arg1 as Resources) val arg1 = args[0]
val name = resources.getResourceEntryName(args[1] as Int) val resources = if (arg1 is Context) arg1.resources else (arg1 as Resources)
val name = resources.getResourceEntryName(args[1] as Int)
if (rawColorMap[name] != null) result = rawColorMap[name] if (rawColorMap[name] != null) result = rawColorMap[name]
} catch (_: Throwable) {
}
}
build()
} }
build() getColorCompat.hook(patch)
getColorCompatLegacy.hook(patch)
} catch (e: Throwable) {
GoonXposed.xposed.Utils.Log.e("Failed to hook getColorCompat: ${e.message}")
} }
getColorCompat.hook(patch)
getColorCompatLegacy.hook(patch)
} }
} }
// Parse HEX colour string to INT. Takes "#RRGGBBAA" or "#RRGGBB" // Parse HEX colour string to INT. Takes "#RRGGBBAA" or "#RRGGBB"
private fun hexStringToColorInt(hexString: String): Int { private fun hexStringToColorInt(hexString: String): Int {
return if (hexString.length == 9) { return try {
// Rearrange RRGGBBAA -> AARRGGBB so parseColor() is happy if (hexString.length == 9) {
val alpha = hexString.substring(7, 9) // Rearrange RRGGBBAA -> AARRGGBB so parseColor() is happy
val rrggbb = hexString.substring(1, 7) val alpha = hexString.substring(7, 9)
"#$alpha$rrggbb".toColorInt() val rrggbb = hexString.substring(1, 7)
} else hexString.toColorInt() "#$alpha$rrggbb".toColorInt()
} else hexString.toColorInt()
} catch (_: Throwable) {
0
}
} }
private fun hookThemeMethod(themeClass: Class<*>, methodName: String, themeValue: Int) { private fun hookThemeMethod(themeClass: Class<*>, methodName: String, themeValue: Int) {

View file

@ -9,6 +9,7 @@ import android.os.Build
import android.widget.Toast import android.widget.Toast
import GoonXposed.xposed.Module import GoonXposed.xposed.Module
import GoonXposed.xposed.Utils import GoonXposed.xposed.Utils
import GoonXposed.xposed.getAppInfo
import java.io.File import java.io.File
object AdditionalBridgeMethodsModule : Module() { object AdditionalBridgeMethodsModule : Module() {

View file

@ -5,6 +5,8 @@ import GoonXposed.xposed.BuildConfig
import GoonXposed.xposed.Constants import GoonXposed.xposed.Constants
import GoonXposed.xposed.Module import GoonXposed.xposed.Module
import GoonXposed.xposed.Utils.Log import GoonXposed.xposed.Utils.Log
import GoonXposed.xposed.hookMethod
import GoonXposed.xposed.method
import java.lang.reflect.Method import java.lang.reflect.Method
/** /**

View file

@ -4,6 +4,8 @@ import android.content.Context
import de.robv.android.xposed.callbacks.XC_LoadPackage import de.robv.android.xposed.callbacks.XC_LoadPackage
import GoonXposed.xposed.Module import GoonXposed.xposed.Module
import GoonXposed.xposed.Utils.Log import GoonXposed.xposed.Utils.Log
import GoonXposed.xposed.hookMethod
import GoonXposed.xposed.safeLoadClass
/** /**
* Hooks Discord's crash reporting to disable Sentry initialization. * Hooks Discord's crash reporting to disable Sentry initialization.

View file

@ -4,6 +4,8 @@ import android.content.Context
import de.robv.android.xposed.callbacks.XC_LoadPackage import de.robv.android.xposed.callbacks.XC_LoadPackage
import GoonXposed.xposed.Module import GoonXposed.xposed.Module
import GoonXposed.xposed.Utils.Log import GoonXposed.xposed.Utils.Log
import GoonXposed.xposed.hookMethod
import GoonXposed.xposed.safeLoadClass
/** /**
* Hooks Discord's deep links tracking to disable AppsFlyer initialization. * Hooks Discord's deep links tracking to disable AppsFlyer initialization.