Compare commits

..

65 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
OTKUSteyler
b083a66afd
Update FontsModule.kt
Some checks failed
Build APK / Build and push (push) Failing after 3m49s
2026-09-03 17:45:01 +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-cio:3.3.1")
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.no_track.BlockCrashReportingModule
import GoonXposed.xposed.modules.no_track.BlockDeepLinksTrackingModule
import GoonXposed.xposed.modules.LogBox.LogBoxModule
import kotlinx.coroutines.CompletableDeferred
object HookStateHolder {

View file

@ -2,182 +2,160 @@ package GoonXposed.xposed
import android.app.Activity
import android.content.Context
import android.os.Build
import android.content.pm.PackageManager
import de.robv.android.xposed.IXposedHookZygoteInit
import de.robv.android.xposed.XC_MethodHook
import de.robv.android.xposed.XposedBridge
import de.robv.android.xposed.XposedHelpers
import de.robv.android.xposed.callbacks.XC_LoadPackage
import kotlinx.serialization.json.JsonObjectBuilder
import java.io.File
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(
val name: String,
val packageName: String,
val version: String,
val versionCode: Long,
val versionCode: Int
)
abstract class Module {
/**
* Builds a JSON payload to be injected into the JavaScript context.
*/
@Deprecated("This will be removed in future versions. Payloads can be replaced via synchronous bridge methods.")
open fun buildPayload(builder: JsonObjectBuilder) {
fun Context.getAppInfo(): AppInfo {
val label = applicationInfo.loadLabel(packageManager).toString()
val packageInfo = try {
packageManager.getPackageInfo(packageName, 0)
} catch (e: PackageManager.NameNotFoundException) {
null
}
/**
* Called during Zygote initialization.
*/
open fun onInit(startupParam: IXposedHookZygoteInit.StartupParam) {}
/**
* 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
}
}
return AppInfo(
name = label,
packageName = packageName,
version = packageInfo?.versionName ?: "",
versionCode = packageInfo?.versionCode ?: 0
)
}

View file

@ -1,9 +1,10 @@
package GoonXposed.xposed
import android.app.AlertDialog
import android.app.AndroidAppHelper
import android.app.Application
import android.content.Context
import android.content.Intent
import de.robv.android.xposed.XposedHelpers
import GoonXposed.xposed.modules.UpdaterModule
import kotlinx.serialization.json.Json
import java.io.File
@ -14,7 +15,9 @@ class Utils {
val JSON = Json { ignoreUnknownKeys = true }
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)
application.startActivity(Intent.makeRestartActivityTask(intent!!.component))
exitProcess(0)

View file

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

View file

@ -1,9 +1,11 @@
package GoonXposed.xposed.modules
import GoonXposed.xposed.hookMethod
import android.content.res.Resources
import de.robv.android.xposed.callbacks.XC_LoadPackage
import GoonXposed.xposed.Constants.Companion.TARGET_PACKAGE
import GoonXposed.xposed.Module
import GoonXposed.xposed.hookMethod
/**
* 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.callbacks.XC_LoadPackage
import GoonXposed.xposed.Constants
import GoonXposed.xposed.HookScope
import GoonXposed.xposed.HookStateHolder
import GoonXposed.xposed.Module
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 kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
@ -54,11 +60,11 @@ object HookScriptLoaderModule : Module() {
private fun hook(instance: Class<*>) = runCatching {
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(
"loadScriptFromFile", String::class.java, String::class.java, Boolean::class.javaPrimitiveType
"loadScriptFromFile", String::class.java, String::class.java, Boolean::class.javaPrimitiveType!!
)
loadScriptFromAssets.hook {
@ -107,14 +113,10 @@ object HookScriptLoaderModule : Module() {
}
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")
if (disabledMarker.exists()) {
Log.i("Script loading disabled by marker; skipping preloads, cached bundle and fallback")
} else {
// Normal behaviour: run preloads then cached main script or fallback to bundled asset
preloadsDir.walk().filter { it.isFile }.forEach(runScriptFile)
if (mainScript.exists()) {

View file

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

View file

@ -4,6 +4,7 @@ import GoonXposed.xposed.Module
import GoonXposed.xposed.Utils.Log
import GoonXposed.xposed.Utils.Companion.reloadApp
import android.content.Context
import de.robv.android.xposed.XC_MethodHook
import de.robv.android.xposed.XC_MethodReplacement
import de.robv.android.xposed.XposedBridge
import de.robv.android.xposed.XposedHelpers
@ -23,11 +24,15 @@ object LogBoxModule : Module() {
val getUseDeveloperSupportMethod =
dcdReactNativeHostClass.methods.first { it.name == "getUseDeveloperSupport" }
getUseDeveloperSupportMethod.hook {
before {
result = true
// 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}")

View file

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

View file

@ -19,6 +19,7 @@ import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
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.XposedBridge
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.Utils.Companion.reloadApp
import GoonXposed.xposed.Utils.Log
import GoonXposed.xposed.hook
import kotlinx.coroutines.*
import org.json.JSONObject
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 GoonXposed.xposed.BuildConfig
import GoonXposed.xposed.Constants
import GoonXposed.xposed.MethodHookBuilder
import GoonXposed.xposed.Module
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 kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
@ -48,7 +52,7 @@ class PayloadGlobalModule(private val modules: List<Module>) : Module() {
"loadScriptFromFile",
String::class.java,
String::class.java,
Boolean::class.javaPrimitiveType
Boolean::class.javaPrimitiveType!!
), 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) {
instance.method(
"loadScriptFromAssets", AssetManager::class.java, String::class.java, Boolean::class.javaPrimitiveType,
"loadScriptFromAssets", AssetManager::class.java, String::class.java, Boolean::class.javaPrimitiveType!!,
).hook(hook)
instance.method(
"loadScriptFromFile", String::class.java, String::class.java, Boolean::class.javaPrimitiveType
"loadScriptFromFile", String::class.java, String::class.java, Boolean::class.javaPrimitiveType!!
).hook(hook)
}
}

View file

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

View file

@ -57,7 +57,7 @@ object UpdaterModule : Module() {
private const val CONFIG_FILE = "loader.json"
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) {
// 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
import android.content.res.AssetManager
@ -27,6 +29,14 @@ import kotlinx.serialization.json.put
import java.io.File
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
data class FontDefinition(
val name: String? = null,
@ -93,28 +103,40 @@ object FontsModule : Module() {
// 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)
}
val client = HttpClient(CIO) {
install(UserAgent) { agent = Constants.USER_AGENT }
install(HttpTimeout) {
requestTimeoutMillis = 15_000
connectTimeoutMillis = 10_000
socketTimeoutMillis = 15_000
}
}.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
import android.R.color
import android.app.AndroidAppHelper
import android.content.Context
import android.os.Build
import androidx.core.content.ContextCompat
import de.robv.android.xposed.XposedHelpers
import GoonXposed.xposed.Module
import kotlinx.serialization.json.*
import java.lang.ref.WeakReference
@ -15,7 +15,11 @@ object SysColorsModule : Module() {
@Deprecated("This method is deprecated in the parent class")
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 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 {
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)
}
}

View file

@ -5,8 +5,12 @@ import android.content.res.Resources
import androidx.core.graphics.toColorInt
import de.robv.android.xposed.callbacks.XC_LoadPackage
import GoonXposed.xposed.Constants
import GoonXposed.xposed.MethodHookBuilder
import GoonXposed.xposed.Module
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.Serializable
import kotlinx.serialization.json.JsonObjectBuilder
@ -42,7 +46,6 @@ object ThemesModule : Module() {
private const val THEME_FILE = "current-theme.json"
@Deprecated("This method is deprecated in the parent class")
@ExperimentalSerializationApi
override fun buildPayload(builder: JsonObjectBuilder) {
@ -82,10 +85,20 @@ 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")
try {
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, 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
// Apply rawColors
@ -107,42 +120,53 @@ 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,
)
try {
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 getColorCompatLegacy = themeManager.getDeclaredMethod(
"getColorCompat", Context::class.java, Int::class.javaPrimitiveType
)
val patch = MethodHookBuilder().run {
before {
val arg1 = args[0]
val resources = if (arg1 is Context) arg1.resources else (arg1 as Resources)
val name = resources.getResourceEntryName(args[1] as Int)
val patch = MethodHookBuilder().run {
before {
try {
val arg1 = args[0]
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"
private fun hexStringToColorInt(hexString: String): Int {
return if (hexString.length == 9) {
// Rearrange RRGGBBAA -> AARRGGBB so parseColor() is happy
val alpha = hexString.substring(7, 9)
val rrggbb = hexString.substring(1, 7)
"#$alpha$rrggbb".toColorInt()
} else hexString.toColorInt()
return try {
if (hexString.length == 9) {
// Rearrange RRGGBBAA -> AARRGGBB so parseColor() is happy
val alpha = hexString.substring(7, 9)
val rrggbb = hexString.substring(1, 7)
"#$alpha$rrggbb".toColorInt()
} else hexString.toColorInt()
} catch (_: Throwable) {
0
}
}
private fun hookThemeMethod(themeClass: Class<*>, methodName: String, themeValue: Int) {

View file

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

View file

@ -5,6 +5,8 @@ import GoonXposed.xposed.BuildConfig
import GoonXposed.xposed.Constants
import GoonXposed.xposed.Module
import GoonXposed.xposed.Utils.Log
import GoonXposed.xposed.hookMethod
import GoonXposed.xposed.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 GoonXposed.xposed.Module
import GoonXposed.xposed.Utils.Log
import GoonXposed.xposed.hookMethod
import GoonXposed.xposed.safeLoadClass
/**
* 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 GoonXposed.xposed.Module
import GoonXposed.xposed.Utils.Log
import GoonXposed.xposed.hookMethod
import GoonXposed.xposed.safeLoadClass
/**
* Hooks Discord's deep links tracking to disable AppsFlyer initialization.