Compare commits

..

260 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
OTKUSteyler
4115c3d4df
Update build.gradle.kts
Some checks failed
Build APK / Build and push (push) Failing after 3m58s
2026-09-03 15:36:30 +02:00
OTKUSteyler
c63eaa5a12
Update FontsModule.kt 2026-09-03 14:58:44 +02:00
OTKUSteyler
531cb9281c
Update FontsModule.kt 2026-09-03 13:00:51 +02:00
OTKUSteyler
bff796d574
Update FontsModule.kt 2026-09-03 12:45:07 +02:00
OTKUSteyler
f7d1b2ea1d
Update FontsModule.kt 2026-09-03 05:58:15 +02:00
OTKUSteyler
9a620545b4
Update FontsModule.kt 2026-09-02 16:17:06 +02:00
OTKUSteyler
6e7f5302e3
Update build.gradle.kts
Some checks failed
Build APK / Build and push (push) Failing after 4m49s
2026-09-02 15:27:31 +02:00
OTKUSteyler
ec787e7cf5
Update build.gradle.kts 2026-09-02 15:27:08 +02:00
OTKUSteyler
8ef48f3608
Update FontsModule.kt 2026-09-02 14:43:48 +02:00
OTKUSteyler
2bddedb96c
Update FontsModule.kt 2026-09-02 14:41:44 +02:00
OTKUSteyler
5d39748a28 Fix overload resolution ambiguity by replacing wildcard import with specific LogBoxModule import 2026-08-26 16:46:01 +02:00
OTKUSteyler
1a9fd61ec3 Fix: Add missing LogBoxModule and onActivity hook - fixes blank plugin/theme tabs 2026-08-26 16:41:59 +02:00
OTKUSteyler
a9a1dc4ad3 chore: Bump version to 1.3.6 2026-08-26 16:29:21 +02:00
OTKUSteyler
aab9fdc101 Fix: Add @Deprecated annotation to buildPayload override 2026-08-26 16:22:22 +02:00
OTKUSteyler
21029f5f72 Fix: Add @Deprecated annotation to buildPayload override 2026-08-26 16:22:10 +02:00
OTKUSteyler
64ffdf4f32 Fix: Add @Deprecated annotation to buildPayload override 2026-08-26 16:21:48 +02:00
OTKUSteyler
68c85dd812 Fix: Skip release signing when keystore not found and suppress deprecated warnings 2026-08-26 16:21:40 +02:00
OTKUSteyler
fd84dd0320 Fix: Remove invalid method parameters from UpdaterModule calls
- Line 86: Remove `isCustomUrlEnabled` reference (doesn't exist in UpdaterModule)
- Line 92: Remove `showUpdateDialog = false` parameter from `downloadScript()` call
- Line 96: Remove `showUpdateDialog = true` parameter from `downloadScript()` call

The `UpdaterModule.downloadScript()` method only accepts an optional `activity` parameter, not `showUpdateDialog`.
The logic has been simplified to check only if the main script exists.
2026-08-26 16:15:12 +02:00
OTKUSteyler
7df631b799
Update README.md 2026-08-26 16:09:02 +02:00
OTKUSteyler
3d45a8d70f
Merge branch 'kmmiio99o:master' into master 2026-08-26 16:05:36 +02:00
OTKUSteyler
2978e3b8d1
Merge branch 'kmmiio99o:master' into master 2026-08-26 06:16:01 +02:00
OTKUSteyler
ffcc29796c
Update LogBoxModule.kt 2026-08-18 10:38:53 +02:00
OTKUSteyler
79298ddf6c
Update LogBoxMenu.kt 2026-08-18 10:37:38 +02:00
OTKUSteyler
e0917110e1
Update LogBoxModule.kt 2026-08-18 10:29:36 +02:00
OTKUSteyler
1de8af79f0
Rename package from ShiggyXposed to GoonXposed 2026-08-18 10:20:54 +02:00
OTKUSteyler
5fe307a852
Update and rename Module.kt.txt to Module.kt 2026-08-18 10:19:28 +02:00
OTKUSteyler
08bfa8199d
Add files via upload 2026-08-18 10:18:42 +02:00
OTKUSteyler
2c94757947
Update CacheModule.kt 2026-08-18 10:09:31 +02:00
OTKUSteyler
85837531de
Delete app/src/main/kotlin/GoonXposed/xposed/xposed directory 2026-08-17 09:07:40 +02:00
OTKUSteyler
3360ee33f1
Rename Module.kt to GoonXposed directory 2026-08-17 09:07:18 +02:00
OTKUSteyler
d882007611
Delete app/src/main/kotlin/GoonXposed/xposed/xposed/Utils.kt 2026-08-17 09:06:16 +02:00
OTKUSteyler
bd9116e1cf
Rename Utils.kt to Utils.kt 2026-08-17 09:05:44 +02:00
OTKUSteyler
5e54c7bf61
Update LogBoxModule.kt 2026-08-17 08:58:41 +02:00
OTKUSteyler
bf6f3c0691
Refactor LogBoxModule to use urlEnabled instead of isEnabled 2026-08-17 08:45:26 +02:00
OTKUSteyler
7eb893bcc9
Update build.gradle.kts 2026-08-17 08:35:12 +02:00
OTKUSteyler
52f05764f4 Merge remote-tracking branch 'upstream/master' 2026-08-17 08:25:42 +02:00
OTKUSteyler
cdbbb3a957
Update README.md 2026-05-22 21:51:39 +02:00
OTKUSteyler
457af73f88
Update OptionsMenu.kt 2026-05-18 01:10:23 +02:00
OTKUSteyler
6be3d89176
Update LogBoxUtils.kt 2026-05-18 01:10:02 +02:00
OTKUSteyler
9267cb3918
Update LogBoxTheme.kt 2026-05-18 01:09:28 +02:00
OTKUSteyler
f55d1a4a62
Rename package from ShiggyXposed to GoonXposed 2026-05-18 01:09:06 +02:00
OTKUSteyler
cc0ffe6fb2
Update LogBoxMenu.kt 2026-05-18 01:08:46 +02:00
OTKUSteyler
acca6bfe47
Rename package from ShiggyXposed to GoonXposed 2026-05-18 01:08:09 +02:00
OTKUSteyler
8148f860e5
Update LogBoxConstants.kt 2026-05-18 01:07:46 +02:00
OTKUSteyler
f0e9503354
Update LogBoxComponents.kt 2026-05-18 01:07:24 +02:00
OTKUSteyler
4f779c9af0
Update RecoveryMenu.kt 2026-05-18 01:07:00 +02:00
OTKUSteyler
202152bbf0
Update LogBoxActions.kt 2026-05-18 01:05:58 +02:00
OTKUSteyler
d55b9dcda0
Rename package from ShiggyXposed to GoonXposed 2026-05-18 01:05:28 +02:00
OTKUSteyler
6f6f2e4abc
Update CustomBundleDialog.kt 2026-05-18 01:05:06 +02:00
OTKUSteyler
c3c21a26e0 Merge remote-tracking branch 'upstream/master' 2026-05-18 01:01:03 +02:00
OTKUSteyler
5e2fd369fe Merge remote-tracking branch 'upstream/master' 2026-02-14 21:00:53 +01:00
OTKUSteyler
7635ac1d90
Merge branch 'kmmiio99o:master' into master 2026-01-10 16:32:15 +00:00
OTKUSteyler
5769fe2211
Update CacheModule.kt 2026-01-03 00:24:07 +00:00
OTKUSteyler
04a799e7f6
Update BridgeModule.kt 2026-01-03 00:23:02 +00:00
OTKUSteyler
ed8d5fb7a8
Update BridgeModule.kt 2026-01-03 00:06:52 +00:00
OTKUSteyler
8038be3023
Update gradle.properties 2026-01-03 00:05:33 +00:00
OTKUSteyler
511fd1df4e
Update PerfPatchesModule.kt 2026-01-03 00:01:21 +00:00
OTKUSteyler
0b63dbbc9a
Update CacheModule.kt 2026-01-02 23:58:25 +00:00
OTKUSteyler
85290a619e Merge remote-tracking branch 'upstream/master' 2026-01-03 00:53:48 +01:00
OTKUSteyler
d1796e4c68 Merge remote-tracking branch 'upstream/master' 2025-12-11 18:29:19 +01:00
OTKUSteyler
f92e36e7d4
Merge branch 'kmmiio99o:master' into master 2025-12-10 07:01:53 +01:00
OTKUSteyler
cbf96e8b45
Update README.md 2025-12-07 23:38:33 +01:00
OTKUSteyler
4537e7d56c
Update README.md 2025-12-07 23:37:35 +01:00
OTKUSteyler
782c40b5d3
Merge branch 'kmmiio99o:master' into master 2025-12-07 23:36:03 +01:00
OTKUSteyler
21effc7f21
Update build.gradle.kts
Some checks failed
Build APK / Build and push (push) Failing after 4m47s
2025-11-30 22:47:37 +01:00
OTKUSteyler
cc1f7fe682
Update LogBoxModule.kt 2025-11-30 22:30:33 +01:00
OTKUSteyler
f619b8702c
Update build.gradle.kts 2025-11-29 11:58:34 +01:00
OTKUSteyler
b4c17583b6
Update Utils.kt 2025-11-28 21:46:14 +01:00
OTKUSteyler
ff308a190c
Update LogBoxModule.kt 2025-11-28 18:54:02 +01:00
OTKUSteyler
c5f3b83cdf
Update build.gradle.kts 2025-11-28 18:45:48 +01:00
OTKUSteyler
03c81d8cc9
Merge branch 'kmmiio99o:master' into master 2025-11-28 18:37:39 +01:00
OTKUSteyler
3c4c9ca555
Update build.yml 2025-11-28 18:33:04 +01:00
OTKUSteyler
19bade53a0 Merge remote-tracking branch 'upstream/master' 2025-11-28 18:28:20 +01:00
OTKUSteyler
351086110c
Lower minSdk and targetSdk to 28 2025-11-28 17:55:38 +01:00
OTKUSteyler
5bc83206b2
Update build.gradle.kts 2025-11-28 16:05:08 +01:00
OTKUSteyler
8aa226d6ca
Update versionCode and versionName to 1.3.2
Some checks failed
Build APK / Build and push (push) Failing after 4m40s
2025-11-28 15:53:59 +01:00
OTKUSteyler
02310df32f
Update LogBoxModule.kt 2025-11-28 15:53:26 +01:00
OTKUSteyler
ffdd65f74d
Update Utils.kt 2025-11-28 12:26:02 +01:00
OTKUSteyler
90b346b75a
Update build.gradle.kts 2025-11-28 11:56:47 +01:00
OTKUSteyler
3619dafd47
Update build.gradle.kts 2025-11-28 11:42:28 +01:00
OTKUSteyler
12491b2235
Update build.gradle.kts 2025-11-28 11:37:47 +01:00
OTKUSteyler
2dd8151138
Update build.gradle.kts 2025-11-28 11:13:01 +01:00
OTKUSteyler
8c033fe878
Update AndroidManifest.xml 2025-11-28 09:57:37 +01:00
OTKUSteyler
f6d2384fa5
Update AndroidManifest.xml 2025-11-28 09:53:05 +01:00
OTKUSteyler
2621359a61
Update AndroidManifest.xml 2025-11-28 09:45:37 +01:00
OTKUSteyler
9f8c521659
Update build.yml 2025-11-26 18:47:57 +01:00
OTKUSteyler
2ffe2f510d
Update build.yml 2025-11-26 18:35:10 +01:00
OTKUSteyler
f4d48fdf90
Delete .github/workflows/release.yml 2025-11-26 18:33:59 +01:00
OTKUSteyler
66bb7fce6a
Update build.yml 2025-11-26 17:27:56 +01:00
OTKUSteyler
42636b29e8
Update build.yml 2025-11-26 16:30:37 +01:00
OTKUSteyler
e0cb50aaea
Update build.yml 2025-11-26 16:24:32 +01:00
OTKUSteyler
f2371d1c7e
Update build.gradle.kts 2025-11-24 17:59:36 +01:00
OTKUSteyler
01f52c1e2f
Delete app/app directory 2025-11-24 17:52:21 +01:00
OTKUSteyler
8d1d6303d3
Add files via upload 2025-11-24 17:50:15 +01:00
OTKUSteyler
bf05c67966
Delete app/src/main/kotlin/GoonXposed/a 2025-11-24 17:48:25 +01:00
OTKUSteyler
4102b2298d
Create a 2025-11-24 17:48:14 +01:00
OTKUSteyler
2171fc59eb
Delete app/src/main/kotlin/io/github/revenge/xposed directory 2025-11-24 17:38:08 +01:00
OTKUSteyler
8389d19545
Add files via upload 2025-11-24 17:37:47 +01:00
OTKUSteyler
5fe672884e
Delete app/src/main/kotlin/ShiggyXposed/xposed/xposed directory 2025-11-24 17:29:20 +01:00
OTKUSteyler
9481d80e5f Update build.gradle.kts 2025-11-24 17:14:07 +01:00
OTKUSteyler
3f309f58bb
Update UpdaterModule.kt 2025-11-24 16:58:04 +01:00
OTKUSteyler
004bba80ae
Update UpdaterModule.kt 2025-11-24 16:57:36 +01:00
OTKUSteyler
0fc969cf00
Update BridgeModule.kt 2025-11-24 16:21:39 +01:00
OTKUSteyler
691db71d12
Update recovery alert title from ShiggyCord to GoonCord 2025-11-23 18:50:47 +01:00
OTKUSteyler
07265446cf
Update ThemesModule.kt 2025-11-23 18:46:38 +01:00
OTKUSteyler
86c9658318
Update SysColorsModule.kt 2025-11-23 18:46:07 +01:00
OTKUSteyler
8c584d7cfc
Update FontsModule.kt 2025-11-23 18:45:44 +01:00
OTKUSteyler
f42f53b375
Rename package from ShiggyXposed to GoonXposed 2025-11-23 18:45:03 +01:00
OTKUSteyler
8c6c43429d
Update BridgeModule.kt 2025-11-23 18:44:06 +01:00
OTKUSteyler
a4c6e4871e
Update BlockCrashReportingModule.kt 2025-11-23 18:42:57 +01:00
OTKUSteyler
f17cdc8fd0
Update BlockDeepLinksTrackingModule.kt 2025-11-23 18:42:29 +01:00
OTKUSteyler
3506cfc97d
Update CacheModule.kt 2025-11-23 18:41:56 +01:00
OTKUSteyler
7d8a71eaf6
Update FixResourcesModule.kt 2025-11-23 18:41:18 +01:00
OTKUSteyler
eeac21e2aa
Update HookScriptLoaderModule.kt 2025-11-23 18:40:46 +01:00
OTKUSteyler
eb58fdfa00
Update LogBoxModule.kt 2025-11-23 18:39:46 +01:00
OTKUSteyler
44288a202d
Update Constants.kt 2025-11-23 18:38:22 +01:00
OTKUSteyler
3196f8a0a4
Update PayloadGlobalModule.kt 2025-11-23 18:11:04 +01:00
OTKUSteyler
63c12c7984
Change package name in PluginsModule.kt
Updated package name from ShiggyXposed to GoonXposed.
2025-11-23 18:10:01 +01:00
OTKUSteyler
9a716efd37
Update Utils.kt 2025-11-23 18:09:07 +01:00
OTKUSteyler
136b6f7c46
Update Main.kt 2025-11-23 18:08:30 +01:00
OTKUSteyler
63d8e86725
Update UpdaterModule.kt 2025-11-23 16:42:24 +01:00
OTKUSteyler
6cfb92faf1
Update UpdaterModule.kt 2025-11-23 16:39:40 +01:00
OTKUSteyler
f47d6d1d93 Merge remote-tracking branch 'upstream/master' 2025-11-22 22:04:25 +01:00
OTKUSteyler
2eb7cbf84e
Update LogBoxModule.kt 2025-11-22 00:30:46 +01:00
OTKUSteyler
3d6f61bb56
Update FontsModule.kt 2025-11-22 00:26:34 +01:00
OTKUSteyler
01fdbe19d7
Update UpdaterModule.kt 2025-11-22 00:23:41 +01:00
OTKUSteyler
4ecfdfce27
Update Main.kt 2025-11-22 00:18:09 +01:00
OTKUSteyler
e198c43ce2
Update ThemesModule.kt 2025-11-22 00:13:17 +01:00
OTKUSteyler
ee7c27d280
Update SysColorsModule.kt 2025-11-22 00:12:52 +01:00
OTKUSteyler
9a6886ad71
Update FontsModule.kt 2025-11-22 00:12:24 +01:00
OTKUSteyler
019075d927
Update LogBoxModule.kt 2025-11-22 00:11:39 +01:00
OTKUSteyler
6131c74016
Update PayloadGlobalModule.kt 2025-11-22 00:10:53 +01:00
OTKUSteyler
52e10aca4c
Update PluginsModule.kt 2025-11-22 00:10:35 +01:00
OTKUSteyler
4b7192edfa
Update AdditionalBridgeMethodsModule.kt 2025-11-22 00:09:44 +01:00
OTKUSteyler
ac5d0e551b
Update BridgeModule.kt 2025-11-22 00:09:14 +01:00
OTKUSteyler
d04d1d608d
Update BlockCrashReportingModule.kt 2025-11-22 00:08:32 +01:00
OTKUSteyler
46e37bfe7e
Update BlockDeepLinksTrackingModule.kt 2025-11-22 00:07:56 +01:00
OTKUSteyler
e9a2a8c51e
Update CacheModule.kt 2025-11-22 00:07:28 +01:00
OTKUSteyler
e453be092b
Update FixResourcesModule.kt 2025-11-22 00:06:49 +01:00
OTKUSteyler
ced4d26d20
Update FixResourcesModule.kt 2025-11-22 00:06:40 +01:00
OTKUSteyler
683137451b
Update HookScriptLoaderModule.kt 2025-11-22 00:06:17 +01:00
OTKUSteyler
61af37117d
Update LogBoxModule.kt 2025-11-22 00:05:06 +01:00
OTKUSteyler
fb8a6119f9
Update PayloadGlobalModule.kt 2025-11-22 00:03:52 +01:00
OTKUSteyler
62513e7a04
Update UpdaterModule.kt 2025-11-22 00:02:59 +01:00
OTKUSteyler
007a6f8a59
Add files via upload 2025-11-21 23:54:09 +01:00
OTKUSteyler
aa0970913c
Update PayloadGlobalModule.kt 2025-11-21 23:48:46 +01:00
OTKUSteyler
4ae471a7b9
Update Main.kt 2025-11-21 23:47:28 +01:00
OTKUSteyler
f19d654e87
Update build.gradle.kts 2025-11-21 23:45:51 +01:00
OTKUSteyler
7348da9aff
Update LogBoxModule.kt
Some checks failed
Build APK / Build and push (push) Failing after 4m44s
2025-11-19 21:50:01 +01:00
OTKUSteyler
9a6d328b47
Update build.gradle.kts 2025-11-19 21:45:50 +01:00
OTKUSteyler
afdda092bc
Update build.gradle.kts 2025-11-10 23:57:59 +01:00
OTKUSteyler
6858113d54
Update build.gradle.kts
Some checks failed
Build APK / Build and push (push) Failing after 4m41s
2025-11-10 23:38:54 +01:00
OTKUSteyler
adeba43dca
Update AndroidManifest.xml
Some checks failed
Build APK / Build and push (push) Failing after 4m38s
2025-11-09 11:14:52 +01:00
OTKUSteyler
660ee94361
Update build.yml
Some checks failed
Build APK / Build and push (push) Failing after 7m3s
2025-11-06 23:26:36 +01:00
OTKUSteyler
ae7ab4fd21
Update AndroidManifest.xml 2025-11-06 23:20:27 +01:00
OTKUSteyler
cf531f247a
Merge pull request #3 from kmmiio99o/master
Update ThemesModule.kt
2025-11-06 23:10:21 +01:00
kmmiio99o
c1e73bc602
Update ThemesModule.kt 2025-11-06 23:06:47 +01:00
OTKUSteyler
ccd0a1f066
Update ThemesModule.kt 2025-11-06 22:58:27 +01:00
OTKUSteyler
f210de66f2
Merge pull request #1 from kmmiio99o/patch-1
Updated ThemesModule.kt
2025-11-06 22:41:04 +01:00
kmmiio99o
1d0c42680f
Update ThemesModule.kt 2025-11-06 22:39:07 +01:00
OTKUSteyler
6e58a231e0
Update Main.kt 2025-11-06 22:34:46 +01:00
OTKUSteyler
4d94dccdfc
Update ThemesModule.kt 2025-11-06 22:34:25 +01:00
OTKUSteyler
4ab61ffcc5
Update Main.kt 2025-11-06 22:33:45 +01:00
OTKUSteyler
5eede3f588
Update Main.kt 2025-11-06 22:28:42 +01:00
OTKUSteyler
28d9fbabc8
Update Main.kt 2025-11-06 22:19:06 +01:00
OTKUSteyler
651e647f29
Update ThemesModule.kt 2025-11-06 22:10:15 +01:00
OTKUSteyler
0a712869d5
Update Main.kt 2025-11-06 22:00:56 +01:00
OTKUSteyler
3d4e9d052b
Update build.yml 2025-11-06 21:43:57 +01:00
OTKUSteyler
a05d9a9b38
Update LogBoxModule.kt 2025-11-06 16:01:06 +01:00
OTKUSteyler
d0160e829f
Update Main.kt 2025-11-06 15:53:54 +01:00
OTKUSteyler
fb8756b371
Update LogBoxModule.kt 2025-11-06 15:21:26 +01:00
OTKUSteyler
2ddfdd3bfa
Update Constants.kt 2025-11-06 15:13:31 +01:00
OTKUSteyler
078604ef25
Update BridgeModule.kt 2025-11-06 13:57:35 +01:00
OTKUSteyler
0bacc54ac4
Update AdditionalBridgeMethodsModule.kt 2025-11-05 23:17:26 +01:00
OTKUSteyler
af9bd39358
Update BridgeModule.kt 2025-11-05 23:16:29 +01:00
OTKUSteyler
67317dde13
Update FontsModule.kt 2025-11-05 23:15:34 +01:00
OTKUSteyler
eccadcdeff
Update SysColorsModule.kt 2025-11-05 23:14:36 +01:00
OTKUSteyler
a854f81dda
Update ThemesModule.kt 2025-11-05 23:14:02 +01:00
OTKUSteyler
eeec556c9f
Update FixResourcesModule.kt 2025-11-05 23:12:47 +01:00
OTKUSteyler
48d1cd7807
Update HookScriptLoaderModule.kt 2025-11-05 23:11:28 +01:00
OTKUSteyler
be25ad7213
Update LogBoxModule.kt 2025-11-05 23:10:08 +01:00
OTKUSteyler
7a90e92892
Update PayloadGlobalModule.kt 2025-11-05 23:07:49 +01:00
OTKUSteyler
c06ab2754d
Update UpdaterModule.kt 2025-11-05 23:06:54 +01:00
OTKUSteyler
02d6302a8c
Update Utils.kt 2025-11-05 23:05:42 +01:00
OTKUSteyler
76f8a3cd01
Update Module.kt 2025-11-05 23:05:00 +01:00
OTKUSteyler
3905a1327d
Update Constants.kt 2025-11-05 23:04:31 +01:00
OTKUSteyler
63892f2a8f
Update Main.kt 2025-11-05 23:03:54 +01:00
OTKUSteyler
62336a53a9
Update AndroidManifest.xml 2025-11-05 22:53:14 +01:00
OTKUSteyler
052edd13fa
Merge branch 'ShiggyCord-Team:master' into master 2025-11-05 22:48:10 +01:00
OTKUSteyler
f9e29656fa
Update build.yml 2025-11-05 20:07:22 +01:00
OTKUSteyler
f035c217e6
Update UpdaterModule.kt 2025-11-03 22:42:24 +01:00
OTKUSteyler
c4734b8b90
Update strings.xml 2025-11-03 22:21:30 +01:00
OTKUSteyler
50994e9278
Rename project from ShiggyXposed to GoonXposed 2025-10-22 23:46:10 +02:00
OTKUSteyler
ed704eb7f8
Rename application and namespace to GoonXposed 2025-10-22 23:45:42 +02:00
OTKUSteyler
f195d82414
Update xposed_init 2025-10-22 23:44:36 +02:00
23 changed files with 1549 additions and 759 deletions

View file

@ -1,13 +1,6 @@
name: Build APK
on:
push:
branches: [ "master" ]
tags: [ "v*", "V*" ]
workflow_dispatch:
permissions:
contents: write
on: [push]
jobs:
build:
@ -72,23 +65,3 @@ jobs:
with:
name: app-release
path: /tmp/ShiggyXposedbuild/apks/app-release.apk
- name: Publish APKs to GitHub Release
if: startsWith(github.ref, 'refs/tags/') || github.ref == 'refs/heads/master'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
TAG_NAME="${GITHUB_REF#refs/tags/}"
else
TAG_NAME=$(gh release view --json tagName -q .tagName 2>/dev/null || echo "")
fi
if [ -n "$TAG_NAME" ]; then
echo "Uploading APKs to release $TAG_NAME..."
gh release upload "$TAG_NAME" \
/tmp/ShiggyXposedbuild/apks/app-release.apk \
/tmp/ShiggyXposedbuild/apks/app-debug.apk \
--clobber
else
echo "No release tag found to upload assets to."
fi

2
.idea/.name generated
View file

@ -1 +1 @@
ShiggyXposed
GoonXposed

203
README.md
View file

@ -1,193 +1,40 @@
# GoonXposed
GoonXposed is an Xposed module (Kotlin) that injects the GoonCord client modifications into the official Discord Android application. It provides an in-app recovery/dev menu (LogBox) that lets you manage bundle loading, themes, and other developer helpers.
GoonXposed is a root Xposed module that injects the GoonCord Discord mobile client modifications into the official Discord application.
This README explains prerequisites, installation, usage, troubleshooting, and credits. If you want help, open an issue on the repository or join the project's support server (links below).
---
## Table of Contents
- [What is this](#what-is-this)
- [Features](#features)
- [Prerequisites](#prerequisites)
- [Installation](#installation)
- [Using the Module](#using-the-module)
- [Recovery / Options / Themes menu](#recovery--options--themes-menu)
- [Bundle injection / Custom bundle URL](#bundle-injection--custom-bundle-url)
- [Development / Building](#development--building)
- [Troubleshooting & FAQ](#troubleshooting--faq)
- [Contributing](#contributing)
- [Credits](#credits)
- [License](#license)
- [Contact & Links](#contact--links)
---
## What is this
GoonXposed injects a patched GoonCord bundle into the official Discord application by using the Xposed framework. It's intended for users who run a rooted Android device and want to apply client-side modifications to Discord.
---
## Features
- Inject a custom GoonCord bundle into Discord.
- Toggle bundle injection on/off (marker-based).
- Load a custom bundle URL and toggle it on/off.
- Recovery/LogBox action sheet for reload, safe-mode, refetch/reset, and options.
- Options to change the LogBox menu appearance (System / Light / Dark) and a set of menu color "flavors".
- In-place UI theming for the recovery/options menus.
- Helpers for refetching or backing up the currently loaded bundle.
---
## Info
* Injects GoonCord client into the Discord mobile app.
* Built in Kotlin.
* Material 3 like Shake menu with some discord popup accents
## Prerequisites
Before installing and using GoonXposed:
- A rooted Android device. Supported root solutions you might use:
- Magisk, KernelSU, KernelSUNext, or Sukisu Ultra.
- Xposed framework installed. The project targets LSPosed or forks because of compatibility with modern Android and Discord:
- Recommended: JingMatrix Fork of [LSPosed](https://github.com/JingMatrix/LSPosed)
- The official Discord Android application (stable/beta/alpha) installed and included in the module scope.
---
To use this module, your device must meet the following requirements:
* **Root Access**: Provided by Magisk, KernelSU, KernelSUNext, or Sukisu Ultra.
* **Xposed Framework**: Use JingMatrix Fork of [LSPosed](https://github.com/JingMatrix/LSPosed).
* **Target Application**: An official Discord application (stable, beta, or alpha version).
## Installation
1. Download the latest version of the module from the [Releases](https://github.com/OTKUSteyler/GoonXposed/releases) page.
2. Install the downloaded APK file on your device.
3. Open the LSPosed manager application.
4. Go to the **Modules** section and enable **GoonXposed**.
5. In the module's settings, set the scope to include the **Discord** application.
6. Reboot your device for the changes to take effect.
1. Download the latest APK from the repository Releases:
- https://github.com/OTKUSteyler/GoonXposed/releases
2. Install the APK on your device (standard package install).
3. Open your LSPosed manager (or chosen Xposed manager).
4. Enable the GoonXposed module and ensure the module's scope includes the Discord app.
5. Reboot your device (or restart the system process) for changes to take effect.
The project is built with Kotlin and inherits its foundational structure from KettuXposed.
---
# Troubleshooting
## Using the Module
- Module not working: Ensure the module is enabled in LSPosed and that Discord is correctly selected in its scope. if still nothing, head to our [discord support server](https://discord.gg/nQykFF9Ud6)
- Any other issues: Same server as above, listing them all is not worth it.
Once installed and active, you can open the bundled Recovery/LogBox menu (depending on the hooked RN dev dialog) to access all tools.
# Thanks & Credits
Recovery → Options → Themes:
- Recovery (Action Sheet) includes:
- Safe Mode toggle (toggle persisted safe-mode setting + optional theme handling)
- Load Custom Bundle
- Reload App
- Options (opens the Options action sheet)
- Options includes:
- Themes (open appearance/flavor controls)
- Toggle bundle injection (rename/add marker file in cache to prevent injection)
- Refetch Bundle (moves or resets current bundle and triggers a reload)
- Clear Cache & Reset (clears cached bundle and resets loader config)
- Back (return to Recovery)
This project was made possible thanks to the work of:
Themes:
- Pick appearance: System / Light / Dark
- Choose Menu Color Flavor: blue, green, mocha, vanilla, purple, amber, teal
- Theme & flavor settings persist under the app's files directory (`files/logbox/LOGBOX_SETTINGS` and `files/logbox/LOGBOX_THEMES`)
Bundle injection:
- When you disable injection, the module will create/rename a marker file in the cache directory so other components (loader) skip injection.
- Toggling injection is handled by `toggleBundleInjection(context)` and is persisted to `LOGBOX_SETTINGS`.
Custom bundle URL:
- The custom bundle loader configuration is stored in `files/pyoncord/loader.json`.
- You can enable/disable the custom URL and provide a remote URL (the module exposes a dialog for this).
---
## Development / Building
If you want to build the project locally:
- Prerequisites: JDK, Android SDK, and Gradle (the project uses the Gradle wrapper).
- From the repository root:
- Build debug APK:
```
./gradlew :app:assembleDebug
```
- Install to connected device (adb required):
```
adb install -r app/build/outputs/apk/debug/app-debug.apk
```
- Run and test on a rooted device/emulator with Xposed/LSPosed installed and Discord in scope.
Notes:
- The project is written in Kotlin and uses Android UI primitives to construct the recovery/LogBox UI. The dev menu is created in-place to avoid modifying Discord views.
---
## Troubleshooting & FAQ
1. Module not working or not showing in Discord?
- Ensure GoonXposed is enabled in LSPosed and Discord is included in the module's scope.
- Reboot after enabling the module.
- Confirm you have the compatible LSPosed fork installed (JingMatrix recommended).
2. Recovery menu shows visual glitches when changing themes?
- The menu attempts to apply colors in-place. If you see transient visual artifacts, try closing and reopening the menu — the settings are persisted and will apply on reopen.
- If an area does not update, please report which dialog/control (and the device/Android version) so it can be targeted.
3. Bundle injection not toggling?
- Toggling injection manipulates files under the app's cache directory. If that fails, check file permissions or whether the cache path changed on your build of Discord.
- You can also manually inspect `data/data/<discord.package>/cache/...` to see `.disabled` marker files (depending on your device and build).
4. I accidentally broke Discord — how do I recover?
- Use the Recovery → Options → Clear Cache & Reset option to remove cached bundles and restore default loader settings, then reload Discord.
- If Discord fails to start, reboot the device to make sure changes are applied cleanly.
If you still have issues, open an issue on GitHub or join the support server (link below). Please include:
- Device model and Android version
- Discord app version
- A clear description of the problem and steps to reproduce
- Any relevant logs or screenshots (redact personal data)
---
## Contributing
Contributions are welcome. If you want to help:
- Open an issue to discuss a bug or feature.
- Send pull requests with focused, well-documented changes.
- When working on UI or theme changes, test on multiple screen sizes and Android versions if possible.
Guidelines:
- Keep commits small and descriptive.
- Include code comments for complex logic (hooks, file-system operations).
- Respect user privacy — do not add telemetry or data exfiltration.
---
## Credits
This project stands on the shoulders of many open-source projects and contributors:
- [LSPosed Team](https://github.com/LSPosed) — the Xposed framework implementation used to run modules.
- [JingMatrix](https://github.com/JingMatrix) — author of the LSPosed fork referenced by many users.
- [cocobo1](https://codeberg.org/cocobo1) — for adapting some features to Xposed which [Revenge Team](https://github.com/revenge-mod) didn't.
- [Revenge Team](https://github.com/revenge-mod) — provided earlier Xposed module work that informed parts of this project.
---
## License
Please refer to the `LICENSE` file in the repository root for the project license (if present). If no license file exists, the repository's default (copyright) status applies — check the repo settings or ask the maintainer for clarification.
---
## Contact & Links
- Releases: https://github.com/OTKUSteyler/GoonXposed/releases
- Repository: https://github.com/OTKUSteyler/GoonXposed
- Support server: https://discord.gg/nQykFF9Ud6
---
Thank you for using GoonXposed.
· The LSPosed Team for the framework.
· JingMatrix for their LSPosed fork.
· [cocobo1](https://codeberg.org/cocobo1) for adjusting the Xposed module for Kettu client mod.
· [Revenge Team](https://github.com/revenge-mod) for making a really good Xposed Module to base of
![OTKUSteyler](https://count.getloli.com/@OTKUSteyler?name=OTKUSteyler&theme=miku&padding=7&offset=0&align=top&scale=1&pixelated=1&darkmode=1)

View file

@ -36,7 +36,7 @@ android {
}
release {
isDebuggable = false
isMinifyEnabled = false
isMinifyEnabled = true
val keystoreFile = file("../release.keystore")
if (keystoreFile.exists()) {
signingConfig = signingConfigs.getByName("release")
@ -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,9 +16,6 @@
android:value="Xposed module to inject GoonCord into discord"
/>
<meta-data android:name="xposedminversion" android:value="82" />
<meta-data
android:name="xposedscope"
android:resource="@array/xposed_scope"
/>
<meta-data android:name="xposedscope" android:value="com.discord" />
</application>
</manifest>

File diff suppressed because one or more lines are too long

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.*
import kotlinx.coroutines.CompletableDeferred
object HookStateHolder {
@ -50,8 +49,7 @@ class Main : Module(), IXposedHookLoadPackage, IXposedHookZygoteInit {
PerfPatchesModule,
FontsModule,
ThemesModule,
SysColorsModule,
GmsCompatModule
SysColorsModule
)
init {
@ -59,41 +57,13 @@ class Main : Module(), IXposedHookLoadPackage, IXposedHookZygoteInit {
}
override fun initZygote(startupParam: IXposedHookZygoteInit.StartupParam) {
dispatch("onInit") { module -> module.onInit(startupParam) }
}
private fun isTargetPackage(pkg: String): Boolean {
return pkg == Constants.TARGET_PACKAGE
|| pkg.startsWith("com.discord")
|| pkg == "dev.shiggy.cord"
|| pkg.contains("discord")
|| pkg.contains("gooncord")
for (module in modules) module.onInit(startupParam)
}
override fun handleLoadPackage(param: XC_LoadPackage.LoadPackageParam) = with(param) {
if (!isTargetPackage(packageName)) return
if (processName.contains(":")) return
if (hooked) return
val activityClassNames = listOf(
Constants.TARGET_ACTIVITY,
"$packageName.react_activities.ReactActivity",
"com.discord.react_activities.ReactActivity",
"com.facebook.react.ReactActivity",
"com.discord.main.MainActivity",
"com.discord.main.MainDefault"
)
var reactActivity: Class<*>? = null
for (className in activityClassNames) {
try {
reactActivity = classLoader.loadClass(className)
if (reactActivity != null) break
} catch (_: Throwable) {}
}
if (reactActivity == null) {
Log.w("Target activity not found by name, falling back to Activity base class")
}
val reactActivity = classLoader.loadClass(Constants.TARGET_ACTIVITY)
ContextWrapper::class.java.hookMethod("attachBaseContext", Context::class.java) {
after {
@ -104,18 +74,10 @@ class Main : Module(), IXposedHookLoadPackage, IXposedHookZygoteInit {
}
}
val targetActivityClass = reactActivity ?: Activity::class.java
targetActivityClass.hookMethod("onCreate", Bundle::class.java) {
reactActivity.hookMethod("onCreate", Bundle::class.java) {
after {
val act = thisObject as Activity
if (reactActivity == null &&
!act.javaClass.name.contains("ReactActivity", ignoreCase = true) &&
!act.javaClass.name.contains("MainActivity", ignoreCase = true) &&
!act.javaClass.name.contains("MainDefault", ignoreCase = true)
) {
return@after
}
Log.i("Received Activity: ${act.javaClass.name}")
Log.i("Received Activity")
if (!HookStateHolder.gotContext) {
Log.w("Activity created before we got Context, process may have been recreated!")
@ -127,53 +89,20 @@ class Main : Module(), IXposedHookLoadPackage, IXposedHookZygoteInit {
}
}
targetActivityClass.hookMethod("onResume") {
after {
val act = thisObject as Activity
this@Main.onResume(act)
}
}
targetActivityClass.hookMethod("onPause") {
after {
val act = thisObject as Activity
this@Main.onPause(act)
}
}
this@Main.onLoad(param)
hooked = true
}
override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) {
dispatch("onLoad") { module -> module.onLoad(packageParam) }
for (module in modules) module.onLoad(packageParam)
}
override fun onContext(context: Context) {
dispatch("onContext") { module -> module.onContext(context) }
for (module in modules) module.onContext(context)
}
override fun onActivity(activity: Activity) {
dispatch("onActivity") { module -> module.onActivity(activity) }
}
override fun onResume(activity: Activity) {
dispatch("onResume") { module -> module.onResume(activity) }
}
override fun onPause(activity: Activity) {
dispatch("onPause") { module -> module.onPause(activity) }
}
private fun dispatch(stage: String, block: (Module) -> Unit) {
for (module in modules) {
val name = module.javaClass.simpleName.ifEmpty { module.javaClass.name }
try {
block(module)
} catch (e: Throwable) {
Log.e("Module $name failed during $stage", e)
}
}
for (module in modules) module.onActivity(activity)
}
}

View file

@ -3,24 +3,15 @@ package GoonXposed.xposed
import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
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 GoonXposed.xposed.Utils.Log
import kotlinx.serialization.json.JsonObjectBuilder
import java.io.File
import java.lang.reflect.Method
data class AppInfo(
val name: String,
val packageName: String,
val version: String,
val versionCode: Long,
)
/**
* Base class for every Xposed side module.
*
@ -28,66 +19,23 @@ data class AppInfo(
* registered [Module] instance. Modules may override any subset of the hooks below.
*/
open 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) {}
/**
* 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) {}
open fun onResume(activity: Activity) {}
open fun onPause(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,
private val proceed: ((XC_MethodHook.MethodHookParam) -> Unit)? = null
) {
fun proceed() {
proceed?.invoke(param)
}
class HookScope(val param: XC_MethodHook.MethodHookParam) {
val thisObject: Any?
get() = param.thisObject
@ -132,12 +80,12 @@ class MethodHookBuilder {
fun build(): XC_MethodHook = object : XC_MethodHook() {
override fun beforeHookedMethod(param: MethodHookParam) {
val scope = HookScope(param = param, proceed = { p -> super.beforeHookedMethod(p) })
val scope = HookScope(param)
beforeCallbacks.forEach { scope.run(it) }
}
override fun afterHookedMethod(param: MethodHookParam) {
val scope = HookScope(param = param, proceed = { p -> super.afterHookedMethod(p) })
val scope = HookScope(param)
afterCallbacks.forEach { scope.run(it) }
}
}
@ -156,27 +104,28 @@ fun ClassLoader.safeLoadClass(name: String): Class<*>? = try {
null
}
fun Class<*>.method(name: String, vararg parameterTypes: Class<*>?): Method =
fun Class<*>.method(name: String, vararg parameterTypes: Class<*>): Method =
XposedHelpers.findMethodExact(this, name, *parameterTypes)
fun Method.hook(hook: XC_MethodHook): XC_MethodHook.Unhook = XposedBridge.hookMethod(this, hook)
fun Method.hook(block: MethodHookBuilder.() -> Unit): XC_MethodHook.Unhook {
fun Method.hook(block: MethodHookBuilder.() -> Unit) {
val builder = MethodHookBuilder()
builder.block()
return XposedBridge.hookMethod(this, builder.build())
XposedBridge.hookMethod(this, builder.build())
}
fun Method.hook(hook: XC_MethodHook) {
XposedBridge.hookMethod(this, hook)
}
fun Class<*>.hookMethod(
name: String,
vararg parameterTypes: Class<*>?,
vararg parameterTypes: Class<*>,
block: MethodHookBuilder.() -> Unit
): XC_MethodHook.Unhook? = runCatching {
): Method {
val method = method(name, *parameterTypes)
method.hook(block)
}.onFailure {
Log.w("Failed to hook method $name on ${this.name}: ${it.message}")
}.getOrNull()
return method
}
fun File.asDir(): File {
if (!exists() || !isDirectory) mkdirs()
@ -188,6 +137,13 @@ fun File.asFile(): File {
return this
}
data class AppInfo(
val name: String,
val packageName: String,
val version: String,
val versionCode: Int
)
fun Context.getAppInfo(): AppInfo {
val label = applicationInfo.loadLabel(packageManager).toString()
val packageInfo = try {
@ -196,15 +152,10 @@ fun Context.getAppInfo(): AppInfo {
null
}
val versionCode = if (packageInfo != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) packageInfo.longVersionCode
else @Suppress("DEPRECATION") packageInfo.versionCode.toLong()
} else 0L
return AppInfo(
name = label,
packageName = packageName,
version = packageInfo?.versionName ?: "",
versionCode = versionCode
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,14 +15,16 @@ 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)
}
fun showRecoveryAlert(context: Context) {
AlertDialog.Builder(context).setTitle("ShiggyCord Recovery Options")
AlertDialog.Builder(context).setTitle("GoonCord Recovery Options")
.setItems(arrayOf("Reload", "Delete Script", "Reset Loader Config")) { _, which ->
when (which) {
0 -> {

View file

@ -1,5 +1,6 @@
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
@ -16,13 +17,7 @@ object FixResourcesModule : Module() {
"getIdentifier", String::class.java, String::class.java, String::class.java
) {
before {
if (args[2] == packageName) {
args[2] = TARGET_PACKAGE
}
val name = args[0] as? String
if (name != null && name.startsWith("$packageName:")) {
args[0] = name.replaceFirst("$packageName:", "$TARGET_PACKAGE:")
}
if (args[2] == packageName) args[2] = TARGET_PACKAGE
}
}
}

View file

@ -1,69 +0,0 @@
package GoonXposed.xposed.modules
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
/**
* Compatibility layer for environments without Google Play Services (microG, GrapheneOS, de-Googled ROMs).
*
* Mocks GoogleApiAvailability and GooglePlayServicesUtil availability checks so Discord does not
* hang, crash, or show "Google Play Services not available" prompts, and provides safe stubs for
* Google Advertising ID queries.
*/
object GmsCompatModule : Module() {
override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) = with(packageParam) {
hookGoogleApiAvailability(classLoader)
hookAdvertisingId(classLoader)
}
private fun hookGoogleApiAvailability(classLoader: ClassLoader) {
listOf(
"com.google.android.gms.common.GoogleApiAvailability",
"com.google.android.gms.common.GoogleApiAvailabilityLight",
"com.google.android.gms.common.GooglePlayServicesUtil",
"com.google.android.gms.common.GooglePlayServicesUtilLight"
).forEach { className ->
val clazz = classLoader.safeLoadClass(className) ?: return@forEach
// ConnectionResult.SUCCESS = 0
clazz.hookMethod("isGooglePlayServicesAvailable", Context::class.java) {
before {
result = 0
}
}
clazz.hookMethod("isGooglePlayServicesAvailable", Context::class.java, Int::class.javaPrimitiveType) {
before {
result = 0
}
}
clazz.hookMethod("isUserResolvableError", Int::class.javaPrimitiveType) {
before {
result = false
}
}
}
}
private fun hookAdvertisingId(classLoader: ClassLoader) {
val adClientClass = classLoader.safeLoadClass("com.google.android.gms.ads.identifier.AdvertisingIdClient") ?: return
val adInfoClass = classLoader.safeLoadClass("com.google.android.gms.ads.identifier.AdvertisingIdClient\$Info")
if (adInfoClass != null) {
adClientClass.hookMethod("getAdvertisingIdInfo", Context::class.java) {
before {
try {
val constructor = adInfoClass.getDeclaredConstructor(String::class.java, Boolean::class.javaPrimitiveType)
constructor.isAccessible = true
result = constructor.newInstance("00000000-0000-0000-0000-000000000000", true)
Log.i("GmsCompat: returned mock AdvertisingIdInfo")
} catch (e: Throwable) {
Log.w("GmsCompat: failed to create mock AdvertisingIdInfo: ${e.message}")
}
}
}
}
}
}

View file

@ -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
@ -59,53 +58,46 @@ object HookScriptLoaderModule : Module() {
).mapNotNull { classLoader.safeLoadClass(it) }.forEach { hook(it) }
}
private fun hook(instance: Class<*>) {
val loadScriptFromAssets = runCatching {
instance.method(
"loadScriptFromAssets", AssetManager::class.java, String::class.java, Boolean::class.javaPrimitiveType
)
}.getOrNull()
private fun hook(instance: Class<*>) = runCatching {
val loadScriptFromAssets = instance.method(
"loadScriptFromAssets", AssetManager::class.java, String::class.java, Boolean::class.javaPrimitiveType!!
)
val loadScriptFromFile = runCatching {
instance.method(
"loadScriptFromFile", String::class.java, String::class.java, Boolean::class.javaPrimitiveType
)
}.getOrNull()
val loadScriptFromFile = instance.method(
"loadScriptFromFile", String::class.java, String::class.java, Boolean::class.javaPrimitiveType!!
)
loadScriptFromAssets?.hook {
loadScriptFromAssets.hook {
before {
Log.i("Received call to loadScriptFromAssets: ${args[1]} (sync: ${args[2]})")
runCustomScripts(loadScriptFromFile, loadScriptFromAssets)
}
}
loadScriptFromFile?.hook {
loadScriptFromFile.hook {
before {
Log.i("Received call to loadScriptFromFile: ${args[0]} (sync: ${args[2]})")
runCustomScripts(loadScriptFromFile, loadScriptFromAssets)
}
}
}.onFailure {
Log.e("Failed to hook script loading methods in ${instance.name}:", it)
}
private fun HookScope.runCustomScripts(loadScriptFromFile: Method?, loadScriptFromAssets: Method?) {
private fun HookScope.runCustomScripts(loadScriptFromFile: Method, loadScriptFromAssets: Method) {
Log.i("Running custom scripts...")
runBlocking {
val isCustomUrl = UpdaterModule.isCustomUrlEnabled
val ready = async { HookStateHolder.readyDeferred.join() }
if (!mainScript.exists() || isCustomUrl) {
val reason = if (isCustomUrl) "Custom URL enabled" else "Main script does not exist"
Log.i("$reason, downloading before load...")
try {
withTimeout(10000L) {
UpdaterModule.downloadScript(showUpdateDialog = false).join()
}
} catch (e: Throwable) {
Log.w("Bundle download did not complete in time", e)
}
if (!mainScript.exists()) {
Log.i("Main script does not exist, downloading before load...")
val download = async { UpdaterModule.downloadScript().join() }
awaitAll(ready, download)
} else {
Log.i("Main script exists, updating in background...")
UpdaterModule.downloadScript(showUpdateDialog = true)
UpdaterModule.downloadScript()
ready.await()
}
}
@ -113,48 +105,32 @@ object HookScriptLoaderModule : Module() {
val runScriptFile = { file: File ->
Log.i("Loading script: ${file.absolutePath}")
if (loadScriptFromFile != null) {
XposedBridge.invokeOriginalMethod(
loadScriptFromFile, thisObject, arrayOf(file.absolutePath, file.absolutePath, loadSynchronously)
)
}
XposedBridge.invokeOriginalMethod(
loadScriptFromFile, thisObject, arrayOf(file.absolutePath, file.absolutePath, loadSynchronously)
)
Unit
}
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()) {
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 && loadScriptFromAssets != null) {
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) {

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

@ -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)

View file

@ -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

View file

@ -9,7 +9,6 @@ import GoonXposed.xposed.Constants
import GoonXposed.xposed.MethodHookBuilder
import GoonXposed.xposed.Module
import GoonXposed.xposed.Utils.Companion.JSON
import GoonXposed.xposed.Utils.Log
import GoonXposed.xposed.hook
import GoonXposed.xposed.method
import GoonXposed.xposed.safeLoadClass
@ -28,11 +27,7 @@ class PayloadGlobalModule(private val modules: List<Module>) : Module() {
buildJsonObject {
put("loaderName", Constants.LOADER_NAME)
put("loaderVersion", BuildConfig.VERSION_NAME)
@Suppress("DEPRECATION") for (module in modules) runCatching {
module.buildPayload(this)
}.onFailure {
Log.e("Module ${module.javaClass.simpleName} failed while building payload", it)
}
@Suppress("DEPRECATION") for (module in modules) module.buildPayload(this)
})
override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) = with(packageParam) {
@ -57,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])
)
@ -79,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

@ -25,20 +25,293 @@ object PerfPatchesModule : Module() {
override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) {
try {
val filesDir = File(packageParam.appInfo.dataDir, Constants.FILES_DIR)
val preloadsDir = File(filesDir, HookScriptLoaderModule.PRELOADS_DIR)
// Build preloads path inside the app data files directory
val filesDir = File(packageParam.appInfo.dataDir, Constants.FILES_DIR).apply { mkdirs() }
val preloadsDir = File(filesDir, HookScriptLoaderModule.PRELOADS_DIR).apply { mkdirs() }
val out = File(preloadsDir, FILE_NAME)
if (out.exists()) {
out.delete()
Log.i("PerfPatchesModule: deleted legacy perf_patches.js")
// Defensive JS runtime patch. Avoid template literals with ${} to prevent Kotlin interpolation.
val js = """
(function(){
try {
if (!globalThis.__SHIGGY_PERF_PATCHES__) {
globalThis.__SHIGGY_PERF_PATCHES__ = { installedAt: Date.now(), enabled: true };
}
function safe(fn) {
return function() {
try { return fn.apply(this, arguments); }
catch (e) { try { console && console.warn && console.warn('shiggy patch error', e); } catch(_){} }
};
}
function memoize(fn, maxEntries) {
if (typeof fn !== 'function') return fn;
var cache = new Map();
maxEntries = Number(maxEntries) || 128;
return function() {
var key;
try { key = JSON.stringify(Array.prototype.slice.call(arguments)); }
catch (e) { key = String(arguments.length) + ':' + String(arguments[0]); }
if (cache.has(key)) return cache.get(key);
var res = fn.apply(this, arguments);
cache.set(key, res);
if (cache.size > maxEntries) {
var it = cache.keys().next();
if (!it.done) cache.delete(it.value);
}
return res;
};
}
function createBatched(fn, intervalMs) {
if (typeof fn !== 'function') return fn;
var buf = [];
var timer = null;
intervalMs = Number(intervalMs) || 2000;
function flush() {
var batch = buf.splice(0, buf.length);
if (!batch.length) return;
try { fn.call(null, batch); }
catch (e) {
// Fallback: call per event asynchronously
try {
batch.forEach(function(ev){
setTimeout(function(){ try { fn(ev); } catch (_) {} }, 0);
});
} catch (_) {}
}
}
return function(event) {
try {
buf.push(event);
if (!timer) timer = setTimeout(function(){ timer = null; flush(); }, intervalMs);
} catch (e) {}
};
}
function patchIfExists(path, wrapper) {
try {
var parts = path.split('.');
var obj = globalThis;
for (var i = 0; i < parts.length - 1; i++) {
obj = obj && obj[parts[i]];
}
if (!obj) return false;
var key = parts[parts.length - 1];
if (typeof obj[key] !== 'function') return false;
obj[key] = wrapper(obj[key]);
return true;
} catch (e) { return false; }
}
// Try memoizing a few well-known functions
['computeHappeningNowState', 'computeNowState'].forEach(function(name){
try { patchIfExists(name, function(orig){ return memoize(orig, 128); }); } catch(_) {}
});
// Try caching RowGenerator.prototype.generate by instance signature
try {
var RG = globalThis && globalThis.RowGenerator;
if (RG && RG.prototype && typeof RG.prototype.generate === 'function') {
(function(){
var orig = RG.prototype.generate;
RG.prototype.generate = function() {
try {
var sig;
if (arguments && arguments.length) {
var a0 = arguments[0];
if (Array.isArray(a0)) {
sig = 'arr:' + a0.length + ':' + a0.slice(0,8).map(function(x){ return x && x.id ? x.id : ''; }).join(',');
} else if (a0 && a0.id) {
sig = 'id:' + a0.id;
} else {
try { sig = JSON.stringify(a0).slice(0,200); } catch(e){ sig = String(a0); }
}
} else sig = 'noargs';
if (this.__shiggy_sig === sig && typeof this.__shiggy_cache !== 'undefined') {
return this.__shiggy_cache;
}
var res = orig.apply(this, arguments);
this.__shiggy_sig = sig;
this.__shiggy_cache = res;
return res;
} catch (e) { return orig.apply(this, arguments); }
};
})();
}
} catch (e) {}
// Batch analytics-like methods (best-effort)
['app_ui_viewed', 'logEvent', 'Analytics.log', 'Analytics.track', 'analytics.trackEvent'].forEach(function(name){
try {
var parts = name.split('.');
var obj = globalThis;
for (var i = 0; i < parts.length - 1; i++) obj = obj && obj[parts[i]];
if (!obj) return;
var key = parts[parts.length - 1];
if (typeof obj[key] === 'function') obj[key] = createBatched(obj[key], 2000);
} catch (e) {}
});
// Make console methods async to avoid blocking hot-paths during startup
(function(){
try {
if (typeof console !== 'undefined') {
['log','info','warn','error','debug','trace'].forEach(function(m){
try {
if (typeof console[m] === 'function') {
var orig = console[m];
console[m] = function() {
var args = Array.prototype.slice.call(arguments);
setTimeout(function(){ try { orig.apply(console, args); } catch(_){} }, 0);
};
}
} catch(e){}
});
}
} catch(e){}
})();
// Deduplicate and slightly delay repeated kv calls to reduce IO contention on startup
(function(){
try {
var pending = new Map();
// Lightweight stats for dedupe wrapper to help telemetry/debugging in-case we need to tune delays.
try {
globalThis.__SHIGGY_PERF_DEDUPE_STATS__ = globalThis.__SHIGGY_PERF_DEDUPE_STATS__ || {
methods: {},
totalRequests: 0,
totalHits: 0,
totalMisses: 0,
pending: 0,
lastUpdated: Date.now()
};
} catch(e) {}
function _incStats(name, key) {
try {
var s = globalThis.__SHIGGY_PERF_DEDUPE_STATS__;
if (!s) return;
s.lastUpdated = Date.now();
var m = s.methods[name] || (s.methods[name] = { requests: 0, hits: 0, misses: 0, pending: 0 });
if (key === 'requests') { m.requests++; s.totalRequests++; }
else if (key === 'hits') { m.hits++; s.totalHits++; }
else if (key === 'misses') { m.misses++; s.totalMisses++; }
else if (key === 'pending_inc') { m.pending++; s.pending++; }
else if (key === 'pending_dec') { m.pending = Math.max(0, m.pending - 1); s.pending = Math.max(0, s.pending - 1); }
} catch(e) {}
}
function wrapAsyncDedupe(kvObj, methodName, delayMs){
try {
var orig = kvObj[methodName];
if (typeof orig !== 'function') return;
kvObj[methodName] = function() {
var argsArr = Array.prototype.slice.call(arguments);
var key;
try { key = JSON.stringify([methodName, argsArr]); } catch(e){ key = methodName + ':' + String(argsArr[0]); }
_incStats(methodName, 'requests');
if (pending.has(key)) { _incStats(methodName, 'hits'); return pending.get(key); }
_incStats(methodName, 'misses');
var self = this;
var p = new Promise(function(resolve, reject){
setTimeout(function(){
try {
var res = orig.apply(self, argsArr);
if (res && typeof res.then === 'function') res.then(resolve, reject);
else resolve(res);
} catch (e) { reject(e); }
}, Number(delayMs) || 20);
});
pending.set(key, p);
_incStats(methodName, 'pending_inc');
var cleanup = function(){ pending.delete(key); _incStats(methodName, 'pending_dec'); };
p.then(cleanup, cleanup);
return p;
};
} catch(e){}
}
if (globalThis.kv) {
if (globalThis.kv.get_many) wrapAsyncDedupe(globalThis.kv, 'get_many', 20);
if (globalThis.kv.get_kv_entries) wrapAsyncDedupe(globalThis.kv, 'get_kv_entries', 20);
}
} catch(e){}
})();
// Defer heavy filesystem/storage calls slightly to prioritize UI rendering
(function(){
try {
function makeDeferredIfAsync(obj, name, delayMs) {
try {
var orig = obj[name];
if (typeof orig !== 'function') return;
var isAsync = orig.constructor && orig.constructor.name === 'AsyncFunction';
var src = String(orig);
if (!isAsync && src.indexOf('return new Promise') === -1 && src.indexOf('.then(') === -1) {
// Likely synchronous - skip deferral to avoid changing semantics
return;
}
obj[name] = function() {
var args = Array.prototype.slice.call(arguments);
var self = this;
return new Promise(function(resolve, reject){
setTimeout(function(){
try {
var res = orig.apply(self, args);
if (res && typeof res.then === 'function') res.then(resolve, reject);
else resolve(res);
} catch (e) { reject(e); }
}, Number(delayMs) || 100);
});
};
} catch(e){}
}
if (globalThis.db && typeof globalThis.db.fs_info === 'function') {
makeDeferredIfAsync(globalThis.db, 'fs_info', 100);
}
if (globalThis.Storage && typeof globalThis.Storage.refresh === 'function') {
makeDeferredIfAsync(globalThis.Storage, 'refresh', 50);
}
} catch(e){}
})();
// Expose lightweight API
try {
if (!globalThis.__SHIGGY_PERF_API__) {
globalThis.__SHIGGY_PERF_API__ = {
installedAt: Date.now(),
enabled: true,
uninstall: function() { try { globalThis.__SHIGGY_PERF_API__.enabled = false; } catch(e) {} }
};
}
} catch (e) {}
} catch (e) {
try { console && console.warn && console.warn('PerfPatches: error', e); } catch(_) {}
}
})();
""".trimIndent()
// Write file if absent or changed
try {
if (!out.exists() || out.readText() != js) {
out.writeText(js)
Log.i("PerfPatchesModule: wrote patch to " + out.absolutePath)
} else {
Log.i("PerfPatchesModule: perf patch already up-to-date")
}
} catch (e: Throwable) {
Log.e("PerfPatchesModule: failed writing patch", e)
}
} catch (e: Throwable) {
Log.e("PerfPatchesModule: cleanup failed", e)
Log.e("PerfPatchesModule:onLoad failed", e)
}
}
@Deprecated("Overrides deprecated Module.buildPayload")
override fun buildPayload(builder: JsonObjectBuilder) {
builder.put("perfPatches", false)
builder.put("perfPatches", true)
}
}

View file

@ -56,9 +56,6 @@ object UpdaterModule : Module() {
private const val ETAG_FILE = "etag.txt"
private const val CONFIG_FILE = "loader.json"
val isCustomUrlEnabled: Boolean
get() = if (::config.isInitialized) config.customLoadUrl.enabled else false
private const val DEFAULT_BUNDLE_URL =
"https://github.com/OTKUSteyler/GoonCord/releases/latest/download/gooncord.js"
@ -80,7 +77,7 @@ object UpdaterModule : Module() {
}.getOrDefault(LoaderConfig())
}
fun downloadScript(activity: Activity? = null, showUpdateDialog: Boolean = true): Job = scope.launch {
fun downloadScript(activity: Activity? = null): Job = scope.launch {
try {
// Respect LogBox setting to disable bundle injections.
// If the file "files/logbox/LOGBOX_SETTINGS" contains the flag bundleInjectionDisabled=true,
@ -132,6 +129,7 @@ object UpdaterModule : Module() {
Log.i("Bundle updated (${bytes.size} bytes)")
// This is a retry, so we show a dialog
if (activity != null) {
withContext(Dispatchers.Main) {
AlertDialog.Builder(activity).setTitle("GoonCord Update Successful")
@ -141,22 +139,6 @@ object UpdaterModule : Module() {
dialog.dismiss()
}.setCancelable(false).show()
}
} else if (showUpdateDialog) {
val act = lastActivity?.get()
act?.runOnUiThread {
AlertDialog.Builder(act)
.setTitle("GoonCord Update Downloaded")
.setMessage("A reload is required for changes to take effect.")
.setPositiveButton("Reload") { dialog, _ ->
reloadApp()
dialog.dismiss()
}
.setNegativeButton("Later") { dialog, _ ->
dialog.dismiss()
}
.setCancelable(false)
.show()
}
}
}

View file

@ -1,3 +1,5 @@
@file:Suppress("DEPRECATION")
package GoonXposed.xposed.modules.appearance
import android.content.res.AssetManager
@ -13,8 +15,6 @@ import GoonXposed.xposed.Constants
import GoonXposed.xposed.Module
import GoonXposed.xposed.Utils.Companion.JSON
import GoonXposed.xposed.Utils.Log
import GoonXposed.xposed.asDir
import GoonXposed.xposed.asFile
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.cio.*
@ -29,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,
@ -42,11 +50,6 @@ object FontsModule : Module() {
private val FILE_EXTENSIONS = arrayOf(".ttf", ".otf")
private const val FONTS_ASSET_PATH = "fonts/"
private val FONT_MANAGER_CLASSES = arrayOf(
"com.facebook.react.views.text.ReactFontManager\$Companion",
"com.facebook.react.common.assets.ReactFontManager\$Companion",
)
private lateinit var fontsDir: File
private lateinit var fontsDownloadsDir: File
private var fontsAbsPath: String? = null
@ -58,33 +61,21 @@ object FontsModule : Module() {
}
override fun onLoad(packageParam: XC_LoadPackage.LoadPackageParam) = with(packageParam) {
val replacement = 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)
}
}
// React Native moved ReactFontManager between releases, so try every known package.
// Never let this throw: an uncaught error here aborts the whole module chain.
val hooked = FONT_MANAGER_CLASSES.any { className ->
runCatching {
XposedHelpers.findAndHookMethod(
className,
classLoader,
"createAssetTypeface",
String::class.java,
Int::class.javaPrimitiveType ?: java.lang.Integer.TYPE,
"android.content.res.AssetManager",
replacement
)
}.onFailure { Log.e("Failed to hook createAssetTypeface on $className", it) }
.isSuccess
}
if (!hooked) Log.e("Failed to hook createAssetTypeface on all known ReactFontManager classes")
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
@ -112,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
@ -13,8 +13,13 @@ object SysColorsModule : Module() {
private lateinit var context: WeakReference<Context>
private fun isSupported() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
@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)
@ -37,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

@ -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}")
}

View file

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="xposed_scope">
<item>com.discord</item>
<item>com.discord.canary</item>
<item>com.discord.ptb</item>
<item>dev.shiggy.cord</item>
</string-array>
</resources>